【Unity】2D横スクロールアクションゲームのキャラクター移動方法
ここでは下の動画のような、2D横スクロールアクションゲームのキャラクター移動について説明します。

この記事はUnity1weekという1週間ゲームジャムで作ったゲームを元にしています。実際の操作感を知りたい場合は以下のリンクからプレイできますので、試してみてください。
ゲームはこちらからプレイできます環境
- Unity 2017.1.0f3
このページの構成
ポイント
- スクリプトの処理手順は以下のとおりです。
- ① 入力を取得する。
- ② 入力に応じてキャラクターの状態を変更する。
- ③ 状態に応じてアニメーションを変更する。
- ④ 移動する。
事前準備
キャラクターの設定
キャラクターには以下のコンポーネントを追加しておいてください。
- Rigidbody2D
- Capsule Collider2D(通常の衝突判定用)
- Box Collider2D(地面との接地判定用)
- Animator
Colliderを分けているのは、着地後すぐにジャンプしたい場合などに、通常の衝突判定用のColliderだけだと反応が悪かったからです。
参考にCollider設定のキャプチャを貼っておきます。

ちなみにここで使用しているキャラは以下のリンクから入手できます。
『コーゲンシティ・オールスターズ!』ユニティちゃんピクセルアートパック for アクションゲーム Vol.2アニメーションの設定
アニメーションは「待機状態」「走っている状態」「上昇中」「下降中」の4種類を切り替えて表示しています。
アニメーションの設定についてはスクリプトの切り替え処理と合わせて後述します。
spriteアニメの設定方法がわからない場合は下記リンクなどを参考にしてみてください。
ステージの設定
地面との接地判定に衝突したオブジェクトのタグを使用しています。同じように動作させたい場合は、地面となるオブジェクトのタグを「Ground」にしておいてください。
スクリプト全文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
Rigidbody2D rb; Animator animator; float jumpForce = 22f; // ジャンプ時に加える力 float jumpThreshold = 1f; // ジャンプ中か判定するための閾値 float runForce = 1.5f; // 走り始めに加える力 float runSpeed = 0.5f; // 走っている間の速度 float runThreshold = 2.2f; // 速度切り替え判定のための閾値 bool isGround = true; // 地面と接地しているか管理するフラグ int key = 0; // 左右の入力管理 string state; // プレイヤーの状態管理 string prevState; // 前の状態を保存 float stateEffect = 1; // 状態に応じて横移動速度を変えるための係数 void Start(){ this.rb = GetComponent<Rigidbody2D> (); this.animator = GetComponent<Animator> (); } void Update () { GetInputKey (); // ① 入力を取得 ChangeState (); // ② 状態を変更する ChangeAnimation (); // ③ 状態に応じてアニメーションを変更する Move (); // ④ 入力に応じて移動する } void GetInputKey(){ key = 0; if (Input.GetKey (KeyCode.RightArrow)||Input.GetKey (KeyCode.D)) key = 1; if (Input.GetKey (KeyCode.LeftArrow)||Input.GetKey (KeyCode.A)) key = -1; } void ChangeState(){ // 空中にいるかどうかの判定。上下の速度(rigidbody.velocity)が一定の値を超えている場合、空中とみなす if (Mathf.Abs (rb.velocity.y) > jumpThreshold) { isGround = false; } // 接地している場合 if (isGround) { // 走行中 if (key != 0) { state = "RUN"; //待機状態 } else { state = "IDLE"; } // 空中にいる場合 } else { // 上昇中 if(rb.velocity.y > 0){ state = "JUMP"; // 下降中 } else if(rb.velocity.y < 0) { state = "FALL"; } } } void ChangeAnimation(){ // 状態が変わった場合のみアニメーションを変更する if (prevState != state) { switch (state) { case "JUMP": animator.SetBool ("isJump", true); animator.SetBool ("isFall", false); animator.SetBool ("isRun", false); animator.SetBool ("isIdle", false); stateEffect = 0.5f; break; case "FALL": animator.SetBool ("isFall", true); animator.SetBool ("isJump", false); animator.SetBool ("isRun", false); animator.SetBool ("isIdle", false); stateEffect = 0.5f; break; case "RUN": animator.SetBool ("isRun", true); animator.SetBool ("isFall", false); animator.SetBool ("isJump", false); animator.SetBool ("isIdle", false); stateEffect = 1f; //GetComponent<SpriteRenderer> ().flipX = true; transform.localScale = new Vector3 (key, 1, 1); // 向きに応じてキャラクターのspriteを反転 break; default: animator.SetBool ("isIdle", true); animator.SetBool ("isFall", false); animator.SetBool ("isRun", false); animator.SetBool ("isJump", false); stateEffect = 1f; break; } // 状態の変更を判定するために状態を保存しておく prevState = state; } } void Move(){ // 設置している時にSpaceキー押下でジャンプ if (isGround) { if (Input.GetKeyDown (KeyCode.Space)) { this.rb.AddForce (transform.up * this.jumpForce); isGround = false; } } // 左右の移動。一定の速度に達するまではAddforceで力を加え、それ以降はtransform.positionを直接書き換えて同一速度で移動する float speedX = Mathf.Abs (this.rb.velocity.x); if (speedX < this.runThreshold) { this.rb.AddForce (transform.right * key * this.runForce * stateEffect); //未入力の場合は key の値が0になるため移動しない } else { this.transform.position += new Vector3 (runSpeed * Time.deltaTime * key * stateEffect, 0, 0); } } // 着地判定 void OnTriggerEnter2D(Collider2D col){ if (col.gameObject.tag == "Ground") { if (!isGround) isGround = true; } } void OnTriggerStay2D(Collider2D col){ if (col.gameObject.tag == "Ground") { if(!isGround) isGround = true; } } |
処理の説明
入力の取得
まず GetInputKey() で入力を取得しています。
処理としては、左右の入力を取得して key に入力内容を格納しているだけです。key の値を 1, -1 でもっているのは、入力方向に応じてキャラクターの向きを変更する処理にも使用するためです。向きの変更については後述する「アニメーションの変更」で説明します。
1 2 3 4 5 6 7 |
void GetInputKey(){ key = 0; if (Input.GetKey (KeyCode.RightArrow)||Input.GetKey (KeyCode.D)) key = 1; if (Input.GetKey (KeyCode.LeftArrow)||Input.GetKey (KeyCode.A)) key = -1; } |
状態の変更
ここではキャラクターの状態を「待機状態」「走行中」「上昇中」「下降中」の4種類持っています。状態の判定は以下のように行なっています。
- 待機状態 : 地面に接地している かつ 左右の入力がない
- 走行中 : 地面に接地している かつ 左右の入力がある
- 上昇中 : 地面に接地していない かつ 上方向に速度がかかっている
- 下降中 : 地面に接地していない かつ 下方向に速度がかかっている
上下の速度は Rigidbody2D.velocity.y で取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void ChangeState(){ // 空中にいるかどうかの判定。上下の速度(rigidbody.velocity)が一定の値を超えている場合、空中とみなす if (Mathf.Abs (rb.velocity.y) > jumpThreshold) { isGround = false; } // 接地している場合 if (isGround) { // 走行中 if (key != 0) { state = "RUN"; //待機状態 } else { state = "IDLE"; } // 空中にいる場合 } else { // 上昇中 if(rb.velocity.y > 0){ state = "JUMP"; // 下降中 } else if(rb.velocity.y < 0) { state = "FALL"; } } } |
アニメーションの変更
状態に応じてアニメーションを変更します。基本的には上記4つの状態に応じた以下のフラグをAnimatorに用意しておき、そのフラグを切り替えることで処理を行っています。
- 待機状態 : isIdle = true
- 走行中 : isRun = true
- 上昇中 : isJump = true
- 下降中 : isFall = true
ここでは走行中のアニメーションを右向きのものしか作成していなかったため、上記設定だけでは左に移動するときも右を向いたアニメーションが表示されていました。
これを回避するため、左入力されている場合は transform.localScale.x にマイナスの値を与えてspriteを反転させています。key の値を 1, -1 でもっているのはこの処理のためです。
※ 公式マニュアルでは処理が重いため、この方法は推奨されていません。SpriteRendererのflipXをtrueにすることでもspriteは反転させることが可能なので、処理速度が気になる方はそちらを使用してください。ただしこの方法ではCollider等は反転しないため、左右対称ではない場合は注意が必要です。
また、stateEffectという変数に状態に応じて値を設定しています。これはジャンプ中に左右のボタンを押した場合、地上と同様の速度で移動してしまうのを防ぐための変数です。移動処理時に使用しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
void ChangeAnimation(){ // 状態が変わった場合のみアニメーションを変更する if (prevState != state) { switch (state) { case "JUMP": animator.SetBool ("isJump", true); animator.SetBool ("isFall", false); animator.SetBool ("isRun", false); animator.SetBool ("isIdle", false); stateEffect = 0.5f; break; case "FALL": animator.SetBool ("isFall", true); animator.SetBool ("isJump", false); animator.SetBool ("isRun", false); animator.SetBool ("isIdle", false); stateEffect = 0.5f; break; case "RUN": animator.SetBool ("isRun", true); animator.SetBool ("isFall", false); animator.SetBool ("isJump", false); animator.SetBool ("isIdle", false); stateEffect = 1f; //GetComponent<SpriteRenderer> ().flipX = true; transform.localScale = new Vector3 (key, 1, 1); // 向きに応じてキャラクターのspriteを反転 break; default: animator.SetBool ("isIdle", true); animator.SetBool ("isFall", false); animator.SetBool ("isRun", false); animator.SetBool ("isJump", false); stateEffect = 1f; break; } // 状態の変更を判定するために状態を保存しておく prevState = state; } } |
2017.12.06追記
Animator側の設定も見せてほしいとご連絡いただいたので、簡単にですが追記します。

上図のように、各状態からどの状態にも遷移できるように設定しています。
animation遷移の設定はどれも同じなので、例として「待機状態(isIdle)から走っている状態(isRun)」と「走っている状態(isRnu)から待機状態(isIdle)」への遷移の設定のみキャプチャを貼っておきます。


デフォルト設定からの変更点は「HasExitTimeのチェックを外す」「FixedDurationにチェックを付ける」「TransitionDurationを0.01にする」の3点です。アニメーションがすぐに切り替わるように設定しています。
移動
入力に応じてキャラクターを移動させます。
移動し始めは等速移動ではない感じにしたかったので、最初はAddforceで力を加えて、途中から一定速度で移動するような処理にしています。
ゲームをプレイしてもらうと分かるのですが、正直ちょっとふわふわした操作感になっているので、この辺りの処理は適宜書き換えてもらったほうがいいかもしれません。いい処理方法をご存知の方は教えてください。
また「アニメーションの変更」のところでも書いていますが、キャラクターの状態に応じた stateEffect という値を移動時に掛けることで、ジャンプ中の横移動速度に制限を加えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void Move(){ // 設置している時にSpaceキー押下でジャンプ if (isGround) { if (Input.GetKeyDown (KeyCode.Space)) { this.rb.AddForce (transform.up * this.jumpForce); isGround = false; } } // 左右の移動。一定の速度に達するまではAddforceで力を加え、それ以降はtransform.positionを直接書き換えて同一速度で移動する float speedX = Mathf.Abs (this.rb.velocity.x); if (speedX < this.runThreshold) { this.rb.AddForce (transform.right * key * this.runForce * stateEffect); //未入力の場合は key の値が0になるため移動しない } else { this.transform.position += new Vector3 (runSpeed * Time.deltaTime * key * stateEffect, 0, 0); } } |
着地判定
着地判定はOnTrigger〜系の処理で行なっています。
衝突したオブジェクトのタグが “Ground” の場合、isGround をtrue に書き換えています。
1 2 3 4 5 6 7 8 9 10 11 12 |
void OnTriggerEnter2D(Collider2D col){ if (col.gameObject.tag == "Ground") { if (!isGround) isGround = true; } } void OnTriggerStay2D(Collider2D col){ if (col.gameObject.tag == "Ground") { if(!isGround) isGround = true; } } |