サイト内検索

用語検索や評価テストのお供 にどうぞ!
空の検索で57件の結果が見つかりました。
- EX 剣を振る/ボールを投げる | Unity1gc2
LessonEX 剣を振る/ボールを投げる EX-1 剣を振る ゲームを作る上で「剣を振って敵を攻撃する」というアクションをしたいと思うことは多々あると思います。ここでは剣を振って、振っている途中の剣に当たり判定を実装してみましょう。 また、剣を振る動作の応用でボールを投げてみましょう。 適当な3Dプロジェクトを開いて、こちらのパッケージを インポートしてください →→→→→→→→→→→→→→→→→ ユニティちゃんと剣のモデル、アニメーションが同梱されて います。 インポートできたら新しいシーンを作成して、地面と ユニティちゃんを配置してください。流れは3Dアクションゲーム編Lesson1 の序盤と同じです。ここでは追尾カメラの実装まではしないので、ユニティちゃんを見やすいようにカメラの位置を調整してください。 パッケージをダウンロード 剣を振るアニメーションを設定するために、ユニティちゃんにアニメーターを設定しましょう。 プロジェクト内で右クリックして「Create」→「Animator Controller」を追加してください。名前は何でもOKです(サンプルでは「PlayerAnim」) PlayerAnimをユニティちゃんにドラッグ&ドロップしてください。 PlayerAnimをダブルクリックして、Animatorウィンドウを開いてください。 Attackトリガーを追加して、IdleステートとAttackステートを作成してください。 (3Dアクションゲーム編1-7 を参考にしましょう) TriggerはBoolと扱いが似ていますが、有効にしたあと自動で元に戻る性質があります。trueにすると次のフレームで勝手にfalseに戻るbool型をイメージすると分かりやすいかと思います。 IdleステートのMotionは「2Hand-Sword-Idle」、AttackステートのMotionは「2Hand-Sword-Attack1」に設定してください。 IdleとAttackを相互に繋げるトランジションを作成してください。 トランジションの設定をしてください。 IdleからAttack: HasExitTimeのチェックを外す、Conditions(遷移条件)はAttackトリガー AttackからIdle: HasExitTimeにチェックを入れる、Conditionsは不要 Animatorウィンドウをドラッグして、Gameウィンドウと同時に見られるように移動してください。 移動できたらゲームを実行して、AnimatorウィンドウのAttackトリガーをクリックしてみましょう。 アニメーションを再生すると足が地面に埋まってしまう場合、IdleとAttackステートを選択してFoot IKにチェックを入れてください。 IKとはInverse Kinematics(インバースキネマティクス)の略で、足の衝突解決を行ってそこから逆算的に膝などの座標を決める手法です。 Unity Tips! Idleアニメーション中は問題ありませんが、Attackアニメーションを再生した瞬間にエラーが発生してしまいます。 これは外部からアニメーションをインポートした時に時々起こる問題で、アニメーションに埋め込まれているアニメーションイベントによるエラーになります。 「Animation」→「2Hand-Sword」→「RPG-Character@2Hand-Sword-Attack1」を選択して、「2Hand-Sword-Attack1.FBX 」内のアニメーションクリップ(三▲ みたいなアイコン)をダブルクリックしてください。アニメーションウィンドウが開きます。 アニメーションウィンドウの15フレーム目に「Hit」という名前のキーが置かれています。これがアニメーションイベントで、アニメーションイベントのタイミングで特定の関数を呼び出すことができる という機能です。アニメーションの制作者が設定してくれたものですが、呼び出す関数がないためエラーが出ているという感じです( アニメーションイベントについては後で詳しく解説します} とにかく今はこのアニメーションイベントを一旦削除したいところです。ですが、FBXに埋め込まれているアニメーションは直接編集することができません。 この問題を解決するためには「2Hand-Sword-Attack1.FBX 」からアニメーションクリップだけを取り出す必要があります。これはアニメーションクリップをコピー&ペーストすることで取り出すことが可能です。 アニメーションクリップ(三▲ のアイコン)を選んだ状態で、キーボードのCtrl+Cを押してからCtrl+Vを押してください。 この措置を行うと「2Hand-Sword-Attack1.anim」が生成されます。 生成されたアニメーションクリップ(2Hand-Sword-Attack1.anim)をダブルクリックして、アニメーションウィンドウを開いてください。 先ほどの措置で生成したアニメーションクリップは編集することができます。15フレーム目のアニメーションイベントを選択して、Deleteキーを押してください。 アニメーションイベントを削除できたら、新しく生成した方のアニメーションクリップをAttackステートのMotionにドラッグ&ドロップしてください。 この状態でゲームを開始して、Attackトリガーをクリックしてください。今度はエラーが起きずに、剣を振るアニメーションを再生できると思います。 いよいよユニティちゃんに剣を持たせます。と言っても、3Dモデルに物を持たせるのは簡単です。 しっかり剣を持たせたい場合はアニメーション側での調整が必要ですが、遠目に持っているように見せるだけであれば、Unity側だけで解決することができます。 まずは「Model」→「LongSword」→「Prefabs」→「LongSword」をシーン 上にドラッグ&ドロップしてください。 剣をユニティちゃんの右手の子オブジェクトにしましょう。子オブジェクトは親オブジェクトに追従するため、右手の子オブジェクトに剣を設定することで剣を持つことが可能です。 インスペクターで「unitychan」→「Character1_Reference」→「Character1_Hips」→「Character1_Spine」→「Character1_Spine1」→「Character1_Spine2」→「Character1_RightShoulder」→「Character1_RightArm」→「Character1_RightForeArm」→「Character1_RightHand」→「Character1_RightHandThumb1」 と開いていき、剣をCharacter1_RightHandThumb1の子オブジェクトにしてください 。 子オブジェクトにできたらゲームを開始してください。 2Hand-Sword-Idle アニメーションから開始するはずなので、この状態でゲームを一時停止 しましょう。 一時停止したらシーンビューに切り替えて、右手に合う位置に剣の座標や回転を調整してください。 【サンプルのパラメータ(参考までに)】 Position X=-0.057 Y=0.019 Z=0.018 Rotation X=48.046 Y=-73.922 Z=39.708 アニメーションウィンドウのAttackトリガーをクリックして、攻撃アニメーションを再生してみましょう。前述した通り、剣が右手に追従するため剣を振ることができます。至近距離で見ると違和感が出ますが、遠くから見る分にはしっかり振っているように見えるはずです。 このようにオブジェクトを手の子オブジェクトにすることで、剣に限らずキャラクターに色々なものを持たせることができます。 ※ まだゲームの実行は止めないでください! しかし、剣の座標調整はゲームの実行中に行ったものであるため、ゲームを止めると値が元に戻ってしまいます 。 それを防ぐために現在のパラメータを保持しておきます。剣のインスペクター内でTransformの右上にある3つの点のボタンをクリックしてください。「Copy」→「Component」と選択することで、値をコピーしておくことができます。 コピーできたらゲームを終了して、再度剣のTransform右上のボタンから「Paste」→「Component Values」を選択してください。実行中に設定したパラメータに戻すことができます。 興味のある人は他のアニメーションも再生して、ユニティちゃんが剣をしっかり持てているか確認してみましょう。 ここまでの流れで剣を持つことができましたが、この剣で衝突判定が取れなければゲームが作れません。剣の攻撃判定を作ってみましょう。 先ほど軽く触れたアニメーションイベント を使って、攻撃アニメーションの開始と終了を判定します。アニメーションイベントとは、スクリプト内の関数をアニメーション中の特定のタイミングで呼び出すことができる機能です(k2Engineにも同じ機能があります) 今回は剣の「振り始め」と「振り終わり」にアニメーションイベントを設定し、剣を振っている間だけ当たり判定を有効にすることで攻撃判定を実装しましょう。 まずは剣に当たり判定を追加しましょう。 LongSwordを選択して、BoxColliderをアタッチしてください。 BoxColliderのEdit Colliderを選択して、剣全体を覆うように当たり判定を調整しましょう。 Is Triggerにチェックを入れておきましょう。これで剣が他の当たり判定を貫通するようになります。 次は攻撃される側のオブジェクトを作成します。今回はサンプルなのでただの箱にします。 Enemyタグを作成して、設定しておいてください(タグについては3Dアクションゲーム編1-6 参照) プレイヤーのスクリプトを実装します。 今回は歩きのアニメーションなどは実装せずに、ただ「WASDキー入力で立ちモーションのまま移動」「Enterキーが押されたら攻撃モーションを再生」といった流れだけにします。 Playerスクリプトを作成して、以下のように入力してください。攻撃以外は3Dアクションゲーム編Lesson1 とほとんど同じです。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; Animator m_animator; void Start() { // 自分にアタッチされているAnimatorを取得 m_animator = GetComponent(); } void Update() { // 移動速度を初期化 Vector3 move = Vector3.zero; // 前後移動 if (Input.GetKey(KeyCode.W)) { move.z += MoveSpeed; } if (Input.GetKey(KeyCode.S)) { move.z += -MoveSpeed; } // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // 移動させる transform.localPosition += move; // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } // エンターキーが押されたら攻撃アニメーションを再生 if (Input.GetKeyDown(KeyCode.Return)) { m_animator.SetTrigger("Attack"); } } } AnimatorクラスのSetTrigger関数は名前の通りトリガーを有効にする関数です。前述の通りTriggerは自動で元に戻るため、引数は対象の名前だけで構いません。 コードが書けたら保存して、ユニティちゃんにPlayerスクリプトをアタッチしてください。実行するとWASDキーで移動して、Enterキーで攻撃ができるようになっているはずです。 しかし、このままでは常に剣の当たり判定が有効になってしまいます。攻撃中だけ剣の当たり判定を有効にするためにアニメーションイベントを設定しましょう。 先ほど作成したAttackのアニメーションクリップをダブルクリックして、Animationウィンドウを開いてください。 再生バーの位置を14フレーム目に合わせて、Add eventボタンをクリックしてください。アニメーションイベントが追加されます。 24フレーム目にもアニメーションイベントを追加してください。 アニメーションクリップをクリックしてから アニメーションイベントをクリックしてください。インスペクターにアニメーションイベントの情報が表示されます。 アニメーションイベントのFunctionで、このアニメーションイベントのタイミングで呼び出す関数の名前を指定することができます。 攻撃開始のタイミングでは「AttackStart」関数、攻撃終了のタイミングでは「AttackEnd」関数を呼ぶようにしてください。 AttackStart関数とAttackEnd関数を追加しましょう。 Playerスクリプトを開いて、赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; Animator m_animator; public BoxCollider SwordCollider; // 剣の当たり判定 void Start() { // 自分にアタッチされているAnimatorを取得 m_animator = GetComponent(); // 剣の当たり判定を無効にする SwordCollider.enabled = false; } void Update() { // 移動速度を初期化 Vector3 move = Vector3.zero; // 前後移動 if (Input.GetKey(KeyCode.W)) { move.z += MoveSpeed; } if (Input.GetKey(KeyCode.S)) { move.z += -MoveSpeed; } // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // 移動させる transform.localPosition += move; // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } // エンターキーが押されたら攻撃アニメーションを再生 if (Input.GetKeyDown(KeyCode.Return)) { m_animator.SetTrigger("Attack"); } } // 攻撃開始 void AttackStart() { // 当たり判定を有効にする SwordCollider.enabled = true; // デバッグ Debug.Log("攻撃開始"); } // 攻撃終了 void AttackEnd() { // 当たり判定を無効にする SwordCollider.enabled = false; // デバッグ Debug.Log("攻撃終了"); } } (コンポーネント名).enabled はコンポーネントの有効/無効を切り替えるためのパラメータです。trueの間は有効で、falseの間は無効になります。 前述の通りAttackStartやAttackEnd関数はアニメーションイベントから呼び出されるので、スクリプト内で呼び出す必要はありません。これで剣を振っている間だけ当たり判定が有効になります。 最後に剣に「当たり判定がEnemyに触れたら、触れたEnemyを削除する」スクリプトをアタッチして完成させましょう。 Swordスクリプトを作成して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Sword : MonoBehaviour { // 自分に何かが接触した瞬間呼ばれる private void OnTriggerEnter(Collider other) { // 触れたオブジェクトのタグがEnemyなら… if (other.CompareTag("Enemy")) { // 対象を削除する Destroy(other.gameObject); } } } PlayerスクリプトにBoxColliderを指定するパラメータが追加されているので、LongSwordをドラッグ&ドロップしてください。ここで指定したBoxColliderが、剣を振っている間だけ有効になります。 あと少しです。最後に剣の設定を完成させましょう。 3Dアクションゲーム編では説明しませんでしたが、実はOnTriggerEnter や OnCollisionStay を使用したい場合、最低でもどちらかにはRigidbodyがアタッチされている必要がある というルールがあります。 剣にRigidbodyをアタッチしてください。ただし重力や物理演算は不要なのでUse Gravity(重力を使用するか)はチェックを外し、Is Kinematic(物理演算を止めるか)はチェックを入れておいてください。ゲームの見た目的には変わりませんが、この措置がないと後ほど攻撃判定が取れないので注意しましょう。 最後に剣にSwordスクリプトをアタッチしてください。 これで剣で攻撃判定を取るための準備が完了しました。 ゲームを実行してEnterキーを押して攻撃してみましょう。コンソールに攻撃開始と攻撃終了のタイミングでログが出力されます。 攻撃中でない時の剣がEnemyに触れても何も起きませんが、攻撃中の剣がEnemyに触れるとEnemyが消えます。確認してみましょう。 これで剣で攻撃する処理は完成です。 サンプルでは攻撃するとEnemyが消えるだけでしたが、体力を実装したりエフェクトを追加することでより実際のゲームに近づけることができます。ぜひ挑戦してみてください。 EX-2 ボールを投げる 剣での攻撃を応用して、ボールを投げてみましょう。 EX1で解説した内容は省略して、新しい要素だけ解説します。 まずはEX1でユニティちゃんに持たせた剣を削除 して、使用するアニメーションを変更してください。 IdleのMotionを「WAIT00」に変更してください。 Attack のMotionを「Unarmed-Attack-L1」に変更してください。 (本当はボールを投げるモーションを使いたかったのですが、ボールを投げるアニメーションがフリーで配布されていなかったのでサンプルではパンチモーションで代用します…) 「Animation」フォルダ内にあるUnarmed-Attack-L1.animをダブルクリックして開いてください。 EX1と同じ流れでアニメーションイベントを設定してください。11フレーム目に「Throw」関数を呼び出すようにしましょう。 投げるボールを実装します。 「3D Object」→「Sphere」を追加して、Rigidbodyをアタッチしてください。大きさも調整しておきましょう。 白色では見えにくいため、目立つ色のマテリアルを作成して貼っておくのがオススメです。 ボールにBallタグを作成して設定してください。 これで事前準備は完了です。 それでは、ボールを投げるために以下の処理を実装していきましょう。 1. ボールとプレイヤーが近くなったらボールを拾う ボールを持っている間、ボールは手の子オブジェクトにする 2. Throw関数でボールに対して前方に力を加える まずは1の「ボールとプレイヤーが近くなったらボールを拾う」処理を実装しましょう。 Playerスクリプトを開いて、赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; Animator m_animator; // 剣は使わないのでコメントアウト //public BoxCollider SwordCollider; // 剣の当たり判定 GameObject BallObject; // ボール bool isBallGet = false; // ボールを持っているかどうか bool isBallCanGet = false; // ボールを持てるかどうか(投げた瞬間に拾えないように) public GameObject Hand; // ボールの親オブジェクトとなる手 void Start() { // 自分にアタッチされているAnimatorを取得 m_animator = GetComponent(); // 剣の当たり判定を無効にする //SwordCollider.enabled = false; // ボールを取得 BallObject = GameObject.FindGameObjectWithTag("Ball"); } void Update() { // 移動速度を初期化 Vector3 move = Vector3.zero; // 前後移動 if (Input.GetKey(KeyCode.W)) { move.z += MoveSpeed; } if (Input.GetKey(KeyCode.S)) { move.z += -MoveSpeed; } // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // 移動させる transform.localPosition += move; // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } // ボールを持っている状態でエンターキーが押されたら攻撃(投げる)アニメーションを再生 if (Input.GetKeyDown(KeyCode.Return) && isBallGet == true ) { m_animator.SetTrigger("Attack"); } // もしボールを持っていない かつボールを拾えるなら if (isBallGet == false && isBallCanGet == false) { // 自分とボールの距離が近いなら拾う if(Vector3.Distance(transform.position, BallObject.transform.position) <= 1.0f) { // ボール所持状態になる isBallGet = true; // ボールの親オブジェクトを自分の手にする BallObject.transform.parent = Hand.transform; // ボールのローカル座標を調整 BallObject.transform.localPosition = Vector3.zero; // ボールの物理演算を止める BallObject.GetComponent().isKinematic = true; } } } // 攻撃開始 void AttackStart() { // 当たり判定を有効にする //SwordCollider.enabled = true; // デバッグ Debug.Log("攻撃開始"); } // 攻撃終了 void AttackEnd() { // 当たり判定を無効にする //SwordCollider.enabled = false; // デバッグ Debug.Log("攻撃終了"); } } 【プログラムの解説】 ・Vector3.Distance 関数は第一引数の座標と第二引数の座標の距離を返す関数です。 今回はプレイヤーとボールの距離を計算して、ボールの取得判定を行っています。 ・GameObject.transform.parent でオブジェクトの親オブジェクトを取得、変更できます。 ・RigidbodyクラスのisKinematicはEX1で解説した通り、物理演算の有効/無効を切り替えるパラメータです。 ボールの親オブジェクトとなるオブジェクトを指定しましょう。 インスペクターで「unitychan」→「Character1_Reference」→「Character1_Hips」→「Character1_Spine」→「Character1_Spine1」→「Character1_Spine2」→「Character1_LeftShoulder」→「Character1_LeftArm」→「Character1_LeftForeArm」→「Character1_LeftHand」→「Character1_LeftHandThumb1」→「Character1_LeftHandThumb2」→「Character1_LeftHandThumb3」と開いて、PlayerコンポーネントのHandにドラッグ&ドロップしてください。 これでボールを拾えるようになりました。 試しに実行して、ボールに近付いてみましょう。 次に2の「Throw関数でボールに対して前方に力を加える」処理を実装します。先ほど追加したアニメーションイベントを使用しましょう 。 Playerスクリプトを開いて、赤い部分のコード を追加してください。 ~前略~ // 攻撃終了 void AttackEnd() { // 当たり判定を無効にする //SwordCollider.enabled = false; // デバッグ Debug.Log("攻撃終了"); } // 投げる void Throw() { // ボールの親オブジェクトをなくす m_ballObject.transform.parent = null; // ボールの物理演算を再開 m_ballObject.GetComponent().isKinematic = false; // ボールに力を加える m_ballObject.GetComponent().AddForce(transform.forward * 10.0f, ForceMode.Impulse); // しばらくボールを拾えない状態になる m_isBallCanGet = true; Invoke("BallCanGet", 1.0f); } // ボールを拾えるようにする void BallCanGet() { m_isBallCanGet = false; } } 【プログラムの解説】 ・Invoke関数 は指定した関数を一定秒数後に呼び出すことができる関数です。k2Engineではタイマーを設定するなどしていたと思いますが、Unityでは単純に一定秒数後に呼び出すだけならInvoke関数を使うと便利です。第一引数に関数名、第二引数に待機時間を設定します。 Invoke(呼び出す関数の名前,何秒後に呼び出すか); ※ 関数名で検索しているので名前間違いに注意! これでボールを投げることができるようになりました。実行して確認してみましょう。 EXの物理マテリアル を設定することでボールを弾ませることもできます。キャラクターに物を持たせたり、アニメーションイベントを使って様々な処理を実装してみてください。 【剣のモデル配布元】 https://assetstore.unity.com/packages/3d/props/weapons/long-sword-212082 【アニメーション配布元】 https://assetstore.unity.com/packages/3d/animations/rpg-character-mecanim-animation-pack-free-65284
- EX UIの機能 | Unity1gc2
LessonEX UIの機能 EX-1 Button UnityにはUIとして使える様々な機能(uGUI )があります。各機能の基本的な使い方を見ていきましょう。uGUIの機能は基本的にはマウス操作前提で、キーボードやゲームパッドで操作するにはスクリプトが必要になる点には注意しましょう。 ※ TextMeshProの導入は事前に終了している前提で進みます まずはUIに欠かせない「ボタン」です。マウスでクリックされた瞬間に何かアクションをさせたい場合に使えます。 「UI」→「Button」を選択してボタンを追加してください。
- 2Dランゲーム編 Lesson1「プレイヤーを実装しよう」 | Unity1gc2
2Dランゲーム編 Lesson1 プレイヤーを実装しよう 1-0 このパートで作るゲームについて 1-0 このパートで作るゲームについて この「2Dランゲーム編」では3Dアクションゲーム編では説明できなかった2Dゲームの作り方や、マウス入力の取得などに重点を置いて説明していきます。また、後からステージを追加したりできるように拡張性の高さも意識していきます。 2Dゲームを作ると言っても基本的な操作は3Dゲームと変わりません 。3Dアクションゲーム編の内容もちょくちょく出てくるので、わからない時は過去のレッスンを見返しながら進めていきましょう。 まずはどんなゲームかイメージできるように2Dランゲーム編のメインページ からサンプルゲームを遊んでみてください。 1-1 新規プロジェクトを作る 1-1 新規プロジェクトを作る まずは新しいプロジェクトを作り、素材をインポートして制作の準備をしましょう。 流れは3Dアクションゲーム編とほぼ同じです。 Unity Hub内の「新しいプロジェクト」をクリックしてください。 プロジェクト名は自分が分かりやすいものでOKです。 今回は2Dゲームを作るので「2D」を選択してください。 プロジェクトが作成できたら、Gameを選択して画面サイズを16:10に変更しておいてください。 素材の配布先(https://drive.google.com/file/d/1KOhAjE9LaF2U1d1DVoeqGcc19NVFcv1g/view?usp=sharing )からダウンロードしたパッケージをプロジェクト内にドラッグ&ドロップして、素材をインポートしてください。 Spriteフォルダが追加されたらOKです。 Scenes内に新しいシーン「Stage1」を作成して、ダブルクリックで切り替えてください。 このシーン内にゲームを作っていきます。 最初にあったSampleSceneは削除しても構いません。 Unity Tips! 今回配布した素材の配布元です。 配布用にカットした素材もたくさんありますので、ぜひダウンロードしてみてください。この作品に取り込むもよし、別のゲームを作るもよし! ・『コーゲンシティ・オールスターズ!』ユニティちゃんピクセルアートパック for アクションゲーム Vol.2 ( ユニティちゃんライセンス を守りましょう) ・ superpowers-asset-packs 今回インポートしてもらったスプライト素材は、ドット表現に適した表現になるように設定を変更しています。自分の好きな素材を使いたい場合は同じ設定にすることをオススメします。 ・Filter Modeを「Bilinear」から「Point(no filter)」に変更 ・Compressionを「Normal Quality」から「None」に変更 ※この設定はあくまでドット絵を使用するゲームに向けた設定 です。普通の画像を使うゲームには適していないので注意してください。 Unity Tips! 1-2 プレイヤーの準備 1-2 プレイヤーの準備 まずはプレイヤーの画像を追加しましょう。 「Sprite」→「Unitychan」→「BasicActions」から「Unitychan_Run_1」の画像をシーン上にドラッグ&ドロップしてください。 目的の画像が見つからない時は右下のスライダーを一番左へ持っていくと名前を一覧で確認できます。 配置したユニティちゃんの画像の座標を左下辺りに移動させて、少し大きくしてください。 (画像ではPosition X=-7 Y=-4 Z=0 ScaleはXY共に4) また、プレイヤーであることを識別できるようにPlayerタグをつけておきます。 MainCameraを選択してサイズを6にしておいてください。 ちょっとした小技を紹介! 値を入力できる項目名にマウスカーソルを合わせて、マウスをドラッグすると値を変更することができます。 大雑把に値を調整したい時に便利です。 ちなみにAltキーを押しながら操作すると小さく、Shiftキーを押しながら操作すると大きく値を変更できます。 Unity Tips! 3Dアクションゲーム編と同じようにプレイヤーに当たり判定と重力を加えましょう。 まずはユニティちゃんが落下するようにします。 ユニティちゃんを選択して、Add Componentから「RigidBody2D」を追加してください。2Dゲームでの物理演算は それぞれコンポーネントの最後に2Dがついている方を選ぶことに注意 しましょう。 RigidBody2Dの設定をします。 ・Linear Drag を1にします。 これは空気抵抗の値で、大きいほど物体が 動きにくくなっていきます。 0のままだと後ほど坂を実装する時にうまく 登れなくなってしまうので変更しておきましょう。 ・Angular Drag を0にします。 これは回転の空気抵抗ですが特に必要ないので 0にしておきましょう。 ・Gravity Scale を4にします。 これは名前の通り重力の強さです。 スピード感が出るように強めにしています。 ちなみにマイナスにすると上方向に 重力が かかるようになります。 ・Collision Detection をContinuousにします。 これはオブジェクトが高速でぶつかった際に すり抜けないようにする設定です。 初期値のDiscreteは処理が軽いですが、高速で 物にぶつかるとすり抜けることがあります。 Continuousに変更すると処理は重くなりますが すり抜けないようになります。 ・Freeze Rotation のZにチェックをいれます。 3Dアクションゲーム編と同じで、Z軸周りに 勝手に回転しないようになります。 (スクリプトでの回転は可能) この時点でゲームを実行するとユニティちゃんが落下していくことが確認できます。 次はユニティちゃんに当たり判定を追加しましょう。 ユニティちゃんを選択してAdd Componentから「Capsule Collider2D」を追加してください 。 当たり判定の位置と大きさを調整します。 Offsetは X=0 Y=0.22、 SizeはX=0.2 Y=0.42 に調整してください。 ユニティちゃんにカプセル型の当たり判定がついたらOKです。 このままでは落下するだけなので、仮の地面を追加しましょう。 ヒエラルキーから「2D Object」→「Sprites」→「Square」を選択して、シーン上に四角いスプライトを追加してください。 追加した地面にユニティちゃんが着地できるように、位置と大きさを調整しましょう。 この地面は後で削除するので適当で構いません。 3Dアクションゲーム編の時は地面に最初からBoxColliderがアタッチされていましたが、スプライトにはColliderがアタッチされていません。 Add Componentから「Box Collider2D」を選択して、地面に当たり判定を追加してください。 ここまでできたらゲームを実行して、ユニティちゃんに重力と当たり判定が適用されていることを確認してみてください。 1-3 プレイヤーを常に右へ移動させる 1-3 プレイヤーを常に右へ移動させる このゲームはゲームオーバーやクリアなど特殊な状況でない限り、プレイヤーは常に右へ移動し続けます。プレイヤーが右へ移動し続けるようにしましょう。 Scriptフォルダを作成して、その中に新しいスクリプトPlayerMoveを追加してください。 PlayerMoveを開いて、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMove : MonoBehaviour { public float MoveSpeed = 8.0f; void Start() { } // Fixedなので注意! void FixedUpdate() { // 常に右へ移動する Vector3 move = Vector3.zero; move.x = MoveSpeed * Time.deltaTime; transform.Translate(move); } void Update() { } } FixedUpdateはUpdateより先に実行される関数です。 FixedUpdateに移動処理を書き、後の1-4でUpdateにカメラの更新処理を書くことで、処理の実行順が移動→カメラになります。 こうすることによってカメラの位置を変更した後にプレイヤーが移動することがなくなり、画面内のプレイヤーの位置を常に同じにすることができます。 Unity Tips! スクリプトが書けたら保存して、PlayerMoveスクリプトをユニティちゃんにアタッチするのを忘れないようにしましょう。 アタッチできたら実行して、ユニティちゃんが右へ移動することを確認してみてください。 例のごとくMoveSpeed変数がインスペクターに表示されているので、お好みで調整してみてください。速いほど爽快感は出ますが、その分難しくなるので注意しましょう。 1-4 カメラをプレイヤーに追尾させる 1-4 カメラをプレイヤーに追尾させる ジャンプの実装をする前に、カメラがプレイヤーについてくるようにしましょう。 今回カメラに実装する処理は、 ・プレイヤーが左下になるように座標を調整する ・一定範囲外に移動しないようにする(ステージの外が見えないように) といったものになります。 Scriptフォルダ内にGameCameraスクリプトを作成してください。 GameCameraスクリプトに以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameCamera : MonoBehaviour { GameObject m_player; [SerializeField] Vector3 CameraAddPos = Vector2.zero; [SerializeField] Vector2 CameraMaxPos = Vector2.zero; [SerializeField] Vector2 CameraMinPos = Vector2.zero; void Start() { // Playerタグのついたオブジェクトをターゲットにする m_player = GameObject.FindGameObjectWithTag("Player"); // 最初に座標更新しておく CameraUpdate(); } void Update() { // プレイヤーがいないなら何もしない if (m_player == null) { return; } // 座標の更新 CameraUpdate(); } void CameraUpdate() { // カメラの位置を設定 transform.position = m_player.transform.position + CameraAddPos; // 座標がオーバーしないように調整 Vector3 cameraPos = transform.position; cameraPos.x = Mathf.Clamp(cameraPos.x, CameraMinPos.x, CameraMaxPos.x); cameraPos.y = Mathf.Clamp(cameraPos.y, CameraMinPos.y, CameraMaxPos.y); transform.position = cameraPos; } } ・[SerializeField] というコードがありますが、これはAttribute(アトリビュート)と呼ばれるもので変数に属性を付与することができます。publicやprivate(アクセス指定子)とは異なり、アトリビュートはインスペクター上での属性を付与するものが主です。 変数をpublicにするとインスペクター上に表示できますが、publicにするということはどこからでもアクセスできてしまう…つまり外部から簡単に変更できてしまうということになります。変数をprivateにしつつインスペクター上に値を表示したい時は、変数の前に[SerializeField] という属性を付与することで実現することができます。 アトリビュートについてはEXでも解説しているので参考にしてみてください。 「アクセスがどうとかよくわかんないよ!」という人は気にしなくてOKです。 【プログラムの解説】 ・Mathf.Clamp関数は3Dアクションゲーム編でも何度か登場しましたが、変数を上限と下限の範囲内に収めてくれる関数です。これによってカメラが上下左右の範囲からはみ出ないようにしています。 ここまでコードが書けたら保存して、MainCameraにアタッチしてください。 インスペクター上に先ほど[SerializeField]を付与した変数が表示されています。publicにしていなくても、この属性を付与した変数はインスペクター上に表示できることを覚えておいてください。 インスペクターからそれぞれのパラメータを調整してください。 ・CameraAddPos … プレイヤーを中心としたカメラの移動量 X=7 Y=1 Z=-10 ・CameraMaxPos … カメラが移動できる最大座標 X=500 Y=19(ステージの大きさによって自由に変えてOK) ・CameraMinPos … カメラが移動できる最小座標 X=0 Y=0(ステージの大きさによって自由に変えてOK ) 設定できたら実行して、カメラがユニティちゃんを追尾すること、ユニティちゃんが落下してもカメラはついていかないことを確認してみてください。 これで追尾カメラの実装は終了です 。 1-5 ジャンプの実装 1-5 ジャンプの実装 ユニティちゃんがジャンプできるようにしましょう。 基本的な処理は3Dアクションゲーム編と同じですがいくつか違う点があります。 ・SPACEキーではなく、左クリックでジャンプする ・空中でも指定した回数までジャンプできるようにする まずは接地判定用の空オブジェクトをユニティちゃんの子オブジェクトとして追加してください。 オブジェクト名は自分が分かりやすいもので構いません。 接地判定用オブジェクトにBoxCollider2Dをアタッチして、位置やサイズを調整してください。 IsTriggerにチェックを入れるのも忘れないようにしましょう。 【教材での設定】 Offset X=0 Y=0.04 Size X=0.1 Y=0.1 ユニティちゃんの足元に箱型の当たり判定が追加されたらOKです。 Scriptフォルダ内にGroundCheckスクリプトを作成して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GroundCheck : MonoBehaviour { // 接地しているかを格納する変数 bool m_isGround; // 地面に触れているかを返す関数 public bool GetIsGround() { return m_isGround; } // 毎フレーム最初に接地判定をリセットする private void FixedUpdate() { m_isGround = false; } // 2Dがつくので注意! private void OnTriggerStay2D(Collider2D collision) { // 地面のタグが付いたオブジェクトに衝突している if (collision.CompareTag("Ground")) { m_isGround = true; } } } 【プログラムの解説】 ・3DではOnTriggerStay関数を使っていましたが、2DではOnTriggerStay2D 関数を使います。間違えないように注意しましょう。関数名は違いますが使い方はほとんど同じです。 次は接地判定をするために地面にGroundタグをつけましょう。 Add Tagから「Ground」タグを作成して、地面にタグを設定してください。 (タグは名前が完全一致するかどうかで検索しているので打ち間違いに注意) 地面にタグを設定できたら、PlayerMoveスクリプトを開いて赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMove : MonoBehaviour { public float MoveSpeed = 8.0f; public float JumpPower = 16.0f; [SerializeField] // privateでもインスペクターには表示される変数 GroundCheck m_groundCheck; [Header("空中ジャンプできる回数")] public int MaxAirJump = 1; int m_airJumpCount = 0; // 2Dなので注意! Rigidbody2D m_player_rb2d; void Start() { // 自身にアタッチされているRigidBody2Dを取得する m_player_rb2d = GetComponent(); // 空中ジャンプ回数を初期化 m_airJumpCount = MaxAirJump; } // Fixedなので注意! void FixedUpdate() { // 常に右へ移動する Vector3 move = Vector3.zero; move.x = MoveSpeed * Time.deltaTime; transform.Translate(move); } void Update() { // 接地しているなら空中ジャンプ回数をリセット if (m_groundCheck.GetIsGround()) { m_airJumpCount = MaxAirJump; } // 左クリックされた時にジャンプ if (Input.GetMouseButtonUp(0) && m_airJumpCount > 0) { if (m_groundCheck.GetIsGround()) { Jump(); } else if (m_airJumpCount > 0) { m_airJumpCount--; Jump(); } } } // ジャンプ void Jump() { // 加わっている力を一旦リセット m_player_rb2d.velocity = Vector2.zero; // 上方向に力を加える m_player_rb2d.AddForce(new Vector2(0.0f, JumpPower), ForceMode2D.Impulse); } } 【プログラムの解説】 ・[Header("表示したい文")] は前述したアトリビュートの一種です。この文を書くことでインスペクター内に文章を表示することができます。日本語も使えます。インスペクターを見やすく整理するためにぜひ使ってみてください。 ・Input.GetMouseButtonUp(0) は「マウスが左クリックされて離された瞬間」にtrueを返します。 他にもGetMouseButton で「押されている間」、GetMouseButtonDown で「押された瞬間」を検知できます。 また、引数の0はマウスの左ボタンを表しており、1で右ボタン、2で中ボタンを指定できます。 ・RigidBodyのメンバ変数であるvelocityは、現在かかっている力を格納する変数です。空中でジャンプしようとした時、オブジェクトには落下する力(重力)がかかっていますが、それを一旦リセットすることで空中でも地上と同じようにジャンプできるようにしています。 よくわからない人は該当するコードをコメントアウトして違いを確かめてみてください。空中でのジャンプに重力がかかって思うように飛べないと思います。 インスペクター内にGroundCheckを格納する変数が表示されているので、GroundCheckerオブジェクトをドラッグ&ドロップしてください。 GroundCheckの関連付けができたらゲームを実行して、クリックでジャンプできることを確認してみてください。 今ある地面のオブジェクトをコピーして簡単なステージを作り、空中でも1回ジャンプできることを確認してみましょう。 プレイヤーのMoveSpeed(移動速度)やJumpPower(ジャンプ力)、MaxAirJamp(空中でジャンプできる回数)もお好みで調整してみてください。 1-6 坂道への対応 1-6 坂道への対応 今のままでもランゲームは作れますが、上り坂や下り坂も使えるようにして、よりゲームの自由度を上げてみましょう。ここでは 少し複雑な話も出てきますが100%理解する必要はありません。 まずは適当に斜め45°の坂道を作ってください。2DゲームではZ軸周り に回転させることで、オブジェクトをそのまま回転させることができます。 そこから横、右下へ伸ばして上り坂と下り坂を作ってみましょう。できるだけ段差ができないようにしてください(完璧じゃなくても大丈夫です) ここで試しにゲームを実行してみると、ユニティちゃんが坂道を登れません。坂を登ろうとしても重力によって落ちていってしまいます。とはいえ、単純に重力を消してしまうとジャンプができなくなってしまいます。 この問題を解決するためには「前方に坂があることを検知」して「坂に触れている間は重力を無視する」システムを実装する必要があります。 前方に坂があることを検知するためには「法線 」と「レイ」を使う必要があります。 【以下の法線の説明は少し上級者向けです】 ポリゴンには法線ベクトル というものがあります。詳しい説明はDirectXなどでも していると思いますが、簡単に言うと面に垂直なベクトルのことです。 (画像はWikipedia から。青い矢印が法線ベクトル) 3Dに限らず、2Dにも法線が存在します。この画面だと平地の法線は真上に、上り坂の法線は左上に伸びています。ユニティちゃんの前方の法線が取得できれば、上り坂を登ろうとしているかどうか検知できるという訳です。 そして前方の法線を取得する時にRay(レイ) という機能を使います。 Rayは「光線」のような意味で、文字通り光線を発射して、衝突したオブジェクトの情報を取得することができる機能です。 ユニティちゃんの足元から右下へレイを発射して、衝突した場所の法線を取得します。そして取得した法線と上ベクトルの成す角度を使って坂道を登ろうとしていることを検知します。 また、この機能を使えばユニティちゃんが壁にぶつかったかどうかも検知できます。 壁にぶつかった時はゲームオーバーにしたいので、坂道と同時にゲームオーバー判定も行うことができます。 高度な話が出てきましたが理解できなくてもOKです。よくわからなかった人は「足元から光線を飛ばして坂道かどうか検知してるんだなー」程度に捉えておいてください。 それでは実際に画像の①のように、ユニティちゃんの足元からレイを飛ばしてみましょう。 ユニティちゃんの子オブジェクトに、レイの始点になる空オブジェクトを追加してください。オブジェクト名は何でもOKですが、教材では「RayPoint」にしておきます。 RayPointの座標をユニティちゃんの右下あたりへ調整します。レイの長さとの兼ね合いで、教材と位置が違うとうまく判定できないことがあるので位置をしっかり合わせてください。 【教材での設定】 Position X=0.1 Y=0.07 RayPointを始点にレイを飛ばしてみましょう。 PlayerMoveスクリプトを開いて赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMove : MonoBehaviour { public float MoveSpeed = 8.0f; public float JumpPower = 16.0f; [SerializeField] // privateでもインスペクターには表示される変数 GroundCheck m_groundCheck; [Header("空中ジャンプできる回数")] public int MaxAirJump = 1; int m_airJumpCount = 0; // 2Dなので注意! Rigidbody2D m_player_rb2d; [SerializeField, Header("レイの始点")] GameObject m_rayOrigin; void Start() { // 自身にアタッチされているRigidBody2Dを取得する m_player_rb2d = GetComponent(); // 空中ジャンプ回数を初期化 m_airJumpCount = MaxAirJump; } // Fixedなので注意! void FixedUpdate() { // 常に右へ移動する Vector3 move = Vector3.zero; move.x = MoveSpeed * Time.deltaTime; transform.Translate(move); } void Update() { // 接地しているなら空中ジャンプ回数をリセット if (m_groundCheck.GetIsGround()) { m_airJumpCount = MaxAirJump; } // 左クリックされた時にジャンプ if (Input.GetMouseButtonUp(0) && m_airJumpCount > 0) { if (m_groundCheck.GetIsGround()) { Jump(); } else if (m_airJumpCount > 0) { m_airJumpCount--; Jump(); } } // 坂道 Slope(); } // ジャンプ void Jump() { // 加わっている力を一旦リセット m_player_rb2d.velocity = Vector2.zero; // 上方向に力を加える m_player_rb2d.AddForce(new Vector2(0.0f, JumpPower), ForceMode2D.Impulse); } // 坂道判定 void Slope() { RaycastHit2D raycastHit2D; Vector3 origin = m_rayOrigin.transform.position; // レイの始点 Vector2 direction = -transform.up; // 下に伸びるベクトル direction = Quaternion.Euler(0.0f, 0.0f, 45.0f) * direction; // 45°回転させる float distance = 1.0f; // レイの長さ // レイ(光線)を発射 raycastHit2D = Physics2D.Raycast( origin, direction, distance ); // デバッグ Debug.DrawRay(origin, direction * distance, Color.green, 0.05f, false); Debug.Log(raycastHit2D.normal); } } 【プログラムの解説】 ・[SerializeField, Header("レイの始点")] のように,(カンマ)で区切ることで複数のアトリビュートを同時に使用することができます。 ・RaycastHit2Dは2Dのレイを発射して得た情報を格納するクラスです。3DではRaycastHitを使用します。 ・direction変数でレイを発射する方向を決めています。 -transform.up で自身の上方向へ伸びるベクトルの逆、下方向へ伸びるベクトルを取得しています。 ・Quaternion.Euler関数で任意の軸周りにベクトルを回転させています。これによって右下へ伸びるベクトルを取得しています。 ・Physics2D.Raycast関数で2Dのレイを発射することができます。 第一引数にレイを発射する始点、第二引数にレイを発射する方向のベクトル、第三引数にレイの長さを指定します。 発射したレイが当たったオブジェクトの情報が、RaycastHit2D型の戻り値として返ってきます。 ・Debug.DrawRayはデバッグ用の関数で、レイを描画することができます(実際のゲーム画面には描画されません) 第一引数にレイの始点、第二引数にレイの方向と長さ(乗算すればOK)、第三引数にレイの色、 第四引数はレイが描画される時間、第五引数はレイがオブジェクトに隠れた時に描画するかどうかの設定です。 ・Debug.Log(raycastHit2D.normal); でレイがヒットした場所の法線をコンソールに出力しています(normalは法線のことです) ここまで書けたら保存して、インスペクターにレイを発射する始点となるオブジェクトを設定しましょう。 先ほど作成したRayPointオブジェクトをドラッグ&ドロップしてください。 自分が発射したレイがユニティちゃん自身に当たらないように設定しておきましょう。 ユニティちゃんを選択して、Layerを「Ignore RayCast」に変更してください。このレイヤーに設定したオブジェクトはレイが当たらなくなります。今後も坂道判定に引っかかってほしくない当たり判定にはこの設定をしていくことになります。 ユニティちゃんの子オブジェクトのレイヤーも変更するか訊かれるのでYesを選択してください。Layerが変更できたら設定は完了です。 ここまで設定できたらゲームを実行して、実行中に一時停止ボタンを押してシーンを確認してみてください(Ctrl+Shift+Pでも可) ユニティちゃんの足元から緑色のレイが発射されていることが確認できます。 これで坂道に衝突した瞬間を判定できるようになりました。 後はこの法線の情報を使って坂道用の処理を実装しましょう。 PlayerMoveスクリプトに以下の赤い部分のコード を追加してください。結構長いコードになりますが頑張りましょう(難しかったらコピー&ペーストでOKです) using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMove : MonoBehaviour { public float MoveSpeed = 8.0f; public float JumpPower = 16.0f; [SerializeField] // privateでもインスペクターには表示される変数 GroundCheck m_groundCheck; [Header("空中ジャンプできる回数")] public int MaxAirJump = 1; int m_airJumpCount = 0; // 2Dなので注意! Rigidbody2D m_player_rb2d; [SerializeField, Header("レイの始点")] GameObject m_rayOrigin; // 坂に触れているかどうか bool m_isSlope = false; // 坂道かつ空中にいるかどうか bool m_isSlopeAir = false; // 重力の初期値を保存 float m_defGravity = 0.0f; // 角度変更タイマー float angleTimer = 0.0f; const float ANGLE_LIMIT = 0.1f; void Start() { // 自身にアタッチされているRigidBody2Dを取得する m_player_rb2d = GetComponent(); // 空中ジャンプ回数を初期化 m_airJumpCount = MaxAirJump; // 重力の初期値を保存 m_defGravity = m_player_rb2d.gravityScale; } // Fixedなので注意! void FixedUpdate() { ~中略~ // 坂道判定 void Slope() { // タイマー減少 if (angleTimer > 0.0f) { angleTimer -= Time.deltaTime; } RaycastHit2D raycastHit2D; Vector3 origin = m_rayOrigin.transform.position; // レイの始点 Vector2 direction = -transform.up; // 下に伸びるベクトル direction = Quaternion.Euler(0.0f, 0.0f, 45.0f) * direction; // 45°回転させる float distance = 0.02 f; // レイの長さ // レイ(光線)を発射 raycastHit2D = Physics2D.Raycast( origin, direction, distance ); // デバッグ //Debug.DrawRay(origin, direction * distance, Color.green, 0.05f, false); //Debug.Log(raycastHit2D.normal); // 下り坂判定 if (raycastHit2D.collider == null) { // 前に何もないなら後ろを判定 direction = -transform.up; direction = Quaternion.Euler(0.0f, 0.0f, -45.0f) * direction; distance = 0.7f; // レイは少し長め raycastHit2D = Physics2D.Raycast( origin, direction, distance ); //Debug.DrawRay(origin, direction * distance, Color.green, 0.05f, false); } // なす角度を計算 float angle = Vector3.SignedAngle(Vector3.up, raycastHit2D.normal, Vector3.forward); //Debug.Log(angle); // 角度に合わせてプレイヤーを回転させる if (Mathf.Abs(angle) < 60.0f && angleTimer <= 0.0f) { transform.rotation = Quaternion.AngleAxis(angle, new Vector3(0, 0, 1)); } // 角度を元に判定 if (Mathf.Abs(angle) < 60.0f && Mathf.Abs(angle) > 20.0f) { // 坂に接地中 if (m_isSlope == false) { m_player_rb2d.velocity = Vector2.zero; m_isSlope = true; // 回転タイマー開始 angleTimer = ANGLE_LIMIT; } // 坂道に接地中は重力を無視する if (m_groundCheck.GetIsGround()) { m_player_rb2d.gravityScale = 0.0f; if (m_isSlopeAir) { m_player_rb2d.velocity = Vector2.zero; m_isSlopeAir = false; } } else { m_player_rb2d.gravityScale = m_defGravity; m_isSlopeAir = true; } } else if (angle >= 60.0f) { // 壁に衝突 GameOver(); } else { // 普通の地面 if (m_isSlope == true) { m_player_rb2d.gravityScale = m_defGravity; m_isSlope = false; m_isSlopeAir = false; // 回転タイマー開始 angleTimer = ANGLE_LIMIT; } } } // ゲームオーバー public void GameOver() { // とりあえず今は自身を削除するだけ Debug.Log("ゲームオーバー!"); Destroy(gameObject); } } 【プログラムの解説】 コードが長いので簡単な説明にしておきます。 ・ANGLE_LIMIT(角度変更の間隔)はユニティちゃんの角度が変更される間隔です。 間隔を設けないと坂道と平地の境目でガタガタしてしまうため、制限を設けています。 ・const を付けた変数は定数として扱われ、プログラムの実行中に値を変更することができなくなります。 ・レイの長さを1.0fから0.06fに短く変更しているので注意してください(ジャンプとの兼ね合いのため) ・前にレイを飛ばしてヒットしなかった場合、後ろにレイを飛ばして下り坂の判定をしています。 実際にどのようにレイが飛んでいるか確認したい場合は、下り坂判定内のコメントアウトしているDrawRay関数を実行してみてください。 ・Vector3.SignedAngle関数によって上方向のベクトルと取得した法線をつかって成す角度を計算しています。実際にどのような戻り値が返ってきているのかはコメントアウトしている Debug.Log(angle); を使って確認してみてください。 ・Mathf.Absは絶対値を返す関数 です。成す角度は上り坂は正の数、下り坂は負の数になっているので絶対値を計算することで「上り坂か下り坂かは関係なく、角度がついている時」を求めています。 ・Quaternion.AngleAxis関数で任意軸周りにオブジェクトを回転させることができます。 ・教材では20°以上60°未満の傾斜を坂道として扱っています。この値はお好みで変更しても構いません。 ・レイが60°以上の傾斜に衝突した際は壁にぶつかったと判定してゲームオーバー処理を実行しています。現時点ではゲームオーバー処理は仮実装で、自身を削除してログを出力しているだけです。 複雑な処理に見えますが、実際にやっていることは比較的シンプルなのでわかる範囲で理解してもらえればOKです。 保存してからゲームを実行して、坂道での判定が取れていることを確認してみてください。 うまく動かない時はコードを見返してみてください。 (上り坂でジャンプすると常に前進している関係上すぐに着地してしまいます。気になる方はJumpPowerを大きめに調整してください) また、壁に衝突するとゲームオーバーになることも確認してみてください。 冒頭でも説明しましたが全部は理解しなくてもOKです。坂道の挙動は人によって好みがあると思いますので、調整できそうな人はパラメータや処理を色々調整してみてください。 【参考程度に教材でのパラメータ設定】 ・Gravity Scale=6 ・MoveSpeed=6 ・JumpPower=24 1-7 プレイヤーのアニメーション 1-7 プレイヤーのアニメーション ここまでで基本の挙動は実装できたので、最後にアニメーションを実装しましょう。 Animatonフォルダを追加して、Runのアニメーションを作成してください。 作成できたらインスペクター内のユニティちゃんにドラッグ&ドロップしてください。自動でAnimatorControllerが作成されます。 RunアニメーションをダブルクリックしてAnimatonウィンドウを開いてください。 Animatonウィンドウは見やすい場所へ移動させて構いません。 Animatonウィンドウが開いたら、インスペクター内のユニティちゃんをクリックしてください。 「Add Property」のボタンがクリックできるようになったらOKです。 プロジェクト内の「Sprite」→「UnityChan」→「BasicActions」を選択して、ユニティちゃんの画像をAnimaton内に配置していってください。 教材では4フレームごとにRun1→2→3→4→5→6→1 と配置しています。 (最後に1を置くのはRun6が4フレーム間表示されるようにするため) ウィンドウ配置を調整して、Runアニメーションがどのように再生されるか確認してみてください。アニメーションは自由に調整してもOKです 。 同じように必要なアニメーションを設定していきましょう。枚数が多いため少し大変ですが、画像を参考に配置してください。 「Create New Clip」を選択してください。 Animatonフォルダ内に「Jump_Ground」アニメーションを追加してください。 以降のアニメーションも4フレームごとに配置していきます。 Jump_Groundアニメーションは、 Jump_Landing→Jump_MidAir_1→Jump_MidAir_2→Jump_MidAir_3 のように配置してください。 Jump_AirStartアニメーションを追加してください。空中ジャンプに入る瞬間だけ再生するアニメーションになります。 Jump_AirStartアニメーションは、0フレーム目と4フレーム目に Jump_Landingを配置してください。 Jump_Airアニメーションを追加してください。空中ジャンプ中に再生するアニメーションになります。 Jump_Airアニメーションは、 Jump_Up_1→Jump_Up_2→Jump_Up_1 のように配置してください。 Jump_Fallアニメーションを追加してください。落下中に再生するアニメーションになります。 Jump_Fallアニメーションは、 Jump_Fall_1→Jump_Fall_2→Jump_Fall_1 のように配置してください。 GameOverアニメーションを追加してください。 GameOverアニメーションは0フレーム目にDamage_4を配置するだけでOKです。 Clearアニメーションを追加してください。 Clearアニメーションは、 Positive_1→Positive_2→Positive_3→Positive_4→Positive_5→Positive_6 のように配置してください。 Positiveの画像はBasicActionsではなくExtraActionsフォルダ内にあるので注意してください。 ここまでで7種類のアニメーションが作成できていればOKです。 アニメーションのループ設定をします。 アニメーションを選択してLoopTimeのチェックを操作してください。 【チェックあり】 GameOver、Jump_Air、Jump_Fall、Run 【チェックなし】 Clear、Jump_AirStart、Jump_Ground AnimatorControllerをダブルクリックしてAnimatorを開いてください。先ほど作ったアニメーションステートが並んでいると思います。 ステートを見やすいように並べてください。 Transition(遷移)を画像のように設定してください。 3Dアクションゲーム編に比べて複雑なので間違えないように注意しましょう。 ClearとGameOverは後のLessonで設定するので、今はどこにも繋がなくてOKです。 【余計わかりにくい気がするけど設定を文章化しました】 ・Runから繋ぐステート Jump_Ground、Jump_AirStart、Jump_Fall ・Jump_Groundから繋ぐステート Run、Jump_AirStart、Jump_Fall ・Jump_AirStartから繋ぐステート Jump_Air ・Jump_Airから繋ぐステート Run、Jump_Fall ・Jump_Fallから繋ぐステート Run、Jump_AirStart 遷移条件を設定する前にパラメータを作成しましょう。 Bool型のパラメータ「JumpFlag」「AirJumpFlag」「FallFlag」を追加してください。 遷移条件を設定していきます。 項目は多いですが、Jump_AirStart→Jump_Air以外の設定は遷移条件以外同じです。 ・Has Exit Timeのチェックを外す ・Transition Durationを0にする ・遷移条件を設定する 【Runから繋ぐトランジション】 【Jump_Ground から繋ぐトランジション】 【Jump_AirStart から繋ぐトランジション】 ※ここだけ他と設定が違うので注意! Jump_AirStartはJump_Airに入る前準備のようなステートで、アニメーションの再生が終わったら自動的にJump_Airに遷移するようになっています。 【Jump_Air から繋ぐトランジション】 【Jump_Fall から繋ぐトランジション】 同時に複数条件を満たした場合の遷移先の優先度を設定することができます。 ステートのインスペクター内のTransitionsで、上に設定されているTransitionが優先 されます。ドラッグ&ドロップで並べ替えてください。 【Run】 ・Run -> Jump_AirStart ・Run -> Jump_Ground ・Run -> Jump_Fall 【Jump_Ground】 ・Jump_Ground -> Jump_AirStart ・ Jump_Ground -> Jump_Fall ・ Jump_Ground -> Run 【Jump_Air】 ・Jump_Air -> Jump_Fall ・ Jump_Air -> Run 【Jump_Fall 】 ・Jump_Fall -> Jump_AirStart ・Jump_Fall -> Run これでAnimatorControllerの設定は完了です。 最後にPlayerMoveスクリプト内で適切なタイミングでパラメータを切り替えましょう。 PlayerMoveスクリプトを開いて赤い部分のコード を追加してください。 ※ Jump関数に引数が追加されています ※ パラメータをリセットするAnimationReset関数が一番最後にあるので注意! using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMove : MonoBehaviour { public float MoveSpeed = 8.0f; public float JumpPower = 16.0f; [SerializeField] // privateでもインスペクターには表示される変数 GroundCheck m_groundCheck; [Header("空中ジャンプできる回数")] public int MaxAirJump = 1; int m_airJumpCount = 0; // 2Dなので注意! Rigidbody2D m_player_rb2d; [SerializeField, Header("レイの始点")] GameObject m_rayOrigin; // 坂に触れているかどうか bool m_isSlope = false; // 坂道かつ空中にいるかどうか bool m_isSlopeAir = false; // 重力の初期値を保存 float m_defGravity = 0.0f; // 角度変更タイマー float angleTimer = 0.0f; const float ANGLE_LIMIT = 0.1f; // アニメーションコントローラー Animator m_animator; // アニメーション管理用フラグ bool m_airFlag; void Start() { // 自身にアタッチされているRigidBody2Dを取得する m_player_rb2d = GetComponent(); // 自身にアタッチされているAnimatorを取得する m_animator = GetComponent(); // 空中ジャンプ回数を初期化 m_airJumpCount = MaxAirJump; // 重力の初期値を保存 m_defGravity = m_player_rb2d.gravityScale; } // Fixedなので注意! void FixedUpdate() { // 常に右へ移動する Vector3 move = Vector3.zero; move.x = MoveSpeed * Time.deltaTime; transform.Translate(move); } void Update() { // 接地しているなら空中ジャンプ回数をリセット if (m_groundCheck.GetIsGround()) { m_airJumpCount = MaxAirJump; } // 左クリックされた時にジャンプ if (Input.GetMouseButtonUp(0) && m_airJumpCount > 0) { if (m_groundCheck.GetIsGround()) { Jump(false); } else if (m_airJumpCount > 0) { m_airJumpCount--; Jump(true); } } // 坂道 Slope(); // 落下アニメーション切り替え if (m_groundCheck.GetIsGround() == false && m_player_rb2d.velocity.y < -9.8f) { AnimationReset(); m_animator.SetBool("FallFlag", true); } // ジャンプアニメーション切り替え if (m_groundCheck.GetIsGround() == false || Input.GetKeyDown(KeyCode.Space)) { m_airFlag = true; } if (m_airFlag && m_groundCheck.GetIsGround() && m_player_rb2d.velocity.y == 0.0f) { m_airFlag = false; // アニメーションも戻す AnimationReset(); } } // ジャンプ void Jump(bool airJump ) { // 加わっている力を一旦リセット m_player_rb2d.velocity = Vector2.zero; // 上方向に力を加える m_player_rb2d.AddForce(new Vector2(0.0f, JumpPower), ForceMode2D.Impulse); // アニメーション m_animator.SetBool("FallFlag", false); if (airJump) { m_animator.SetBool("AirJumpFlag", true); } else { m_animator.SetBool("JumpFlag", true); } } // 坂道判定 void Slope() { // タイマー減少 if (angleTimer > 0.0f) { angleTimer -= Time.deltaTime; } RaycastHit2D raycastHit2D; Vector3 origin = m_rayOrigin.transform.position; // レイの始点 Vector2 direction = -transform.up; // 下に伸びるベクトル direction = Quaternion.Euler(0.0f, 0.0f, 45.0f) * direction; // 45°回転させる float distance = 0.02f; // レイの長さ // レイ(光線)を発射 raycastHit2D = Physics2D.Raycast( origin, direction, distance ); // デバッグ //Debug.DrawRay(origin, direction * distance, Color.green, 0.05f, false); //Debug.Log(raycastHit2D.normal); // 下り坂判定 if (raycastHit2D.collider == null) { // 前に何もないなら後ろを判定 direction = -transform.up; direction = Quaternion.Euler(0.0f, 0.0f, -45.0f) * direction; distance = 0.7f; // レイは少し長め raycastHit2D = Physics2D.Raycast( origin, direction, distance ); //Debug.DrawRay(origin, direction * distance, Color.green, 0.05f, false); } // なす角度を計算 float angle = Vector3.SignedAngle(Vector3.up, raycastHit2D.normal, Vector3.forward); //Debug.Log(angle); // 角度に合わせてプレイヤーを回転させる if (Mathf.Abs(angle) < 60.0f && angleTimer <= 0.0f) { transform.rotation = Quaternion.AngleAxis(angle, new Vector3(0, 0, 1)); } // 角度を元に判定 if (Mathf.Abs(angle) < 60.0f && Mathf.Abs(angle) > 20.0f) { // 坂に接地中 if (m_isSlope == false) { m_player_rb2d.velocity = Vector2.zero; m_isSlope = true; // アニメーションも戻す AnimationReset(); // 回転タイマー開始 angleTimer = ANGLE_LIMIT; } // 坂道に接地中は重力を無視する if (m_groundCheck.GetIsGround()) { m_player_rb2d.gravityScale = 0.0f; if (m_isSlopeAir) { m_player_rb2d.velocity = Vector2.zero; m_isSlopeAir = false; } } else { m_player_rb2d.gravityScale = m_defGravity; m_isSlopeAir = true; } } else if (angle >= 60.0f) { // 壁に衝突 GameOver(); } else { // 普通の地面 if (m_isSlope == true) { m_player_rb2d.gravityScale = m_defGravity; m_isSlope = false; m_isSlopeAir = false; // 回転タイマー開始 angleTimer = ANGLE_LIMIT; } } } // ゲームオーバー public void GameOver() { // とりあえず今は自身を削除するだけ Debug.Log("ゲームオーバー!"); Destroy(gameObject); } void AnimationReset() { // アニメーションのパラメータをリセットする m_animator.SetBool("JumpFlag", false); m_animator.SetBool("AirJumpFlag", false); m_animator.SetBool("FallFlag", false); } } PlayerMoveスクリプトが非常に長くなってしまいましたが、基本的な処理はこれで完成です(プレイヤーのスクリプトはどうしても長くなってしまうものですが…) うまく動かない場合はコピー&ペーストしても構いませんが、【プログラムの解説】 はしっかり読んでおいてください。 コードが書けたら保存して、アニメーションが再生できていることを確認してみてください。 プレイヤーが完成したので次はステージを作っていきましょう。 【評価テスト】 https://forms.gle/nTjX8eCvsnExYdw99 評価テスト Next Lesson2「ステージを作ろう」 ページ TOP 1-0 このパートで作るゲームについて 1-1 新規プロジェクトを作る 1-2 プレイヤーの準備 1-3 プレイヤーを常に右へ移動させる 1-4 カメラをプレイヤーに追尾させる 1-5 ジャンプの実装 1-6 坂道への対応 1-7 プレイヤーのアニメーション 評価テスト
- リンクQ&A | Unity1gc2
リンクQ&A 場所がわかりにくそうな内容へのサイト内リンクを置いたり、説明が大変な内容を外部サイトに誘導して誤魔化すためのページです。 【エラー】 ・Visual Studioの予測変換がおかしい! ・スクリプトをアタッチする時にエラーが出る! ・デバッグの方法がわからない… ・OnCollisionEnterやOnTriggerStayが動かない ・WebGL出力がうまくいかない! 【ヒント】 ・HPバーの作り方が知りたい ・Buttonの使い方が分からない 【豆知識】 ・作ったゲームを楽に公開したい! 【おすすめサイト】 ・シェーダー関連 ・パーティクル関連 【エラー】 VisualStudio VisualStudioでGameObjectやTransformなどの予測変換が出ない場合は、 「UnityとVisualStudioの関連付けについて」 を参照してください。 Visual Studioの予測変換がおかしい! スクリプトをアタッチ スクリプトをアタッチしようとすると Can't add script というエラーが出る場合は3Dアクションゲーム編1-3 を確認してください。 スクリプトをアタッチする時にエラーが出る! ゲームがうまく動かないというときは「何のコードを書いたらおかしくなったか 」をまず確認しましょう。 次はそこにブレークポイントを置いてその処理が本当に実行されているか確認したり、値をDebug.Logで出力しておかしな値になっていないか確認しましょう(3Dアクションゲーム編3-3参照 ) プログラムは原則上から順番に実行 されます。関数をたくさん使っているとややこしくなりがち。実行順にも気を付けましょう。 よくあるエラー文についてはLessonEXで解説 していますが、ここで解説していないエラー文が出た場合はそれをコピペして検索 したり、翻訳にかけてみましょう 。 デバッグの方法がわからない… デバッグの方法がわからない… OnCollisionEnter や OnTriggerStay が動かない OnCollisionEnterなどの当たり判定用の関数は最低でもどちらかのオブジェクトにRigidbodyがアタッチされていないと動作しません 。 Rigidbodyをアタッチしてみましょう。 OnCollisionEnter や OnTriggerStay が動かない WebGL出力がうまくいかない! WebGLでブラウザゲームを出力した際に、 Unable to parse Build/〇〇〇.framework.js.gz! というエラーが出てしまう場合、こちらのサイト を参考にしてください。 WebGL出力がうまくいかない! 【ヒント】 HPバーの作り方が知りたい HPの最大値と現在値を使うことでHPバーを作ることができます。2Dランゲーム編4-3の進行度バーの作り方 を参考にしてください。 HPバーの作り方が知りたい Buttonの使い方が分からない Buttonコンポーネントを使うことで、簡単にクリックできるボタンを作成できます。2Dランゲーム編5-3 か、LessonEXのEventTrigger を参考にしてください。 Buttonの使い方が分からない 【豆知識】 作ったゲームを楽に公開したい! 「unityroom 」というサイトではUnityで作成したゲームをWebGL出力することで公開することができます。限定公開も可能です。 また、不定期ですが「Unity 1週間ゲームジャム 」というイベントも開催されます。お題に沿って1週間でゲームを作って公開するイベントです。Unityの実力を高めるいい機会ですので、ぜひ挑戦してみましょう! 【関連】WebGL出力がうまくいかない! 作ったゲームを楽に公開したい! 【おすすめサイト】 シェーダー関連 ↓シェーダーの学習におすすめのサイトです↓ ・シェーダープログラミング入門におすすめ ・シェーダーグラフ入門におすすめ シェーダー関連 パーティクル関連 ↓パーティクルの学習におすすめのサイトです↓ ・Particle System各モジュール一覧 ・パーティクルシステムを1日で自在に操るコツ パーティクル関連
- EX モンスター図鑑 | Unity1gc2
LessonEX モンスター図鑑 EX-1 ベースの作成 EX-1 ベースの作成 ここでは簡易的なモンスター図鑑の実装方法を解説していきます。モンスター図鑑を実装する予定のないゲームでも、キャラクター図鑑やアイテム欄、ステージセレクトなどにも応用できる機能なのでぜひ使ってみてください。手順自体は多いですが、プログラム量は少なめです。 制作前にざっくりと画面のレイアウトを考えておきましょう。左側にモンスターの画像を並べて、クリックされると右側の情報表示用ウィンドウに画像や説明文が表示されるようにします。 プロジェクトを開いて、使用するモンスター画像とウィンドウの素材をインポートしてください。 使用する素材は自由ですが、モンスター画像は縦と横のサイズが同じでないとアスペクト比がおかしくなってしまうので注意してください 。 【サンプルで使用しているサイト(モンスター画像)】 Rド 様 ※ 配布されているのはbmp形式なので、サンプルでは手動でpng形式に透過しています 【サ ンプルで使用している ウィンドウ画像】 画面のアスペクト比 はサンプルでは16:10にしておきます。制作中のゲームに実装する人は変更しなくても構いません。 ここからダウンロード まずはウィンドウを配置します。 ウィンドウを作成するときはSpriteEditorのSlice機能を使うと便利です。普通にウィンドウを変形させると左の画像のように四隅が歪んでしまいますが、この機能を使うと右の画像のように四隅の大きさを固定したままウィンドウの大きさを変えることができます(2Dランゲーム編Lesson4参照 ) ウィンドウのスプライトの設定を変更します。SpriteModeをMultipleに、MeshTypeをFull Rectに変更してください。変更したら右下のApplyボタンをクリックします。 Applyできたら「Sprite Editor」を開いてください。 2Dゲームの場合はそのままSpriteEditorを開けますが、3Dゲームの場合はパッケージのインストールが必要になります。 「Window」→「Package Manager」を選択してください。 左上のPackagesを「Unity Registry」に変更して 、右上の検索欄に「Sprite」と入力しましょう。「2D Sprite」が左側に表示されるのでそれを選択して、右下のInstallボタンをクリックしてください(参考 ) Unity Tips! SpriteEditorを開いたら、まずはウィンドウ画像全体を囲うようにドラッグしてください。 画像を選択できたらSpriteウィンドウのBorderの値を変更して、四隅を切り取れるようにウィンドウを分割してください。サンプルでは各項目20に設定しています。 設定できたら右上のApplyを選択してください。 「UI」→「Image」を追加して、Source Imageにウィンドウ画像をドラッグ&ドロップしてください。ウィンドウ画像が反映されます。 モンスター画像を表示するためのウィンドウ画像を配置してください。Scaleもお好みで調整しましょう。 2つ目のウィンドウを追加して、モンスターの情報を表示するウィンドウも配置してください。 情報表示用ウィンドウの中に仮のUIを配置していきます。中身はスクリプトで差し替えるので適当で構いません。 まずはTextMeshProと日本語のフォントを導入しておきます。3Dアクションゲーム編Lesson4 を参考にしてください。日本語導入はこちら から。 導入が面倒な方はレガシー版のTextを使用してください。 情報表示ウィンドウの子オブジェクトにモンスターの名前、説明文、画像を追加してください。場所や大きさはお好みでOKです。 【サンプルで使用しているフォント】https://logotype.jp/font-corpmaru.html 次は左側のウィンドウに表示するボタンを実装します。「UI」→「Button」を選択してボタンを追加してください。 今回は画像を直接ボタンにするので、子オブジェクトのTextは削除しておきます。Source Imageはスクリプトから差し替えるので、適当でOKです。 次はボタンにアタッチするスクリプトを作成します。 MonsterButtonスクリプトを作成して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; // 忘れないように注意! public class MonsterButton : MonoBehaviour { // モンスター番号 int m_monsterNo = -1; // 初期化用の関数 // モンスターの番号、画像、登録済みかどうかを指定する public void Set(int no, Sprite monsterImage, bool interactable) { // モンスター番号を登録する m_monsterNo = no; // モンスター画像を変更する GetComponent().sprite = monsterImage; // 押せるかどうか設定する GetComponent().interactable = interactable; } // ボタンが押された時に呼び出す関数 public void ButtonDown() { // 情報表示用ウィンドウを更新(後で実装) } } コードが書けたら保存して、先ほど作成したボタンにアタッチしておいてください。 Buttonコンポーネントには自身がクリックされた時に実行する関数を設定することができます(実行する関数はpublicである必要があります) ButtonコンポーネントのOnClick()内にあるプラスボタンをクリックしてください。 クリック時に実行したいコンポーネントをドラッグ&ドロップしてください。今回はMonsterButtonコンポーネントです。 実行したい関数を選択します。「No Function」→「MonsterButton」と選択して、先ほど記述したButtonDown関数をクリックしてください。 この関数は今は中身がありませんが、後で書くので気にしないでください。 ここまでできたら、このボタンを量産できるようにプレハブ化しましょう。ボタンをプロジェクト内にドラッグ&ドロップしてください。 プレハブ化できたらシーン内にあるボタンは不要なので削除しておきます。 EX-2 モンスター一覧の実装 EX-2 モンスター一覧の実装 モンスター一覧を実装していきましょう。 Canvas内に「UI」→「Scroll View」を追加してください。 Scroll Viewの位置と大きさがそのままボタンが表示される範囲になるので、ウィンドウに合うように調整してください。 ColorのA(不透明度)を0にして、背景を透明にしておきましょう。 今回スクロールしたいのは上下のみで、左右のスクロールは不要なのでScroll RectのHorizontalのチェックを外してください。 また、Scroll View内のScrollbar Horizontalも削除してください。 Scrollbar VerticalのBottomを0にして、スクロールバーの縦幅を調整してください。 Scroll View→Viewportの子オブジェクトであるContentに、AddComponentから「Grid Layout Group」と「Content Size Flitter」を追加してください。これらはスクロールではなく、ボタンを綺麗に並べるためのコンポーネントです。 ContentSizeFilterの2つの要素を「Preferred Size」にしましょう。 Grid Layout Groupのパラメータをお好みに調整してください。CellSizeでボタンの大きさを、Constraintで列の数を指定することができます。他にも列の間隔や並べ方も指定できます。 実際の表示をプレビューしたい場合は、Contentの子オブジェクトにボタンのプレハブを複数追加してください。実行することでスクロールの確認もできます。ボタンは設定が終わったら削除しましょう 。 設定が終わったらContent下のボタンは削除して、ボタンを生成するスクリプトを準備しましょう。 新しい空オブジェクトZukanObjectを作成して、新しいスクリプトZukanSystemを作成してアタッチしてください。 ZukanSystemスクリプトを開いて、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; // 忘れないように注意! using TMPro; // TextMeshProを使っている場合は必要 public class ZukanSystem : MonoBehaviour { // これをつけると構造体をインスペクターで編集できるようになる [System.Serializable] // モンスター情報の構造体 public struct Monster { public Sprite MonsterSprite; // 画像 public string Name; // 名前 // これをつけるとインスペクター内でのString入力で6行表示できる [SerializeField, MultilineAttribute(6)] public string Setumei; // 図鑑説明 public bool Register; // 登録フラグ } // これをつけるとprivateな変数をインスペクターで編集できるようになる [SerializeField] // Monster構造体を用いたモンスターデータ Monster[] MonsterData; // 生成するボタンのプレハブ [SerializeField] GameObject Button; // ボタンを追加するオブジェクト [SerializeField] GameObject Content; // 表示用データ [SerializeField] GameObject Data_Sprite, Data_Name, Data_Setumei; void Start() { // 最初は非表示にしておく Data_Sprite.SetActive(false); Data_Name.SetActive(false); Data_Setumei.SetActive(false); // ボタンを生成 for (int i = 0; i < MonsterData.Length; i++) { // プレハブからボタンを生成 GameObject button = Instantiate(Button); // 作成したオブジェクトを子オブジェクトにする button.transform.SetParent(Content.transform); // 子オブジェクトにしたことでサイズが変わるので元に戻す button.transform.localScale = Vector3.one; // コンポーネントを取得 MonsterButton monsterButton = button.GetComponent(); // 生成したボタンに番号や画像を設定する monsterButton.Set(i, MonsterData[i].MonsterSprite, MonsterData[i].Register, this); } } // 引数に入力された値のデータを表示する public void ZukanSet(int no) { // アクティブにする Data_Sprite.SetActive(true); Data_Name.SetActive(true); Data_Setumei.SetActive(true); // 更新する Data_Sprite.GetComponent().sprite = MonsterData[no].MonsterSprite; Data_Name.GetComponent().text = MonsterData[no].Name; Data_Setumei.GetComponent().text = MonsterData[no].Setumei; // ※ TextMeshProではなくレガシーのTextを使用している場合は以下の書き方 // Data_Name.GetComponent().text = MonsterData[no].Name; // Data_Setumei.GetComponent().text = MonsterData[no].Setumei; } } Set関数の部分で存在しない第四引数を指定しているためエラーになりますが、後で追加するため今は気にしなくても構いません。 【プログラムの解説】 ・[System.Serializable]や[SerializeField]はアトリビュートと呼ばれるもので、変数やクラスに属性を付与することができます。詳しくはEXや3D脱出ゲーム編 などで解説しているためここでは省略します。 ・transform.SetParent(親オブジェクトのtransform); は、親オブジェクトを変更する関数です。ここで生成したボタンをContentの子オブジェクトにしています。 MonsterButtonスクリプトを開いて、赤い部分のコード を追加してください。 ZukanSystemとMonsterButtonはお互いに干渉する形になっているので、両方のコードを見てどのように処理が流れているか確認してみてください。 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; // 忘れないように注意! public class MonsterButton : MonoBehaviour { // モンスター番号 int m_monsterNo = -1; // 図鑑システム ZukanSystem m_zukan_System; // 初期化用の関数 // モンスターの番号、画像、登録済みかどうかを指定する public void Set(int no, Sprite monsterImage, bool interactable, ZukanSystem zukan_System ) { // モンスター番号を登録する m_monsterNo = no; // モンスター画像を変更する GetComponent().sprite = monsterImage; // 押せるかどうか設定する GetComponent().interactable = interactable; // 図鑑システムを登録する m_zukan_System = zukan_System; } // ボタンが押された時に呼び出す関数 public void ButtonDown() { // モンスター番号を渡すと表示が更新される m_zukan_System.ZukanSet(m_monsterNo); } } 複雑な処理に見えますが、ボタン生成時にボタンにモンスター番号を教える→ボタンが押されたらそのモンスター番号を使って表示を更新する…といった流れになっています。 ZukanObjectのインスペクターで、モンスターの画像や名前、説明文を自由に設定してください。登録フラグ(Register)がfalseだとボタンが半透明になり押せなくなります。 生成するボタンのプレハブ、Content、情報表示用の要素をそれぞれ設定してください。 これで図鑑機能の完成です。 左側の画像をクリックすると、右側のウィンドウに情報が表示されます。 今回はクリック時に右側のウィンドウを更新していましたが、例えばここでシーンを切り替えればステージセレクト画面にもなります。Scroll Viewを自作するとなるとかなり大変だと思いますので活用していきましょう。 モンスター情報の管理も、実際の開発を想定するなら配列ではなくScriptableObjectを使うと良いでしょう(3D脱出ゲーム編2-2参照 ) また、今回使用したScroll ViewやGrid Layout Groupは一覧を表示するだけでなく他の使い方もあります。あくまで今回使用例として図鑑を作っただけなので、誤解しないようにしましょう。 Scroll Viewは、Contentの子オブジェクトにサイズの大きい画像を入れることで、スクロールすることもできます。Contentのサイズがそのままスクロールできる範囲になります。 Scroll Viewは大きな画像を狭い範囲に表示したい時に役立ちます。例えば、巨大な地図を表示したい時に役立つでしょう。 デフォルトのスクロールバーの見た目は味気ないですが、画像も差し替えられます。 Grid Layout Groupも一覧に限らず、オブジェクトを整列させたい時に役立ちます。ぜひ 様々なUIを実装してみてください。 Unity Tips! ページ TOP EX-1 ベースの作成 EX-2 モンスター一覧の実装
- 3Dアクションゲーム編 Lesson3「ステージを作ろう」 | Unity1gc2
3Dアクションゲーム編 Lesson3 ステージを作ろう プレイヤーの体力を内部的に実装して、ステージを構成するギミックを実装していきます。全てのギミックが完成したら、プレハブ化してオリジナルのステージを作ってみましょう。 3-1 プレイヤーの体力 3-1 プレイヤーの体力 ここからはプレイヤーではなくステージを作っていきます 。 まずはプレイヤーに体力の要素を実装します。と言ってもまだUIがありませんので、内部的に値を用意するだけです。 新しいスクリプト「PlayerHitPoint」を作成してください。 PlayerHitPointスクリプトを以下のように入力してください。Start関数は削除して構いません。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerHitPoint : MonoBehaviour { public int MaxHitPoint = 3; public int HitPoint = 3; // ダメージ処理 public void Damage(int damage) { // Mathf.Clampは値が範囲内に収まるようにする関数 HitPoint = Mathf.Clamp(HitPoint - damage, 0, MaxHitPoint); } void Update() { } } 保存できたら、PlayerHitPointスクリプトをユニティちゃんにアタッチしてください。 ユニティちゃんのインスペクターにPlayerHitPointコンポーネントが表示されていたらOKです。今はこれだけですが、後でギミックを作る時に使用します。 3-2 星の実装 3-2 星の実装 最初のギミックである、星を実装しましょう。 星を全て集めることがこのゲームのクリア条件になります。 「Model」→「Christmas-Presents」→「FBX」→「Star」 から、シーン上にスターをドラッグ&ドロップしてください。 見覚えのある星が出現します。 星のプレハブ化を解除します。 ヒエラルキー内のStarを右クリックして「Prefab」→「Unpack Completely」を選択してください。何をしているかよくわからないと思いますが、今はあまり気にしなくても大丈夫です。 星が大きすぎるのでScaleを調整します。X、Y、Z全てに0.5を入力してください。 座標も好きな場所に動かして構いません。 星の影が少しおかしい点が気になる方はMesh RenderのLightingからCast Shadowsを「Two Sided 」に設定してください。これについては清原先生が記事を公開している ので詳しく知りたい方は読んでみてください。 Unity Tips! ユニティちゃんが星に近づいたら星を取得できるように、Colliderを設定しましょう。 今回は球体の当たり判定、Sphere Colliderを使用します 。StarにSphere Colliderを設定してください。 コライダーの追加方法を忘れた人は、1-4を参考にしてください。 コライダーを設定できたらRadius(半径)をお好みの大きさにして、Is Triggerにチェックを入れてください。 星にStarタグをつけておきます。1-6でGroundタグを作った時と同じように設定してみましょう。 それでは「ユニティちゃんがSphere Colliderに接触したら星を取得する」処理を実装します。 新しいスクリプト「StarObject」を追加して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class StarObject : MonoBehaviour { void Start() { } void Update() { // Y軸周りに回転する transform.Rotate(0.0f, 0.2f, 0.0f); } private void OnTriggerEnter(Collider other) { // もし自身と衝突したオブジェクトのタグがPlayerだったら… if (other.CompareTag("Player")) { // ここで星を取得する処理を書く // 自身を削除する Destroy(gameObject); } } } 【プログラムの解説】 ・transform.Rotate関数は回転を加算する関数です。 Rotate(X軸周りの加算量,Y軸周りの加算量,Z軸周り の加算量); 今回はY軸周りに回転を加えています。 ・OnTriggerEnter関数はOnTriggerStay関数と異なり、衝突した瞬間にだけ呼ばれる関数です。 ・Destroyは引数に入力したものを削除する関数です。 k2EngineのようにDestroy(this);と入力してしまうと、オブジェクトではなく自身の"コンポーネントが"削除されてしまいます 。自身のオブジェクトを削除する際の引数はgameObjectです。間違えやすいので注意してください。 コードの入力が終わったら、StarにStarObjectをアタッチするのを忘れないように してください。 アタッチできたらゲームを実行して、ユニティちゃんが星に近づくと星が消えることを確認してください。 これで見た目上は星の取得ができていますが、内部では獲得した星の数を数えられていません。 次は // ここで星を取得する処理を書く という部分で獲得した星の数を増やしましょう。 獲得した星の数を数えるために、新しい空オブジェクトを追加してください。 オブジェクトの名前は「Game」にしておきます。この名前は後で使用する ので間違えないように 注意してください。 星を数えるためのスクリプトを作成します。 新しいスクリプト「StarCount」を追加して、以下のように入力してください。 このスクリプトは簡単なので一部穴埋め式になっています。青色の部分 が抜けているコードです。 書き方はC++と同じなので挑戦してみましょう。 できるだけ答えを見ずに、過去のLessonを見て解いてください (答えはあくまで答え合わせ用です) 【ヒント】② と ③ は「戻り値」を設定しています!戻り値については EXも参照 ! using System.Collections; using System.Collections.Generic; using UnityEngine; public class StarCount : MonoBehaviour { private int m_nowStarCount = 0; // 今持っている星の数 private int m_maxStarCount = 0; // 最大の星の数 void Start() { // Starタグがついたオブジェクトを全て取得する GameObject[] stars = GameObject.FindGameObjectsWithTag("Star"); m_maxStarCount = stars.Length; // 要素数はオブジェクトの数 } // 今持っている星の数を1増やす public void StarAdd() { // ① m_nowStarCountを1増やす (ここにコードを書く) } // 今持っている星の数を取得 public int GetNowStarCount() { // ② m_nowStarCountを返す (ここにコードを書く) } // 最大の星の数を取得 public int GetMaxStarCount() { // ③ m_maxStarCountを返す (ここにコードを書く) } } 降参 or 答え合わせの方はこちら 【プログラムの解説】 ・GameObject.FindGameObjectsWithTag(タグ名)で、引数に設定したタグを持つオブジェクトを全て取得することができます 。今回はStarタグがついたオブジェクトを全て取得しています。 戻り値は配列なので GameObject[] stars で受け取っています。 Lesson2でGameObject.FindGameObjectWithTag というものが出ましたが、こちらは指定したタグを持つオブジェクト1つ を取得する関数です。ちなみに対象となるオブジェクトが複数ある場合はどのオブジェクトが返ってくるかはわからないので、こちらはシーンに1つしかないオブジェクト(プレイヤーなど)を取得する時に使います。 ・取得した配列の要素数は、指定したタグがついたオブジェクトの数と等しくなります。 今回は stars.Length でStarタグがついたオブジェクトの数(ステージにある星の数)を取得しています 。 星の数を自動で数えるスクリプトを書くことで、後から星を追加しても問題なく動作するようになるため便利です。 コードが書けたら、先ほど作成した「Game」オブジェクトにStarCountをアタッチしておいてください。 後はStarObjectスクリプトからStarCountのStarAdd関数を呼び出せば、獲得した星の数を数えることができます。 今回はGameオブジェクトを名前で検索してStarCountコンポーネントを取得したいと思います。 赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class StarObject : MonoBehaviour { StarCount m_starCount; void Start() { // Gameという名前のオブジェクトを検索する GameObject gameObj = GameObject.Find("Game"); // 検索したオブジェクトからStarCountコンポーネントを取得する m_starCount = gameObj.GetComponent(); } void Update() { // Y軸周りに回転する transform.Rotate(0.0f, 0.2f, 0.0f); } private void OnTriggerEnter(Collider other) { // もし自身と衝突したオブジェクトのタグがPlayerだったら… if (other.CompareTag("Player")) { // ここで星を取得する処理を書く m_starCount.StarAdd(); // 自身を削除する Destroy(gameObject); } } } 【プログラムの解説】 ・GameObject.Find(オブジェクト名) でシーン内に存在する同名オブジェクトを取得することができます。名前で検索するので、完全一致でないと取得できません 。後からオブジェクト名を変更してしまうとうまく動作しないので注意しましょう。 (Findはタグで検索するより処理が重たいので、タグで検索できる時はFindGameObjectWithTagを使用することをオススメします) 【評価テスト】 https://forms.gle/bVYdh4yR3WjmGVhH7 評価テスト 3-3 デバッグ 3-3 デバッグ 3-2で星に触れると星が消えて獲得数が増加する処理を実装しました。 ですが、実際に星の獲得数が増加しているか、今のままではわかりません。Unityのデバッグ機能を使って本当にm_nowStarCountが増加しているか確かめてみましょう。 デバッグで使える機能はいくつかあるのですが、今回は2つ紹介します。 まずはDebug.Log です。 Debug.Logが実行されるとUnity上のコンソールにメッセージが出力されます。 例えばDebug.Log("Test");と入力すればコンソールウィンドウに「Test」と出力されます。 StarObjectに以下の赤い部分のコード を入力してください。 ~中略~ private void OnTriggerEnter(Collider other) { // もし自身と衝突したオブジェクトのタグがPlayerだったら… if (other.CompareTag("Player")) { // ここで星を取得する処理を書く m_starCount.StarAdd(); // デバッグ:獲得した星の数を表示 Debug.Log(m_starCount.GetNowStarCount()); // 自身を削除する Destroy(gameObject); } } } 入力できたら保存して、実行してみてください。 星を取得すると、獲得した星の数がコンソールに表示されます。 Debug.Logの引数に変数を入力すると、その中身がコンソールに出力されます。 コンソールに出力された値をダブルクリックすると、プログラムのどこから出力されたかを確認することができます。 Unity Tips! コンソールにDebug.Logが出力できていることが確認出来たらOKです。もし数値が増えていなかったら3-2を見返して星を取得するコードが間違っていないか確認しましょう。 このコードは残しておいても構いませんし、確認できたなら消しても構いません。 2つ目はお馴染みのブレークポイント です。今までと同じように使うことができます 。 ブレークポイントを置いてから上部にある「Unityにアタッチ」を選択してください。 Unityに戻るとUnityへのアタッチの許可を要請してきますので、OKしてください。 「Unityにアタッチ」が機能している状態で星を取ると、ブレークポイントでプログラムが一時停止します。 ブレークポイントで停止中にF10キーを押すとプログラムを1行進めることができます。m_starCountにマウスカーソルを合わせて、m_nowStarCountの値が変化することを確認してみてください。 デバッグの方法はどちらを使ってもOKです。処理を継続するDebug.Logと処理を一時停止するブレークポイント、うまく使い分けてみてください。 3-4 トゲの実装 3-4 トゲの実装 次は触れるとダメージを受けるトゲを実装します。 と言っても、星を実装した時と処理はあまり変わりません。 「Model」→「Toge」からトゲのモデルをシーンにドラッグ&ドロップしてください。 トゲのプレハブ化を解除します。 ヒエラルキー内のTogeを右クリックして「Prefab」→「Unpack Completely」を選択してください。何をしているかよくわからないと思いますが、今はあまり気にしなくても大丈夫です。 ヒエラルキー内のTogeの横にある ▶ を押して開くと出てくるTogeModelを選択してください。 TogeModelにBoxColliderをアタッチしてください。コライダーのつけ方を忘れた人は1-4を参考にしてください。 BoxColliderのIsTriggerにチェックを入れてください。 これがトゲに衝突した時のダメージ用当たり判定になります。 新しいスクリプト「DamageObject」を作成して、以下のように入力してください。 青色の部分 の穴埋めにも挑戦してみましょう。過去の実装を参考にしてみてください。 特に②はUnityを扱う上で非常に重要なコードです。 【ヒント】① インスペクターに変数を表示したい時に必要な修飾子があったはず… ② StarObjectスクリプトのStart関数をチェック! ③ 引数に注意しましょう! using System.Collections; using System.Collections.Generic; using UnityEngine; public class DamageObject : MonoBehaviour { // ① 触れた時のダメージ量を設定するint型の変数「DamagePower」 // 後からインスペクターで調整できるようにする (ここにコードを書く) private void OnTriggerStay(Collider other) { // もし自身と衝突したオブジェクトのタグがPlayerだったら… if (other.CompareTag("Player")) { // ② 衝突したオブジェクトotherにアタッチされている // PlayerHitPointコンポーネントを取得 PlayerHitPoint playerHitPoint = (ここにコードを書く) // ③ playerHitPointを使用してダメージを与える // ダメージ量は①で設定した変数「DamagePower」 (ここにコードを書く) } } } 降参 or 答え合わせの方はこちら コードが書けたら保存して、TogeModelにDamageObjectをアタッチしてください。BoxColliderがアタッチされているものと同じオブジェクトにアタッチしないと OnTriggerStayが反応しない ので注意してください。 接触時のダメージ量を表すDamagePowerを1にしておきます。 設定できたらゲームを実行してトゲに触れてみてください。インスペクターで現在の体力を確認すると、体力がちゃんと減っていることが確認できます。 ですが、HitPointがとてつもない勢いで減少して一瞬で0になっています。 OnTriggerStay関数はプレイヤーが触れている間は毎フレーム処理が呼ばれるので、 このような現象が起こってしまいます。 このままだとユニティちゃんはオーバーキルになってしまうので、無敵時間を実装しましょう。 PlayerHitPointに赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerHitPoint : MonoBehaviour { public int MaxHitPoint = 3; public int HitPoint = 3; public float InvincibleTime = 3.0f; // 無敵時間 private float m_invincibleTimer = 0.0f; // 無敵時間のタイマー private bool m_invincibleFlag = false; // 無敵フラグ // ダメージ処理 public void Damage(int damage) { // 無敵時間中は実行されないようにする if (m_invincibleFlag) { return; } // Mathf.Clampは値が範囲内に収まるようにする関数 HitPoint = Mathf.Clamp(HitPoint - damage, 0, MaxHitPoint); // 無敵時間開始 m_invincibleFlag = true; // 無敵タイマーをセットする m_invincibleTimer = InvincibleTime; } void Update() { if (m_invincibleFlag) { // 無敵時間のタイマー減少 m_invincibleTimer -= Time.deltaTime; if (m_invincibleTimer <= 0.0f) { m_invincibleFlag = false; // 無敵時間終了 } } } } 【プログラムの解説】 ・関数の途中で return を呼ぶと、処理を途中で中断できます。今回はダメージ関数の最初に無敵フラグを確認して、無敵状態ならその先の処理は不要なので return を使って中断しています。 インスペクターにInvincibleTimeが追加されています。初期値は3秒ですが少なくすることで無敵時間が短くなり、難易度が上がり ます。自由に調整してOKです。 ゲームを起動して、無敵時間が動作していることを確認してみてください。 トゲに触れ続けても、しばらくはダメージを受けません。 これでトゲの実装は終了です。 3-5 大砲の実装 3-5 大砲の実装 次は大砲を実装します。 一定間隔で弾が発射され、ユニティちゃんがぶつかるとダメージを受けます。 「Model」→「Cannon」から大砲のモデルをシーン上にドラッグ&ドロップしてください。 大砲から発射される弾も準備します。 インスペクターから「3D Object」→「Sphere」を選択して球体を作成してください。 Sphereの名前を「Bullet」に変更しておきます。 大砲に対して弾が大きすぎるので、Scaleを全て0.5に調整しましょう。 白単色では弾として味気ないので、弾に色を設定しましょう。 Cannonフォルダ内にMaterialを作成してください。 マテリアルの名前はわかりやすい名前で大丈夫です。今回は「BulletMaterial」にしておきます。 インスペクターからBulletMaterialの設定をします。 ・Albedoを灰色に変更してください。 ・Metallicを0に変更してください。 ・Smoothnessを0.8に変更してください。 (この3つの要素はあくまで目安なので、好きに変更しても構いません) BulletMaterialの設定ができたら、インスペクター内のBulletにBulletMaterialをドラッグ&ドロップしてください。 これで真っ白で味気ない見た目から解放されました。 シーン上にあるBulletに直接ドラッグ&ドロップしたり、インスペクターにドラッグ&ドロップしたりしても同じようにマテリアルを設定できます。 好きな方法でOKです。 Unity Tips! モデルの準備ができたので、実装の準備をしていきましょう。 まずはインスペクターのCannonを右クリックしてプレハブ化を解除してください。星やトゲを実装した時と同じです。 大砲に当たり判定を設定しましょう。 CannonにBoxColliderをアタッチしてください。 BoxColliderの大きさや位置は以下のように設定するのがオススメです。 ・Center X=-0.4 Y=0.7 Z=0 ・Size X=2.8 Y=1.6 Z=1 この当たり判定は貫通してほしくないので Is Triggerにチェックを入れていません。試しにプレイしてユニティちゃんが衝突することを確認してみてください。 大砲や壁に衝突した際にカメラがガタガタする現象が気になる方は、新しくFixedUpdate関数を追加して、Playerスクリプト内のAction関数をFixedUpdate関数内に移動させてください。 そしてジャンプ関連の処理だけをUpdate関数内に移動させてください。 FixedUpdate関数はUpdate関数と異なり、一定間隔で実行される関数です。また、Update関数より先に実行されるという特徴もあります。今まではプレイヤーの移動もカメラの移動もUpdateにありましたが、プレイヤーの移動をFixedUpdateに書くことで「プレイヤーが移動してからカメラが移動する」ようになります。 Unity Tips! 大砲の上に乗ってもジャンプできるように、大砲にGroundタグをつけておきましょう。 Bulletには最初からSphereColliderがついていますが、これをTrigger判定にしておいてください。 それでは一定間隔で弾を発射するスクリプトを書きましょう。 新しいスクリプト「CannonObject」を作成して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class CannonObject : MonoBehaviour { public GameObject Bullet; // 発射するオブジェクト public float CannonInterval = 3.0f; // 発射間隔 public float CannonSpeed = 2.0f; // 弾の速度 public float CannonLimit = 5.0f; // 弾の生存時間 public float BulletAddY = 1.2f; // 弾の発射位置を持ち上げる量 public int DamagePower = 0; // ダメージ量 private float m_cannonTimer = 0.0f; void Update() { // タイマー減少 m_cannonTimer -= Time.deltaTime; if (m_cannonTimer <= 0.0f) { // 発射座標を計算する Vector3 bulletPos = transform.position; bulletPos.y += BulletAddY; // 発射処理 GameObject bullet = Instantiate(Bullet, bulletPos, Quaternion.identity); // ここに弾に移動速度を教える処理を書く! // タイマーを戻す m_cannonTimer = CannonInterval; } } } 【プログラムの解説】 ・Instantiate関数 は第一引数に設定したゲームオブジェクトを生成する関数です。今回はBullet(大砲の弾)を生成しています。 Instantiate(生成するオブジェクト, 座標, 回転); 戻り値として生成したゲームオブジェクトを返します。 弾に移動速度を教える処理は後で書きます。 次は弾のスクリプトを実装しましょう。 新しいスクリプト「BulletObject」を作成して以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class BulletObject : MonoBehaviour { private Vector3 m_moveSpeed = Vector3.zero; // 移動量 private float m_timer = 0.0f; // 生存時間 private int m_damage = 0; // ダメージ量 private bool m_setFlag = false; // 初期化フラグ // 初期化 public void Set(Vector3 move,float limit,int damage) { m_moveSpeed = move; m_timer = limit; m_damage = damage; m_setFlag = true; } // Fixedなので注意! void FixedUpdate() { // 未初期化なら実行しない if (m_setFlag == false) { return; } // 移動 transform.Translate(m_moveSpeed * Time.deltaTime); // 生存時間 m_timer -= Time.deltaTime; // 生存時間が終わったら自身を削除する if (m_timer <= 0.0f) { Destroy(gameObject); } } private void OnTriggerEnter(Collider other) { // もし自身と衝突したオブジェクトのタグがPlayerだったら… if (other.CompareTag("Player")) { // プレイヤーにダメージを与える PlayerHitPoint playerHitPoint = other.GetComponent(); playerHitPoint.Damage(m_damage); // 自身を削除する Destroy(gameObject); } } } 【プログラムの解説】 ・Translate関数は引数に設定したベクトル分、座標を移動します。 とりあえず移動したい時に使いやすい関数です。 ・生存時間のタイマーが0になったら弾に自身を削除させています。弾がシーン上に増え続けるとゲームが重くなってしまうので、適時削除するようにしましょう。 ・プレイヤーに触れた瞬間にダメージを与える処理の流れはトゲと同じですが、弾がプレイヤーを貫通すると不自然なので、衝突した瞬間に削除しています。オブジェクトを削除する処理は星の取得と同じです。 コードが書けたらCannonObjectスクリプトに戻って、移動速度を教える処理を実装しましょう。 赤い部分のコード を追加してください。 ~中略~ if (m_cannonTimer <= 0.0f) { // 発射座標を計算する Vector3 bulletPos = transform.position; bulletPos.y += BulletAddY; // 発射処理 GameObject bullet = Instantiate(Bullet, bulletPos, Quaternion.identity); // BulletObjectを取得する BulletObject bulletObject = bullet.GetComponent(); // 移動量を計算する(大砲の発射口が左にあるモデルなので左方向を取得) Vector3 move = -transform.right * CannonSpeed; // 移動速度、生存時間、ダメージ量を弾に教える bulletObject.Set(move, CannonLimit, DamagePower); // タイマーを戻す m_cannonTimer = CannonInterval; } } } 【プログラムの解説】 ・移動速度の計算では大砲の左方向に移動速度をかけた値を使用しています。 自身の左方向を取得したい時は右方向にマイナスをかけることで取得できます。 コードが書けたらCannonObjectを大砲に、BulletObjectを弾にアタッチしてください。 後は大砲に発射させるオブジェクトを大砲に教えるだけです。 ここではプレハブ という手法を使います。シーン上にあるオブジェクトをプロジェクト内に保存して、ハンコのように量産できるようにする仕組みです。 物は試しなので大砲の弾をプレハブ化してみましょう。 新しくPrefabフォルダを作成してください。 Prefabフォルダ内にヒエラルキーからBulletをドラッグ&ドロップしてください。プロジェクト内にBulletが追加されたらプレハブ化完了です。 プレハブ化できたらシーン上にあるBulletは必要ないので削除しましょう。オブジェクトを選択した状態でDeleteキーを押す(または右クリックして「Delete」を選択)ことで削除できます。 シーン上にある弾を削除してもプレハブ化された弾は消えません 。オブジェクトを素材として保持できるのがプレハブの利点です。 後はこのプレハブをCannonObjectに登録しましょう。 Cannonのインスペクターを開いて、Bulletに弾のプレハブをドラッグ&ドロップしてください。 これで大砲が完成しました。 時間経過で弾が発射されて、弾に触れるとダメージを受けることを確認してみてください。 例のごとくCannonのパラメータはpublicになっているので自由に変更できます。CannonInterval(発射間隔)やCannonSpeed(移動速度)を変更してどう変わるか確認してみてください。 プロジェクト内にあるプレハブをダブルクリックするとプレハブを編集することができます。後から弾の大きさを変更したくなったり、新しいスクリプトをアタッチしたくなった時はここから編集することで、全ての弾に反映させることができます。 Unity Tips! 3-6 移動床の実装 3-6 移動床の実装 最後のギミックとして移動する床を実装しましょう。 開始地点と目標地点を自動で往復し、プレイヤーが乗って移動することができます。 まずはCubeを作成してください。 名前をMoveCubeにして、ジャンプできるようにGroundタグをつけます。 座標や大きさは好みで調整してください。 真っ白な箱のままだと普通の床と区別がつかないので、マテリアルを貼りましょう。 今回は箱に画像を貼ります。 新しくMaterialフォルダを追加してください。 Materialフォルダ内に使いたい画像をドラッグ&ドロップしてください。使いたい画像が特にない場合は以下の画像を使用してください。 ここからダウンロード 大砲の弾と同じようにMaterialを作成してください。名前は何でもOKですが、教材ではMoveMaterialにしています。 MaterialのAlbedoに使用したい画像をドラッグ&ドロップし、パラメータを好みに調整してください。 設定ができたらMoveCubeにマテリアルをドラッグ&ドロップして貼りつけてください。 移動床の準備ができたのでプログラムを実装していきましょう。 MoveObjectスクリプトを作成して、以下のように入力してください。少し複雑な処理ですが、全てを理解する必要はありません。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoveObject : MonoBehaviour { // 目標となる2つの座標 Vector3 m_startPosition = Vector3.zero; // 開始地点 public Vector3 TargetPosition = Vector3.zero; // 目標地点 // スピード public float MoveSpeed = 1.0f; // 移動速度 float m_nowTime = 0.0f; // 線形補間用の経過時間 // 移動状態 bool m_moveFlag = false; // 目的地に向かっているか帰っているか // ウェイト float m_waitTimer = 0.0f; // 待機時間のタイマー public float WaitTime = 2.0f; // 移動後の待機時間 bool m_waitFlag = false; // 待機状態 void Start() { // 開始地点を保存 m_startPosition = transform.position; } // ↓「Update」ではないので注意! void FixedUpdate() { // 待ち状態かどうか if (m_waitFlag == false) { // 移動状態 if (m_moveFlag == false) { Move(m_startPosition, TargetPosition); } else { Move(TargetPosition, m_startPosition); } } else { Wait(); } } // 移動中の処理 void Move(Vector3 Start, Vector3 Target) { // 現在の位置 m_nowTime += Time.deltaTime * MoveSpeed; if (m_nowTime > 1.0f) { m_nowTime = 1.0f; } // オブジェクトの移動 transform.position = Vector3.Lerp(Start, Target, m_nowTime); // もし目的地に着いていたら状態遷移 if (transform.position == Target) { m_waitTimer = WaitTime; m_waitFlag = true; } } // 待機中の処理 void Wait() { m_waitTimer -= Time.deltaTime; if (m_waitTimer <= 0.0f) { m_waitFlag = false; m_moveFlag = !m_moveFlag; // フラグを反転させる m_nowTime = 0.0f; } } } 【プログラムの解説】 ・Lerp関数は線形補間で、2点間の座標を0.0~1.0の範囲で補間してくれる関数です。 Vector3.Lerp(開始地点, 目標地点, 補間量); 例えば第三引数に0.0を入れると、開始地点の座標を返します。 そして第三引数に0.5を入れると、開始地点と目標地点のちょうど中間の座標を返します。 プログラムが書けたら保存して、移動床にMoveObjectスクリプトをアタッチしてください。 移動先の座標であるTargetPositionはpublicにしてあるのでインスペクターから編集することができます。 移動先の座標を設定してください。値は自由です。 移動速度や移動の待機時間も変更して構いません。 これで移動床が開始地点と目標地点を往復するようになりました。 しかし、プレイヤーが乗ってもプレイヤーは床に置いて行かれてしまいます。このままではかなり遊びにくいギミックになってしまいます… これを解決する方法として"直前座標との差をプレイヤーに教える" 方法 があります。プレイヤーが乗っている間は前フレームとの座標の差をプレイヤーの座標に加算します。 MoveObjectスクリプトに以下の赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoveObject : MonoBehaviour { // 目標となる2つの座標 Vector3 m_startPosition = Vector3.zero; // 開始地点 public Vector3 TargetPosition = Vector3.zero; // 目標地点 // スピード public float MoveSpeed = 1.0f; // 移動速度 float m_nowTime = 0.0f; // 線形補間用の経過時間 // 移動状態 bool m_moveFlag = false; // 目的地に向かっているか帰っているか // ウェイト float m_waitTimer = 0.0f; // 待機時間のタイマー public float WaitTime = 2.0f; // 移動後の待機時間 bool m_waitFlag = false; // 待機状態 Vector3 m_beforePosition = Vector3.zero; // 直前の座標 void Start() { // 開始地点を保存 m_startPosition = transform.position; } // ↓「Update」ではないので注意! void FixedUpdate() { // 移動前の座標を保存しておく m_beforePosition = transform.position; // 待ち状態かどうか if (m_waitFlag == false) { // 移動状態 if (m_moveFlag == false) { Move(m_startPosition, TargetPosition); } else { Move(TargetPosition, m_startPosition); } } else { Wait(); } } // 移動中の処理 void Move(Vector3 Start, Vector3 Target) { // 現在の位置 m_nowTime += Time.deltaTime * MoveSpeed; if (m_nowTime > 1.0f) { m_nowTime = 1.0f; } // オブジェクトの移動 transform.position = Vector3.Lerp(Start, Target, m_nowTime); // もし目的地に着いていたら状態遷移 if (transform.position == Target) { m_waitTimer = WaitTime; m_waitFlag = true; } } // 待機中の処理 void Wait() { m_waitTimer -= Time.deltaTime; if (m_waitTimer <= 0.0f) { m_waitFlag = false; m_moveFlag = !m_moveFlag; // フラグを反転させる m_nowTime = 0.0f; } } void OnCollisionStay(Collision collision) { // 接触しているオブジェクトのタグが"Player"のとき if (collision.gameObject.CompareTag("Player")) { // 直前の座標との変化量をプレイヤーに加算する collision.gameObject.transform.localPosition += transform.position - m_beforePosition; } } } 【プログラムの解説】 ・OnCollisionStayはOnTriggerStayとは異なり、IsTriggerにチェックが入っていないオブジェクトの衝突判定に使います。使い方はあまり変わりませんが、この2つの関数を間違えるとうまく動作しないので注意してください。 今まで様々な当たり判定の関数を紹介してきましたが、これらの関数は適切に使い分けないと期待通りの判定が取れないので、少しずつ覚えていきましょう。関数名は以下の法則で決まっています。 ・OnCollision→ Is Triggerにチェックなし OnTrigger→ Is Triggerにチェックあり ・Enter→触れた瞬間 Stay→触れている間 Exit→離れた瞬間 ・最後に2Dとついているものは2D用 これで移動床が完成しました。実際に乗って、ユニティちゃんが床と一緒に移動することを確認してみてください。 評価テスト 【評価テスト】 https://forms.gle/NW2pdEvKZRdXQQGk6 3-7 落下処理の実装 3-7 落下処理の実装 ステージとはあまり関係がないのですが、実装するタイミングがないのでここで実装します。 ユニティちゃんが落下するとダメージを受けて初期位置に戻るようにしましょう。 PlayerFallスクリプトを作成して以下のように入力してください。 今回はほとんど穴埋めです。青い部分 を埋めてみてください。コメントや過去の処理を参考にしてみましょう。 【ヒント】自分の座標は transform.position で取得できます! using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerFall : MonoBehaviour { // ① 落下判定になるY(低さ)を決めるためのpublicなfloat型変数 (ここに入力) // ② 初期座標を保存するためのprivateなVector3型変数 (ここに入力) // ③ 自身のPlayerHitPointを保持するためのprivateな変数 (ここに入力) // ④ 落下時のダメージを指定するpublicなint型変数 (ここに入力) void Start() { // ⑤ 自分の初期座標を②で宣言した変数に代入 (ここに入力) // ⑥ 自身にアタッチされているPlayerHitPointコンポーネントを取得して③に代入 (ここに入力) } void Update() { // ⑦ もし自分のY座標が①で指定したボーダー以下になったら // ダメージを受けて初期座標に戻る // (ダメージを与える方法はDamageObjectを参照!) (ここに入力) } } 降参 or 答え合わせの方はこちら 完成したらユニティちゃんにアタッチしてください。 試しに落下してみて、ボーダーラインまで落下したらダメージを受けて初期座標に戻ることを確認してみましょう。 3-8 タイマーの実装 3-8 タイマーの実装 こちらもステージ本体とは関係ないのですが、クリアタイムを計測するために内部的にタイマーを設定します。 GameTimerスクリプトを作成して以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameTimer : MonoBehaviour { float m_second; // 秒 int m_minute; // 分 // 分と秒を返す関数 public float GetSecond() { return m_second; } public int GetMinute() { return m_minute; } void Update() { // タイマー加算 m_second += Time.deltaTime; if (m_second > 60.0f) { // 1分経過 m_minute++; m_second -= 60.0f; } } } 1フレームごとの経過時間を加算していき、1分経過したらminuteを増やすスクリプトです。 入力できたらGameオブジェクトにアタッチしておいてください 。変数はprivateなので値を確認できませんが、デバッグ機能で確認することはできます。今後のLessonで出番がありますが、今はこれだけでOKです。 3-9 ステージの制作 3-9 ステージの制作 それでは今まで作ったギミックを配置してステージを作ってみましょう。 星、トゲ、大砲、移動床をプレハブ化します。 4つのオブジェクトをPrefabフォルダ内にドラッグ&ドロップしてください。 プレハブ化したオブジェクトをステージに配置して、自由にステージを作ってみてください。 ・ユニティちゃんはGroundタグがついたオブジェクトが足元にないとジャンプできないので注意! ・クリア条件となる星を1つ以上配置してください! ・地面に画像を貼ったり、新しいギミックを実装できるとGOOD! ・親子関係(1-6参照 )を使うことで移動床と他ギミックを組み合わせることができます。 今まで学んだ内容を応用することで、様々なギミックを作ることができるはずです。 ここではオススメのギミックと実装のヒントを紹介します。ぜひ挑戦してみましょう! 【トランポリン】 プレイヤーが上に乗った時に、そのプレイヤーに対して上方向に大きく力を加えてみましょう。ジャンプの処理が参考になります。 他にもプレイヤーが触れている間だけ一定方向へ移動させることで「動く歩道」、プレイヤーが触れると下へ移動する「落ちる足場」など、応用することで様々なギミックを作れます。 【回復アイテム】 触れると体力が回復するアイテムを作ってみましょう。オブジェクトを削除する処理は星のスクリプトを確認しましょう。 【チェックポイント】 触れると復活する場所が変化するオブジェクトを作ってみましょう。PlayerFallスクリプトを改造して、落下時に復活する座標を外部から変更できるようにする必要があります。 教材では使っていませんが「3D Object」→「Cylinder」で円柱を作成できるので、Cubeと組み合わせて旗を作ることができます。チェックポイントのモデルとして使ってみましょう。 【スイッチ】 触れた瞬間に足場を出現させたり、消去したりすることで謎解きのギミックを作ってみましょう。 足 場を出現させたり、消したりしたい時はオブジェクトのアクティブ/非アクティブを切り替えることでスムーズに処理できます。 SetActive関数 を使うことでオブジェクトのアクティブ状態を変更することができます。 非アクティブにしたオブジェクトは 表示されず、当たり判定も無効になります。 インスペクター内の名前の左側にあるチェックマークがオブジェクトのアクティブ状態を示しています。 最初は非表示にしておきたいオブジェクトがある場合はチェックマークを外しておきましょう 。 Unity Tips! ステージが完成したらLesson3は終了です。 次のLessonではUIを実装して、獲得した星の数や体力を画面上に表示できるようにしましょう。 【評価テスト】 https://forms.gle/jUrj4tHN8fCrDvx16 評価テスト Next Lesson4「UIを作ろう」 ページ TOP 3-1 プレイヤーの体力 3-2 星の実装 評価テスト 3-3 デバッグ 3-4 トゲの実装 3-5 大砲の実装 3-6 移動床の実装 評価テスト 3-7 落下処理の実装 3-8 タイマーの実装 3-9 ステージの制作 評価テスト
- 3Dアクションゲーム編 Lesson5「シーンを作ろう」 | Unity1gc2
3Dアクションゲーム編 Lesson5 シーンを作ろう タイトル画面、リザルト画面、ゲームオーバー画面を実装してそれぞれが適切な条件で遷移するようにします。このLessonでゲームは一通り完成なので頑張りましょう。 5-1 タイトル画面 5-1 タイトル画面 UnityではタイトルやインゲームなどをScene というもので分けて、それぞれを繋げることができます。今まで制作してきたのはMainというシーンでした。タイトル、ゲームオーバー、リザルトのシーンを作成しましょう。 まずはタイトルのシーンを作ります。Scenesフォルダ内にSceneを作成してください。名前は「Title」にしておきます。この名前は後で参照するので間違えないように注意してください。 Titleシーンをダブルクリックするとライトとカメラだけのまっさらなシーンが開きました。インゲーム用のオブジェクトはMainシーンに配置していましたが、タイトル用のオブジェクトはTitleシーンに配置していきます。 UIからPanelを追加して、同梱したタイトル用の画像を設定してください。タイトル用の画像はSpriteフォルダに入っています。 これでタイトル画面の画像を設定できました。 Unityでは2Dの画像をSpriteと呼びますので、この教材でも今後はスプライトと呼称します。 5-2 ゲームオーバー画面 5-2 ゲームオーバー画面 次はゲームオーバー画面を作ります。 Titleシーンと同じようにGameOverシーンを作成してください。このシーンの名前も後から参照するので間違えないように注意してください。 GameOverシーンにPanelを配置して、GameOverの画像を設定してください。 5-3 リザルト画面 5-3 リザルト画面 最後にリザルトを作ります。 Resultシーンを作成してください。このシーンの名前も後から参照します。 今回はPanelではなくImageを用いてクリア画像を表示しましょう。 位置とサイズを自由に調整して、クリア画像を設定してください。 リザルトは他と違って、ゲームクリアの文字の画像しか用意していません。背景の画像は皆さんが用意してみてください。何でもOKです。 追加したい画像をSpriteフォルダ内にドラッグ&ドロップしてください。 このままではUIに使えないのでスプライトに変換します。 追加した画像を選択して、TextureTypeを「Sprite」に 変更してください。 変換できたら後は今までと同じようにUIに追加するだけです。Panelを追加して画像を設定してください。 UIの描画順はヒエラルキー内の順番に依存しています。ヒエラルキー内で下にあるUIオブジェクトが前面に表示されます。 ヒエラルキーの 順番を変えたい時はドラッグ&ドロップで動かしてください。 クリアタイムを表示するテキストを追加します。TextMeshProを追加してください。 内容は仮で「ClearTime 99:99」にしておきます。 位置や装飾はお任せするので、自由に配置してみてください。 物足りない方は他にも画像を追加して構いません。オリジナルのリザルト画面にしてください。 5-4 クリア画像を動かす 5-4 クリア画像を動かす これで一応各シーンは完成しましたが、少し物足りないのでクリア画像を動かしてみましょう。 画像を動かす、となるとスクリプトを書くことを考えた人もいるかもしれません。しかしUnityでは簡単な動きであればAnimatorを用いて実装することができます。 AnimationフォルダにAnimationを追加してください。 作成したアニメーションをクリア画像にドラッグ&ドロップしてください。Animator Controllerが自動で作成されたらOKです。 作成したアニメーションをダブルクリックするとアニメーションウィンドウが開きます。この状態でインスペクターからクリア画像を選択すると、クリア画像にアニメーションを設定することができます。 アニメーションさせる項目はAdd Propertyで追加できます。項目を追加すると3dsMaxと同じようにキーを配置してアニメーションを作ることができます。 座標だけでなく、大きさや回転なども扱えるので自由にアニメーションを作ってみてください。 左下のCurvesボタンを押すとカーブエディタに切り替わります。カーブ上をクリックしてキーを追加したり、カーブの形状を変えたりできます。 アニメーションをループ再生したい場合はアニメーションを選択してLoopTimeにチェックを入れます。 左上の再生ボタンでアニメーションを確認して、納得いくものができたらリザルトのUIは完成です。 他の画像やテキストにアニメーションをつけても構いません。自由にリザルト画面を作ってみましょう。 5-5 シーンを繋げる 5-5 シーンを繋げる これでMain(ゲーム部分)、Title、GameOver、Resultの4つのシーンが完成しました。適切なタイミングで4つのシーンを切り替えてみましょう。 シーン同士の関係は以下のようにします。 シーンを繋げる前に、各シーンがビルド対象になるように設定します。 新しく作ったシーンは最初はビルドされない設定になっており、スクリプトでそれぞれを繋ぐことができません。 File→Build Settingを選択してください。 Add Open Scenesをクリックして、Main、Title、GameOver、Resultのシーンをビルド対象に設定してください(シーンを直接ドラッグ&ドロップしても可) ゲームを起動した時は一番上のシーンから始まります 。 最初は当然タイトルから始まってほしいので、タイトルシーンを一番上に動かしてください。 まずはタイトルからゲーム画面に遷移できるようにします。スぺースキーが押されたらシーンが切り替わるようにしましょう。 先ほどの図を思い出してください。ゲームオーバーとリザルトからタイトルに戻る際にもスぺースキーを押してシーンを切り替える処理を実装する予定になっています。そのため、後から処理を使いまわせるように実装しましょう。 新しいスクリプトSceneChangeを作成して以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; // シーンを扱う時に必要 public class SceneChange : MonoBehaviour { public string SceneName; // 遷移先のシーン名 void Update() { // スペースキーが押されたらシーンを切り替える if (Input.GetKeyDown(KeyCode.Space)) { SceneManager.LoadScene(SceneName); } } } 【プログラムの解説】 ・TextMeshProの時と同じように、Sceneを扱う際は using UnityEngine.SceneManagement; を追加する必要があります。 ・LoadScene関数でシーンを切り替えることができます。第一引数に遷移したいシーンの名前を入力することで遷移できます。 名前以外にもシーン番号を入力しても遷移できます。 SceneManager.GetActiveScene().name で現在のシーン名を取得できます。 LoadScene(現在のシーン名) を実行することで、ステージを最初からやり直す機能なども実装できます(詳しい使い方は2Dアクションゲーム編にて! ) Unity Tips! スクリプトが完成したらタイトルに空オブジェクトを追加して、SceneChangeをアタッチしてください。オブジェクト名は何でもOKです。 変数SceneNameをpublicにしているので、毎度のごとくインスペクターに表示されています。ここに遷移先のシーンの名前を入力しましょう。 タイトルから遷移したいのは当然メインなので「Main」と入力してください。 これでタイトル画面でスぺースキーを押すとゲーム画面へ切り替わるようになりました。実際にゲームを実行して確認してみてください。 シーンを切り替えた際にライティングが適用されていない問題が発生した場合の対処法です。 これはシーンの明るさを事前計算できていないことが原因になっています。 Window→Rendering→Lightingを選択してライティングウィンドウを開いてください。Mainシーンを開いた状態で「Generate Lighting」をクリックしてください(Auto Generate にチェックが入っていた場合は外してください) 実行してしばらくするとライトマップが作成され、シーンを切り替えても明るさが反映されるようになります。 Unity Tips! 次はゲーム画面からゲームオーバーとゲームクリアへの遷移を実装します。ゲームオーバーの条件は「体力が0になったら」、ゲームクリアの条件は「全ての星を集めたら」です。既にステージにある全ての星の数は取得しているので、それを使ってみてください。 新しいスクリプトSceneChangeMainを追加して以下のように入力してください。青い部分 は穴埋めなのでヒントを参考に埋めてみましょう。 【ヒント】④ は PlayerHitPoint 、 ⑤ は StarCountスクリプトも確認してみましょう! using System.Collections; using System.Collections.Generic; using UnityEngine; // ① Sceneを扱う場合、追加する必要がある (ここに入力) public class SceneChangeMain : MonoBehaviour { // ② PlayerHitPointを保存するpublicな変数(4-3を参考に!) (ここに入力) // ③ StarCountを保存するpublicな変数(4-3を参考に!) (ここに入力) void Update() { // ④ もしも体力が0以下になったらゲームオーバーシーンに遷移する (ここに入力) // ⑤ もしも集めた星の数と最大の星の数が同じになったら // リザルトシーンに遷移する // (それぞれの情報を取得する関数は実装済み!確認してみよう) (ここに入力) } } 降参 or 答え合わせの方はこちら スクリプトが書けたらMainシーンのGameオブジェクトにアタッチしてください。 ②と③で変数をpublicにしているので、ヒエラルキーから適切なオブジェクトをドラッグ&ドロップしてください。4-3を参考にしてみましょう。 体力が0になったらゲームオーバー画面に切り替わることと、星を全て集めたらリザルト画面に切り替わることを確認してみてください。 最後はゲームオーバーとリザルトで「SPACEキーが押されたらタイトルに戻る」処理を実行するだけです。先ほど作成したSceneChangeスクリプトを使いましょう。 ゲームオーバーシーンに新しい空オブジェクトを作成して、SceneChangeスクリプトをアタッチしてください。アタッチしたらインスペクターからSceneName(遷移先のシーン名)を「Title」に設定してください。 リザルトも同じように空オブジェクトを作成→SceneChangeスクリプトをアタッチ→シーン名を設定 という流れを行ってください。 これで全てのシーンが繋がり、ゲームを繰り返し遊べるようになったと思います。ゲームを実行して確認してみてください。 5-6 シーン越しに変数を共有 5-6 シーン越しに変数を共有 これで一通り完成…と行きたいところですが、Resultシーンのクリアタイムが99:99のままになっています。 しかしクリアタイムを計測しているのはMainシーンのGameTimerコンポーネントになります。シーンを変更すると前のシーンにあったオブジェクトは基本的に全て破棄されてしまいます 。 シーンを跨いで変数を共有してみましょう。 staticを使ってゲームからリザルトに変数を引き継いでみましょう。GameTimerスクリプトを以下のように変更してみてください。赤い部分 が変更箇所です。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameTimer : MonoBehaviour { static float m_second; // 秒 static int m_minute; // 分 // 分と秒を返す関数 static public float GetSecond() { return m_second; } static public int GetMinute() { return m_minute; } void Start() { // 計測時間を初期化 m_ second = 0.0f; m_minute = 0; } void Update() { // タイマー加算 m_second += Time.deltaTime; if (m_second > 60.0f) { // 1分経過 m_minute++; m_second -= 60.0f; } } } 【プログラムの解説】 ・ 関数にstatic public をつけると、 クラス名.関数名 でどのクラスからも関数を参照できるようになります。 注意しなければならないのが、staticにした変数はシーンを跨いでも持ち越される=そのままでは初期化されない ということです。初期化をしないとステージに再挑戦しても時間がリセットされずどんどん加算されていくことになってしまいます。Startに初期化を記述することでゲームが始まった時に毎回タイムを初期化するようにしています。 この変更を行うとTimerUIスクリプトに関数をstaticに変更したことによるエラーが発生します。 代わりに型名を使用してくださいと書いてある通り、変数ではなく型名を使うようにしましょう 。赤い部分 のように修正してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; public class TimerUI : MonoBehaviour { TextMeshProUGUI m_timerTMP; //public GameTimer Game_Timer; void Start() { m_timerT MP= GetComponent(); } void Update() { // 現在の時間を表示 string timeString = ""; timeString = GameTimer .GetMinute().ToString("00"); timeString += ":"; timeString += Mathf.Floor(GameTimer .GetSecond()).ToString("00"); m_timerT MP.text = timeString; } } 後は同じようにリザルトでもタイムを取得、表示するだけです。どの方法を使っても構いませんが、教材では新しくResultTimerUIスクリプトを作成してアタッチしています 。 参考にしたい方はResultTimerUIスクリプトを作成して以下のように入力してください。 using System.Collections.Generic; using UnityEngine; using TMPro; public class ResultTimeUI : MonoBehaviour { TextMeshProUGUI m_timerTMP; void Start() { m_timer TMP= GetComponent(); } void Update() { // 現在の時間を表示 string timeString = ""; timeString += "ClearTime "; timeString += GameTimer.GetMinute().ToString("00"); timeString += ":"; timeString += Mathf.Floor(GameTimer.GetSecond()).ToString("00"); m_timer TMP.text = timeString; } } ResultTimerUIスクリプトをリザルトシーンのクリアタイム用TextMeshProにアタッチしてください。 実行してクリアタイムがリザルトに引き継がれていたらOKです。 一通りの機能は実装できたのでこれで完成でもよいのですが、最後のLessonでは音やエフェクトを実装してゲームを装飾してみましょう。 変数にstatic publicをつけると クラス名.変数名 でどのクラスからも変数を参照できるようになります。 Unity Tips! 評価テスト 【評価テスト】 https://forms.gle/KoEY5bHuj7zMnL449 Next Lesson6「クオリティを上げよう」 ページ TOP 5-1 タイトル画面 5-2 ゲームオーバー画面 5-3 リザルト画面 5-4 クリア画像を動かす 5-5 シーンを繋げる 5-6 シーン越しに変数を共有 評価テスト
- 404 | Unity1gc2
There’s Nothing Here... We can’t find the page you’re looking for. Check the URL, or head back home. Go Home
- 3Dアクションゲーム編 Lesson6「クオリティを上げよう」 | Unity1gc2
3Dアクションゲーム編 Lesson6 クオリティを上げよう ここからはゲームのクオリティアップには欠かせない、音やエフェクトを実装していきます。スクリプトをあまり書かないパートなので気軽にチャレンジしてみてください。 6-1 BGMを再生 6-1 BGMを再生 無音のままではゲームとして寂しいのでBGMを再生してみましょう。インゲーム中に再生したいBGMを用意してください。ループ再生に対応しているものを推奨します。ファイル形式はmp3、ogg、wavあたりが対応しています。 【サンプルで使用しているBGM】https://maou.audio/game_village05/ 好きな素材を用意できたらSoundフォルダを作成して、BGM素材をドラッグ&ドロップしてください。 Unityにおいてmp3などのサウンドファイルは「オーディオクリップ」と呼ばれます。追加したオーディオクリップを選択して、再生方式をストリーミング再生に変更してください。 デフォルトのDecompress On Loadはサウンドを全て読み込んでから再生する方式で、Streamingは序盤だけ最初に読み込んで、再生しながらその後のデータをロードしていく方式です(Youtubeの再生バーをイメージするとわかりやすいと思います) SEと異なりBGMファイルはサイズが大きいので、全てを読み込んでいると再生開始までに時間がかかってしまいます。 設定変更後にはApplyボタンを押してください。 後はBGMを設定するだけです。MainシーンのGameオブジェクトにAdd ComponentからAudio Sauceをアタッチしてください。Audio Sauceは名前の通り音の発生源となるコンポーネントです。 追加されたAudioSourceのAudioClipに先ほど追加したBGMファイルをドラッグ&ドロップしてください。 PlayOnAwakeとLoopにチェックを入れておいてください。 PlayOnAwakeは最初に自動で再生を開始するかどうかの設定、Loopは文字通りループ再生するかどうかの設定です。 設定できたら実行してBGMが再生されることを確認してみてください。 解説ではMainシーンの設定のみ行いますが、同じように他のシーンにBGMを設定してもOKです。 6-2 SEを再生 6-2 SEを再生 次は効果音を再生してみましょう。解説ではジャンプ音、星の取得音、大砲の発射音の設定のみ行います。どれも頻繁に鳴る音なので短い音がオススメです。 BGMと同じように効果音素材を用意してください。 【サンプルで使用しているジャンプ音】https://maou.audio/se_system33/ 【サンプルで使用している星の取得音】https://maou.audio/se_system47/ 【サンプルで使用している大砲の発射音】https://maou.audio/se_battle_explosion03/ こちらも素材を用意できたらSoundフォルダ内にドラッグ&ドロップしてください。 効果音のオーディオクリップに対しては特に設定は必要ありません。 ユニティちゃんを選択してAudioSourceを追加してください。 AudioClipにはジャンプの効果音を設定し、PlayOnAwakeとLoopのチェックは両方外してください。 後はジャンプの瞬間にAudioSourceを再生するだけです。 Playerスクリプトを開いて赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; public float JumpPower = 6.0f; Rigidbody m_rigidBody; Animator m_playerAnimator; GameObject m_mainCamera; AudioSource m_audioSource; // 接地判定用スクリプト public GroundChecker Ground_Checker; bool m_moveFlag, m_jumpFlag, m_airFlag; void Start() { // 自分にアタッチされているRigidBodyを取得する m_rigidBody = GetComponent(); // 自分にアタッチされているAnimatorを取得する m_playerAnimator = GetComponent(); // 自分にアタッチされているAudioSauceを取得する m_audioSource = GetComponent(); // メインカメラのゲームオブジェクトを取得する m_mainCamera = Camera.main.gameObject; } ~中略~ // ジャンプ if (Input.GetKeyDown(KeyCode.Space) && Ground_Checker.GetIsGround()) { m_rigidBody.AddForce(new Vector3(0.0f, JumpPower, 0.0f), ForceMode.VelocityChange); m_jumpFlag = true; // 効果音を再生 m_audioSource.Play(); } 【プログラムの解説】 ・今までと同じように自身にアタッチされたAudioSourceをGetComponentで取得しています。 ・AudioSourceのPlay関数でAudioClipを再生することができます。 これでジャンプの効果音が再生されます。実際にプレイして確認してみてください。 星の取得音も再生してみましょう。ジャンプと同じように再生したいところですが、星は取得すると消えてしまうので、星にAudioSourceをつけても取得した瞬間に一緒に消えてしまいます 。 そのため、星を取得した瞬間に効果音再生用のオブジェクトを生成する必要があります 。 また、効果音再生用のオブジェクトは、効果音の再生が終わったら自動で消えるようにしないとシーン上にずっと残り続けてしまいます。無駄なオブジェクトが残っているとゲームが重くなる原因になってしまいます。 それでは効果音再生用のオブジェクトを作りましょう。空のオブジェクトを追加して名前を「OneShotSE」にします。 OneShotSEにAudioSourceをアタッチしてください。PlayOnAwakeとLoopにはチェックを入れないようにします。 それ以外は何も設定しなくてOKです。 新しいスクリプトOneShotAudioClipを作成します。以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class OneShotAudioClip : MonoBehaviour { AudioSource m_audioSource; bool m_IsPlay = false; public void PlaySE(AudioClip audioClip) { // 自分にアタッチされているAudioSourceを取得 m_audioSource = GetComponent(); // オーディオクリップを設定 m_audioSource.clip = audioClip; // 再生 m_audioSource.Play(); // 再生フラグを立てる m_IsPlay = true; } void Update() { // 再生フラグが立っていて、オーディオソースの再生が終わったら… if(m_IsPlay && m_audioSource.isPlaying == false) { // 自身を削除する Destroy(gameObject); } } } OneShotAudioClipをOneShotSEにアタッチしてください。最終的にインスペクターはこのようになります。 ここまで進んだらOneShotSEをプレハブ化してください。プロジェクト内のPrefabフォルダにOneShotSEをドラッグ&ドロップします。 プレハブ化できたらヒエラルキー内のOneShotSEを削除してください。 次はStarObjectスクリプトにOneShotSEを生成する処理を追加します。赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class StarObject : MonoBehaviour { StarCount m_starCount; // 取得時の効果音 public AudioClip StarGetSE; // 効果音再生用オブジェクト public GameObject OneShotPrefab; void Start() { // Gameという名前のオブジェクトを検索する GameObject gameObj = GameObject.Find("Game"); // 検索したオブジェクトからStarCountコンポーネントを取得する m_starCount = gameObj.GetComponent(); } void Update() { // Y軸周りに回転する transform.Rotate(0.0f, 0.2f, 0.0f); } private void OnTriggerEnter(Collider other) { // もし自身と衝突したオブジェクトのタグがPlayerだったら… if (other.CompareTag("Player")) { // 取得時の効果音を再生 GameObject oneShotObj = Instantiate(OneShotPrefab); oneShotObj.GetComponent().PlaySE(StarGetSE); // ここで星を取得する処理を書く m_starCount.StarAdd(); // デバッグ:獲得した星の数を表示 //Debug.Log(m_starCount.GetNowStarCount()); // 自身を削除する Destroy(gameObject); } } } 【プログラムの解説】 ・Instantiate関数は引数にしたオブジェクトを生成する関数です。自信のない人は大砲の処理(3-5)を参考にしてください。 例のごとくpublicにした変数がインスペクターに表示されています。星のプレハブをダブルクリックして開いてください。 プレハブを直接編集すると、同じプレハブのオブジェクト全てに変更が反映されます 。既にステージに設定したたくさんの星をいちいち設定する必要はないということです。 StarGetSEには星の取得音のAudioClipを、OneShotPrefabには先ほど作成したOneShotSEのプレハブを設定してください。 これで星を取得した時に効果音が再生されるようになりました。実際に星を取得して確認してみてください。 ここまでのOneShotAudioClipを用いた一連の内容はどのゲームでも使える処理です。皆さんがゲームを作る際にもぜひこのプログラムを流用してください。 星の取得音は問題なし…と思いきや最後に取得した星の取得音だけが正常に再生されなかったと思います。 これは最後の星を取得した瞬間にシーンが切り替わったから です。OneShotSEを作成したことで星が消えても効果音が再生されるようになりましたが、シーンが切り替わるとOneShotSEもまとめて消えてしまいます。 シーンが切り替わってもオブジェクトが勝手に消えないようにするコードがあります。OneShotAudioClipに画像のような関数を追加してください。 Awake関数はStart関数よりも先に実行される関数 です。正確には、オブジェクトが生成された直後に実行されるのはAwake関数で、Start関数は1回目のUpdate関数が呼ばれる前に実行される関数になります。 DontDestroyOnLoad(gameObject); を実行することで、引数に設定したオブジェクトがシーンの変更に巻き込まれなくなります。この場合Destroy(gameObject); などで直接消してあげないとオブジェクトが残り続けてしまうので注意しましょう。 ちなみにLesson5ではクリアタイムの変数がシーンをまたげるようにstaticを使用しましたが、DontDestroyOnLoadを用いて変数を保持する方法もあります。それぞれメリットとデメリットがありますが、最初の内は使いやすい方で構いません。 Unity Tips! 大砲の発射音も再生しましょう。 大砲は消えることはないのでOneShotSEを使う必要はありません。ですが大砲の音がどこにいても聞こえてしまうと、大砲をたくさん設置した際にかなりうるさくなってしまいます。今まではどこにいても均等に音が聞こえる「2D」サウンドでしたが、ここでは距離によって音量が減衰していく「3D」サウンドを使用します。 大砲にAudioSourceを追加しましょう。Cannonのプレハブを開き、Add Componentから追加してください。 AudioSourceのパラメータを設定します。今まではSpatial Blendが2Dでしたが、これを3Dに変更することで距離によって効果音の音量が変わるようになります 。スライダーを一番右へ動かして3Dに変更しましょう。 AudioClipには大砲の効果音を設定して、PlayOnAwakeとLoopのチェックは外しておいてください。 サンプルの発射音は少し長く、重たい印象を受けるのでPitchを2に上げています 。ピッチを上げると音が高く、速く再生されるようになります。ピッチは好みに応じて自由に調整してください。 AudioSauce下部にある3D Sound Settingを開きます。ここでは3Dサウンドの聞こえ方を調整することができます。 シーン内に見える青い球体が音の聞こえる範囲です。サンプルではMaxDistance(音が聞こえる最大距離)を100に調整しています。これも世界の広さに応じて自由に調整してください。 下部にあるグラフを見ると距離が50以上になるとほとんど音は聞こえないことが分かります。実際にはBGMに紛れてほぼ聞こえないはずです。そのため最大距離は大きく設定しています。 ここまでできたらCannonObjectスクリプトを開いて発射音を再生するコードを書きましょう。 赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class CannonObject : MonoBehaviour { public GameObject Bullet; // 発射するオブジェクト public float CannonInterval = 3.0f; // 発射間隔 public float CannonSpeed = 2.0f; // 弾の速度 public float CannonLimit = 5.0f; // 弾の生存時間 public float BulletAddY = 1.2f; // 弾の発射位置を持ち上げる量 public int DamagePower = 0; // ダメージ量 private float m_cannonTimer = 0.0f; AudioSource Audio_Source; void Start() { // 自分にアタッチされたAudioSourceを取得 Audio_Source = GetComponent(); } void Update() { // タイマー減少 m_cannonTimer -= Time.deltaTime; if (m_cannonTimer <= 0.0f) { // 発射座標を計算する Vector3 bulletPos = transform.position; bulletPos.y += BulletAddY; bulletPos += -transform.right * 2.0f; // 発射処理 GameObject bullet = Instantiate(Bullet, bulletPos, Quaternion.identity); // BulletObjectを取得する BulletObject bulletObject = bullet.GetComponent(); // 移動量を計算する(大砲の発射口が左にあるモデルなので左方向を取得) Vector3 move = -transform.right * CannonSpeed; // 移動速度、生存時間、ダメージ量を弾に教える bulletObject.Set(move, CannonLimit, DamagePower); // 発射の効果音を再生 Audio_Source.Play(); // タイマーを戻す m_cannonTimer = CannonInterval; } } } これで大砲の発射音が再生されるようになりました。実際にプレイして距離に応じて音量が変化していることを確認してみてください。 ちなみにサウンドにおける「耳」の役割を果たしているのはAudioListenerというコンポーネントです。これは最初からカメラにアタッチされています。 例えばこれをユニティちゃんにアタッチすれば、ユニティちゃんと大砲の距離で音量が変化するようになります。ただしAudioListenerはシーン上に2つ以上存在できない ので注意しましょう。 解説ではこれ以上の効果音は実装しませんが、今までの内容を参考にぜひ他の効果音も追加してみてください。 6-3 パーティクルの追加 6-3 パーティクルの追加 Unityには簡単なエフェクトを作ることのできる「Particle System 」 という機能があります。パーティクルとは塵や粒子という意味です。 今回はこれを使ってユニティちゃんがダメージを受けた際に光の粒が飛び散るようにしましょう。 ちなみにパーティクルを実装するパーティクルエンジンという概念は他のゲームエンジンにもあります。Unityのパーティクルエンジンは「Shuriken(シュリケン)」と呼ばれています。 外部の解説サイトなどでShurikenというワードを見た時はこのParticle Systemとほぼ同じ意味と思ってもらって構いません。 Unity Tips! ヒエラルキーから「Effects」→「Particle System」を追加してください。 シーン上に光の粒子を放つオブジェクトが追加されました。見えやすい場所に移動して、名前もわかりやすいようにDamageParticleに変更しておきましょう。 Particlesウィンドウからプレビューの再生、停止ができます。 エフェクトの周りに表示されるオレンジ色の枠が邪魔な人は右上のGizmoボタンをクリックして、Selection Outlineのチェックを外してください。 Unity Tips! ParticleSystemにはたくさんのパラメータがあり、様々な設定をすることができます。ここでは全て説明することはできませんが、興味のある方は色々調べてみてください。 まずは基本となるパラメータを調整します。以下のパラメータを設定してください。 (数値はあくまで例なので自由に調整して構いません) ・Durationを0.2にする →エフェクトの再生時間です。小さくして テンポよく表示されるように 短くしています。 ・Looping のチェックを外す →ループ再生にするかどうか設定します。 チェックを外すとループ再生がされなくなり エフェクトが終わるようになります。 ・Start Lifetimeを0.6にする →1つ1つの粒子の生存時間です。 小さくすると早く消えるように なります。 ・Start Speedを2にする →パーティクルの初速です。初期値の5では 粒子が遠くに飛びすぎているので、 小さく 調整しています。 ・Start Sizeを0.4にする →パーティクルの初期サイズです。 少し小さくして粒子っぽくしています。 ・Stop ActionをDestroyにする →パーティクルが終了した際の処理 です。 Destroyにするとパーティクルが終了した時に 自動でオブジェクトが削除されるように なります。 他にもStartColorで粒子の色を変えたり、Gravity Modifierで粒子に重力をかけたりできます。色々変更してみましょう。 また、パラメータの右側にある▼をクリックすると時間の経過に応じてパラメータを変化させることができます。 「Curve」にすると下部に赤いグラフが表示され、マウスで操作することができます。サンプルではStart Speedがだんだん小さくなるグラフを設定しています。 ※ グラフが出てこない人は下部のParticle System Curveをクリックして上に動かしてみましょう。 「Random Between Two Curves」を選択すると2つのグラフの間からランダムに値が決定されるようになります。これによってパラメータの初期値を散らして、エフェクトの単調さを軽減できます。 Unity Tips! 残りのパラメータを調整します。同じように以下のパラメータを設定してください。 それぞれの項目が閉じていたらクリックして 開いてください。 ・EmissionのRateOverTimeを100にする →1秒間に放出される粒子の数です。 大きくすると大量に粒子が放出される ようになります。 ・ShapeのShapeをSphereにする →粒子が放出される形を設定します。 Coneは文字通りコーン型(円錐 ) 、 Sphereは球体なので全方向に 放出されます。 ・Size Over Lifetimeにチェックを入れ、 Sizeのグラフを設定する →時間に応じて粒子の大きさを 変化させることができます。 右下のボタンを押すとだんだん減少 していくグラフが 自動で設定されます。 納得のいくダメージエフェクトが完成したら、Prefabフォルダにドラッグ&ドロップしてプレハブ化してください。 プレハブ化できたらヒエラルキー内にあるDamageParticleは削除してください。 後はダメージを受けた時にDamageParticleのプレハブを生成するだけです。 PlayerHitPointスクリプトを開いて、赤い部分のコード を追加してください。青い部分 は穴埋めなのでコメントを参考に記述しましょう。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerHitPoint : MonoBehaviour { public int MaxHitPoint = 3; public int HitPoint = 3; public float InvincibleTime = 3.0f; // 無敵時間 private float m_invincibleTimer = 0.0f; // 無敵時間のタイマー private bool m_invincibleFlag = false; // 無敵フラグ // ①ダメージエフェクトを格納するpublicなGameObject型の変数 (ここに記述) // エフェクトが足元に出ないように持ち上げる補正量 public float DamageEffect_Y_Up = 0.8f; // ダメージ処理 public void Damage(int damage) { // 無敵時間中は実行されないようにする if (m_invincibleFlag) { return; } // Mathf.Clampは値が範囲内に収まるようにする関数 HitPoint = Mathf.Clamp(HitPoint - damage, 0, MaxHitPoint); // 無敵時間開始 m_invincibleFlag = true; // 無敵タイマーをセットする m_invincibleTimer = InvincibleTime; // エフェクトを表示する座標を計算する Vector3 effectPos = transform.position; effectPos.y += DamageEffect_Y_Up; // ②ダメージを受けたらダメージエフェクトを生成する // 座標はeffectPos(CannonObjectの発射処理を参考に!) (ここに記述) } void Update() { if (m_invincibleFlag) { // 無敵時間のタイマー減少 m_invincibleTimer -= Time.deltaTime; if (m_invincibleTimer <= 0.0f) { m_invincibleFlag = false; // 無敵時間終了 } } } } 降参 or 答え合わせの方はこちら DamageEffect_Y_Upはエフェクトの出現位置を上に持ち上げる量を保持する変数です。ユニティちゃんの座標は足元になっているので、補正しないとエフェクトが足元から出てしまいます。この変数も例のごとくpublicなので自由に調整してください。 これでダメージを受けた時にパーティクルが放出されるようになりました。ゲームを実行して確認してみてください。Stop ActionをDestroyにしているので、再生が終わったオブジェクトは自動で削除されます。 パーティクルが気に入らない時はプレハブをダブルクリックしてパラメータを調整してみましょう。 落下した時にエフェクトが再生されないのは、落下した位置にエフェクトが出現しているからです。 PlayerFallスクリプトの落下処理の順番を入れ替えて、位置を初期位置に戻してからダメージを受けるようにすればエフェクトの位置も正常になります。 Unity Tips! ここではダメージエフェクトのみ実装しますが、ぜひ星を取得した時のエフェクトや大砲の弾が消える時のエフェクトなども実装してみてください。今回触らなかったパラメータも色々変更してみましょう。 【色々なParticleSystemの設定が載っているオススメのサイト】 https://styly.cc/ja/tips/tips-unity_particle/ 6-4 ポストエフェクト 6-4 ポストエフェクト 最後の仕上げに画面にポストエフェクト をかけます。ポストエフェクトとは、 描画の最後にフィルターをかけるように画面を加工する処理のことです。 これで画面を少しリッチにして、この3Dアクションゲーム編を締めくくろうと思います。 上部の「Window」→「Package Manager」を選択してください。 パッケージマネージャーが開いたら左上の表示範囲の設定を確認してください。 もし「Packages: In project」になっていたらクリックして「Unity Registry」に変更してください。 右上の検索欄に「Post Processing」と入力してください。同名のパッケージが表示されるので、右下のInstallボタンをクリックしてインストールしてください。 Post Processing(以下ポストプロセス)は、Unityでポストエフェクトをかけるために必要な素材をまとめたパッケージです。 Post Processingの横に緑のチェックマークが出たら導入完了です。パッケージマネージャーを閉じてください。 ポストプロセスの設定をしていきましょう。 インスペクターのMainCameraを選択して、Add Componentから「Post-Process Layer」を検索して追加してください。 Layerの設定をNothingからDefaultに変更してください。 次はGameオブジェクトを選択して、Add Componentから「Post-Process Volume」を検索して追加してください。 Is Globalにチェックを入れてからNewボタンをクリックしてください。 Newボタンを押すと下部に「Add effect…」のボタンが追加されます。 このボタンを押してUnityを選択すると様々なポストエフェクトが表示され、クリックすることでゲームに追加することができます。 今回はこの中でもよく使う、いくつかのポストエフェクトを追加していきます。 まずは「Ambient Occlusion」です。 これを追加すると折り目や角、狭い場所が暗くなります。 これによって物体がその場に置かれている雰囲気が出て、画面の現実感が増します。 Intensityの値を調整すると影響度が変化します(教材では0.3) ただ、少し重く時間のかかる処理なのでスマートフォンゲームなどで実装する際は注意が必要です。 以下の画像を見比べてみると、Ambient Occlusionなしに比べてありの方は足場に影がついて、現実感が上がっていることが分かると思います。今はシンプルなモデルなのでわかりにくいですが、複雑なモデルだと効果を実感しやすいです。 次は「Bloom」です。 これを追加すると光があふれ出ているように見えるようになります。特に暗闇で光るネオンのような物体が綺麗に見えるようになります。 このゲームに暗闇で光る物体はありませんが、それでも画面がぼんやり明るくなります。 Intensityの値を調整すると光の強さが変化します(教材では1) また、Thresholdの値を調整するとBloomの対象になる明るさのボーダーが変化します。 以下の画像を見比べてみると、Bloomありの方は足場の端が明るくなっています。空や画面も全体的に明るくなり、野外にいる雰囲気が増しています。 最後は「Depth of Field」です。 被写界深度と呼ばれるもので、ピントが合っている部分以外がぼやけるようになります。ピントが合ってほしいのは当然ユニティちゃんなので、ユニティちゃんにピントが合うようにパラメータを調整してみてください。 Focus Distanceはピントの合う距離です(教材では1.6) Apertureはピントの合う範囲です。値が小さいほど広い範囲がぼやけます(教材では15) ゲームを実行しながらインスペクターの値を操作すると調整しやすいです。ゲームなのでかなり弱めにかけていますが、グラフィックを強調したいシーン(イベントシーンなど)では強くかけることで印象的な画面作りができます。 以下の画像を見比べてみると、Depth of Fieldがオンの方はユニティちゃん以外の場所がぼやけています(違いがわかりやすいように強めにかけています) 他にもポストプロセスには色調補正のできるColor Gradingや画面を歪ませるLensDistortionなど様々なエフェクトがあります。ぜひ色々触ってみてください。 【解説しなかったポストプロセスについて載っているサイト】https://styly.cc/ja/tips/unity_postprocessing_master/#Post_Processing_Stack 6-5 ゲームのビルド 6-5 ゲームのビルド これで3Dアクションゲーム編のLessonはおしまいです。 物足りない人はLessonEX を見て様々な要素を加えてゲームのクオリティを上げていってください。最初に実装するものとしてオススメなのはポーズ画面 です。 ゲームが完成したらビルド して、他の人が遊べる形式にしてみましょう。 左上の「File」から「Build Settings…」を選択してください。 ビルド前に左下の「Player Settings…」をクリックしてください。 Playerを選択するとゲーム名やバージョン、アイコンなどの設定ができます。好みの設定にしておいてください。 マウスを使うゲームであればカーソル画像の設定もできます。 (ゲーム名は日本語でも大丈夫なはずです) 下部の「Resolution and Presentation」をクリックして、Fullscreen Modeから起動時のモードを変更できます。 起動時はフルスクリーンですが、Windowedモードにすることでスクリーンサイズを指定することができます。Gameウィンドウで指定した16:10というサイズはあくまでプレビュー用のサイズで、実際のビルドサイズはここで指定できます。教材では16:10に合わせて1600×1000にしておきます。 準備ができたら右下の「Build And Run」を選択してください。 適当な場所に空のフォルダを作り、出力先に指定します。 ビルドが終わるとゲームが開きます。これでビルドは完了です。 無料版Unityで制作したゲームは起動時にUnityのロゴが出ます。どうしても消したいという方は有料プランへの加入が必要です。Player SettingsのSplash Screen で多少の内容調整は可能です。 ビルドしたゲームは他の人に配布して遊んでもらうことができます。今までのLessonを参考に色々なゲームを作ってみましょう。 ※ (趣味の範疇であれば)好きな版権キャラを使って自由にゲームを作ってOKです。もちろん2DでもOK!とにかく数をこなせば、いつの間にかUnityが上達しているはずです! 完成させるのが一番ですが、完成しなくてもまずはUnityをたくさん触ってみましょう! お疲れさまでした! ここから先のLessonは皆さんが自由に選んでください。 2Dゲームやマウス操作について知りたい方は「2Dランゲーム編 」 1人称視点やより発展的な3Dゲームについて知りたい方は「3D 脱出ゲーム編 」 ほとんどが穴埋め式で練習に最適な「2Dシューティングゲーム編 」 ゲーム制作に役立つ小ネタ集を見ながら自分だけのゲームを作りたい方は「LessonEX 」 評価テスト 【評価テスト】 https://forms.gle/GWe18ytPTraLxL4v7 ページ TOP 6-1 BGMを再生 6-2 SEを再生 6-3 パーティクルの追加 6-4 ポストエフェクト 6-5 ゲームのビルド 評価テスト
- EX HD2Dもどき | Unity1gc2
LessonEX HD-2Dもどき EX-1 3D空間に2D画像を表示 ドット絵の2Dキャラクターと3Dの背景を組み合わせて表示する手法をHD-2D と呼びます。有名なのはOCTOPATH TRAVELER(オクトパストラベラー)あたりでしょうか。 同じように3D空間に2D画像を表示してみましょう(本家は背景のテクスチャがドット絵ですが、ドット絵テクスチャの3Dモデルは用意できなかったのであくまで「もどき」です) (公式サイトより転載) 画面から読み取れる特徴としては、 ・3Dで作られたステージに2Dのドット絵で描かれたキャラクターが配置されている ・影やライトの影響を受ける ・被写界深度が強めにかかっている といったところです。 それでは早速、3D空間に2D画像を配置してみましょう。 教材で使われている素材はここからDLできます →→→→→→ 自前で用意した素材を使っても構いません。 ダウンロードした素材はそのままプロジェクト内に ドラッグ&ドロップで追加してOKです。 それでは3Dのプロジェクトを作成して「3D Object」→「Plane」で地面を追加してください。 素材をダウンロード プロジェクトに追加した画像素材を全て選んで、 FilterMode :Point(no filter) Compression :None に変更してからApplyをクリックしてください。 どちらの設定もUnityが勝手に画像を加工するのを防ぎ、元の素材(ドット絵)をそのまま表示するための設定です。 Ground_Normal(法線)を選び、TextureTypeをNormal mapに変更してからApplyをクリックしてください。 「Create」→「Material」で新しいマテリアルを作成して、名前をGroundにしておきます。 Albedoには「Ground_Albedo」、Normal Mapには「Ground_Normal」をドラッグ&ドロップしてください。 Metallic、Smoothness、Tilingなどのパラメータを調整して見栄えを整えましょう(お好みでOK) マテリアルができたら、Planeにドラッグ&ドロップで貼り付けてください。 ChikenとFireを選んでください。TextureTypeを「Sprite」に、SpriteModeを「Multiple」に変更してから、Applyをクリックしてください。 ※ 自前の画像を使う場合、分割する画像だけをMultipleに変更してください 「Window」→「Package Manager」を選択してください。 左上のPackagesを「Unity Registry」に変更して、右上の検索欄に「Sprite」と入力しましょう。「2D Sprite」が左側に表示されるのでそれを選択して、右下のInstallボタンをクリックしてください。 2D SpriteはHD-2Dを作るときに必ずしも必要なものではないですが、画像を分割する時などに必要になります。 インストールできたら、ChickenのSprite Editorを開いてください。 左上のSliceボタンをクリックし、 ・Type を「Grid By Cell Count」に変更 ・Cloumn&Row をC=6、R=4に変更 以上の手順を実行してから、Sliceをクリックしてください。画像が分割されます。 最後に右上のApplyボタンをクリックして、Sprite Editorを閉じてください。 Chikenの正面の画像をシーン上にドラッグ&ドロップしてください。 Chikenの位置や大きさを調整してください。 少し大きめで、Y座標を少し上に上げておくのをオススメします。 カメラの位置や回転を調整して、Chikenが大きく見えるようにしてください。 画像が動かない点が気になるので、「Create」→「Animation」でアニメーションを作成してChikenにドラッグ&ドロップしてください。 ※ アニメーションの設定はやらなくてもOKです アニメーションをダブルクリックして、アニメーションウィンドウを開いてください。 アニメーションウィンドウに画像をドラッグ&ドロップするとアニメーションを作成できます。「Chicken_0」「Chicken_1」「Chicken_2」を使って、待機アニメーションを作成してみましょう。 アニメーションのLoop Timeにチェックがなかった場合、 チェックを入れておいてください。 ゲームを実行してみると、3D空間上にある2D画像がアニメーションします。 ですが、地面をよく見ると影が落ちていません。このままでは不自然なので 、次は影が落ちるようにしてみましょう。 新しいマテリアルMaterial2Dを作成して、RenderingModeはCutoutにしてください。 マテリアルをChikenにドラッグ&ドロップしましょう。 ライトの影響を受けるようになりますが、まだ影は落ちません。 インスペクター右上のボタンを押して、デバッグモードに変更してください。 Unityのデバッグモードでは、普段は隠れているパラメータを確認することができます。影に関するパラメータも普段は隠されていますが、デバッグモードにすることで表示することができます。 SpriteRenderのCastShadowsを「On」に、ReceiveShadowsにチェックを入れてください。 CastShadows は「影を落とすかどうか」の設定です。これで地面に影が落ちるようになります。 ReceiveShadows は「影を落とされるかどうか」の設定です。これにチェックをいれることで、物陰に隠れたときに自分に影が落ちるようになります。 この設定ができたら、デバッグモードは解除しておきましょう。 CastShadows を「Two Siled」にすると、後ろから光が当たっても影が落ちるようになります(3Dアクションゲーム編の3-2 で解説した内容と同じ) Unity Tips! ChikenにBoxColliderとRigidBodyをアタッチしてください。特に設定は変更しなくても構いません(HD-2Dの実装には不要ですが、影が落ちているのをわかりやすくするためにサンプルでは重力を実装します) EX-2 背景を作る 同梱した3Dモデルを使って簡単な町を作ってみてください。後ほど被写界深度を実装するので、奥と手前に配置することを意識しましょう。地面が狭い場合は広くしても構いません。 作った町を歩き回れるようにしましょう。お試しなのでジャンプなどは実装せず、ただ移動できるだけです。 新しいスクリプトPlayerを作成して、赤い部分のコード を追加してください。内容は3Dアクションゲーム編1-3 と同じなので、スクリプトを流用しても構いません。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; void Start() { } void Update() { // 移動速度を初期化 Vector3 move = Vector3.zero; // 前後移動 if (Input.GetKey(KeyCode.W)) { move.z += MoveSpeed; } if (Input.GetKey(KeyCode.S)) { move.z += -MoveSpeed; } // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // 移動させる transform.localPosition += move; } } コードがかけたらChikenにアタッチしておいてください。 今回は簡易版なので、カメラの実装も簡単に行いましょう。子オブジェクトは親オブジェクトに追従するというルールがあるため、カメラをプレイヤーの子オブジェクトにすることで簡単な追尾カメラを作ることができます。ただし、プレイヤーが回転するとカメラも回転してしまうため注意しましょう。 焚火を設置します。 FireのSprite Editorを開いて、Chickenと同じようにFireの画像を分割してください。 分割できたらFireのスプライトごと、シーン上にドラッグ&ドロップしてください。自動でループアニメーションが作成されます。 3Dモデルにドラッグ&ドロップしてしまうとテクスチャとして貼り付けられてしまうので、何もない空間にドラッグ&ドロップしましょう。 アニメーションの名前や保存場所を決めるウィンドウが開きますが、名前は何でもOKです。保存ボタンをクリックしてください。 Fireをステージ上のいい感じの場所へ移動させ、拡大率を調整してください。 Chikenと同じようにマテリアルを貼り、デバッグモードで設定を行うことでFireも影が落ちるようになります。先ほどの内容を参考にFireにも影が落ちるように設定してみてください。 ですが、焚火なのに画像が暗くなってしまいます。これでは焚火っぽくありません。 新しいマテリアルを作成してください。サンプルではMaterial2D_Unlitと名前をつけています。 作成したシェーダーの種類を「Standard Unlit」に変更して、Rendering Modeを「Cutout」にしてください。 マテリアルをFireに貼り付けてください。 画像は明るいままで、地面に影が落ちるようになります。 「Light」→「Point Light」と選択して、ポイントライトを設置しましょう。 ポイントライトをオレンジ色にして、Range(範囲)、Intensity(強さ)を調整してください。 Shadows Typeを変更すると影が落ちるようになりますが、処理は重いので注意しましょう。 最後に3Dアクションゲーム編6-4 と同じようにポストプロセスを導入してみましょう。 オススメのエフェクトは以下の通りです。 ・Ambient Occlusion … 角になっている部分などが暗くなる ・Bloom … 明るい部分が更に明るくなる ・Depth of Field(被写界深度) … ピントの合っていない部分をぼかす ・Vignette … 画面の端を暗くする 特にDepth of Field は強めにかけておきます。 パラメータを調整して、印象的な画面作りに挑戦してみてください。
- 3Dアクションゲーム編 Lesson2「カメラを実装しよう」 | Unity1gc2
3Dアクションゲーム編 Lesson2 カメラを実装しよう プレイヤーを追尾するカメラを実装します。また、カメラを考慮してプレイヤーの移動も変更し、ゲームの基礎となるシステムを完成させましょう。 2-1 追尾するカメラ 2-1 追尾するカメラ 現在、カメラは初期位置から全く動きません。プレイヤーへの追尾と回転ができるカメラを作りましょう。 新しいスクリプト「GameCamera」を追加してください。スクリプトの作り方を忘れた人は1-3を参考にしてください。 GameCameraスクリプトを以下のように入力してください。少し長いですが、これだけでカメラの処理は完成になります。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameCamera : MonoBehaviour { public float RotSpeed = 0.5f; public float RotUpLimit = 40.0f; public float RotDownLimit = -20.0f; public float CameraRange = 3.0f; public float CameraY_Up = 1.5f; private GameObject m_player; private float m_nowX_Rot = 0.0f; void Start() { // Playerタグがついたオブジェクトを探す m_player = GameObject.FindGameObjectWithTag("Player"); // 初期X軸の回転量を保存 m_nowX_Rot = transform.localEulerAngles.x; } void Update() { // 上下 float Up_rot = 0.5f; if (Input.GetKey(KeyCode.UpArrow)) { Up_rot = RotSpeed; } else if (Input.GetKey(KeyCode.DownArrow)) { Up_rot = -RotSpeed; } else { Up_rot = 0.0f; } // 上下角度制限 m_nowX_Rot += Up_rot; if (m_nowX_Rot > RotUpLimit || m_nowX_Rot < RotDownLimit) { m_nowX_Rot = Mathf.Clamp(m_nowX_Rot, RotDownLimit, RotUpLimit); Up_rot = 0.0f; } transform.RotateAround(m_player.transform.position, this.transform.right, Up_rot); // 左右 float Left_rot; if (Input.GetKey(KeyCode.LeftArrow)) { Left_rot = RotSpeed; } else if (Input.GetKey(KeyCode.RightArrow)) { Left_rot = -RotSpeed; } else { Left_rot = 0.0f; } transform.RotateAround(m_player.transform.position, Vector3.up, Left_rot); // 座標計算 // カメラの前方向を使って移動量を計算 Vector3 cameraMove = transform.forward * -CameraRange; // カメラを少し持ち上げる cameraMove.y += CameraY_Up; // 座標設定 transform.position = m_player.transform.position + cameraMove; } } 【プログラムの解説】 ・Start関数にある GameObject.FindGameObjectWithTag("Player") は、引数に設定した名前のタグ(今回はPlayer)が設定されているオブジェクトを取得する関数 です。ただし同じタグが設定されているオブジェクトが複数ある場合は、どのオブジェクトの情報を返すか不定なので注意しましょう。 ・Mathf.Clamp関数は数値を範囲内に収めてくれる関数です。第一引数に対象となる値、第二引数に下限、第三引数に上限を入力します。 ・transform.RotateAround関数は第一引数に入力した座標を中心に回転させる関数です。今回はプレイヤーを中心に第二引数(Y軸周り)に回転させています。 このスクリプトは「Player」タグがついたオブジェクトをターゲットにします。まだゲーム上にPlayerタグがついたオブジェクトがありませんので、このままではエラーが起きてしまいます。 カメラに注目してほしいのは当然ユニティちゃんなので、ユニティちゃんにPlayerタグを設定しましょう。Player タグはUnity側がデフォルトで用意してくれています。 忘れないようにスクリプトのアタッチをしましょう。MainCameraにGameCameraスクリプトをドラッグ&ドロップしてください。 これでカメラがプレイヤーについてくるようになりました。 例のごとくpublicに設定した変数はインスペクターから変更できます。好みに調整しても構いません。 RotSpeed … カメラの回転速度 RotUpLimit … カメラの回転上限 RotDownLimit … カメラの回転下限 CameraRange … プレイヤーとカメラの距離 CameraY_Up … カメラの高さ MainCameraのCameraコンポーネントからField of View(画角)やNear・Far(近平面・遠平面)も設定できます。何のこっちゃいという人はゲームプログラミングの教材で復習してみてください。 Unity Tips! 2-2 プレイヤーの移動を修正 2-2 プレイヤーの移動を修正 これでカメラの処理は完成しました。 しかし、カメラを回転させるとユニティちゃんの移動がおかしくなってしまいます。ゲームプログラミングの授業でも起こりましたが、ユニティちゃんがカメラを考慮して移動していないのでこのような現象が起こってしまいます。よって解決方法も同じです。 ゲームプログラミングではパッドを使いましたが、キーボード操作でも考え方は変わりません。カメラの前方向と右方向を使ってプレイヤーの移動ベクトルを計算しましょう。 Playerスクリプトを開いて赤い部分のコード を記述してください。 ※移動と回転の処理が変わっているので注意!!古い処理は消してから記述してください! using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; public float JumpPower = 6.0f; Rigidbody m_rigidBody; Animator m_playerAnimator; GameObject m_mainCamera; // 接地判定用スクリプト public GroundChecker Ground_Checker; bool m_moveFlag, m_jumpFlag, m_airFlag; void Start() { // 自分にアタッチされているRigidBodyを取得する m_rigidBody = GetComponent(); // 自分にアタッチされているAnimatorを取得する m_playerAnimator = GetComponent(); // メインカメラのゲームオブジェクトを取得する m_mainCamera = Camera.main.gameObject; } ~中略~ // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // カメラを考慮した移動 Vector3 PlayerMove = Vector3.zero; Vector3 forward = m_mainCamera.transform.forward; Vector3 right = m_mainCamera.transform.right; forward.y = 0.0f; right.y = 0.0f; right *= move.x; forward *= move.z; // 移動速度に上記で計算したベクトルを加算する PlayerMove += right + forward; // 移動させる transform.localPosition += PlayerMove * Time.deltaTime; // 移動フラグの更新 if (PlayerMove .sqrMagnitude == 0.0f) { m_moveFlag = false; } else { m_moveFlag = true; } // 回転 if (PlayerMove .sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(PlayerMove .normalized); } // ジャンプ 【プログラムの解説】 ・Camera.main.gameObject でメインカメラ(画面に表示しているカメラ)のゲームオブジェクトを取得できます。 ・Time.deltaTime は1つ前のフレームから今のフレームを実行するまでにかかった時間を返します。k2Engineと同じようにタイマーに使うこともできます。 Time.deltaTimeを使うことによって、もしゲームが重くなっても移動速度が一定になるようになります。 Time.deltaTimeを追加したことで移動速度が遅くなっています。MoveSpeedの値を調整してください。5あたりがオススメです。 これでカメラを考慮した移動が完成しました。ゲームをプレイして確認してみてください。 Time.deltaTimeは移動処理を安定して動作させるために欠かせないものですが、最初はいつ使えばいいかわからない人も多いと思います。 Update関数は毎フレーム実行される関数です。つまりFPSが60なら1秒間に60回、FPSが10なら1秒間に10回実行されます。 例えばUpdate関数にX座標を+1する処理を書いていた場合、FPS60では1秒間でX座標は+60されますが、FPS10の環境では1秒間でX+10になってしまいます。このようにFPSの違いによって、実行結果に差が出る場合にTime.deltaTimeを使ってください 。 最初は使いどころをイメージしづらいかもしれませんが、テストプレイとして意図的にFPSを落とすなどして確認してみましょう(デバッグ用にFPSを意図的に落とす方法 ) Unity Tips! Next Lesson3「ステージを作ろう」 評価テスト 次はプレイヤーの体力を実装して、ステージを制作していきます。プレイヤーの次に重要な部分なので、頑張って作っていきましょう! 【評価テスト2】 https://forms.gle/fPs8tcZBSEjz3e2g6 ページ TOP 2-1 追尾するカメラ 2-2 プレイヤーの移動を修正 評価テスト
- EX Unityでモデリング | Unity1gc2
LessonEX Unityでモデリング EX-1 ProBuilder導入 ProBuilder はシーン内でポリゴンや頂点を変形させたりUV展開することができる、Unityの公式ツールです。簡単に言うと「Unity内でモデリングできる機能」です。皆さんは過去に3dsMaxの授業を行っていると思いますが、基本的な使い方はあまり変わりません。モデリングの技術はそのまま応用できると思います。 複雑なモデリングであれば変わらず3dsMaxに軍配が上がりますが、ProBuilderの強みは作ったモデルをスムーズにゲームに導入できる点にあります。 Unity上で直接メッシュを操作できるため調整がしやすく、制作時間の短縮にもなります。 シンプルな3Dモデルが必要な時は、ProBuilderの導入を検討してみてください。 【サンプルの動画】 この教材ではこういったステージ↓の制作を通してProBuilderの基本的な機能を解説します。 まずはProBuilderを導入しましょう。 「Window」→「Package Manager」を選択してください。 左上のPackagesを「Unity Registry」に変更して、右上の検索欄に「ProBuilder」と入力します。 ProBuilderが表示されたら、右下のInstallボタンをクリックしてインストールしましょう。 インストールが完了したらPackage Managerを閉じてください。 「Tools」→「ProBuilder」→「ProBuilder Window」を選択してください。 ProBuilderウィンドウの「New Shape」を選択すると、作る形を選択するウィンドウが表示されます。今回は立方体 を選択してください。 他にもアーチや階段など、Unityのプリミティブなオブジェクトだけでは実装が大変なパーツがあります。色々試してみましょう。 Unity Tips! 3dsMaxと同じように立方体を作成することができます。 シーン内をドラッグして、地面となる立方体を作成してください。 Center Pivotで基点を 中央にすることができます。 中央上のボタンでターゲットになる要素を変更できます。左から2番目のボタン は頂点編集モードです。頂点を選択して移動させることができます。 左から3番目のボタン は辺編集モードです。同じように辺を選択して移動や回転させることができます。 一番右のボタン は面編集モードです。面を選択して移動や回転させることができます。 まずは面の中に面を作成してみましょう。 拡大モードにして、地面の上の面を選択します。SHIFTキーを押しながら縮小すると、面が作成されます。 作成した面を上に移動したり回転させて、丘を作ってみましょう。 次はキノコを作ってみましょう。 先ほどと同じように円柱を作成してください。 上の面を選択して、SHIFTキーを押しながら上に移動させてみましょう。面を押し出すことができます。 上半分と下半分で別方向に回転、移動させることで円柱を折り曲げることができます。 先ほどと同じように面を上に押し出して、押し出した面を拡大しましょう。 SHIFTキーを押しながら押し出し→面の縮小を繰り返してキノコの形を完成させていきましょう。 岩を作成しましょう。 ProBuilderウィンドウから「New Poly Shape」を選択してください。これは フリーハンドでメッシュを作成することができる機能です。 岩の形になるように線を繋げてみてください。 線を繋げたら、面を上方向に持ち上げてください。 作成した面を分割しましょう。 ProBuilderウィンドウの「Cut Tool」を選択してください。 「Snap to existing edges and vertices」にチェックを入れておきましょう。 面を分割するラインを引きましょう。 分割できたら「Complete」ボタンをクリックします。 作成された辺を移動したり面を回転させたりして、岩を作っていきましょう。 マテリアルは今までと同じように貼ることができます。 マテリアルの調整をするときは「UV Editor」を開いてください。 今回はテクスチャの大きさを調整します。UV Editorで全ての面を選択して、縮小しましょう。 マテリアルを調整して完成になります。 デフォルトでMesh Colliderが設定されているので、このままステージとして使うことができます。簡単なモデルを作る時に3dsMaxなどのモデリングソフトを介する必要がなくなるので、作るゲームによっては導入を検討してみましょう。
- 3Dアクションゲーム編 Lesson1「プレイヤーを実装しよう」 | Unity1gc2
3Dアクションゲーム編 Lesson1 プレイヤーを実装しよう ゲームに必須なプレイヤーを実装します。プレイヤーの実装を通して移動やジャンプ、当たり判定やオブジェクト同士の関連付けなどの基本的な処理に挑戦しましょう。 1-0 このパートで作るゲームについて 1-0 このパートで作るゲームについて この「3Dアクションゲーム編」ではUnityの基本操作を中心に勉強していきます。 どんなゲームができるかイメージできるように、アクションゲーム編のメインページ に完成品のゲームをアップしています。まずは遊んでみてください。 1-1 新規プロジェクトを作る 1-1 新規プロジェクトを作る それでは3Dアクションゲームを作るためのプロジェクトを追加します。 Unity Hubのプロジェクトから「新しいプロジェクト」ボタンをクリックしてください。 今回作るのは3Dゲームですので、コアは「3D」を選択します。 上部からバージョンを変更できます。画像とバージョンが違うかもしれませんが、Lesson0でダウンロードした最新のバージョンで構いません。 プロジェクト名は自分がわかりやすい名前にしてください。名前を日本語にすると予期しない動作を起こす可能性があるので、英語にするようにしましょう 。 選択できたら「プロジェクトを作成」ボタンをクリックしてください。 ここで選んだ「2D」「3D」はあくまでUnityの編集モードなので、後から変更できます !それぞれ2Dゲームや3Dゲーム作成に適した設定に自動で設定してくれます。 モードによる具体的な違いは公式マニュアル を参照してください。 Unity Tips! しばらく待つとUnityの画面が開きます。この画面を中心にゲームを作っていくことになります。 次にゲームに使用する素材をまとめたパッケージをインポートします。 配布所(https://drive.google.com/file/d/1CWTRhDRR5bXsEAvWAdU49cLXy_nFcP3s/view?usp=share_link )からUnityのパッケージをダウンロードしてください。 「UC_Run_Package.unitypackage」をダウンロードできたら、パッケージをUnityの下部にあるProjectウィンドウへドラッグ&ドロップ します。 全てにチェックが入った状態で「Import」を選択します。 素材のインポートができたら、画面下部のProjectウィンドウにModelとSpriteフォルダが追加されます。 Scenesフォルダを選択して「Main」をダブルクリックしてください。画面的にはあまり変わっていませんが気にしないでください。 次にゲームを再生した時の画面サイズ(解像度)を設定しておきましょう。 初期状態ではスクリーンの大きさに合わせて自動でゲームの画面サイズが変わるのですが、今回は作りやすいように16:10に設定しようと思います。 画面中央にある「Game」タブを選択してください。 Free Aspect を選択するとプルダウンメニューが開きます。16:10 を選択してください。 ここで選んだサイズはあくまでエディタ上でのサイズで、ビルド(出力)した際の画面サイズは別の場所で設定します。とりあえず今はあまり気にしなくてOK! (詳細はLesson6-5 ) Unity Tips! これで制作の準備が完了しました。 次からはメインとなるアクションゲームの部分を作っていきます。 1-2 地面とプレイヤーを追加 1-2 地面とプレイヤーを追加 制作を始める前にUnityの画面について少し説明しておきます。 ① ヒエラルキー … ゲーム空間上にあるオブジェクトの一覧。 (プレイヤーやカメラ、ギミックなど) ② インスペクター … ヒエラルキーで選択中のオブジェクトの情報が表示される。 ③ プロジェクト … ゲームに使用する素材の一覧。 ④ シーン … オブジェクトを配置していくゲーム空間。 それぞれをヒエラルキーウィンドウ、インスペクターウィンドウ、プロジェクトウィンドウ、シーンビューなどと呼ぶこともあります。使っているうちに覚えると思いますが、どのウィンドウも超重要なので場所と用途を確認しておいてください。 以降の解説では各ウィンドウを「ヒエラルキー」や「インスペクター」などと呼称します。 各ウィンドウの位置は自由に変更できるので、人によっては配置が違うこともあります。解説ではデフォルトの配置を使用していますが、使いやすい配置に動かしても構いません。 右上のボタンでウィンドウ配置を変更、リセットすることもできます。 Unity Tips! 長くなりましたが、ようやくメインのアクションゲーム制作に入ります。 早速プレイヤーを追加したいところですが、今のゲーム空間上には地面がないので先に地面を追加しましょう。 ヒエラルキー(Hierarchy)にある+ボタンをクリックするとゲーム空間上にオブジェクトを追加できます。 「3D Object」→「Cube」を選択してゲーム空間上に箱を追加してみてください。 シーン(Scene)に白い箱が表示されました。 Unityではこのように簡単な箱や球を作成することができます。 ヒエラルキー内を右クリック、または上部にある「GameObject」をクリックしてもゲーム空間上にオブジェクトを追加することができます。好きな方法で構いません。 Unity Tips! ヒエラルキーに「Cube」の項目が追加されているのでクリックしてみてください。するとインスペクター(Inspector)に選択したCubeの情報が表示されます。 k2Engineと同じようにUnityでもPosition(座標)、Rotation(回転)、Scale(大きさ)でオブジェクトを扱います。 試しにCubeを動かしてみましょう。 インスペクター内のPositionに好きな値を入力してみてください。 UnityにおいてXは左右、Yは上下、Zは奥行きを表します (この定義はツールによって違うので注意しましょう) 座標、回転、大きさの編集はシーン内でもできます。シーン内のボタンを選択して移動モードにしてから、箱を自由に動かしてみてください。3dsMaxと似た感覚で動かせると思います。 シーン内のカメラも操作できますので同時に練習してみましょう。 マウスの右を押しながらマウスを動かすと、カメラを回転できます。マウスのホイール操作でカメラの画角を変更できます。マウスの中(真ん中、ホイールの部分)を押しながらマウスを動かすとカメラを移動できます。 移動ツールの切り替えはショートカットキーでも行えます。 一番上のシーンビュー操作ボタンをQキーとして、移動はWキー、回転はEキー…と順番に割り振られています。 Unityには便利なショートカットがたくさんあります。ここでは紹介しきれませんが、ぜひ色々調べてみてください。 Unity Tips! 箱を使ってUnityの操作方法に一通り慣れたら、この箱を地面として使える位置に設定しましょう。 Cubeを選択した状態でヒエラルキー内の座標、回転、大きさを設定してください。 Positionは全て0、Rotationは全て0、ScaleのXは10、Yは1、Zは10です。 オブジェクトの名前もヒエラルキーから変えることができます。わかりやすいように「Ground」に変更しておきましょう。 地面ができたのでプレイヤーとなるユニティちゃんを配置します。 プロジェクトから「Assets」→「Model」→「unity-chan!」 →「Unity-chan! Model」→「Art」→「Models」 と選択して、ユニティちゃんのモデルをシーンにドラッグ&ドロップしてください。 すると、シーン内にユニティちゃんの3Dモデルが追加されます。 ヒエラルキーから追加されたユニティちゃんのオブジェクトを選択して、地面と同じように座標を調整してください。 PositionはXは0、Yは0.5、Zは0です。 地面と床がゲーム実行時にどのように見えるか確認してみましょう。 中央上部にある三角のボタンを押すと、ゲームが実行されます 。 今は何の動きもありませんが、ユニティちゃんと地面がゲーム空間に存在することを確認しておいてください。 ゲーム画面を確認できたら、もう一度再生ボタンを押してゲームを終了 してください。 Unityはテストプレイしやすいようにゲームの実行中でもゲームの編集ができます。便利な機能ですが、この変更はゲームを終了すると元に戻ってしまう一時的な変更です。 「ゲームを編集したのに、実は裏でゲームが実行中で終了すると元に戻ってしまった!」ということが割と起こるので、悲劇を避けるためにも確認が終わったゲームはちゃんと終了する癖をつける ことをオススメします。 評価テスト 1-3 プレイヤーの移動 1-3 プレイヤーの移動 【評価テスト】 https://forms.gle/74AvCZAfegqZ7yEz6 このままではユニティちゃんを眺めるだけの画面なので、まずはユニティちゃんが動けるようにしましょう。 新しくAssets内に「Script」フォルダを作成しましょう。 プロジェクト内の適当な空間を右クリックして「Create」→「Folder」と選択 してください。作成したフォルダの名前を「Script」に変更しましょう。 プロジェクト内で右クリックして「Create」という操作は今後も頻繁に使いますので、覚えておいてください。 名前を間違えた際は作成したフォルダを右クリックして「Rename」を選択することで変更できます。 次はScriptフォルダ内に新しいスクリプトを作成します。 作成したScriptフォルダをダブルクリックして開き、先ほどと同じように適当な空間を右クリックして「Create」→「C# Script」を選択 してください。 作成したスクリプトの名前は「Player」にしておいてください。 作成したスクリプトをダブルクリックするとVisual Studioが開きます。 ここからはVisual Studioを使ってC# Scriptを記述していきます。Lesson0でも解説した通りC#はC++と共通している部分の多い言語なので、身構えずにやっていきましょう。 今回はユニティちゃんがWASDキーで動けるようにします。下記の赤い部分のコード を入力してください。 ※ Wixの文字コードの問題なのか、ここからコピペすると体裁が崩れるので注意 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; void Start() { } void Update() { // 移動速度を初期化 Vector3 move = Vector3.zero; // 前後移動 if (Input.GetKey(KeyCode.W)) { move.z += MoveSpeed; } if (Input.GetKey(KeyCode.S)) { move.z += -MoveSpeed; } // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // 移動させる transform.localPosition += move; } } 「Visual StudioでInputなどの予測変換が出ない!」という方は「UnityとVisual Studioの関連付けについて」 を参考にしてください。 今後プログラムをたくさん書いていく上で予測変換が出ないとかなり辛いですので、早めに直しましょう。 Unity Error! プログラムが書けたらCtrl+Sで保存 してください。保存しないとUnity側に反映されない ので、保存する癖をつけましょう。 保存できたらUnityに戻ってください。 【プログラムの解説】 ・k2EngineでPlayer.hとPlayer.cppのように2つのファイルがありましたが、UnityではPlayer.csのように1つのファイルを扱います。戸惑うかもしれませんが少しずつ慣れていきましょう。 ・Update関数はk2Engineと同じように毎フレーム自動で呼ばれる関数 です。ほぼ同じものという認識で構いません。 ・Input.GetKey関数でキーが押されているか取得することができます。引数にはキーの番号を入力しますが、KeyCode.(キー名)で取得することができます。 ・transform.localPosition で自身のローカル座標を取得したり、変更することができます。 単純に自身の座標を取得したい時はtransform.position を使用してください。 さて、せっかく書いたスクリプトですが、ユニティちゃんにアタッチしないと意味がありません 。 Playerスクリプトをヒエラルキーのユニティちゃんにドラッグ&ドロップしてアタッチします。 ユニティちゃんを選択した状態で「Add Component」からPlayerと検索してアタッチすることもできます。やりやすい方法で構いません。 Unity Tips! CantAdd スクリプトをアタッチしようとして Can't add script というエラーが出た人は、プロジェクト内でスクリプトの名前をRenameした人だと思います。 プロジェクトで表示されている名前と実際のクラス名が異なるとエラーが出てしまうので、クラス名を修正してください。 Unity Error! ユニティちゃんにPlayerスクリプトをアタッチできたら、上部の再生ボタンを押してWASDキーで移動できることを確認してみてください。 ユニティちゃんの移動速度を表す変数MoveSpeedはpublicに設定されています。 publicに設定した変数はインスペクター内に表示され、いつでも自由に変更することができます 。ゲームを微調整する時にも便利なので覚えておいてください。 異なるオブジェクトにスクリプトをアタッチ、または複数アタッチしてしまった場合は、右上にある三つの点のボタンをクリックした後「Remove Component」を選択してください。 ちなみに「スクリプト」は クラスを格納した設計図で、それをオブジェクトにアタッチすることで実体を持った「コンポーネント」になります(ややこしいので無理に理解しなくてもOK) Unity Tips! 1-4 重力と当たり判定 1-4 重力と当たり判定 さて、移動のスクリプトを書いたことでユニティちゃんが移動できるようになりましたが、足場からはみ出しても落下しません。 このままでは物理的におかしいので、ユニティちゃんに重力を加えてみましょう。 ユニティちゃんのインスペクターから「Add Component」を選択して「Rigidbody」を検索してアタッチしてください。 このRigidbodyがアタッチされたオブジェクトには重力が適用されます。ゲームを実行してどうなるか確認してみてください。 確かに重力は適用されましたが、ユニティちゃんが地面を貫通して落下してしまいました。原因は、ユニティちゃんに当たり判定が設定されていないからです。 Unityには様々な形のCollider(コライダー)があります。コライダーをアタッチすることで当たり判定を取ることができます。 ちなみに地面(Cube)には最初からBox Colliderがアタッチされています。 後はユニティちゃんにコライダーをアタッチすれば、双方で当たり判定が取れます。 Unity Tips! プレイヤーにアタッチするコライダーとしてメジャーなのはCapsule Colliderになります。名前通り薬のカプセルのような形をしたコライダーです。 なぜCapsule Colliderが適しているのかというと、Box Colliderと比べて小さな段差に引っかからないから、というのが大きな理由になります。 それではRigidbodyの時と同じように、Capsule Colliderをアタッチしてみましょう。 コライダーのサイズや位置がモデルと噛み合っていないので、以下のように設定してみてください。ユニティちゃんを覆うように当たり判定が設定されます。 Center : X=0 Y=0.8 Z=0 Radius : 0.3 Height : 1.6 ユニティちゃんにRigidbody(重力)とCollider(当たり判定)をアタッチしたことで物理的な動作ができるようになりました。 実際にプレイして、「ユニティちゃんが地面に着地すること」と「地面からはみ出ると落下すること」を確認してみてください。 これで重力と当たり判定は完成…としたいところですが、実は少し不完全です。 しばらく移動すると… ユニティちゃんが倒れてしまいました。このままだと起き上がれない亀みたいで悲しいので、ユニティちゃんの回転に制限をかけましょう。 Y軸周りの回転は後で実装するので、X軸とZ軸周りの回転だけできないようにします。 Rigidbodyを選択してX軸とZ軸の回転を制限してください。 Freeze Rotationのチェックが入っている軸周りの回転は起こらなくなります(スクリプトで回転させることは可能) 1-5 進行方向に回転する 1-5 進行方向に回転する 次はユニティちゃんが進行方向に回転するようにしましょう。 Playerスクリプトを開いて、Updateの最後に赤い部分のコード を追加してください。 ~中略~ // 移動させる transform.localPosition += move; // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } } } 【プログラムの解説】 ・Vector3.sqrMagnitudeはベクトルの長さを返す関数で、k2Engineで今まで使ってきたVector3.Lengthと同じように使えます。 ここではプレイヤーが移動しようとしているかどうかをベクトルの長さを用いて判定しています。 ・Quaternion.LookRotationは引数にしたベクトルの方向に回転する関数です。この処理では移動しようとしている方向のベクトルを引数として渡すことで、進行方向へ回転するようにしています。 スクリプトの変更をCtrl+Sで保存するのを忘れないようにしてください。 保存できたらゲームを実行して、ユニティちゃんが進行方向に回転することを確認してください。 評価テスト 【評価テスト】 https://forms.gle/ejFXMy1785Pfnk9F8 1-6 ジャンプの実装 1-6 ジャンプの実装 次はユニティちゃんがジャンプできるようにしましょう。少し難しい内容ですが、 超重要 なプログラムがたくさん出てくるパートなので心して(?)挑んでください。 RigidBodyにはAddForce という関数があり、引数に設定したベクトル分の力をオブジェクトに加えることができます。サッカーボールを蹴る瞬間にボールに力を加えるようなイメージです。 今回はスぺースキーが押された瞬間にユニティちゃんに上方向へ強い力を加えることでジャンプしてみましょう。Add Forceを実行するためには自分(プレイヤー)にアタッチされているRigid Bodyにアクセスする必要があります。 Playerスクリプトを開いて赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; public float JumpPower = 6.0f; Rigidbody m_rigidBody; void Start() { // 自分にアタッチされているRigidBodyを取得する m_rigidBody = GetComponent(); } ~中略~ // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } // ジャンプ if (Input.GetKeyDown(KeyCode.Space)) { m_rigidBody.AddForce(new Vector3(0.0f, JumpPower, 0.0f), ForceMode.VelocityChange); } } } 書いてもらったプログラムには重要な処理がたくさん含まれています。一気に全て理解するのは難しいかもしれませんが、少しずつ覚えていってください。 【プログラムの解説】 ・publicがついていない変数m_rigidBodyはprivateな変数として扱われます。 privete RigidBody m_rigidBody と宣言しても構いません。 この変数を使ってRigidBodyにアクセスします。 今までC++をたくさん書いてきた方は Rigidbody m_rigidBody; にアクセスするというと「ん?」と思ったかもしれません。C#ではRigidbody* m_rigidBody; のように書くのではなく*抜きでインスタンスを格納する変数を宣言します 。これがポインタみたいなものと思ってください(厳密には違いますが…) 意味がよくわからない人は気にしないでください。 ・Start関数もk2Engineと同じようにUpdateが実行される前に1度だけ実行されます。オブジェクトの初期化や取得を行うのに適しています。 ・GetComponent関数は対象となるゲームオブジェクトからコンポーネントを取得する関数です。恐らくUnityを理解する上で最初の関門になる関数 かつ一番使う関数 になると思われます。 逆に言うとこれを理解できれば大抵のゲームは作れるはずです。頑張って慣れましょう。 ・GetKeyDown関数は"押された瞬間"のみtrueを返す関数です。 移動に使用したGetKey関数は押されている間常にtrueを返す点で違いがあります。 ・AddForce関数の第一引数には力を加える方向を渡します。 その場で変数を生成する方法が今までと少し違うので注意してください。 第二引数には力を加えるモードを渡します。今回は瞬間的に力を加えるモードです。 今はあまり気にしなくて構いません(このサイト が詳しく解説してます) スクリプトの変更を保存するのを忘れないようにしてください。 保存できたらゲームを実行して、スぺースキーでユニティちゃんがジャンプすることを確認してみてください。 これでジャンプできるようになりました…が、空中でスぺースキーを連打してみてください。ユニティちゃんが空中でも無限にジャンプできてしまいます。このままではゲームが成り立ちません。ジャンプに制限を実装しましょう。 2段ジャンプなどは今回実装しないので、まずユニティちゃんがジャンプできる条件 は何か考えてみてください。 こう言われて「ユニティちゃんが地面に触れている時」と答えた方は惜しいです。 正確には「ユニティちゃんの足元が地面に触れている時 」ジャンプできるという処理が正しいです。 ただ「地面に触れていたら」という判定だと、例えば壁や階段に横から触れていてもジャンプできてしまい、完全なジャンプ制限になりません。 屁理屈のような話で混乱したかもしれません。ただプログラム的には壁や階段も「地面」なのです。なので「ユニティちゃんの"足元が " 地面に触れている時」という風に触れている場所を指定する必要があります。 この問題を解決する方法は色々あるのですが、今回は「ユニティちゃんの足元に接地判定用の当たり判定を追加する」方法をとります。 長くなりましたが、まずは設置判定用のオブジェクトを追加しましょう。なぜ新しいオブジェクトを作るのかは後でわかるので、今は気にしないでください。 ヒエラルキー内の+ボタンをクリックして「Create Empty」を選択してください。 当たり判定用の空オブジェクトが追加されました。これは座標などの基礎的なデータを持っているだけの透明なオブジェクトです。 オブジェクトの名前は自分がわかりやすいもので構いません。この解説では「GroundCheck」という名前にしておきます。 座標は後で調整するので今はどこでもOKです。 このままでは当たり判定としては機能しないので、コライダーを追加しましょう。 今回はSphere Colliderを使用します。Add Componentから検索してSphere Colliderをアタッチしてください。 Sphere Colliderのパラメータを調整してください。 Is Triggerにチェックを入れ、Radius(半径)を0.1に設定します。 Is Triggerにチェックを入れたコライダーは物理的接触が起こらなくなります。しかし「衝突しているかどうか」を取得することは可能です。 例えばマップの移動ポイントやアイテムの取得判定で、触れたプレイヤーが弾かれると困りますよね?衝突しているかどうかは判定したいけど物理的に衝突してほしくないという時にはIs Triggerにチェックを入れましょう。 これで接地判定用オブジェクトの準備はできましたが、今のままではプレイヤーについてきてくれません。接地判定用オブジェクトは常にプレイヤーの足元にないと困ります。 ここでプログラムを書いてプレイヤーへの追尾を実装してもよいのですが、もっと簡単な方法があります。 ヒエラルキー内で接地判定用オブジェクト「GroundCheck」をユニティちゃんにドラッグ&ドロップしてください。 ドラッグ&ドロップしたことで接地判定用オブジェクトがユニティちゃんの下に移動しました。 この状態を「親子関係 」といいます。今はGroundCheckがunitychanの子になった状態です。 子になったオブジェクトは親オブジェクトの座標・回転・大きさが変化すると追従するように変化します。例えば親オブジェクトが右に移動すれば、全ての子オブジェクトも同じように右に移動します。しかし、子が移動しても親の座標は変化しません。 接地判定用オブジェクトの座標を全て0に設定してください。オブジェクトがユニティちゃんの足元に移動します。 試しにユニティちゃんがジャンプしている時のGroundCheckの位置を確認してみると、しっかり足元にあることが確認できます。これで接地判定オブジェクトの準備ができました。 実際に確かめたい人はユニティちゃんのジャンプ中に上部の一時停止ボタンをクリックして、シーンから確認してみてください。Ctrl+Shift+Pでもゲームを一時停止できます。 Unity Tips! それでは設置判定をするプログラムを書いていきます。 Playerスクリプトを作った時を参考に、新しいスクリプト「GroundChecker」を作成してください。 GroundCheckerを開いて、以下のプログラムを入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GroundChecker : MonoBehaviour { // 接地しているかを格納する変数 bool m_isGround; // 地面に触れているかを返す関数 public bool GetIsGround() { return m_isGround; } // 毎フレーム最初に接地判定をリセットする private void FixedUpdate() { m_isGround = false; } // 自身に何かが衝突している間呼ばれる private void OnTriggerStay(Collider other) { // 地面のタグが付いたオブジェクトに衝突している if (other.CompareTag("Ground")) { m_isGround = true; } } } 【プログラムの解説】 ・publicにした関数は外部から呼ぶことが可能です。この辺りはC++と同じです。 今回は接地しているかどうかのフラグを返す関数を作成しています。 ・FixedUpdate関数はUpdate関数より先に呼ばれる関数です。 ・OnTriggerStayは、このスクリプトがアタッチされたオブジェクトのコライダー(Is Triggerにチェックが入っている)に何かが衝突している間毎フレーム呼ばれる関数です。 CompareTagはゲームオブジェクトの関数で、オブジェクトについているタグが引数と一致していた場合にtrueを返す関数です。 「タグ 」はそれぞれのオブジェクトに設定でき、プレイヤーや敵、地面などを区別する時に使います。 今回は地面に「Ground」というタグをつけてみましょう。 TagをクリックしてAdd Tagを選択してください。 +ボタンをクリックして新しいタグ「Ground」を追加してください。タグの検索は名前が完全一致しないといけないので、間違えないように注意してください。 地面のオブジェクトを選択して、Groundタグを設定してください。 あと少しです。Playerスクリプトを改造して、接地判定ができるようにしましょう。GroundCheckerのGetIsGround関数を呼び出して、ユニティちゃんが接地しているかどうかを取得します。 Playerスクリプトに赤い部分のコード を追加してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; public float JumpPower = 6.0f; Rigidbody m_rigidBody; // 接地判定用スクリプト public GroundChecker Ground_Checker; void Start() { // 自分にアタッチされているRigidBodyを取得する m_rigidBody = GetComponent(); } ~中略~ // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } // ジャンプ if (Input.GetKeyDown(KeyCode.Space) && Ground_Checker.GetIsGround() ) { m_rigidBody.AddForce(new Vector3(0.0f, JumpPower, 0.0f), ForceMode.VelocityChange); } } } スクリプトを保存したら、Unityに戻ってください。 ユニティちゃんにPlayerをアタッチした時と同じように、接地判定用オブジェクトにGroundCheckerをアタッチしてください。 先ほど書いたプログラムで public GroundChecker Ground_Checker; という宣言をしていました。以前説明した通り、publicにした変数はインスペクターに表示されます。 GroundCheckerとPlayerを関連付けしましょう。GroundCheckを新しく増えたGround_Checkerにドラッグ&ドロップしてください。これだけで2つのコンポーネントが繋がりました。 長くなりましたがようやく接地判定の完成です。ゲームを実行して、空中での無限ジャンプができなくなっていることを確認してみてください。 ジャンプの実装だけでUnityにおいて重要なことをたくさん説明しました。オブジェクトに力を加える関数、タグや親子関係、コンポーネントの取得やIsTriggerの使い方など、どれもゲームを作る上で欠かせないことばかりです。 一度に全てを覚えるのは難しいですが、今後のレッスンでもちょくちょく出てくる内容なのでわからなくなったときは見返してみてください。 1-7 プレイヤーのアニメーション 1-7 プレイヤーのアニメーション プレイヤーの実装もあと少しです。ユニティちゃんがTポーズで動き回るのは悲しいので、プレイヤーのアニメーションを実装しましょう。 新しいフォルダ「Animation」を作成してください。フォルダの作り方は1-3を参考にしてください。 Animationフォルダ内にAnimator Controllerを作成してください。 Animator Controllerには自分のわかりやすい名前をつけておいてください。 (解説では「PlayerAnimation」にしています) 作成したAnimator Controllerをヒエラルキー内のユニティちゃんにドラッグ&ドロップしてください。 ユニティちゃんのAnimatorに自動で設定されます。 ここからはAnimator Controllerの設定をしていきます。Animator Controllerをダブルクリックしてください。アニメーターが開きます。 まずは右側のBase Layer内を右クリックして「Create State」→「Empty」を選択してください。 ステートもオブジェクトと同じようにインスペクターでパラメータを管理することができます。 作成したステートの名前を「Idle」にしてください。 Idleと同じように「Run」「Jump」のステートも作成してください。ステートの場所は自分が見やすい場所で構いません。 次は各ステートにアニメーションを設定します。 Motion横の+ボタンをクリックしてアニメーションを選択してください。同梱されているアニメーションの中には似た名前のものもあるので注意 しましょう。 【設定するアニメーション】 Idle →「WAIT00」 Run →「RUN00-F」 Jump →「JUMP00B」 ステートの遷移条件となるパラメータを設定します。パラメータにもBoolやFloatといった馴染みのある型が使えるので、用途に応じて選んでください。今回はBool型のみ使用します。 Bool型の「MoveFlag」「JumpFlag」というパラメータを追加してください。 ステートとパラメータの設定が終わりましたが、これだけではアニメーションは切り替わりません。それぞれのステートが遷移する条件を設定する必要があります 。 例えばMoveFlagがfalseの時はIdleアニメーションを実行して、MoveFlagがtrueの時はRunアニメーションを実行する、といった感じです。 各ステートを繋げる時は、遷移前のステートを右クリックして「Make Transition」を選択します。すると遷移前のステートから矢印が伸びるので、遷移先のステートをクリックすることで矢印を繋げられます。 IdleステートからRunステートに繋がる遷移を作ってください。 新しく伸びた矢印をクリックするとインスペクターにパラメータが表示されます。 インスペクターでは「アニメーションが終了してから遷移するか」「どのくらいの時間をかけて遷移するか(補間時間)」といったアニメーションの設定や、「パラメータがどうなったら遷移するか」といった遷移条件 を設定することができます。 この矢印を各ステートに伸ばしていきます。6本あるのでややこしいですが、特に遷移条件に注意して設定してください。 【IdleとRun】 【IdleとJump】 Idle→Jumpは他と少し違うので注意してください。 Jump→Idleは遷移条件が2つあります。 【JumpとRun】 Jump→Runは遷移条件が2つあります。 Has Exit Timeにチェックが入っていると、アニメーションがExit Timeのところへ進むまで遷移を待ってくれます。例えばExit Timeを1にしておくと、アニメーションが一通り終わるまで遷移を待ってくれるようになります。今回は必要ないのでチェックを外しています。 Transition Durationの値は遷移を補間する時間です。0にするとアニメーションがパキッと切り替わる形になります。 これでアニメーターの設定は完了です。 後はスクリプト内でパラメータを操作するだけでアニメーションが切り替わります。 Playerスクリプト内でアニメーションを管理したいところですが、移動やジャンプの処理と混ざるとわかりにくくなるので関数で分割しています。 長いですが以下のように入力してください。微妙に変わっている部分もあるので一度消して最初から打ち直すことをオススメします。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public float MoveSpeed = 0.01f; public float JumpPower = 6.0f; Rigidbody m_rigidBody; Animator m_playerAnimator; // 接地判定用スクリプト public GroundChecker Ground_Checker; bool m_moveFlag, m_jumpFlag, m_airFlag; void Start() { // 自分にアタッチされているRigidBodyを取得する m_rigidBody = GetComponent(); // 自分にアタッチされているAnimatorを取得する m_playerAnimator = GetComponent(); } void Update() { // 移動やジャンプ Action(); // アニメーション Animation(); } private void Action() { // 移動速度を初期化 Vector3 move = Vector3.zero; // 前後移動 if (Input.GetKey(KeyCode.W)) { move.z += MoveSpeed; } if (Input.GetKey(KeyCode.S)) { move.z += -MoveSpeed; } // 左右移動 if (Input.GetKey(KeyCode.D)) { move.x += MoveSpeed; } if (Input.GetKey(KeyCode.A)) { move.x += -MoveSpeed; } // 移動させる transform.localPosition += move; // 移動フラグの更新 if (move.sqrMagnitude == 0.0f) { m_moveFlag = false; } else { m_moveFlag = true; } // 回転 if (move.sqrMagnitude > 0.0f) { transform.rotation = Quaternion.LookRotation(move.normalized); } // ジャンプ if (Input.GetKeyDown(KeyCode.Space) && Ground_Checker.GetIsGround()) { m_rigidBody.AddForce(new Vector3(0.0f, JumpPower, 0.0f), ForceMode.VelocityChange); m_jumpFlag = true; } } private void Animation() { // 移動フラグ m_playerAnimator.SetBool("MoveFlag", m_moveFlag); // ジャンプフラグ if(m_jumpFlag && m_airFlag == false && Ground_Checker.GetIsGround() == false) { m_airFlag = true; m_playerAnimator.SetBool("JumpFlag", true); } if(m_jumpFlag && m_airFlag && Ground_Checker.GetIsGround()) { m_airFlag = false; m_jumpFlag = false; m_playerAnimator.SetBool("JumpFlag", false); } } } 【プログラムの解説】 ・Start関数内で自身にアタッチされているAnimatorを取得しています。取得している対象が違うだけでRigidBodyの時と同じです。 重ね重ね言いますがGetComponentは超重要関数 ですので、少しずつでも使い方を覚えていきましょう。 ・Animatorの関数にSetBoolやSetFloatなどがあります。この関数で先ほど設定したパラメータ(MoveFlagなど)を操作することができます。 第一引数に操作したいパラメータの名前、第二引数に値を入力します。 ジャンプ関連でちょっとややこしい処理を挟んでいますが、これはジャンプした瞬間に接地していると判定されてジャンプアニメーションがキャンセルされないように、一度地面から離れてから再度地面に触れたらJumpFlagをfalseにするという処理を行っています。今は無理に理解しなくてもOKです。 これでアニメーション処理は完成です。 長かったですがこれでユニティちゃんが走ってジャンプできるようになりました。ゲームを実行して確認してみてください。 次のレッスンでは追尾・回転するカメラを実装しましょう。 評価テスト 【評価テスト】 https://forms.gle/azFgPKpBTuVrdA5K8 おまけ Next Lesson2「カメラを実装しよう」 ページ TOP 1-0 このパートで作るゲームについて 1-1 新規プロジェクトを作る 1-2 地面とプレイヤーを追加 評価テスト 1-3 プレイヤーの移動 1-4 重力と当たり判定 1-5 進行方向に回転する 評価テスト 1-6 ジャンプの実装 1-7 プレイヤーのアニメーション 評価テスト
- EX 基礎知識 | Unity1gc2
LessonEX 基礎知識 EX-1 プログラムのルール EX-1 プログラムのルール この教材を読むときに必要な前提知識である「変数」「if文」「関数」 について簡潔に解説するページです。教材はこの3つの要素をある程度理解している前提で進むので、自信のない人はここで復習してみましょう。C++の教科書を見た方がわかりやすいはずですが、教科書を開くのも面倒だと思うので… まずはプログラムのルールです。 C#でもC++と同じように以下のルールのもと、プログラムを書く必要があります。 ルールその1 : プログラムは上から順番に実行される C++と同じでC#のコードも原則、上から順番に実行されます。一見複雑に見えるコードも(基本的には)このルールに則って書かれています。 画像では文章を出力する処理を3つ書いていますが、上から順番に実行されていることがわかると思います。 ルールその2 : コードは半角で書く 全角文字と半角文字は私たちから見ると大した違いではないかもしれませんが、コンピューターから見ると文字コードというものが異なる、れっきとした「違う文字」になります。プログラムは半角で書くようにしましょう。 ルールその3 : 文の最後に;(セミコロン)をつける これもC++と同じです。C#では文の最後に;(セミコロン)をつけることで文が終了したことを表します。日本語で例えるなら句点(。)です。 セミコロンをつけないとエラーになるため、セミコロンをつける癖を身につけましょう。 ここからは実際にプログラムを書いて覚えましょう。 まずは3D脱出ゲーム編1-1 を参考に、新しい3Dプロジェクトを作ってください。 左上のヒエラルキー内の+ボタンをクリックして「3D Object」→「Cube」を選択してください。シーン上に箱が作成されます。 Cubeを選択した状態で、右側のインスペクターにあるPosition(座標)を全て0にしてください。 次は下側のプロジェクトウィンドウの空いている場所を右クリックして「Create」→「C# Script」を選択してください。 C#スクリプトが作成されるので、わかりやすいように名前をつけておいてください。サンプルでは「Test 」にしておきます。 作成したTestスクリプトをCubeにドラッグ&ドロップしてください。 これでスクリプトがアタッチ(接続)され、スクリプトを書くことでCubeを操作できるようになります。 アタッチしようとするとエラーが出る場合はこちら を参照してください。 EX-2 変数 EX-2 変数 「変数 」はプログラムの中で値を格納するために確保した領域のことです。変数はしばしば「箱」に例えられます。 変数を宣言することで、箱が用意されます。箱には好きな名前をつけることができます。 変数を宣言する時は、 (変数の型)(変数名)(初期値); の順番で定義します。 画像では HPという名前の箱に100が、MPという名前の箱に50が入っていることになります。 新しい箱を用意して、そこにHPとMPの合計を入れることができます。 =を境に上記のコードは左辺と右辺に分けられます。そして 、右辺の値が左辺に代入されます。これもプログラムのルールです。 変数には型 というものがあります。これは変数に格納するデータの種類をあらかじめ定義しておくためのものです。 この教材で使われる主な変数の型には以下のようなものがあります。 int型 :整数を格納する型です(5、10、32など…) float型 :実数を格納する型です。上記int型には小数を格納できませんが、こちらでは格納すること ができます(1.2、3.14、123.4567など…) bool型 :false(偽)とtrue(真)のみ格納できる型です。 ゲーム中のフラグ管理によく使われま す。(ジャンプ中フラグ、ボスを倒したフラグなど…) string型 :文字列を格納する型です。"(ダブルクォーテーション)で囲んで定義します。 他にもUnity側で用意した変数の型としてVector3型があります。これはx、y、zという名前のfloat型変数3つをまとめたものです。 Vector3型の初期値の指定方法は上記と少し違うので注意してください。 上記で解説していない変数の型も出てきますが、それらは基本的に他のコンポーネントの参照になります。ここでは気にしなくても構いません。 長々とした説明になってしまいました。 ここからはプログラムを書いて、先ほど作成したCubeに変化を与えてみましょう。プロジェクト内のTestスクリプトをダブルクリックしてください。Visual Studioが起動します。 void Start() の {} の中に書いたコードはゲーム開始時に一度だけ実行されます。 void Update() の {} の中に書いたコードは毎フレーム実行されます(間隔をあけて何度も実行されます) まずは移動量を定義するfloat型の変数を宣言しましょう。変数の名前はMoveにしておきます。 次はUpdate関数内に処理を書きます。 まずはVector3型の変数を宣言して、そこに自分の座標を格納します。Unityでは自分の座標を「transform.position」で取得できます。 次に取得した座標のYに先ほど宣言したMove変数を加算します。 最後に加算後の値を自分の座標に代入します。 少し回りくどい処理になっていますが、Unityでは座標の要素を直接変更しようとするとエラーが出てしまいます。そのため、一度座標を別の場所に格納してから加算して、元の場所に戻しています。 コードが書けたらCtrl+Sキーを押して保存してください。スクリプトを変更した後は保存しないとゲームに変更が反映されないので注意しましょう。 Unityの中央上部にある再生ボタンを押して、ゲームを実行してください。 ゲームを実行すると、箱が上方向に移動していきます。変数はゲームを作る上で欠かせない要素なので、使いこなせるように練習しましょう。 ちなみにpositionの要素は変更できませんが、positionを直接変更することはできるので、このような書き方をすると1行で移動処理を実装できます。 EX-3 if文 EX-3 if文 「if文 」は特定の条件を満たした時だけ実行したい処理がある時に使用します。 ゲームを進行する上で「○○したら、△△する」という処理はたくさん必要になります。例えば「Aボタンが押されたら、ジャンプする」「ボスを倒したら、ファンファーレが鳴る」「自分のHPが0になったら、ゲームオーバーになる」といった感じです。 if文は if(条件式){ 実行する処理 } のように記述します。 上記のif文では「もしHPの値が0と同じになったら」という分岐を作っています。 ここで注意しなくてはいけないのは int HP = 50; の「=」とHP == 0 の「==」は意味が全く異なるという点です 。 int HP = 50; の=は右辺の値を左辺に代入する、という代入の「=」です。 HP == 0 の==は左辺の値と右辺の値が一致したら、という比較の「==」です。 最初はどちらを使えばよいか戸惑うかもしれませんが、とりあえず「==を使うのはif文の()の中だけ 」と覚えておけば間違いないはずです。 それでは実際にコードを書いてみましょう。 先ほどEX-2で書いたコードは削除しておいてください。 今回は「エンターキーが5回押されたら、オブジェクトの大きさを2倍にする」コードを書きます。まずはエンターキーが押された回数を記憶するためのint型の変数を宣言してください。 Update関数内に、エンターキーが押されたことを検知するif文を記述します。 if (Input.GetKeyDown(KeyCode.Return)){ } と書くことで、エンターキーが押された瞬間のみ実行される処理を作ることができます。 (変数名)++; と書いている部分はインクリメント と言い、変数の値を+1する処理になります。 コードが書けたら保存して、ゲームを実行してみてください。 Enterキーを5回押すと、オブジェクトの大きさが2倍になります。 ちなみに if (Input.GetKeyDown(KeyCode.Return)){ } と書くだけで分岐できる理由は、if文が「( )の中身がtrueなら、{ }の中の処理を実行する 」という意味だからです。 なので、処理的には if (Input.GetKeyDown(KeyCode.Return)){ } と if (Input.GetKeyDown(KeyCode.Return) == true){ }は同じものになります。「== t rue」の部分はあってもなくてもよいのですが、単純にコードが少なくなるので基本的に省略しています。 if文には論理演算子 というものがあります。 論理演算子を使うことで、if文に複数の条件を指定することができます。 例えば(条件式A)&&(条件式B) と記述すると、条件式Aと条件式Bの両方の条件を満たす時 のみ実行されるif文になります。 (条件式A)||(条件式B) と記述すると、条件式Aと条件式Bのどちらかの条件を満たす時 に 実行されるif文になります。 ( ) を使うことで複雑な分岐を作ることも可能です。 EX-4 関数 EX-4 関数 「関数 」は複数の処理をひとまとめにしたものです。 例えば「様々なタイミングで五十音を出力したい」という状況があったとします(そんな状況ないだろってツッコミはなしですよ…) その時に以下のようなコードを書くと、無駄に長く読みにくいコードになってしまいます。 また、後から「濁音も出力したい」と思った時にどうなるでしょうか。全てのif文に濁音を出力する処理をわざわざ追加しなくてはいけなくなります。 コードが長くなってくると、対象のif文を全て探すのはかなり面倒ですね。 ですが、五十音を出力する処理を関数にすればコードを一気に読みやすくすることができます。 以下の画像では五十音を出力するLog関数を作成し、それをエンターキーが押されたタイミングなどで呼び出しています。 関数は以下の要素で構成されています。 (戻り値の型) (関数名) ( (引数) ) { (処理) } 関数は要素が少し多いので、いきなり覚えるのは難しいかもしれません。 まずは引数 についてみていきましょう。引数は関数に値を教えるためのものです。 引数は変数の宣言と同じように、型と名前をセットで宣言しましょう。 所持金の増減を行う関数を作ってみましょう。 所持金の増減を行った後、所持金がログに出力されます。所持金がマイナスになった場合、追加で「お金が足りません」と出力されます。 関数を呼び出すときに ( ) の中にgetMoneyに代入する値を指定します。 今回はAキーが押された時に所持金が100増加し、Bキーが押された時に100減少します。 コードが書けたら保存して、ゲームを実行してみてください。 キーボードのAキーを押すと所持金が100ずつ増加していきます。Bキーを押すと100ずつ減少していきます。 何度もBキーを押して、所持金がマイナスになると「お金が足りません」と出力されます。 引数は ,(カンマ)で区切ることで複数宣言することもできます。 もう一つの関数の要素として戻り値 があります。 戻り値を使うことで、関数の実行結果を返すことができます。 以下のコードは、引数に指定した値段の5%オフがいくらになるか返す関数になります。試しに書いてみましょう。 関数名の前に戻り値の型を指定します。今回はfloat型です。 戻り値を返すときは return (返したい値); と指定しましょう。 戻り値を使うときは、関数を呼び出した側に戻り値を受け止めるための受け皿となる変数を用意してあげる必要があります。今回は計算結果を保存するためのfloat型の変数を用意しています。 ※ Update関数に書くと毎フレーム実行されるのでStart関数に書いてください コードが書けたら保存して、実行してみましょう。ログに計算結果が出力されます。 戻り値を使うときは必ず return で返さないといけないので注意してください。 ただし戻り値にあたる部分が void になっている関数は戻り値がありません 。戻り値を使わない関数はvoidから書き始めましょう(voidは空っぽ、空虚 という意味です) 関数は他にも色々あるのですが、この教材を読む分にはこれだけ理解してもらえればOKです。 Unityでは簡単にゲームを作れるとはいえ、プログラムの基礎は必要になるので何度もコードを書いて覚えていきましょう。 ページ TOP EX-1 プログラムのルール EX-2 変数 EX-3 if文 EX-4 関数
- 3D脱出ゲーム編 Lesson1「プレイヤーを実装しよう」 | Unity1gc2
3D脱出ゲーム編 Lesson1 プレイヤーを実装しよう 1-0 このパートで作るゲームについて 1-0 このパートで作るゲームについて この「3D脱出ゲーム編」では3Dアクションゲーム編を終えた人向けに 、 より高度な3Dゲームの作り方を解説していきます。このサイトの中でも一番難度の高い教材になりますが、難しい処理を完全に理解する必要はありません 。3Dアクションゲーム編の内容もちょくちょく出てくるので復習しながら進めていってください。 また、作ったゲームに敵を配置してホラーゲームにしたり、VRゲームへの対応も行うEXレッスンもあります。 まずはどんなゲームかイメージできるように3D脱出ゲーム編のメインページ からサンプルゲームを遊んでみてください。 このゲームではゲームパッドを使用します 。キーボード操作でも遊べるように作っていきますが、スムーズに制作を進めるためにゲームパッドを用意しておいてください。 1-1 新規プロジェクトを作る 1-1 新規プロジェクトを作る まずはゲームを制作していくためのプロジェクトを作成しましょう。 今回はURP(Universal Render Pipeline) というテンプレートを使ってゲームを作っていきます。実は3Dアクションゲーム編で使用した通常の3Dコアの描画の仕組みは古いもので、今後はURPが標準になっていくと言われています。 URPの特徴としては以下のようなものが挙げられます。 ・描画のクオリティは3Dコアと大差ないが、描画処理が最適化されており負荷が少ない 。 ・ShaderGraphなどの新しい機能が使える (3Dコアは旧式の描画方法なので、今後新機能を追加する予定はないと言われています) ・通常の3Dコアとは描画方法が異なるため、3Dコアで作ったシェーダーをURPでそのまま使用することはできない 。 URPを使用するといっても、今まで学んだUnityの知識が無駄になるわけではありません。ほとんどの機能はそのまま使えるので、3Dコアと異なる部分だけ覚えていきましょう。 それではUnity Hubを起動して、新しいプロジェクトを作成してください。 使用するテンプレートは3D(URP)を選択してください(ない場合はインストールしてください) プロジェクトにわかりやすい名前をつけたら「プロジェクトを作成」をクリックしましょう。 プロジェクトを作成できたら、まずは画面サイズを16:10に変更しておきます。 次に素材配布所(URL)からパッケージをダウンロードして、3Dアクションゲーム編と同じようにドラッグ&ドロップでインポートしてください。 【スクリプト素材元】https://assetstore.unity.com/packages/tools/particles-effects/quick-outline-115488?locale=ja-JP 同梱されているMainシーンをダブルクリックして開いてください。 壁や床などは既に配置されています。 SampleSceneは使用しません。 マウスの右を押した状態でWASDキーを押すと、1人称ゲームのようにカメラを移動することができます。これをフライスルーモードと言い、実際のゲーム画面での見栄えを確認するのに便利です。 また、QEキーで上下に移動でき、ホイールで移動速度を変更できます。SHIFTキーを押して速く移動することもできます。 まずはフライスルーモードでステージを見て回ってみてください。 1-2 ゲームパッドとキーボードの両立 1-2 ゲームパッドとキーボードの両立 デフォルトの入力設定ではゲームパッド操作とキーボード操作を両立することはできません。ですが、ゲームパッドがないときでも遊べるようにキーボード操作にも対応しておきたいところです 。 ここで設定を間違えると後でうまく操作できないので、教材と見比べながらしっかり設定してください。 EditのProject Settingsを開いて「Input Manager」を選択してください。選択したらAxesの左側にある▼をクリックして開いてください。 まずはSizeを4つ増やしてください。 今回は初期値が30だったので34にしています(バージョンによって初期値が異なる可能性があります) 1番上にあるHorizontalとVerticalを開き「Negative Button」と「Positive Button」内のテキストを削除してください。 次に先ほど追加した4つの項目を設定します。名前が同じになっているためややこしいですが、下から4番目の項目を選択してください。 1番目の項目は以下のように設定してください。テキストの打ち間違いに注意してください。 ・Name→「Horizontal2」 ・Descriptive Name→「Stick R left/right」 ・Negative Button→空白 ・Positive Button→空白 ・Gravity→0 ・Dead→0.19 ・Sensitivity→1 ・Axis→4th axis(Joysticks) 2番目の項目は以下のように設定してください。 ・Name→「Vertical2」 ・Descriptive Name→「Stick R up/down」 ・Negative Button→空白 ・Positive Button→空白 ・Gravity→0 ・Dead→0.19 ・Sensitivity→1 ・Axis→5th axis(Joysticks) 3番目の項目は以下のように設定してください。変更箇所が多いので注意しましょう。 ・Name→「Horizontal2」 ・Descriptive Name→「Stick R left/right」 ・Negative Button→空白 ・Positive Button→空白 ・Alt Negative Button→left ・Alt Positive Button→right ・Gravity→3 ・Dead→0.01 ・Sensitivity→3 ・Type→Key or Mouse Button ・Axis→4th axis(Joysticks) 4番目の項目は以下のように設定してください。 ・Name→「Vertical2」 ・Descriptive Name→「Stick R up/down」 ・Negative Button→空白 ・Positive Button→空白 ・Alt Negative Button→down ・Alt Positive Button→up ・Gravity→3 ・Dead→0.01 ・Sensitivity→3 ・Type→Key or Mouse Button ・Axis→5th axis(Joysticks) 最初に追加した4つの項目を設定できたらOKです。Project Managerを閉じてください。 設定した項目の解説は割愛します。各項目の詳細は公式のリファレンス を参考にしてください。 1-3 ポストプロセスの調整 1-3 ポストプロセスの調整 早くプレイヤーを作りたいかもしれませんが、先にポストプロセスを調整しておきます。URPでは最初からポストプロセスが導入されています。 ヒエラルキーからGlobal Volumeを選択してください。 インスペクターからポストプロセスの調整をしていきましょう。 Tonemapping(画面の明るすぎる部分などを調整する)はデフォルトのままで構いません。 Bloom(明るい部分をネオンのように光らせる)はThresholdを1、Intensityを0.05に設定してください。 Vignette(画面の端の色を変更する)はそのままで構いません。 新しい項目を追加しましょう。下部の「Add Override」ボタンをクリックして「Depth Of Field」と「Color Adjustments」を追加してください。 Depth Of Field(被写界深度、指定した距離の物体がぼやけるようになる)はModeを「Gaussian」にして、Startを40、Endを140に設定してください。 Color Adjustments(画面のトーンや明るさを調整)はPostExposureを1にして、Color Filterのチェックを入れておいてください。 Tonemappingは画面全体の明るさを調整してくれる便利なものですが、画面が暗く地味になってしまう問題があります。 それを解決するのがColor AdjustmentsのPostExposureです。この値を上げると画面が全体的に明るくなり、トーンマップを使いながら画面を明るくすることができます。 次にAmbient Occlusion(物体の角などを暗くして画面の現実感を上げる)です。3Dアクションゲーム編 では同じようにVolumeで設定していましたが、URPでは設定場所が変わっています。 Settingsフォルダを開いてください 。 フォルダ内にあるURP-HighFidelity-Rendererを選択してください。 SSAOのIntensity(強度)を50に上げてください。本棚や木箱など、角になっている部分がより暗くなるようになります。 最後にポストプロセスとは異なりますが、ディレクションライトの設定を調整しましょう。 ヒエラルキーからDirectional Lightを選択して、RotationをX=80 Y=60 Z=0に、ShadowsのStrength(強度)を0.6に設定してください。 1-4 プレイヤーの移動 1-4 プレイヤーの移動 準備ができたところで、一人称視点のプレイヤーを作成して移動できるようにしましょう。 開発中はステージを見やすいように天井を非表示にしておきましょう。ヒエラルキーからCeilingを選択して、非アクティブにしてください。 準備ができたところで、一人称視点のプレイヤーを作成して移動できるようにしましょう。 新しい空オブジェクトを作成して、名前をPlayerにしてください。タグはPlayerに設定し、PositionはX=40 Y=5.6 Z=-53にしましょう。 次はプレイヤーに当たり判定と重力を追加しましょう。流れは3Dアクションゲーム編の1-4 と同じです。 プレイヤーに「Add Component」からCapsule Colliderを追加してください。 コライダーのRadius(半径 )を1.2に、Height(高さ)を10に設定してください。 次に重力を設定しましょう。 「Add Component」からRigidbodyを追加してください。ConstraintsのFreeze RotationのXYZ全てにチェックを入れて、物理演算による回転を制限しておきましょう。 カメラをプレイヤーの子オブジェクトにしましょう。これによってPlayerオブジェクトが移動するとカメラも同じように移動するようになります。 ヒエラルキー内でMain CameraをPlayerにドラッグ&ドロップしてください。 → 子オブジェクトの座標は親オブジェクト(プレイヤー)を基準にしたローカル座標になります。PositionはX=0 Y=4.5 Z=0に設定してください。 また、今回は一人称カメラなので近平面が近くなるように0.1に設定しておきます。遠平面は500にしておきましょう。 後は移動処理のスクリプトを作成するだけになります。 プロジェクトウィンドウを右クリック→「Create」→「Folder」を選択して、 スクリプトを入れておくためのScriptフォルダを作成してください。 Scriptフォルダ内にPlayerMoveスクリプトを作成して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMove : MonoBehaviour { // パラメータ [SerializeField] float MoveSpeed = 1000.0f; // キャッシュ Rigidbody m_rigidBody; GameObject m_gameCameraObj; void Start() { // 必要な情報を取得 m_rigidBody = GetComponent(); m_gameCameraObj = Camera.main.gameObject; } // Fixedなので注意! void FixedU pdate() { // カメラを考慮した移動 Vector3 PlayerMove = Vector3.zero; Vector3 stickL = Vector3.zero; stickL.z = Input.GetAxis("Vertical"); stickL.x = Input.GetAxis("Horizontal"); Vector3 forward = m_gameCameraObj.transform.forward; Vector3 right = m_gameCameraObj.transform.right; forward.y = 0.0f; right.y = 0.0f; right *= stickL.x; forward *= stickL.z; // 移動速度に上記で計算したベクトルを加算する PlayerMove += right + forward; // プレイヤーの速度を設定することで移動させる PlayerMove = (PlayerMove * MoveSpeed * Time.deltaTime); PlayerMove.y = m_rigidBody.velocity.y; m_rigidBody.velocity = PlayerMove; } } 基本的には3Dアクションゲーム編のプレイヤーと同じですが、一部異なる点があります。 【プログラムの解説】 ・[SerializeField] というコードがありますが、これはAttribute(アトリビュート)と呼ばれるもので変数に属性を付与することができます。publicやprivate(アクセス指定子)とは異なり、アトリビュートはインスペクター上での属性を付与するものが主です。 変数をpublicにするとインスペクター上に表示できますが、publicにするということはどこからでもアクセスできてしまう…つまり外部から簡単に変更できてしまうということになります。変数をprivateにしつつインスペクター上に値を表示したい時は、変数の前に[SerializeField] という属性を付与することで実現することができます。 アトリビュートについてはEXでも解説しているので参考にしてみてください。 「アクセスがどうとかよくわかんないよ!」という人は気にしなくてOKです。 ・Input.GetAxis("Vertical") はゲームパッドの左スティックの上下の入力量を取得する関数です。入力量は-1.0f~1.0fの間で返されます。 また、キーボードのWキーが押されると1.0fを、Sキーが押されると-1.0fを返すようにもなっています。これによって、ゲームパッドがないときにはキーボードで操作を代用できるようになります。 同じようにInput.GetAxis("Horizontal") は左スティックの左右の入力量、AキーとDキーの入力を取得することができます。 ・Rigidbodyクラスのパラメータvelocity は「物体がどの方向にどれだけの速度で移動しているか」を表すVector3型のパラメータです。移動量を計算してvelocityに代入することで、プレイヤーの移動を実現しています。 コードが書けたら保存して、PlayerMoveスクリプトをPlayerにドラッグ&ドロップしてアタッチしてください。 これでプレイヤーが移動できるようになりました。ゲームを実行して、ゲームパッドの左スティックか、WASDキーで移動してみてください。 まだゲームの最適化をしていないので描画に負荷がかかりますが、これは後ほど修正します。 前述のとおり移動速度用の変数MoveSpeedに[SerializeField]というアトリビュート(属性)を付与しているので、privateな変数でありながらインスペクターに表示されています。 移動速度はお好みの値に調整してください。 AddForceやvelocityなど物理演算を扱う処理は、FixedUpdateに書くことが推奨されています 。FixedUpdateは決まった間隔で実行されますが、UpdateはFPSによって実行間隔が変わるため、移動処理に使うとガタガタした移動になってしまいます。 気になる方は実際に移動処理をUpdateに書いてみてください。移動が不安定になるはずです。 Unity Tips! 1-5 カメラ操作 1-5 カメラ操作 次はカメラ操作を実装しましょう。こちらも基本的な内容は3Dアクションゲーム編と同じです。 GameCameraスクリプトを作成して、以下のように入力してください。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameCamera : MonoBehaviour { [Header("カメラの回転速度"), SerializeField] float RotSpeed = 200.0f; [Header("カメラの回転モード(X)"), SerializeField] bool CameraModeX = false; [Header("カメラの回転モード(Y)"), SerializeField] bool CameraModeY = false; [Header("X回転最大値"), SerializeField] float MaxX = 60.0f; [Header("X回転最小値"), SerializeField] float MinX = -40.0f; // 初期座標 Vector3 m_defPos; void Awake() { // 初期ローカル座標を保存 m_defPos = transform.localPosition; } void Update() { } void LateUpdate() { // 右スティックでカメラ回転 // 上下 float rot = Time.deltaTime * RotSpeed; if (Input.GetAxisRaw("Vertical2") != 0.0f) { rot *= Input.GetAxisRaw("Vertical2"); if (CameraModeY) { // モードに応じて変更 rot *= -1.0f; } } else { rot = 0.0f; } transform.RotateAround(transform.position, transform.right, rot); // X角度制限 float nowRot = transform.localEulerAngles.x; // 取得した角度は0~360°なので補正する if (nowRot >= 180.0f) { nowRot -= 360.0f; } if (nowRot > MaxX) { transform.localEulerAngles = new Vector3( MaxX, transform.localEulerAngles.y, transform.localEulerAngles.z); } if (nowRot < MinX) { transform.localEulerAngles = new Vector3( MinX, transform.localEulerAngles.y, transform.localEulerAngles.z); } // 左右 rot = Time.deltaTime * RotSpeed; if (CameraModeX) { // モードに応じて変更 rot *= -1.0f; } if (Input.GetAxisRaw("Horizontal2") != 0.0f) { rot *= Input.GetAxisRaw("Horizontal2"); } else { rot = 0.0f; } transform.RotateAround(transform.position, Vector3.up, rot); // Z軸の回転があるとややこしいので制限する Vector3 angles = transform.eulerAngles; angles.z = 0.0f; transform.eulerAngles = angles; // 座標は固定 transform.localPosition = m_defPos; } } 【プログラムの解説】 ・[Header("表示したい文")] は前述したアトリビュートの一種です。この文を書くことでインスペクター内に文章を表示することができます。日本語も使えます。インスペクターを見やすく整理するためにぜひ使ってみてください。 ・[SerializeField, Header("テキスト")] のように,(カンマ)で区切ることで複数のアトリビュートを同時に使用することができます。 コードが書けたら保存して、Main CameraにGameCameraスクリプトをアタッチしてください。ゲームを実行して、右スティックか矢印キーでカメラが回転することを確認しましょう。 段差を通った時などに大きくジャンプしてしまうことがあるため、重力を強くしましょう。今回は重力を5倍にします。 EditのProject Settingsを開いて「Physics」を選択してください。 Unityではパラメータ内で四則演算を行うこともできます。GravityのYに「*5」を追加してEnterキーを押してください。重力が-49.05になります。 今回はジャンプやアニメーションなどは必要ないため、プレイヤーの移動処理は一通り完成になります。 1-6 最適化 1-6 最適化 ゲームに負荷がかかる要因は色々あるのですが、このゲームでは描画の負荷がかなり大きくなっています。このマップには大量のオブジェクトが配置されており実行時に大きい負荷がかかるため、ここで最適化を行いましょう。 DirectXの授業などで解説されているかと思いますが、オブジェクトを描画する際にはドローコール というものが行われます。これはCPUからGPUに「絵を描いて!」と命令する処理になります。 この処理が多ければ多いほど描画の処理は重くなるため、ドローコールを削減することがゲームの軽量化に繋がります。 Gameウィンドウの右上にあるStatsボタンを押した状態でゲームを実行すると、FPSや描画回数などの情報を確認できます。 Batchesは描画するメッシュをCPUからGPUに転送した回数です。実際に見てみると多くて3000回のドローコールが呼ばれています。参考までにスマートフォンゲームでのBatchesの目安は100回前後と言われていたりします。かなりの負荷がかかっていることがわかります。 マップにはたくさんのオブジェクトがありますが、そのオブジェクトのメッシュ1つ1つをGPUに送っているとその数だけドローコールが呼ばれることになります。ですが、メッシュを全てまとめて1つのメッシュとして送ればドローコールが1回で済みますよね? Unityにはメッシュをまとめて「バッチ」を作成するバッチング という機能があります。まずはバッチングの設定をして最適化を行いましょう。 (実際には周囲のメッシュをまとめる処理のようで、全てのメッシュを1つにまとめているわけではないようです) 長々と説明しましたが、内容を全て理解する必要はありません。とりあえず「今のままだとドローコールが多いから、ドローコールを減らす措置を行うんだなー」くらいに思っておいてください。 オブジェクトをバッチングの対象にする方法は簡単です。インスペクター内のStatic右側にある▼をクリックして、Batching Staticをクリックするだけになります。Mapを選択してから、Batching Staticを選択してください。 子オブジェクトの設定も変更するか訊かれるので「Yes, change children」を選択してください。 この状態でゲームを実行すると、Batchingが修正前の10分の1ほどになったことがわかります。また、サンプルの環境ではFPSが10~20ほど上昇しました。 バッチングは背景などに使うと便利ですが、メッシュを1つにまとめてしまうため、ゲームの実行中にオブジェクトを動かしたり回転させたりすることができなくなってしまうので注意しましょう。 もう一つ最適化を行いましょう。 3D描画技術にはオクルージョンカリング というものがあります。 これは「他のオブジェクトに隠れているオブジェクトを描画しない」ことで、描画の負荷を軽減する技術になります。 例えばこの画面だと、壁の向こうにあるオブジェクトを描画する必要はありません。ですが、今の状態では木箱やテーブルを描画した後に壁を描画しています。これだと少し勿体ないですよね。 オクルージョンカリングをするためには、事前に遮蔽判定のデータを生成しておく必要があります。 まずはヒエラルキー内のMapの子オブジェクトを確認してください 。 インスペクター内のStatic右側にある▼をクリックして、Occluder StaticかOccludee Staticを設定しましょう。 Occluder Staticは遮蔽する側、Occludee Staticは遮蔽される側 に設定します。 Platform(足場)にはOccludee Static、Wall(壁)にはOccluder Static、Furniture(家具)にはOccludee Staticをそれぞれ設定してください。 遮蔽の設定ができたら、遮蔽判定用のデータを生成しましょう。 「Window」→「Rendering」→「Occlusion Culling」を選択してください。 Occlusionウィンドウ内の「Bake」を選択して、下部にあるBakeボタンを押してください。 青い立方体が画面にたくさん表示されたら完了です。 ゲームを実行するとBatchesがさらに削減され、FPSが改善したことがわかると思います。 この先のLessonでギミックやライトの設置など負荷のかかる処理を色々行いますが、事前に最適化をしておくことでスムーズに制作を進めることができます。 Occlusionウィンドウの「Visualization」 を選択した状態でゲームを実行してシーンビューを確認すると、実際にどのようにカリングが行われているか見ることができます。 実は他にもフラスタムカリング というものがあります。フラスタムカリングは視錐台カリングとも呼ばれ「カメラに映らないオブジェクトは描画しない」という手法になります。 例えばプレイヤーの後ろにある壁などはカメラに絶対に映らないため描画する必要はありません。こういったカメラの範囲外のオブジェクトの描画を省略する手法がフラスタムカリングになります。 フラスタムカリングは設定しなくてもUnity側で自動で行われる 最適化のため、実装において気にする必要はありません。 まとめるとオクルージョンカリングは「他のオブジェクトに隠れたオブジェクトを描画しない」手法、フラスタムカリングは「カメラの範囲外のオブジェクトを描画しない」手法になります。 オクルージョンカリングとフラスタムカリングは混同されがちですが、実際は違う技術なので間違えないように注意しましょう。 Unity Tips! Lesson1ではプレイヤーの作成と今後の制作に向けた最適化を行いました。 次のLessonではいよいよゲーム進行の鍵となるアイテムを実装していきます。 【評価テスト】 (いつかURLを貼る予定) 評価テスト Next Lesson2「アイテムを作ろう」 ページ TOP 1-0 このパートで作るゲームについて 1-1 新規プロジェクトを作る 1-2 ゲームパッドとキーボードの両立 1-3 ポストプロセスの調整 1-4 プレイヤーの移動 1-5 カメラ操作 1-6 最適化 評価テスト
- 2Dシューティングゲーム編 Lesson1「プレイヤーを実装しよう」 | Unity1gc2
2Dシューティングゲーム編 Lesson1 プレイヤーを実装しよう 1-0 このパートで作るゲームについて (執筆中!ちょっと待ってね)