PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

Lumberyard ScriptCanvasで簡単なゲームロジック作成

この記事はLumberyard Advent Calendar 2017の25日目の記事です。
Lumberyard Advent Calendar 2017 - Qiita

環境

Lumberyard 1.12.0.0


参考資料

プレイヤーが移動するまでは下記2つを追ってもらえれば同じようなのができます!
【Lumberyard】Script Canvas入門 - ユーセンブログ
Script Canvas Tutorials - Lumberyard


空のレベルの作成

ScriptCanvasに入る前に適当にレベルを作成しておきます。
プロジェクトは新規作成したものでも、既存のものでも問題ありません。
f:id:PaperSloth:20171224120529p:plain

新規プロジェクト作成方法はこちら
papersloth.hatenablog.com
もし、Defaultで作成できなかったという時はEmptyも試してみてください。


プレイヤーの作成

続いてPlayerを作成していきましょう。

Entitiy OutlinerかViewport上で右クリックして
Create entity
f:id:PaperSloth:20171224121650p:plain

名前を変更したいので、Entity OutlinerかEntity Inspectorから
名前をPlayerに変更
f:id:PaperSloth:20171224121832p:plain


このままだと空のEntityなので、見た目を追加します。
Entity InspectorからAdd Compoenent
f:id:PaperSloth:20171224121959p:plain

RenderingカテゴリのMeshを追加
f:id:PaperSloth:20171224122104p:plain

まだ見た目がないので、設定していきましょう。
MeshComponentのMesh assetを選択
f:id:PaperSloth:20171224122424p:plain

Engine > EngineAssets > Objects > Default.cgfを選択
f:id:PaperSloth:20171224122548p:plain
この時に面、頂点、LOD、マテリアルの情報とプレビューが表示されるのは見やすいですね。

とりあえずプレイヤーの見た目は一旦これで終了です。
f:id:PaperSloth:20171224123128p:plain


ですが、このままだと何も起こらないので次は物理を追加しましょう。
再びAdd Componentから
Physics > Rigid Body Physicsを追加しました。
f:id:PaperSloth:20171224123410p:plain
Unityユーザーには馴染みがあるかもしれませんね。


次に入力を取れるようにします。
このあたりはtachi1427さんの解説と重複している箇所があるため
読み飛ばしていただいても問題ありません。
【Lumberyard】Script Canvas入門 - ユーセンブログ


Lumberyardでは空のEntityでは入力の取得ができない仕組みになっています。
Add Componentから Gameplay > Input
f:id:PaperSloth:20171224123728p:plain

入力の設定ファイルを作成します。
Input to event binding内にアーケードコントローラーみたいなボタンを押します。
f:id:PaperSloth:20171224124207p:plain

するとAsset Editor Windowが開くので、
File > New > Input bindingを選択します。
f:id:PaperSloth:20171224124342p:plain

(ProjectName) > inputbinding内に
player.inputbindingsを追加しました。
f:id:PaperSloth:20171224124531p:plain


作成するとこんな感じになります。
f:id:PaperSloth:20171224124753p:plain
もし、反映されていなければ
File > Openから探して開きましょう。

続いて入力イベントを登録していきます。
Input Event Groupの+ボタンを押下します。
f:id:PaperSloth:20171224125548p:plain

MoveX(左右)とMoveY(前後)を登録しました。
LumberyardはZ-upなので、Yが前後になりますね。
f:id:PaperSloth:20171224130043p:plain

続いてMoveX, MoveYにそれぞれどのキーを割り当てるかを登録していきます。
Event Generators > +
f:id:PaperSloth:20171224130729p:plain

Inputクラス作成のダイアログが出るのでOKを押します。
f:id:PaperSloth:20171224130900p:plain

するとEvent Generatorに登録され
Input Device TypeやInput Nameが設定可能になりました。
f:id:PaperSloth:20171224131035p:plain

とりあえずは前後移動のWSキーを登録しました。
左右移動のADキーも同様に追加します。
f:id:PaperSloth:20171225133423p:plain

全て追加したらFile > Saveを選択して保存します。

作成はされましたが、登録はされていないので登録します。
Playerを選択してInput > Input to event bindingsに作成したplayer.inputbindingsを追加します。
f:id:PaperSloth:20171225133839p:plain
f:id:PaperSloth:20171225133932p:plain

一旦はこれでプレイヤー周りのコンポーネントの追加、作成は完了です。
いよいよScript Canvasに入っていきます。


Playerの処理の追加

さぁ、ようやくScript Canvasの記述です。
先ずはScript Canvasを開きましょう。
Tools > Script Canvas(PREVIEW)です。
f:id:PaperSloth:20171225134223p:plain

Script Canvasを開いたらFile > New ScriptかCtrl + Nで新規作成します
f:id:PaperSloth:20171225134311p:plain

一旦ここで保存します。
File > Save(Ctrl + S)かFile > Save as...(Ctrl + Shift + S)です。
Lumberyard/1.12.0.0/dev/(ProjectName)/ScriptCanvasにPlayerController.scriptcanvasで保存しました。
f:id:PaperSloth:20171225134630p:plain


