Unityでの2Dジャンプアクションゲームの作成(Playerキャラクター作成編)

[前回の記事](https://qiita.com/tokoroten_346/items/e8ef6462e1fb429ebb6f)でステージを作ったので今回はプレイヤーキャラを作っていきます。

まずAssetsフォルダ内にプレイヤーキャラクターを格納するCharactersフォルダを作成します。

![rapture_20210110150813.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/21295ee9-246e-1baf-81b3-967b152d9564.jpeg)

"Assets/Pixel Adventure 1/Assets/Main Characters/Ninja Frog"内にあるNinja FrogくんのIdle状態を実装します。
Idle(32×32)の中身をすべて選択したらHierarchyタブへドラッグアンドドロップします。保存ダイアログが出てくるので先ほど作成したCharactersフォルダへ"PlayerIdleAnimation"として保存します。


Hierarchyタブ内にIdleファイルが出来ている事を確認したら再生ボタンを押します。するとこんな感じで小さいNinja Frogくんが動いてる事が確認できます。

![test1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/5f099dd0-9b65-00c0-67a2-c1f142081dc6.gif)

このままでは小さすぎるのでstageで行った操作と同様に"Assets/Pixel Adventure 1/Assets/Main Characters/Ninja Frog"のIdleを全て選択したらPixel Per Unitを100→32へ変更してApplyを押します。
するとこんな感じでちょうどいい大きさになります。

![test3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/500e68c0-3287-269c-0a73-220277ddbba2.gif)

わかりやすくするために名前をPlayerにリネームしておきます。
![rapture_20210124233025.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/18f6fce5-513d-cdc3-13c4-5df417254603.jpeg)


次にPlayerキャラに物理演算を付与していきます。

Playerを選択した状態でInspectorタブの"Add Component"をクリックして「Rigidbody 2D」を追加します。
![rapture_20210110154139.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/ea50f0d5-4837-791f-56ce-769f4934b5c0.jpeg)


「Rigidbody 2D」を追加すると重力が付与されます。

![tes5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/eca165dc-66b1-3a14-a245-c42646b76bd5.gif)

しかしこのままだとNinja Frogくんがstageを突き抜けてしまうので当たり判定を追加していきます。

Plyaerを選択してAdd Componentから「Box Collider 2D」を追加。
次にTilemapを選択してAdd Componentから「Tilemap Collider 2D」を追加。
以上の操作によりPlayerとstageに当たり判定が追加されます。
またStageに引っ掛かる事があるのでTilemapへAdd Componentから「Composite Collider 2D」を追加します。Composite Collider 2Dを追加するとRigidbody 2Dも付いてくるのでBody TypeをStaticに変更します。

その状態でTilemap Colider 2Dの「Userd By Compoisite」へチェック入れる事でstageが一枚となります

![rapture_20210124231631.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/d467c6f7-f562-e11f-1aab-02c2a23e9417.jpeg)



![tes6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/c6b756dd-1715-f87a-cdd5-9f4a2a3413ba.gif)

この操作によりPlayerキャラがStageを突き抜けて落ちていく事がなくなりました。

またPlayerキャラが転がっていかないようにPlayerを選択してRigidbody 2DのConstraints→Freeze RotationについてZ軸へチェックを入れておきましょう。
![rapture_20210114163720_New.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/d0a34f3c-1c49-fb1c-c7b1-f2fae7022bb9.jpeg)




次にキャラクターを動かしていきます。
Assets内にScriptsフォルダを作成してスクリプトを格納していきます。

先ほど作成したScriptsフォルダ内でProjectのすぐ下にある+をクリックしてC# Scriptを選択します。
![rapture_20210110225751.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/c7c54a61-ab67-08bc-472d-3eb562c22d8d.jpeg)
![C#.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/58032bd3-5ca4-0b43-0460-ab13bbb1f3e9.png)

Playerを操作するスクリプトを書く予定なのでC#ファイルの名前はPlayerManagementとします。
早速スクリプト書いていきます。

まずはRigidbody2Dのコンポーネントを取得します

```C#
Rigidbody2D rbody2D;

private void Start(){
	rbody2D = GetComponent<Rigidbody2D>();
}
```
自分についているRigidbody2DをGetしてrbody2Dへ格納しています。


次にキーの入力を取得するためのUpdate、物理演算を行うFixedUpdateを記述していきます。
UpdateとFixedUpdateについては以下のような違いがあります。
Update........フレーム毎に呼び出される
FixedUpdate...一定時間毎に呼び出される

Inputはキー入力を行ったフレームでしか有効にならないため、FixedUpdateでは同期がずれる可能性があるためUpdateに記述していきます。
Rigidbodyなどの物理演算を行う時は呼び出し回数が一定であるFixedUpdateに記述していきます。

キャラクターを動かすためにはRigidbody2DのVelocityを使います。
こんな感じ

```C#
	float speed; // 移動速度を格納する
	// 動作状態を定義する 
	private enum MOVE_TYPE{
		STOP,
		RIGHT,
		LEFT,
	}
	MOVE_TYPE move = MOVE_TYPE.STOP; // 初期状態はSTOP 

	// Updateではキー入力を取得してFixedUpdateへ渡す
	private void Update(){
		float horizonkey = Input.GetAxis("Horizontal");  // 水平方向のキーの取得

		// 取得した水平方向のキーを元に分岐
		if (horizonkey == 0){
			// キー入力なしの場合は停止
			move = MOVE_TYPE.STOP;
		}
		else if(horizonkey > 0){
			// 取得したキー入力が正の場合は右へ移動する
			move = MOVE_TYPE.RIGHT;
		}
		else if(horizonkey < 0){
			// 取得したキー入力が負の場合は左へ移動する
			move = MOVE_TYPE.LEFT;
		}
	}
	// Updateで取得したキー入力を元にRigidbody2Dを動かしていく
	private void FixedUpdate(){
		// Playerの方向を決めるためにスケールの取り出し
		Vector3 scale = transform.localScale;
		if(move == MOVE_TYPE.STOP) {
			// 状態がSTOPの場合はspeed 0
			speed = 0;
		}
		else if(move == MOVE_TYPE.RIGHT) {
			scale.x = 1; // 右向き
			// 右へ移動の場合はspeed値が正
			speed = 3;
		}
		else if (move == MOVE_TYPE.LEFT) {
			// scale.x = -1; // 左向き
			// 左へ移動の場合はspeed値が負
			speed = -3;
		}
                transform.localScale = scale; // scaleを代入
		// rigidbody2Dのvelocity(速度)へ取得したspeedを入れる。y方向は動かないのでそのままにする
		rbody2D.velocity = new Vector2(speed, rbody2D.velocity.y);
	}
}
```


ついでにジャンプの動作も作っていきます。
Update内にスペースを押したらジャンプする処理を追加していきます。

```C#
	// spaceを押したらジャンプ関数へ
	if (Input.GetKeyDown("space")) {
		Jump();
	}

```

Jump関数はこんな感じ

```C#
	void Jump() {
		// 上方向に力を加える事でジャンプする
		rbody2D.AddForce(Vector2.up * 300);
	}
```
下の部分の300を好きな数字に変更する事でジャンプ力調整できます。

```
rbody2D.AddForce(Vector2.up * 300);
```

PlayerManagement.cs内の記述は以下のようになります。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerManagement : MonoBehaviour
{
	// 動作状態を定義する 
	private enum MOVE_TYPE{
		STOP,
		RIGHT,
		LEFT,
	}
	MOVE_TYPE move = MOVE_TYPE.STOP; // 初期状態はSTOP 
	Rigidbody2D rbody2D;			 // Rigidbody2Dを定義
	float speed;                     // 移動速度を格納する変数

	private void Start(){
		// Rigidbody2Dのコンポーネントを取得
		rbody2D = GetComponent<Rigidbody2D>(); 
	}

	// キー入力はUpdateで処理する
	private void Update(){
		float horizonkey = Input.GetAxis("Horizontal");      // 水平方向のキー取得

		// 取得した水平方向のキーを元に分岐
		if (horizonkey == 0){
			// キー入力なしの場合は停止
			move = MOVE_TYPE.STOP;
		}
		else if(horizonkey > 0){
			// キー入力が正の場合は右へ移動する
			move = MOVE_TYPE.RIGHT;
		}
		else if(horizonkey < 0){
			// キー入力が負の場合は左へ移動する
			move = MOVE_TYPE.LEFT;
		}

		// spaceを押したらジャンプ関数へ
		if (Input.GetKeyDown("space")) {
			Jump();
		}
	}

	// 物理演算(rigidbody)はFixedUpdateで処理する
	private void FixedUpdate(){
		// Playerの方向を決めるためにスケールの取り出し
		Vector3 scale = transform.localScale;		
		if(move == MOVE_TYPE.STOP) {
			speed = 0;
		}
		else if(move == MOVE_TYPE.RIGHT) {
			scale.x = 1; // 右向き
			speed = 3;
		}
		else if (move == MOVE_TYPE.LEFT) {
			scale.x = -1; // 左向き
			speed = -3;
		}
		transform.localScale = scale; // scaleを代入
		// rigidbody2Dのvelocity(速度)へ取得したspeedを入れる。y方向は動かないのでそのままにする
		rbody2D.velocity = new Vector2(speed, rbody2D.velocity.y);

	}
	void Jump() {
		// 上に力を加える
		rbody2D.AddForce(Vector2.up * 300);
	}
}

```

スクリプトの記述が終わったらPlayerManagement.csをPlayerへドラッグ＆ドロップ。
![New.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/1c704109-c6a9-0cc5-9b40-488b7c62e74e.jpeg)

PlayerのInspectorにScriptが追加されていることを確認します。
![rapture_20210114115716.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/9c0d8d0a-fb72-a26d-6b6d-11b57e421eac.jpeg)


その状態で再生ボタンを押すとこんな感じ。
![jump.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/5471f3ee-84b9-c583-aa6e-01f0d3e82147.gif)


スペースを連打すると上の方にかっ飛んでいくので連続ジャンプを禁止するため、地面と接触している時のみジャンプが可能という処理を追加します。


地面と接触しているか判断するためTilemapにlayerを追加します。
TilemapのInspector内にあるLayerを選択しAdd layerをクリック。"stage"という名前のレイヤーを作ります。
![rapture_20210114140618.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/008f6813-0c36-cb3d-9026-2744811589d1.jpeg)
![rapture_20210114140645.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/ba5cac59-6f06-d9e1-25f8-b0bc3565dca6.jpeg)
stageレイヤーを作ったらTilemapのレイヤーをDefaultからstageへ変更しておきます。
![rapture_20210114141039.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/16ed9141-cc70-a234-85c5-02a85f55db7f.jpeg)

このstageレイヤーと接触した時のみジャンプが可能という処理を作っていきます。


さっき作成したstageレイヤーを外から設定出来るようにするためPlayerManagement.csへ以下の記述を追加。

```C#
	public LayerMask StageLayer;
```

スクリプトへ記述することによりPlayerのInspectorタブにあるスクリプトの欄にStage Layerが追加されるのでNothing→stageへ変更します。
![rapture_20210126222613_New.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/24d32ecb-f444-0b4a-55b1-47f0a4a5246a.jpeg)



地面への接触判定ですが「Physics2D.Linecast」を使って追加します。
公式リファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Physics2D.Linecast.html

開始点と終了点を決めてベクトルを引き、指定のレイヤーと接触してるかどうかを判定します。
イメージとしてはこんな感じ。
![rapture_20210114161317_New.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/10049fab-451b-c69b-a4d7-9d786aa8a5ae.jpeg)

Playerキャラクターの中心(開始点)と足元(終了点)を設定し、ベクトルとstageレイヤーが接触した場合にのみジャンプするようにスクリプトを変更していきます。
まずはキャラクターと地面が接触しているかどうかチェックするGroundChk()を作ります。

```C#

	bool GroundChk(){
		Vector3 startposition = transform.position;						// Playerの中心を始点とする
		Vector3 endposition = transform.position - transform.up * 0.3f;	// Playerの足元を終点とする

		// Debug用に始点と終点を表示する
		Debug.DrawLine(startposition, endposition, Color.red);

		// Physics2D.Linecastを使い、ベクトルとStageLayerが接触していたらTrueを返す
		return Physics2D.Linecast(startposition, endposition, StageLayer);
	}
```

次にUpdate内のspace入力を受け付ける前の処理に地面と接触してるかどうかのチェックを記述します。

```C#
	// 地面と接触しているかどうかチェック
	if (GroundChk()) {
		// spaceを押したらジャンプ関数へ
		if (Input.GetKeyDown("space")) {
			Jump();
		}
	}
```

プログラムの全体は以下のようになります。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerManagement : MonoBehaviour
{
	public LayerMask StageLayer;

	// 動作状態を定義する 
	private enum MOVE_TYPE{
		STOP,
		RIGHT,
		LEFT,
	}
	MOVE_TYPE move = MOVE_TYPE.STOP; // 初期状態はSTOP 
	Rigidbody2D rbody2D;			 // Rigidbody2Dを定義
	float speed;                     // 移動速度を格納する変数

	private void Start(){
		// Rigidbody2Dのコンポーネントを取得
		rbody2D = GetComponent<Rigidbody2D>(); 
	}

	private void Update(){
		float horizonkey = Input.GetAxis("Horizontal");      // 水平方向のキー取得

		// 取得した水平方向のキーを元に分岐
		if (horizonkey == 0){
			// キー入力なしの場合は停止
			move = MOVE_TYPE.STOP;
		}
		else if(horizonkey > 0){
			// キー入力が正の場合は右へ移動する
			move = MOVE_TYPE.RIGHT;
		}
		else if(horizonkey < 0){
			// キー入力が負の場合は左へ移動する
			move = MOVE_TYPE.LEFT;
		}

		// spaceを押したらジャンプ関数へ
		if (GroundChk()) {
			if (Input.GetKeyDown("space")) {
				Jump();
			}
		}
	}

	// 物理演算(rigidbody)はFixedUpdateで処理する
	private void FixedUpdate(){
		// Playerの方向を決めるためにスケールの取り出し
		Vector3 scale = transform.localScale;
		if(move == MOVE_TYPE.STOP) {
			speed = 0;
		}
		else if(move == MOVE_TYPE.RIGHT) {
			scale.x = 1; // 右向き
			speed = 3;
		}
		else if (move == MOVE_TYPE.LEFT) {
			scale.x = -1; // 左向き
			speed = -3;
		}
		transform.localScale = scale; // scaleを代入
		// rigidbody2Dのvelocity(速度)へ取得したspeedを入れる。y方向は動かないのでそのままにする
		rbody2D.velocity = new Vector2(speed, rbody2D.velocity.y);
	}

	void Jump() {
		// 上に力を加える
		rbody2D.AddForce(Vector2.up * 300);
	}

	bool GroundChk(){
		Vector3 startposition = transform.position;						// Playerの中心を始点とする
		Vector3 endposition = transform.position - transform.up * 0.3f;	// Playerの足元を終点とする

		// Debug用に始点と終点を表示する
		Debug.DrawLine(startposition, endposition, Color.red);

		// Physics2D.Linecastを使い、ベクトルとStageLayerが接触していたらTrueを返す
		return Physics2D.Linecast(startposition, endposition, StageLayer);
	}
}

```

再生ボタンを押してSceneタブからPlayerキャラに設定したベクトルがどうなっているかチェックします。
GroundChk内で記述した。

```C#
	// Debug用に始点と終点を表示する
	Debug.DrawLine(startposition, endposition, Color.red);
```
によって、ベクトルが赤色で表示されます。
![rapture_20210114163101.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/4ca72c9a-c16c-05a0-3fd2-da6310398839.jpeg)

終点がお腹のあたりまでしかないので地面と接触する事がなくこのままではジャンプできません。
なので終点を0.3fから0.53fあたりに変更するとこんな感じで足元まで線が伸びてくれるようになりました。

![rapture_20210114164443.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542570/62e9d814-5533-bdf8-0f40-6c355dc3860f.jpeg)

以上の処理を行う事で空中での連続ジャンプを禁止して、地面と接触した時のみジャンプするようにできました。

[次回](https://qiita.com/tokoroten_346/items/46687bb71bd1e0f2dfd9)は敵キャラを作っていきます。