【Unity/2D】タッチ移動版CharacterController2D

CharacterController2D(別記事参照のこと)を使用時に、タッチした位置に移動させるパターン

別記事で作ったCharacterController2Dをベースに、タッチ移動を実装したパターンの奴。
タッチした位置まで一定速度で歩きます(Y座標は無視)。
画面押しっぱなしで走り続ける事も可能。

キーボード or 画面ボタン式の元記事は以下。

サンプル動画

40秒程のテスト動画。
ジャンプはZキーで適当なタイミングにやっております。

*このアセットの坂道には、当たり判定が滑らかに付かないので、適当なチップと差し替え。

使用手順

  1. プレイヤーの当たり判定を設定、回転を固定。
  2. プレイヤーのピボット(原点)を足元に設定。
  3. 地面にGroundタグを設定。
  4. Playerスクリプトに次項のコードをコピペ。

元記事の使用手順の項

【コード】GameManager

インプット部分のサンプル。

  • スクリーン座標をワールド座標に変換して、Playerの移動用メソッドへXだけ渡す。
  • ドラッグも取得するので、Input.GetMouseButtonで判定する。

//このサンプルでは使わなかったので、一応コメントアウト。
//using System.Collections;
//using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{

	public static GameManager Instance = null;


//インスペクターからプレイヤーを紐付けしておく。
	[SerializeField]
	Player player;


	Camera mainCamera;
	Vector3 touchPosition;



	void Awake()
	{
		if (Instance == null)
			Instance = this;
		else if (Instance != this)
			Destroy(gameObject);    

		DontDestroyOnLoad(gameObject);



		mainCamera = Camera.main;
	}


	void Update()
	{
		CheckInput();
	}


	void CheckInput()
	{
		if (Input.GetMouseButton(0)) {
			touchPosition = Input.mousePosition;

			player.MoveToTouchPosition(mainCamera.ScreenToWorldPoint(touchPosition).x);
		}
	}


}

【コード】Player

変更箇所

元記事のコードからの変更箇所。

  • 横移動量関連を削除。
  • 余り重要でないコメントを削除。
  • 横移動処理部分をタッチ式に変更。
  • タッチ移動処理を追加。
  • 移動関連変数を追加。


//一応コメントアウトしたけど、リスト等を扱う場合は戻しておいてください。
using System.Collections;
//using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{

//各コンポーネントをキャッシュしておく。インスペクターから紐付けすればAwake内の取得部分を消しても良い。
	public Transform tf;

	[SerializeField]
	Rigidbody2D rb2d;

	float moveSpeed = 10.0f;

	static float HorizontalMoveSpeedLimit = 1.0f;
	static float VerticalMoveSpeedLimit = 1.0f;

//【任意の値】接地判定を坂道に対応させる為にズラす値(大きい程、深い角度に対応)。
	static readonly float Tolerance = 0.08f;

//【要調整】接地判定をプレイヤーの原点からズラす値(足元が原点の場合は、radiusの値 - Tolerance)。
	Vector2 offset = new Vector2(0, 0.25f - Tolerance);

//【要調整】接地判定の円の半径。プレイヤーより一回り小さい(壁を床と判定しない為)値。
	float radius = 0.25f;

//【任意の値】ジャンプの高さ(ユニット単位)。
	static readonly float JumpHeight = 3.0f;
	static readonly float HighJumpHeight = 5.0f;

//【任意の値】歩行可能な傾斜の制限(度単位)。歩行不可能な角度の坂は重力によって滑り落ちる。
	float slopeLimit = 45.0f;



	Collider2D[] results = new Collider2D[1];
	ContactPoint2D[] groundContacts = new ContactPoint2D[10];
	LayerMask groundLayerMask;
	ContactFilter2D groundContactFilter;
	static readonly string GroundTag = "Ground";
	bool isGrounded;
	bool isHitGround;
	bool isJumpHappened;


//プレイヤーが左を向いた時の角度(使用する画像によっては左右逆かも)。
	Quaternion leftRotation = Quaternion.Euler(0, 180, 0);
//プレイヤーが右を向いた時の角度。
	Quaternion rightRotation = Quaternion.Euler(0, 0, 0);

	Vector2 velocity;
	float verticalMovement = 0;

//ジャンプの初期加速度。平方根の計算は負荷が掛かるので、ジャンプ毎に計算せず、宣言時に結果を保持しておく。
	float jumpInitialVelocity = Mathf.Sqrt(2 * DefaultGravityAbs * JumpHeight);
	float highJumpInitialVelocity = Mathf.Sqrt(2 * DefaultGravityAbs * HighJumpHeight);

	static readonly float DefaultGravityAbs = 9.81f;
	float jumpVelocityModifier = DefaultGravityAbs * 0.01f;
	Vector2 groundNormal;
	Vector2 projectOnPlane;
	int contactCount;

	Vector2 previousPosition;
	Vector2 currentPosition;
	Vector2 nextMovement;

	Vector2 tempNormal;



//【追加箇所・始】
//移動中フラグ。
	bool isMoving;

//移動処理関連。
	float currentPositionX;
	float nextPositionX;
	float moveTargetPositionX;
	Coroutine move;