早速処理を書いていきましょう。
操作はUE4のBlueprintを触ったことがある人は入りやすいかもしれません。
先ずは入力の取得から。
Script Canvas上で右クリックし、inputと入力すると下記のようになります。
Input Handlerを選択しましょう。
f:id:PaperSloth:20171225140330p:plain

MoveXとMoveYとLogを下図のように繋いで先ずは入力が取れているか確認します。
f:id:PaperSloth:20171225140638p:plain

playerを選択してAdd ComponentでScript Canvasを追加します。
f:id:PaperSloth:20171225140805p:plain

Script Canvas Assetの参照も追加します。
f:id:PaperSloth:20171225140908p:plain

先程作成したplayercontroller.scriptcanvasを割り当てたら
実行してWASDキーを押下して、WDで1.0、ASで-1.0が出力されることを確認します。
f:id:PaperSloth:20171225141130p:plain


入力が取れていることが確認できたら移動処理を書きます。
移動にはMove Entityノードを使います。
Sourceは移動ターゲット
Directionは移動方向ですね。
f:id:PaperSloth:20171225141835p:plain

ですが、InputのValueとMoveEntityのDirectionは接続できません。
Number型とVector3型でScript Canvasでは自動でキャストしてくれないからですね。
f:id:PaperSloth:20171225142031p:plain

Constructノードを使ってNumberからVector3を生成します。
f:id:PaperSloth:20171225142233p:plain
同様にMoveYも組んでみると上下左右に移動できると思います。
ノードの複製はCtrl + Dで行えます。

これで動かしてみるとすごく高速移動します
f:id:PaperSloth:20171225142602g:plain


続いて速度の補正を加えることにします。
Multiplyノードで0.1をかけ、1/10の速度にします。
f:id:PaperSloth:20171225142946p:plain

どちらも同じ値なのでとりあえず変数にまとめちゃいます。
Create Variable > Numberで作成します。
f:id:PaperSloth:20171225150732p:plain

Speedという名前で保存しました。
f:id:PaperSloth:20171225150810p:plain

これで1/10の速度で動いてだいぶいい感じになりました。
f:id:PaperSloth:20171225161510g:plain

カメラの追加

動画を見ての通り、カメラ処理がないのでカメラを追加していきます。

先ずはカメラの作成から。
Create Entityして、Camera Componentをつけるだけ!
f:id:PaperSloth:20171225153248p:plain

ですが、これだと動かしてもカメラの位置が変わらないので
Playerの子にすることで追従します。
この辺はUnityと同じですね。
f:id:PaperSloth:20171225153637p:plain

また、位置も調整して少し斜め後ろから見下ろすようにしました。
f:id:PaperSloth:20171225153844p:plain

動かすと分かりますが、少し酔いますね。
f:id:PaperSloth:20171225162442g:plain


カメラの追従を緩やかにする。

このままでもカメラは追従しますが、動きが早いと酔ってしまうので緩やかにする追従するようにします。
UnityだとVector3.MoveTowards, UE4だとVInterpToでサクッと実装できますが
Lumberyardでそれに該当する関数が見つけられなかったので、自作していきます。
CameraRig Componentを活用すればもっと楽に実装できる気もします。

CameraにScript Canvasを追加し、新規にCamera Managerという名前で追加しました。
f:id:PaperSloth:20171225170022p:plain

Camera側でPlayerを参照するには
Playerを選択した状態でReference Selected Entityを選択します。
UE4のLevel BlueprintでLevel上のActorを取得する時と同じようなイメージですね。
f:id:PaperSloth:20171225170238p:plain

先ずはCameraとPlayerの距離ベクトルをDistanceという変数を作成し、保存します。
On Graph StartノードはUnityでいうStart, UE4でいうBeginPlayです。
f:id:PaperSloth:20171225183410p:plain

次は長いので、小分けに説明していきます。
OnTickノードはUnityでいうUpdate, UE4でいうTickです。
InterpSpeed, Offsetという変数を作成しました。
InterpSpeedで追従の速度を調整することになります。
f:id:PaperSloth:20171225183752p:plain

次にPlayerの位置に先程のプレイヤーからカメラの距離を加算します。
この値をカメラの座標にセットすると親子階層にした時と同様に固定距離でべったり貼り付くカメラになりますね。
f:id:PaperSloth:20171225184153p:plain

計算がもう少しだけ続きます。
Playerにカメラとの距離を加算した値からカメラの位置を引きます。
その値に先程求めたOffsetを掛けます。
f:id:PaperSloth:20171225185644p:plain

最後にその値をカメラ座標に足してやれば完成です。
f:id:PaperSloth:20171225185842p:plain

全体像が見えるように折り返して写しました。
中でやってる計算はUE4のVInterpToの簡素なバージョンです。
f:id:PaperSloth:20171225190035p:plain

また、このままでは正常に動作しないため
CameraをPlayerの子階層から外しておきましょう。
f:id:PaperSloth:20171225190430p:plain

計算が多いのはノードエディタと相性が悪いため、LuaC++で書いた方がいいですね。
関数の作成方法や独自のノードの作成方法が分かればリファクタリングしたいところです。

