🐡

【Unity】キャラクターごとに上下に通り抜け可能な足場を作る

2021/06/29に公開

概要

前回、比較的楽なやり方で上下両方から通り抜け可能な足場を作りました。
しかし、このやり方は複数のキャラクターを操作したいときには不適切です。

今回はキャラクターごとにすり抜け可能な足場を作ってみようと思います。

要点

・すり抜け状態のレイヤーを追加する
・足場をすり抜けるとき、該当キャラクターのレイヤーを一時的に変更する

実装手順

前回の記事からの続きとなります。
本来は接地判定処理を実装してからの方が良いのですが面倒なので簡易的なやり方で行きたいと思います。

1. レイヤーを追加する

  • 足場通り抜け用のレイヤーを追加する
    • 名前はPlayerJump等などにする
  • Layer Collision Matrixから接触判定をOFFにする
    • 今回の場合はPlayerJumpPlatformのところをOFF

2. 足場のコリジョンを変更する

前回は別途足場用のコリジョンを追加しましたがここを変更します。

  • Platformオブジェクトにコリジョンを追加する
    • 今回はこっちが足場のコリジョンになる
  • Platformオブジェクト直下のCollisionから足場用のコリジョンを除去する
    • トリガーはそのままにしておく


3. トリガー侵入/脱出時にプレイヤーのレイヤー設定を行う

下側から通り抜けできるようにするため、トリガーを使用した処理を修正します。

  • トリガーに侵入したらプレイヤーのレイヤーをPlayerJumpにする
  • トリガーから脱出したらプレイヤーのレイヤーをPlayerにする
Platform.cs
private void OnTriggerEnter(Collider col) {
	if (col.gameObject.tag == "Player") {
		col.gameObject.layer = LayerMask.NameToLayer("PlayerJump");
	}
}

private void OnTriggerExit(Collider col) {
	if (col.gameObject.tag == "Player") {
		col.gameObject.layer = LayerMask.NameToLayer("Player");
	}
}

4. プレイヤーが降りる操作をしたときにレイヤー設定を行う

上側から通り抜けできるようにするため、プレイヤーが降りる操作をした時の処理を修正します。

  • 降りる操作をした時、レイヤーを変更する
Player.cs
public void DescendPlatform() {
	// しゃがみ状態でジャンプ入力時に降りる
	if (isCrouch && isJump) {
		var radius = 1.0f;
		var platformLayerNo = LayerMask.NameToLayer("Platform");
		var platforms = Physics.OverlapSphere(transform.position + groundOffset, radius, 1 << platformLayerNo)
								.Select(platform => platform.GetComponent<Platform>())
								.OrderBy(platform => platform?.transform.position.x)
								.ToList();

		if (!platforms.Any()) { return; }

		gameObject.layer = LayerMask.NameToLayer("PlayerJump");
	}
}

注意点

キャラクター別のやり方ですが、思っていたよりもお手軽に実装できるのではないかと思います。
注意点としては以下に点でしょうか。

  • トリガーでしかレイヤー制御をしていないため、ジャンプ中の時や地面に接地していない等の条件を追加すると良いかも
  • レイヤーを増やす手法のため、レイヤー数不足には注意が必要

ソースコード

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

public class Player : MonoBehaviour {

	[SerializeField] private Rigidbody rbody;
	[SerializeField] private float moveSpeed;
	[SerializeField] private float jumpForce;
	[SerializeField] private Vector3 groundOffset;

	private Vector3 moveDirection;
	private bool isJump;
	private bool isCrouch;

	/// <summary>
	/// 更新処理
	/// </summary>
	private void Update() {
		moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, 0);
		isJump = Input.GetButtonDown("Jump");
		isCrouch = Input.GetAxis("Vertical") <= -0.1;		// 下ボタン入力時

		Jump();
		DescendPlatform();
	}

	/// <summary>
	/// 更新処理(一定間隔)
	/// </summary>
	private void FixedUpdate() {
		transform.position += moveDirection * moveSpeed * Time.deltaTime;
	}

	/// <summary>
	/// ジャンプ
	/// </summary>
	private void Jump() {
		if (isJump && !isCrouch) {
			rbody.AddForce(new Vector3(0, jumpForce, 0), ForceMode.Acceleration);
		}
	}

	/// <summary>
	/// 足場から降りる
	/// </summary>
	public void DescendPlatform() {
		// しゃがみ状態でジャンプ入力時に降りる
		if (isCrouch && isJump) {
			var radius = 1.0f;
			var platformLayerNo = LayerMask.NameToLayer("Platform");
			var platforms = Physics.OverlapSphere(transform.position + groundOffset, radius, 1 << platformLayerNo)
									.Select(platform => platform.GetComponent<Platform>())
									.OrderBy(platform => platform?.transform.position.x)
									.ToList();

			if (!platforms.Any()) { return; }

			gameObject.layer = LayerMask.NameToLayer("PlayerJump");
		}
	}
}
Platform.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Platform : MonoBehaviour {
	[SerializeField] private BoxCollider boxCollider;		// 足場用のコリジョン

	/// <summary>
	/// 起動前初期化
	/// </summary>
	private void Start() {
		boxCollider.enabled = true;
	}

	/// <summary>
	/// トリガー判定(Enter)
	/// </summary>
	/// <param name="col">コリジョン</param>
	private void OnTriggerEnter(Collider col) {
		if (col.gameObject.tag == "Player") {
			col.gameObject.layer = LayerMask.NameToLayer("PlayerJump");
		}
	}

	/// <summary>
	/// トリガー判定(Exit)
	/// </summary>
	/// <param name="col">コリジョン</param>
	private void OnTriggerExit(Collider col) {
		if (col.gameObject.tag == "Player") {
			col.gameObject.layer = LayerMask.NameToLayer("Player");
		}
	}
}

参考

Discussion