Unity2Dで地面に着地したかどうかを調べる方法

#サンプル
今回作成したサンプルは[こちら](https://github.com/KyoheiOkawa/UnityJampSample) からダウンロードできます。

今回使用したUnityのバージョンは2017.4.1f1です。

####サンプルの操作方法
スペースキー:ジャンプ
左、右の矢印キー：左右の移動

#ジャンプの処理を書く上で出てくる問題点
プレイヤーがジャンプするゲームを作る際に以下のような問題に遭遇すると思います。

+ 無段階にジャンプできてしまう
+ 床に着地したという判定はできたが側面にくっ付いた状態で無段階にジャンプできてしまう

私もゲームを作り始めたばかりの頃は上の問題に悩まされていました。
Unityの接地判定のとり方を聞かれ、
せっかくの機会だと思ったので記事にしようと思い書きました。
技術的な記事を書くのが初めてで分かりにくいところもあると思いますが、
この問題で悩んでいる方の手助けになることができれば幸いです。

#サンプルコード

```Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class Player : MonoBehaviour 
{
	Rigidbody2D rigid;

	/// 何段階ジャンプできるか
	[SerializeField]
	uint maxJumpSteps = 1;

	/// 現在何段階目のジャンプか
	uint jumpCount = 0;

	void Start()
	{
		rigid = GetComponent<Rigidbody2D> ();
	}
		
	void Update()
	{
		Move ();

		if (Input.GetKeyDown (KeyCode.Space))
			Jump ();
	}

	//テスト用に書いた移動処理なので参考にしないほうが良い
	void Move()
	{
		if (Input.GetKey (KeyCode.RightArrow))
			rigid.AddForce (Vector2.right * 3.0f);
		else if (Input.GetKey (KeyCode.LeftArrow))
			rigid.AddForce (Vector2.left * 3.0f);
		else
			rigid.velocity = new Vector2 (0.0f,rigid.velocity.y);
	}

	//ジャンプ処理
	void Jump()
	{
		//最大の段階のジャンプを超えてたらジャンプしない
		if (jumpCount >= maxJumpSteps)
			return;

		//ジャンプの力を加える
		rigid.AddForce (Vector2.up * 5.0f, ForceMode2D.Impulse);

		//ジャンプの段階を１段階あげる
		jumpCount++;
	}

	/// <summary>
	/// 自分が何かと接触した時に自動的に呼ばれる関数
	/// </summary>
	/// <param name="other">接触しているオブジェクトの情報</param>
	void OnCollisionEnter2D(Collision2D other)
	{
		//自分があるオブジェクトと接触しているポイントを一つづつ調べる
		foreach (var contact in other.contacts) 
		{
			//自分から接触ポイントへのベクトル
			Vector2 dir = contact.point - (Vector2)transform.position;

			//接触しているゲームオブジェクトの下向きのベクトル
			Vector2 contactObjectDown = -contact.collider.gameObject.transform.up;

			//接触しているオブジェクトの下向きのベクトルと自身から接触しているポイントへのベクトルの
			//角度が１０度未満であった場合にジャンプの段階数のリセットする
			if (Vector2.Angle (contactObjectDown, dir) < 10.0f)
				jumpCount = 0;

			break;
		}
	}
}

```

#解説
今回は接地判定についての記事なので移動処理については解説しません。
~~移動処理は適当に書いているので...~~

まずJumpメソッドです

```Player.cs
	void Jump()
	{
		//最大の段階のジャンプを超えてたらジャンプしない
		if (jumpCount >= maxJumpSteps)
			return;

		//ジャンプの力を加える
		rigid.AddForce (Vector2.up * 5.0f, ForceMode2D.Impulse);

		//ジャンプの段階を１段階あげる
		jumpCount++;
	}
```

多段階ジャンプにも対応できるようにmaxJumpStepsで何段階ジャンプできるかを定義しています。
そして、１回ジャンプするごとにjumpCountをカウントアップしていきます。
jumpCountがmaxJumpSteps以上になったら処理を打ち切ってジャンプしないようにしています。

余談ですがAddForceメソッドの第二引数のForceMode2D.Impulseは一度に力を加える時に指定し、
継続的に力を加える場合はForceMode2D.Forceを使用します。(第二引数を省略するとデフォルトでForceMode2D.Forceになります)




今回一番重要になってくる部分がOnCollisionEnterの部分です

```Player.cs
	/// <summary>
	/// 自分が何かと接触した時に自動的に呼ばれる関数
	/// </summary>
	/// <param name="other">接触しているオブジェクトの情報</param>
	void OnCollisionEnter2D(Collision2D other)
	{
		//自分があるオブジェクトと接触しているポイントを一つづつ調べる
		foreach (var contact in other.contacts) 
		{
			//自分から接触ポイントへのベクトル
			Vector2 dir = contact.point - (Vector2)transform.position;

			//接触しているゲームオブジェクトの下向きのベクトル
			Vector2 contactObjectDown = -contact.collider.gameObject.transform.up;

			//接触しているオブジェクトの下向きのベクトルと自身から接触しているポイントへのベクトルの
			//角度が１０度未満であった場合にジャンプの段階数のリセットする
			if (Vector2.Angle (contactObjectDown, dir) < 10.0f)
				jumpCount = 0;

			break;
		}
	}
```

foreach(var contact in other.contacts)はotherと接触している場所の数だけ繰り返します。
other.contactsの情報を一つづつ取り出しcontactに入れてループを回します。
基本的に接触する場所は一箇所しかないと思いますが一応確認していきます。

コードだけだと分かりにくいので、変数との関係を図にしてみました。
![slide1.png](https://qiita-image-store.s3.amazonaws.com/0/188476/13054c47-276b-1b4a-cf1e-a6ded75e8a25.png)

床の上むきのベクトルを反対にして、dirとの角度が0に近いかどうかで着地しているか判定しています。
着地していると判定されると、jumpCountが0にリセットされて再び多段階ジャンプができるようになります。

この判定のとり方だと床の側面に接触した時は着地していると判定されなくなります。
(ステージに床を配置するときSceneビューでY軸が上を向くように配置してください)
また、床を斜めにおいた場合でも着地しているかの判定を取ることができます。

今回は2Dでサンプルを作成しましたが、3Dでも同じ方法で判定を取れると思います。

#最後に
分かりずらいことがあったらコメントください！

初めて記事を書いてみて、このくらいの内容でも結構まとめるのに時間がかかる事にびっくりしました。
普段、記事に助けられているので記事を書いてくださっている方にはとても感謝しています。

宣伝になりますがAppStoreにCocos2d-x,Unity,OpenGLESで作ったゲームをリリースしているのでぜひ遊んでみてください！
→[AppStore](https://itunes.apple.com/jp/developer/kyohei-okawa/id1209907691)