//毎回newしないで、キャッシュしておく。
	static readonly WaitForFixedUpdate FixedWait = new WaitForFixedUpdate();
//1フレームでの移動量が、この値以下だと移動終了。
	static readonly float MoveEndThreshold = 0.01f;

//SmoothDampし終わるまでの大体の時間(移動速度の最低値として考えれば良いかも)。
	float smoothTime = 0.1f;
//移動速度(= SmoothDampでの移動速度のリミット)。
	float velocityX;
//【追加箇所・終】



	void Awake()
	{
		tf = transform;
		rb2d = GetComponent<Rigidbody2D>();



//地面のレイヤーを設定。
		groundLayerMask = 1 << LayerMask.NameToLayer(GroundTag);
		groundContactFilter.SetLayerMask(1 << LayerMask.NameToLayer(GroundTag));

	}


//【PC用】ジャンプの入力取得。
	void Update()
	{
		if (Input.GetKeyDown("z")) {
			Jump();
		}
	}



	void FixedUpdate()
	{

		contactCount = rb2d.GetContacts(groundContactFilter, groundContacts);

		if (0 < contactCount) {
			isHitGround = false;
			groundNormal = Vector2.up;

			for (int i = 0; i < contactCount; i++) {
				tempNormal = groundContacts[i].normal;

				if (Vector2.Angle(Vector2.up, tempNormal) <= slopeLimit) {
					isHitGround = true;
					groundNormal = tempNormal;
				}
			}
		}



		if (isJumpHappened) {
			isJumpHappened = false;
		} else {
			isGrounded = 0 < Physics2D.OverlapCircleNonAlloc(rb2d.position + offset, radius, results, groundLayerMask);
		}


		if (!isGrounded || !isHitGround) {
			verticalMovement += Physics2D.gravity.y * Time.fixedDeltaTime;
			groundNormal = Vector2.up;
		} else {
			verticalMovement = 0;
		}



//【変更箇所・始】
//地面が平らな場合は通常通りに移動。傾いている場合は、坂道に沿ったベクトルに変える。
		if (isMoving) {
			if (groundNormal == Vector2.up) {
				nextMovement = Vector2.right * Mathf.Clamp(nextPositionX - currentPositionX, -HorizontalMoveSpeedLimit, HorizontalMoveSpeedLimit);
			} else {
	//地面とのノーマル(垂直なベクトル)を90度傾けた方向に向かって、横の移動を適用。
				projectOnPlane.Set(groundNormal.y, -groundNormal.x);
				nextMovement = projectOnPlane * Mathf.Clamp(nextPositionX - currentPositionX, -HorizontalMoveSpeedLimit, HorizontalMoveSpeedLimit);
			}
		}
//【変更箇所・終】


		nextMovement.y += verticalMovement * Time.fixedDeltaTime;
		nextMovement.y = Mathf.Clamp(nextMovement.y, -VerticalMoveSpeedLimit, VerticalMoveSpeedLimit);


		previousPosition = rb2d.position;
		currentPosition = rb2d.position + nextMovement;
		velocity = (currentPosition - previousPosition);


		rb2d.MovePosition(currentPosition);
		nextMovement = Vector2.zero;



		if (velocity.x != 0) {
			if (velocity.x < 0) {
				tf.rotation = leftRotation;
			} else {
				tf.rotation = rightRotation;
			}
		}
	}


//【共通】スマホ時には、ジャンプボタンへ、リスナー登録。
	public void Jump()
	{
		if (!isGrounded)
			return;

		isGrounded = false;
		isHitGround = false;
		isJumpHappened = true;

//JumpHeight = 1で実行すると、Y = 0.9996482に到達する精度のジャンプ。
		verticalMovement = jumpInitialVelocity + jumpVelocityModifier;
	}



	public void MoveToTouchPosition(float x)
	{
//多重実行を防止。        
		if (move != null) {
			StopCoroutine(move);
		}

		moveTargetPositionX = x;
		move = StartCoroutine(Move());
	}

	IEnumerator Move()
	{
		if (!isMoving) {
			isMoving = true;
			velocityX = 0;
		}

//移動時に壁に引っ掛かった(nextPositionXのみが目的座標にある)場合、瞬間移動してしまうのを防ぐ。
		nextPositionX = rb2d.position.x;

		while (true) {
			currentPositionX = rb2d.position.x;
			nextPositionX = Mathf.SmoothDamp(nextPositionX, moveTargetPositionX, ref velocityX, smoothTime, moveSpeed, Time.fixedDeltaTime);

//一定スピード以下になると終了。
			if (-MoveEndThreshold <= velocityX && velocityX <= MoveEndThreshold) {
				isMoving = false;
				move = null;
				yield break;
			}


			yield return FixedWait;


//空中で壁に移動した時に、歩行時間分浮いてしまう問題への対処。
			if (-MoveEndThreshold <= (currentPositionX - rb2d.position.x) && (currentPositionX - rb2d.position.x) <= MoveEndThreshold) {
				isMoving = false;
				move = null;
				yield break;
			}
		}
	}
}

タイトルとURLをコピーしました