TPSシューティング奮闘記~ダッシュ(Sprint)処理!~
今回は、至ってシンプル。
通常移動速度からの全力ダッシュ処理だ。
ダッシュボタン(キー)・・・左Shiftキー、左スティック押し込み
Shiftキー押してない時は、通常移動速度(375のスピード)で移動するが、
Shiftキー押した時は、速度が「600」になり、通常よりも速い速度で移動が可能だ。
ダッシュボタン処理は、AxisMappingsで作成している。
但し、エイム中はダッシュ不可にしてある。
エイムボタンを押した時に、Bool値「Aim」がTrueになる為、Trueの先に繋がってる「MaxWalkSpeed375」に処理が走る。
つまり、エイム中は強制的に通常移動速度になるわけだ。
移動アニメーションは、ブレンドスぺースで作成している。
移動速度
0・・・Idle(待機)
225・・・Walk
375・・・Run(小走り)
600・・・Sprint(ダッシュ)
実際にプレイしてみよう。
分かりづらかったら申し訳ないが、しっかり移動速度が変化している。
シューティングゲームではよくあるシステムだと思うので、ぜひ参考に。
それではまた!^^ノシ
TPSシューティング奮闘記~武器の変更~
前回の続き~!
前にも武器変更の記事書いたけど、今回は「銃」の切り替え!
~武器変更の為のボタン(キー)~
ピストルに変更・・・1キー、十字キー上
ライフルに変更・・・2キー、十字キー右
ショットガンに変更・・・3キー、十字キー左
プレイヤーのイベントグラフ
「CurrentWeapon」という変数は、「現在装備中の武器」が格納されてる。
※Current・・・現在 Weapon・・・武器
初期装備として「Pistol」を装備させてるので、「CurrentWeapon」には「Pistol(武器名NFP-16)」のBPが格納されてる。
DoOnceがあるのは、連打してアニメーションを何度も連発させない為。
例:Rifleに変更したい場合・・・
まず当然Rifleを持ってなきゃ変更できないので、IsValidでRifleがあるかどうかを判定してる。
もし持ってればIsValidはTrueになる。
そして、Rifleを所持していて尚且つ現在装備中の武器がライフル以外の物なら、NotEqualはTrueになる。
というわけだ。
つまり、Rifleに武器変更したい場合は・・・
・変更したい武器(Rifle)を所持。
・Rifle以外の武器を装備中
この2つの条件を満たしていれば武器の変更が可能になるのだ。
武器変更した際、どの武器に変更するかのEnum値をセットしている。
例えば、Rifleに武器変更する場合はEnum値が「Rifle」にセットされる。
※ 初期装備として「Pistol(NFP-16)」を装備させてる。
武器変更ボタン(キー)押した時、まず現在武器変更中という情報を格納する為に、「IsWeaponChange??」というBool値をTrueにセット。
そして、武器変更アニメーション(モンタージュ)を再生。
武器変更した際にセットしたEnum値は、ここで使われる。
「AttachWeapon」というカスタムイベントが呼ばれた時、実際に武器を変更する処理を行う。
各武器によってアタッチする場所が違うのでスイッチを使用している。
ちなみに、「AttachWeapon」カスタムイベントはAnimBPから呼び出されている。
この「ChangeWeapon」というのはモンタージュに設定したAnimNotify通知の事だ。
武器変更ボタンを押し、アニメーションが再生され、「ChangeWeapon」通知が来た時に、プレイヤーの武器変更処理を行う為のカスタムイベント「AttachWeapon」を呼び出す。
といった感じだ。
アタッチ処理はこんな感じ。
アタッチする場所は、すでに各武器に備わってる構造体「WeaponInfo」に格納されてるので、そのピンをただ繋げればいいだけ。(もっといい方法があるはずだけどなぁ・・・笑)
そして、アタッチ後変更した武器を現在装備中の武器として「CurrentWeapon」に格納。
最後に、武器ごとのアニメーション(IdleやWalkなど)を変更するために、トランディションルールBool値をセット。
ピストルに変更した際は、「EquipPistol」がTrue.「EquipRifle/ShotGun」がFalse。
ライフルかショットガンに変更した際は、「EquipPistol」をFalse。「EquipRifle/ShotGun」をTrue。
Animグラフ側
Pistol→Rifle/ShotGun
Rifle/ShotGun→Pistol
アタッチ処理が終わった後、「WeaponChangeEnd」カスタムイベントが呼び出される。呼び出された時、武器変更が終了したという事で「IsWeaponChange??」をFalseにする。
そして、DoOnceのResetに処理が流れ、もう一度武器変更が可能になる。
ちなみに、「WeaponChangeEnd」カスタムイベントはAnimBP側から呼び出されている。
「Reset」というのは、アニメーションモンタージュで設定したAnimNotify通知の事だ。
アニメーションの終わり辺りに設置している。
これでプレイしてみよう!
しっかり武器変更が行われている!
最後に実際の処理の流れも!見づらいようならごめん!笑
それではまた!^^ノシ
TPSシューティング奮闘記~武器の取得~
以前書いたような内容だと思うが、
まあ進捗という事でw
武器BPの作成
「WeaponMaster」という名の親BPを作成。
その子として、それぞれ
「Pistol」
「Rifle」
「ShotGun」
「SubMachinegun」
の子BPを作成した。
WeaponMaster
「WeaponInfo」という自作の構造体変数をセット。
イベントグラフ
WeaponMasterを継承した子BPそれぞれに、武器やウィジェットを設定していく。
(画像はWeaponMasterを継承した子BP「ShotGun」)
構造体・列挙体(Enum)の作成
武器の種類を格納するための列挙体「WeaponType」
武器の特徴を格納するための構造体「WeaponInfo」
武器取得処理
武器取得した時、このカスタムイベントが呼び出される。
参照したWeaponMasterから「WeaponInfo」構造体を参照。
「WeaponInfo」を分解。
「WeaponInfo」の中にある「WeaponType(自作した列挙体「WeaponType」)」で処理をスイッチ(切り替える)。
もっといい方法があるはずだが、まあ進捗なんでw
例えば、「Rifle」タイプの武器を取得したら、スイッチによって「Rifle」に繋がってる処理が行われる。
「Rifle」タイプの武器をスポーンし、プレイヤーにアタッチ。
その後、アタッチした武器のSphereコンポーネントを破壊。
これを破壊しないと、Sphereコンポーネントによって行われてるウィジェット表示とアウトライン表示が消えないから。
最後に、落ちていた武器を破壊。
ちなみに、初期装備として「Pistol」は最初っからプレイヤーにアタッチさせてる。
これでプレイしてみよう!
しっかり武器の取得が行われている!
ちょっと見づらいかもだが実際の処理の流れも。
ちょっとした参考までに!
それではまた!^^ノシ
TPSシューティング奮闘記~エイム処理~
エイム時の処理について僕がよく組んでいる処理を紹介する。
(何か以前にもエイム処理の記事書いたような気がするが気にしない気にしない)
エイム時の処理
エイムボタン押したら、「Aim」というBool値をTrueにセット。
放したら、Falseにセット。
この「Aim」変数は、エイムアニメーションに移行するためのトランディションルールBool値である。
エイム中(エイムボタン押した時)、平行移動させる。
OrientRotationToMovementのチェックを外し、UseControllerRotationYawにチェックを入れれば、常にカメラのほうに向く。
被写界深度処理(カメラのポストプロセスの強度)をセット。
エイム中は、被写界深度をON(1.0)。非エイム時は、被写界深度をOFF(0.0)にしている。
被写界深度処理は後程出てくる。
タイムラインでカメラの位置とプレイヤーとの距離を変更。
CameraBoom(SpringArm)の長さをグッと縮めて、キャラの肩越し視点になるようにする。
更に、CameraBoomのSocketOffsetの位置をプレイヤーの真横辺りにずらしている。
タイムラインは以下の通り。
タイムラインの長さは「0.1」
SpringLengthのタイムラインで、SpringArmの長さを300~100にしている。
SpringOffSetのタイムラインで、SpringArmのX,Zをプレイヤーの右方向にずらしている。
タイムライン処理の後、
Aim中(True)なら、SetTimerByFunctionNameへ。
非Aim時(False)なら、SetTimerByFunctionNameを止める処理へ。
SetTimerByFunctionNameのFunctionNameには、被写界深度処理を行うためのカスタムイベント「DepthOfField」を指定している。
「DepthOfField」イベントが呼び出されると、「DepthOfFieldSystem」という関数に処理が流れる。
「DepthOfFieldSystem」関数の中身。
プレイヤーカメラの位置から「5000」の距離までトレースを飛ばす。
ヒットした位置とプレイヤーカメラ位置の差を計算し、その結果を「FocalDistance」Float変数にセット。
カメラのポストプロセス変数を出し、「FocalDistance」変数をFocalDistanceピンに繋ぐ。
Apeture(F-stop)の値は「0.5」にした。
Methodは「CircleDOF」に。
この被写界深度処理については、以前も書いたのでそちらも参考に。
これでプレイしてみよう!
エイム中は、カメラがグッとプレイヤーに近づき、プレイヤーの肩越し辺りにカメラが来ていた!
更に、エイム中は被写界深度処理がしっかり行われていた!
その証拠に、エイム中は近くのものがくっきりしたりぼやけていたりしていた!
TPSシューティングを作る際は、ぜひとも参考にしてみてほしい。
もちろん不満ならいくらでも改造して構わない。
それではまた!^^ノシ
イライラ棒的なゲームを作ろう!(棒じゃないけどw)
イライラ棒、いやイライラ「球」を作ろう!
仕様としては・・・
・マウスのドラッグ操作で球を動かす
・弾が何かにヒットしたらゲームオーバー
・ゴールに到達したらクリアー
~カメラの設置~
上から全体を見下ろせる位置にカメラを設置。
~プレイヤーコントローラー・ゲームモード作成~
「IraIra_Con」という名のPlayerControllerを作成。
クラスのデフォルトの詳細パネル「MouseInterface」欄の赤枠で囲った部分にチェック。
これでクリックイベントが有効になり、ゲーム開始時常にマウスカーソルが表示されるようになった。
「IraIraGameMode」という名のゲームモードを作成。
クラスのデフォルト詳細パネルの「Classes」欄の赤枠で囲った部分を変更する。
先程作成したプレイヤーコントローラー「IraIra_Con」を割り当てよう。
デフォルトポーンは、「None」で。
ワールドセッティングのGameModeに、作成したゲームモード「IraIraGameMode」を割り当てる。
~球の作成~
「IraIra」という名のアクタークラスのBPを作成。
コンポーネントを以下の通り。
ドラッグ操作するための処理
このアクターがクリックされたときに呼ばれるイベント「OnClicked」と、
このアクターがクリックし終わった時に呼ばれるイベント「OnReleased」を追加。
「OnClicked」は、GateノードのOpenに。
「OnReleased」は、Closeに繋げる。
GetHitResultUnderCursorByChannel
TraceChannel・・・カーソルのトレースチャンネル(コリジョン)
HitResult・・・カーソルのヒット情報
ResultValue・・・カーソルがヒットしたかどうか
このノードは、「GetPlayerController」からじゃないと出てこない。
マウスカーソル位置に球を追従させたいので、HitResultを分解(BreakHitResult)し、Locationを分解(BreakVector)する。
XとYのみ自由に移動させるようにする。Zは「80」で。
理由は後程。
移動させる処理として、SetActorLocationを使用。
Sweepにチェックを入れるのを忘れずに。
さて、先ほどの
XとYのみ自由に移動させるようにする。Zは「80」
の理由だが、
例えば、Zを「0」にしたらどうなるか。
埋まる。
理由は、床の位置とちょうど被るから。
これは床を球とぶつからないように下げるか、球のZを「80」にすればいい。
今度は、以下のようにしたらどうなるだろう。
カメラのほうに向かって勢いよく向かってくる。
なぜなら、
カメラはワールド内のZの位置に設置している。
という事は、実際球を動かす時XとYを自由に動かせるようにしないといけない。
Zにまで移動可能にすると、Zの位置に設置してあるカメラのほうに向かってきてしまうから。
だから、Zにまで動かす必要が無いのだ。
何かとぶつかった時の処理
シンプル。
何かとヒットした時に、爆発エフェクトを出して、球を破壊してるだけだ。
ここで、適当にステージを組もう。
~UMGの作成~
クリアーしたときのUMG
「Clear」という名のUMGを作成。
「CLEAR!」というテキストを追加。
真ん中に設置。
ゲームオーバーした時のUMG
「GameOver」という名のUMGを作成。
先程とほとんど同じ。
ゴール位置の表示
「Goal」という名のUMGを作成。
先程と同じ。
~ゲームオーバー処理~
「IraIra」のイベントグラフに以下の処理を追加。
DestroyActorの後に、GameOverUMGを表示。
~クリアー処理~
まず、ゴールを設置。
ボックストリガーを置こう。
設置したボックストリガーに、コンポーネントを追加しよう。
Widgetの詳細にある「UserInterface」欄は以下のように設定。
Space・・・Screen
WidgetClass・・・作成したUMG「Goal」を設定。
「IraIra」のイベントグラフに以下の処理を追加。
ブランチを追加し、Conditionには「Clear?」という名のBool値を繋いだ。
デフォルト値はFalseに。
レベルBPに以下の処理を。
レベル内に設置したボックストリガーを選択状態にしたまま、レベルBPのイベントグラフを右クリックし、
OnActorBeginOverLapを追加。
設置したゴール(ボックストリガー)に、「IraIra」がオーバーラップしたら、「Clear」UMGを表示し、「IraIra」BPにある「Clear」Bool値をTrueにセット。
こうすることで、ドラッグ操作による球の移動が不可能になる。
これでプレイしてみよう!
プレイ映像(死亡ラッシュ)
クリアー時
非常にシンプルだが、とりあえずゲームっぽいものはできた。
何回も死ぬ羽目になるので、リセット機能は必須かもしれないw
他にも、いろいろとステージやギミックを増やして面白くするのもいいかもしれない。
それではまた!^^ノシ
SetTimerByEvent・SetTimerByFunctionNameとは??
~SetTimerByEvent~
「Time」に入れた秒数後に、「Event」に繋がってるカスタムイベントを実行する。
Delayのような感じ。
「Looping」で、「Time」に入れた秒数後、Timerをリセットして再度カウントダウンを始める。
~使用例~
Qキー押す。
↓
SetTimerByEventが実行され、「Time」に入れた秒数分カウントダウンを開始。(画像だと「3.0」と入れたので、3秒間のカウントダウンとなる)
↓
3秒後、作成したカスタムイベント「TimerTest」に処理が走り、カスタムイベントに繋がってる処理(画像だと「SpawnEmitteratLocation」)が実行される。
サードパーソンキャラの位置に、爆発エフェクトを出すようにしてある。
実際にやってみると・・・
こんなかんじになるはずだ(適当)
Loopingにチェックを入れると・・・
3秒おきにエフェクトが出ているのが分かる。
~SetTimerByFunctionName~
SetTimerByEventと違うのは、
実行するカスタムイベント名を指定できる事。
SetTimerByEventは直接ピンで繋げていたが、SetTimerByFunctionNameは「FunctionName」に実行させたいカスタムイベント名を入れる事が可能。
あとはSetTimerByEventと同じ。
Timeに入れた秒数分カウントダウン開始。
Loopingはカウントダウンをループさせるかどうか。
Object??わかんネ(マジすいません・・・)
先程とほぼ同じ処理。
キャラの位置でエフェクトを出す。
「1秒」おきに何度もエフェクトを出すようにしてある。
「FunctionName」には実行させたいカスタムイベント名を入力。
しっかり「1秒」おきにエフェクトが出てるのが分かる。
~カウントダウンの止め方~
さて、SetTimerByEventとSetTimerByFunctionNameの機能は分かったが、
もしかしたら「途中でカウントダウンを止めたい!」という時があるかもしれない。
止めるときはこれを使う。
「ClearandInvalidateTimerbyHandle」だ。
~使用例~
「0.5秒」おきに繰り返しカスタムイベントの処理を実行。
Qキー押した時、SetTimerByEventを実行し、カウントダウンを開始。
Qキー放した時に、実行されてるSetTimerByEventのカウントダウンを停止。
ClearandInvalidateTimerbyHandleの「Handle」に、停止させたいSetTimerByEventの値を繋ぐ。
途中でしっかり処理が止まってるはずだ。
実際にやってみよう。
もちろんSetTimerByFunctionNameでも同じことができるぞ。
~小ネタ~
SetTimerByEvent・SetTimerByFunctionNameを使って、EventTickとほぼ同じ動作をさせることが可能だ。
Timeに「0.008333」を入れてやれば、ほぼEventTickと同じ動作が可能だ。
非常に目に痛い動画だが、EventTickとほぼ同等の動作と言える。
これで、SetTimerByEventとSetTimerByFunctionNameの機能と、ちょっとした小ネタを知ることが出来た!
それではまた!^^ノシ
Splineで遊ぼう!~Splineでちょっとしたプロシージャルメッシュ作成~
ActorクラスのBPを作成。
名前は何でも。(自分は「BP_Spline」にした。)
~Splineコンポーネント追加~
「Spline」というコンポーネントを追加。
~ConstractionScript~
~変数~(全部編集可能にする)
NumberOfMeshs・・・配置するメッシュの数
OrientToSpline・・・メッシュの向き(角度)をSplineの角度に合わせるか??
NumberOfMeshsで配置するメッシュの数を決める。
デフォルト値は「2」にした。
配置するメッシュの数が「0」、つまりNumberOfMeshsが「0」の場合、ブランチの条件によりFalseに処理が流れる。
NumberOfMeshsの値「2」から1引いた値をForLoopのLastIndexに渡す。
同時にNumberOfMeshsの値「2」から1引いた値をGetSplineLengthの値で割る。
GetSplineLength・・・Splineの長さを取得
ちなみに、Splineの長さはデフォルトだと「100」
つまり、NumberOfMeshsのデフォルト値には「2」を入れたので、
100÷1=「100」
になる。
「100」をIndexの値で掛け算する。
FirstIndex「0」
LastIndex「1」なので・・・
0×100=「0」
1×100=「100」
となる。
この値は、配置するメッシュの位置を決める値となる。
この値を、GetLocationAtDistanceAlongSplineでVector値に変換。
これを、InverseTransFormLocationで相対座標に変換。
相対座標に変換した値をMakeTransFormで一纏めにして、AddStaticMeshComponentノードのRelativeTransFormに繋ぐ。
これで、メッシュを指定した数の分だけ配置が可能になった。
最後に、GetRotationAtDistanceAlongSplineでSplineの角度をワールド座標の角度に変換。
Distanceには、先程の「Indexの値×100」の値を繋ぐ。
GetRotationAtDistanceAlongSplineでワールド座標に変換したSplineの角度を、SetWorldRotationに渡す。
その前に、ブランチを挟む。
ConditionがTrue・・・メッシュの角度をSplineの角度に合わせる。
ConditionがFalse・・・合わせない。
さてどうなっているか!?
見事!(自己満)
ここで整理しよう。
NumberOfMeshsの値が「2」だった場合。
相対座標、X「0」とX「100」の位置にメッシュが配置される。
「0」「100」は、先程やった「Indexの値×100」の奴だ。
0×100=「0」・・・1個目に配置するメッシュの位置
1×100=「100」・・・2個目に配置するメッシュの位置
となる。
まあ、Splineは昨日触ったばっかなので、色々違う部分はあるかもしれないw
その場合は遠慮なく指摘してほしい。
それではまた!^^ノシ