とりあえず動きとしてはよくなってきました。
f:id:PaperSloth:20171225190743g:plain


弾を撃つ

続いて攻撃処理を作っていきます。
移動できて、弾が撃てて敵が倒せればゲームが作れそうですよね。

先ずは弾となるEntityを作成します。
Create Entityをし、Projectileと命名しました。
f:id:PaperSloth:20171225200422p:plain

Meshを追加し、SampleProject > Objects > default > primitive_sphereを使用します。
サイズも大きいので、Scaleを0.2にしておきます。
f:id:PaperSloth:20171225200628p:plain

次にコリジョンを追加します。
Add Component > Rigid Body Physics, Primitive Colliderを追加します。
f:id:PaperSloth:20171225220259p:plain

Primitive ColliderのAdd Required Componentから
Sphere Shapeを選択します。
Primitive ColliderのSource Typeにはmat_defaultを設定します。
f:id:PaperSloth:20171225220501p:plain


続いて弾の移動処理を追加します。
ProjectileにScript Canvasを追加して、Projectileという名前のScript Canvasを追加しました。
f:id:PaperSloth:20171225201801p:plain

処理は開始時にY軸方向に100倍した値を速度として渡しています。
因みにこのY軸の値は必ずしも1.0ではありません。
f:id:PaperSloth:20171225220943p:plain

最後に弾の消滅処理を書きます。
DelayとDestroyを使うわけですが、Script Canvasは少しおもしろい書き方ができます。
削除は下図の形でつなぎます。
Delayで3秒後に自身をDestroyします。
f:id:PaperSloth:20171225221314p:plain

Script Canvasでは1つのOutピンから複数の入力ピンにつなげることができます。
UE4ではSequenceノードがその役割を果たしていますね。

弾の処理に関しては一旦これで終了です。


弾をSliceとして登録する。

スライスと動的スライス - Lumberyard
SliceとはUnityでいうところのPrefabです。
UE4ではClass Blueprintが近いですね。
f:id:PaperSloth:20171225221704p:plain

Lumberyard/1.12.0.0/dev/(ProjectName)/slicesに保存しました。
f:id:PaperSloth:20171225221821p:plain

Sliceとして保存するとEntity Outliner上で青く表示されます。
f:id:PaperSloth:20171225221920p:plain


ドキュメントを読めば書いてあるのですがこのスライスは実行時に生成ができません。
そのため、動的スライスとして登録します。
先程作成したSliceをAsset Browser上から探し、右クリックでSet Dynamic Sliceを選択します。
f:id:PaperSloth:20171225222341p:plain

これで動的スライス(DynamicSlice)として登録され、実行時の生成が可能になりました。
実行して、弾が無事に飛んでいくことを確認すれば、Entitiy OutlinerからProjectileを削除します。


プレイヤーから弾を撃つ

弾が登録できたので、次はプレイヤーから撃ちます。

Player EntityにSpawnerを追加します。
f:id:PaperSloth:20171225222741p:plain

Dynamic Sliceに先程登録したProjectileを設定します。
f:id:PaperSloth:20171225222915p:plain


次に発射ボタンの登録を行います。
player.inputbindingsを開き、Fire Eventとしてマウスの左ボタンを追加しました。
f:id:PaperSloth:20171225223108p:plain
この時、保存を忘れないようにしましょう。

PlayerControllerのScript Canvasを開き、先ずはログで入力の確認。
左クリックで-1が出力されれば入力が取得できています。
f:id:PaperSloth:20171225223328p:plain

弾の発射処理はこれだけです。
SpawnがSelfになっていることに違和感を感じた方もいるかもしれませんが、
これはSpawnerに登録されているDynamic SliceをSpawnするという意味なのでこれで問題ありません。
f:id:PaperSloth:20171225224347p:plain


ちょっと見た目を変えたりしておしまい。
f:id:PaperSloth:20171225230436g:plain


まとめ

なんとなくゲームを作る雰囲気が見えてきたでしょうか。

幾つかやり残したことがあって次回の課題としたいと思います。
・カメラの回転
 カメラ追従にこったりせずにこっちを実装すればよかったと今更思います。
・エフェクトの発生
 弾を撃つ時、弾を当てた時のマズルフラッシュと爆発エフェクト
・サウンドの再生
 発射音、ヒット音の再生
・敵の追加
 AIを作るには時間が足りないのですが、当たり判定の解説用に作ればよかったところです。
・UIの作成
 Hit Pointや制限時間などなど
・キャラクターのアニメーション
 これに関しては資料というかサンプルが豊富なので、なんとかなりそうです。
 1.12からシステムが変わったようなので、時間を作って解析したいところ。
・Levelの遷移
 Level遷移は少し資料を探すのが大変そうで難しいかなと思っている点です。
 Forumを探したら意外と直ぐに見つかる気もします。

一先ずゲームを完成させた!というにはまだまだ解説する要素が足りなかったですね。
ですが、Lumberyardは2018年で大きく変わるようですので、本当に楽しみです。

来年のAdvent Calendarをもっともっと盛り上げていければと思います。
最後までお付き合いいただき、ありがとうございました!