目次
前回のチュートリアル
スクリプトの作成
「Scripts」フォルダを右クリックして「Create>C# Script」を選択して
「PlatformController」と「RaycastController」を作成します
そして、それぞれのスクリプトファイルを開いて下記のコードを貼り付けて保存します
Player.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof (Controller2D))] public class Player : MonoBehaviour { public float jumpHeight = 4; public float timeToJumpApex = .4f; float accelerationTimeAirborne = .2f; float accelerationTimeGrounded = .1f; float moveSpeed = 6; float gravity; float jumpVelocity; Vector3 velocity; float velocityXSmoothing; Controller2D controller; void Start() { controller = GetComponent<Controller2D> (); gravity = -(2 * jumpHeight) / Mathf.Pow (timeToJumpApex, 2); jumpVelocity = Mathf.Abs(gravity) * timeToJumpApex; print ("Gravity: " + gravity + " Jump Velocity: " + jumpVelocity); } void Update() { if (controller.collisions.above || controller.collisions.below) { velocity.y = 0; } Vector2 input = new Vector2 (Input.GetAxisRaw ("Horizontal"), Input.GetAxisRaw ("Vertical")); if (Input.GetKeyDown (KeyCode.Space) && controller.collisions.below) { velocity.y = jumpVelocity; } float targetVelocityX = input.x * moveSpeed; velocity.x = Mathf.SmoothDamp (velocity.x, targetVelocityX, ref velocityXSmoothing, (controller.collisions.below)?accelerationTimeGrounded:accelerationTimeAirborne); velocity.y += gravity * Time.deltaTime; controller.Move (velocity * Time.deltaTime); } }
Controller2D.cs
using UnityEngine; using System.Collections; public class Controller2D : RaycastController { float maxClimbAngle = 80; float maxDescendAngle = 80; public CollisionInfo collisions; public override void Start() { base.Start (); } public void Move(Vector3 velocity, bool standingOnPlatform = false) { UpdateRaycastOrigins (); collisions.Reset (); collisions.velocityOld = velocity; if (velocity.y < 0) { DescendSlope(ref velocity); } if (velocity.x != 0) { HorizontalCollisions (ref velocity); } if (velocity.y != 0) { VerticalCollisions (ref velocity); } transform.Translate (velocity); if (standingOnPlatform) { collisions.below = true; } } void HorizontalCollisions(ref Vector3 velocity) { float directionX = Mathf.Sign (velocity.x); float rayLength = Mathf.Abs (velocity.x) + skinWidth; for (int i = 0; i < horizontalRayCount; i ++) { Vector2 rayOrigin = (directionX == -1)?raycastOrigins.bottomLeft:raycastOrigins.bottomRight; rayOrigin += Vector2.up * (horizontalRaySpacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.right * directionX, rayLength, collisionMask); Debug.DrawRay(rayOrigin, Vector2.right * directionX * rayLength,Color.red); if (hit) { if (hit.distance == 0) { continue; } float slopeAngle = Vector2.Angle(hit.normal, Vector2.up); if (i == 0 && slopeAngle <= maxClimbAngle) { if (collisions.descendingSlope) { collisions.descendingSlope = false; velocity = collisions.velocityOld; } float distanceToSlopeStart = 0; if (slopeAngle != collisions.slopeAngleOld) { distanceToSlopeStart = hit.distance-skinWidth; velocity.x -= distanceToSlopeStart * directionX; } ClimbSlope(ref velocity, slopeAngle); velocity.x += distanceToSlopeStart * directionX; } if (!collisions.climbingSlope || slopeAngle > maxClimbAngle) { velocity.x = (hit.distance - skinWidth) * directionX; rayLength = hit.distance; if (collisions.climbingSlope) { velocity.y = Mathf.Tan(collisions.slopeAngle * Mathf.Deg2Rad) * Mathf.Abs(velocity.x); } collisions.left = directionX == -1; collisions.right = directionX == 1; } } } } void VerticalCollisions(ref Vector3 velocity) { float directionY = Mathf.Sign (velocity.y); float rayLength = Mathf.Abs (velocity.y) + skinWidth; for (int i = 0; i < verticalRayCount; i ++) { Vector2 rayOrigin = (directionY == -1)?raycastOrigins.bottomLeft:raycastOrigins.topLeft; rayOrigin += Vector2.right * (verticalRaySpacing * i + velocity.x); RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask); Debug.DrawRay(rayOrigin, Vector2.up * directionY * rayLength,Color.red); if (hit) { velocity.y = (hit.distance - skinWidth) * directionY; rayLength = hit.distance; if (collisions.climbingSlope) { velocity.x = velocity.y / Mathf.Tan(collisions.slopeAngle * Mathf.Deg2Rad) * Mathf.Sign(velocity.x); } collisions.below = directionY == -1; collisions.above = directionY == 1; } } if (collisions.climbingSlope) { float directionX = Mathf.Sign(velocity.x); rayLength = Mathf.Abs(velocity.x) + skinWidth; Vector2 rayOrigin = ((directionX == -1)?raycastOrigins.bottomLeft:raycastOrigins.bottomRight) + Vector2.up * velocity.y; RaycastHit2D hit = Physics2D.Raycast(rayOrigin,Vector2.right * directionX,rayLength,collisionMask); if (hit) { float slopeAngle = Vector2.Angle(hit.normal,Vector2.up); if (slopeAngle != collisions.slopeAngle) { velocity.x = (hit.distance - skinWidth) * directionX; collisions.slopeAngle = slopeAngle; } } } } void ClimbSlope(ref Vector3 velocity, float slopeAngle) { float moveDistance = Mathf.Abs (velocity.x); float climbVelocityY = Mathf.Sin (slopeAngle * Mathf.Deg2Rad) * moveDistance; if (velocity.y <= climbVelocityY) { velocity.y = climbVelocityY; velocity.x = Mathf.Cos (slopeAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign (velocity.x); collisions.below = true; collisions.climbingSlope = true; collisions.slopeAngle = slopeAngle; } } void DescendSlope(ref Vector3 velocity) { float directionX = Mathf.Sign (velocity.x); Vector2 rayOrigin = (directionX == -1) ? raycastOrigins.bottomRight : raycastOrigins.bottomLeft; RaycastHit2D hit = Physics2D.Raycast (rayOrigin, -Vector2.up, Mathf.Infinity, collisionMask); if (hit) { float slopeAngle = Vector2.Angle(hit.normal, Vector2.up); if (slopeAngle != 0 && slopeAngle <= maxDescendAngle) { if (Mathf.Sign(hit.normal.x) == directionX) { if (hit.distance - skinWidth <= Mathf.Tan(slopeAngle * Mathf.Deg2Rad) * Mathf.Abs(velocity.x)) { float moveDistance = Mathf.Abs(velocity.x); float descendVelocityY = Mathf.Sin (slopeAngle * Mathf.Deg2Rad) * moveDistance; velocity.x = Mathf.Cos (slopeAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign (velocity.x); velocity.y -= descendVelocityY; collisions.slopeAngle = slopeAngle; collisions.descendingSlope = true; collisions.below = true; } } } } } public struct CollisionInfo { public bool above, below; public bool left, right; public bool climbingSlope; public bool descendingSlope; public float slopeAngle, slopeAngleOld; public Vector3 velocityOld; public void Reset() { above = below = false; left = right = false; climbingSlope = false; descendingSlope = false; slopeAngleOld = slopeAngle; slopeAngle = 0; } } }
PlatformController.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; public class PlatformController : RaycastController { public LayerMask passengerMask; public Vector3 move; List<PassengerMovement> passengerMovement; Dictionary<Transform,Controller2D> passengerDictionary = new Dictionary<Transform, Controller2D>(); public override void Start () { base.Start (); } void Update () { UpdateRaycastOrigins (); Vector3 velocity = move * Time.deltaTime; CalculatePassengerMovement(velocity); MovePassengers (true); transform.Translate (velocity); MovePassengers (false); } void MovePassengers(bool beforeMovePlatform) { foreach (PassengerMovement passenger in passengerMovement) { if (!passengerDictionary.ContainsKey(passenger.transform)) { passengerDictionary.Add(passenger.transform,passenger.transform.GetComponent<Controller2D>()); } if (passenger.moveBeforePlatform == beforeMovePlatform) { passengerDictionary[passenger.transform].Move(passenger.velocity, passenger.standingOnPlatform); } } } void CalculatePassengerMovement(Vector3 velocity) { HashSet<Transform> movedPassengers = new HashSet<Transform> (); passengerMovement = new List<PassengerMovement> (); float directionX = Mathf.Sign (velocity.x); float directionY = Mathf.Sign (velocity.y); // Vertically moving platform if (velocity.y != 0) { float rayLength = Mathf.Abs (velocity.y) + skinWidth; for (int i = 0; i < verticalRayCount; i ++) { Vector2 rayOrigin = (directionY == -1)?raycastOrigins.bottomLeft:raycastOrigins.topLeft; rayOrigin += Vector2.right * (verticalRaySpacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, passengerMask); if (hit) { if (!movedPassengers.Contains(hit.transform)) { movedPassengers.Add(hit.transform); float pushX = (directionY == 1)?velocity.x:0; float pushY = velocity.y - (hit.distance - skinWidth) * directionY; passengerMovement.Add(new PassengerMovement(hit.transform,new Vector3(pushX,pushY), directionY == 1, true)); } } } } // Horizontally moving platform if (velocity.x != 0) { float rayLength = Mathf.Abs (velocity.x) + skinWidth; for (int i = 0; i < horizontalRayCount; i ++) { Vector2 rayOrigin = (directionX == -1)?raycastOrigins.bottomLeft:raycastOrigins.bottomRight; rayOrigin += Vector2.up * (horizontalRaySpacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.right * directionX, rayLength, passengerMask); if (hit) { if (!movedPassengers.Contains(hit.transform)) { movedPassengers.Add(hit.transform); float pushX = velocity.x - (hit.distance - skinWidth) * directionX; float pushY = -skinWidth; passengerMovement.Add(new PassengerMovement(hit.transform,new Vector3(pushX,pushY), false, true)); } } } } // Passenger on top of a horizontally or downward moving platform if (directionY == -1 || velocity.y == 0 && velocity.x != 0) { float rayLength = skinWidth * 2; for (int i = 0; i < verticalRayCount; i ++) { Vector2 rayOrigin = raycastOrigins.topLeft + Vector2.right * (verticalRaySpacing * i); RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up, rayLength, passengerMask); if (hit) { if (!movedPassengers.Contains(hit.transform)) { movedPassengers.Add(hit.transform); float pushX = velocity.x; float pushY = velocity.y; passengerMovement.Add(new PassengerMovement(hit.transform,new Vector3(pushX,pushY), true, false)); } } } } } struct PassengerMovement { public Transform transform; public Vector3 velocity; public bool standingOnPlatform; public bool moveBeforePlatform; public PassengerMovement(Transform _transform, Vector3 _velocity, bool _standingOnPlatform, bool _moveBeforePlatform) { transform = _transform; velocity = _velocity; standingOnPlatform = _standingOnPlatform; moveBeforePlatform = _moveBeforePlatform; } } }
RaycastController.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof (BoxCollider2D))] public class RaycastController : MonoBehaviour { public LayerMask collisionMask; public const float skinWidth = .015f; public int horizontalRayCount = 4; public int verticalRayCount = 4; [HideInInspector] public float horizontalRaySpacing; [HideInInspector] public float verticalRaySpacing; [HideInInspector] public BoxCollider2D collider; public RaycastOrigins raycastOrigins; public virtual void Start() { collider = GetComponent<BoxCollider2D> (); CalculateRaySpacing (); } public void UpdateRaycastOrigins() { Bounds bounds = collider.bounds; bounds.Expand (skinWidth * -2); raycastOrigins.bottomLeft = new Vector2 (bounds.min.x, bounds.min.y); raycastOrigins.bottomRight = new Vector2 (bounds.max.x, bounds.min.y); raycastOrigins.topLeft = new Vector2 (bounds.min.x, bounds.max.y); raycastOrigins.topRight = new Vector2 (bounds.max.x, bounds.max.y); } public void CalculateRaySpacing() { Bounds bounds = collider.bounds; bounds.Expand (skinWidth * -2); horizontalRayCount = Mathf.Clamp (horizontalRayCount, 2, int.MaxValue); verticalRayCount = Mathf.Clamp (verticalRayCount, 2, int.MaxValue); horizontalRaySpacing = bounds.size.y / (horizontalRayCount - 1); verticalRaySpacing = bounds.size.x / (verticalRayCount - 1); } public struct RaycastOrigins { public Vector2 topLeft, topRight; public Vector2 bottomLeft, bottomRight; } }
動く床の配置
「Quad (1)」「Quad (2)」は不要になったので、
それぞれ選択して Delete を押して削除します
「Quad」を選択した状態で Ctrl + D を押します
複製した「Quad (1)」を選択した状態でパラメータを下記のように設定します
- 「Position」の入力欄に左から順に「0」「-1.25」「0」と入力
- 「Rotation」の入力欄に左から順に「0」「0」「0」と入力
- 「Scale」の入力欄に左から順に「2」「0.35」「1」と入力
「PlatformController」スクリプトを「Quad (1)」にドラッグします
「Quad (1)」を選択して「Move」に自由にパラメータを入力します
- 「X」に「1」と入力すると右に移動します
- 「X」に「-1」と入力すると左に移動します
- 「Y」に「1」と入力すると上に移動します
- 「Y」に「-1」と入力すると下に移動します
「Passenger Mask」のプルダウンメニューから「Player」を選択します
これで Unity を再生すると、
プレイヤーが動く床に押されたり乗ったりできることが確認できます