目次
前回のチュートリアル
スクリプトの更新
「Scripts」フォルダを右クリックして「Create>C# Script」を設定します
作成したスクリプトに「PlayerInput」と名前を付けます
そして、各スクリプトファイルを開いて下記のコードを貼り付けて保存します
CameraFollow.cs
using UnityEngine; using System.Collections; public class CameraFollow : MonoBehaviour { public Controller2D target; public float verticalOffset; public float lookAheadDstX; public float lookSmoothTimeX; public float verticalSmoothTime; public Vector2 focusAreaSize; FocusArea focusArea; float currentLookAheadX; float targetLookAheadX; float lookAheadDirX; float smoothLookVelocityX; float smoothVelocityY; bool lookAheadStopped; void Start() { focusArea = new FocusArea (target.collider.bounds, focusAreaSize); } void LateUpdate() { focusArea.Update (target.collider.bounds); Vector2 focusPosition = focusArea.centre + Vector2.up * verticalOffset; if (focusArea.velocity.x != 0) { lookAheadDirX = Mathf.Sign (focusArea.velocity.x); if (Mathf.Sign(target.playerInput.x) == Mathf.Sign(focusArea.velocity.x) && target.playerInput.x != 0) { lookAheadStopped = false; targetLookAheadX = lookAheadDirX * lookAheadDstX; } else { if (!lookAheadStopped) { lookAheadStopped = true; targetLookAheadX = currentLookAheadX + (lookAheadDirX * lookAheadDstX - currentLookAheadX)/4f; } } } currentLookAheadX = Mathf.SmoothDamp (currentLookAheadX, targetLookAheadX, ref smoothLookVelocityX, lookSmoothTimeX); focusPosition.y = Mathf.SmoothDamp (transform.position.y, focusPosition.y, ref smoothVelocityY, verticalSmoothTime); focusPosition += Vector2.right * currentLookAheadX; transform.position = (Vector3)focusPosition + Vector3.forward * -10; } void OnDrawGizmos() { Gizmos.color = new Color (1, 0, 0, .5f); Gizmos.DrawCube (focusArea.centre, focusAreaSize); } struct FocusArea { public Vector2 centre; public Vector2 velocity; float left,right; float top,bottom; public FocusArea(Bounds targetBounds, Vector2 size) { left = targetBounds.center.x - size.x/2; right = targetBounds.center.x + size.x/2; bottom = targetBounds.min.y; top = targetBounds.min.y + size.y; velocity = Vector2.zero; centre = new Vector2((left+right)/2,(top +bottom)/2); } public void Update(Bounds targetBounds) { float shiftX = 0; if (targetBounds.min.x < left) { shiftX = targetBounds.min.x - left; } else if (targetBounds.max.x > right) { shiftX = targetBounds.max.x - right; } left += shiftX; right += shiftX; float shiftY = 0; if (targetBounds.min.y < bottom) { shiftY = targetBounds.min.y - bottom; } else if (targetBounds.max.y > top) { shiftY = targetBounds.max.y - top; } top += shiftY; bottom += shiftY; centre = new Vector2((left+right)/2,(top +bottom)/2); velocity = new Vector2 (shiftX, shiftY); } } }
Controller2D.cs
using UnityEngine; using System.Collections; public class Controller2D : RaycastController { public float maxSlopeAngle = 80; public CollisionInfo collisions; [HideInInspector] public Vector2 playerInput; public override void Start() { base.Start (); collisions.faceDir = 1; } public void Move(Vector2 moveAmount, bool standingOnPlatform) { Move (moveAmount, Vector2.zero, standingOnPlatform); } public void Move(Vector2 moveAmount, Vector2 input, bool standingOnPlatform = false) { UpdateRaycastOrigins (); collisions.Reset (); collisions.moveAmountOld = moveAmount; playerInput = input; if (moveAmount.y < 0) { DescendSlope(ref moveAmount); } if (moveAmount.x != 0) { collisions.faceDir = (int)Mathf.Sign(moveAmount.x); } HorizontalCollisions (ref moveAmount); if (moveAmount.y != 0) { VerticalCollisions (ref moveAmount); } transform.Translate (moveAmount); if (standingOnPlatform) { collisions.below = true; } } void HorizontalCollisions(ref Vector2 moveAmount) { float directionX = collisions.faceDir; float rayLength = Mathf.Abs (moveAmount.x) + skinWidth; if (Mathf.Abs(moveAmount.x) < skinWidth) { rayLength = 2*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,Color.red); if (hit) { if (hit.distance == 0) { continue; } float slopeAngle = Vector2.Angle(hit.normal, Vector2.up); if (i == 0 && slopeAngle <= maxSlopeAngle) { if (collisions.descendingSlope) { collisions.descendingSlope = false; moveAmount = collisions.moveAmountOld; } float distanceToSlopeStart = 0; if (slopeAngle != collisions.slopeAngleOld) { distanceToSlopeStart = hit.distance-skinWidth; moveAmount.x -= distanceToSlopeStart * directionX; } ClimbSlope(ref moveAmount, slopeAngle, hit.normal); moveAmount.x += distanceToSlopeStart * directionX; } if (!collisions.climbingSlope || slopeAngle > maxSlopeAngle) { moveAmount.x = (hit.distance - skinWidth) * directionX; rayLength = hit.distance; if (collisions.climbingSlope) { moveAmount.y = Mathf.Tan(collisions.slopeAngle * Mathf.Deg2Rad) * Mathf.Abs(moveAmount.x); } collisions.left = directionX == -1; collisions.right = directionX == 1; } } } } void VerticalCollisions(ref Vector2 moveAmount) { float directionY = Mathf.Sign (moveAmount.y); float rayLength = Mathf.Abs (moveAmount.y) + skinWidth; for (int i = 0; i < verticalRayCount; i ++) { Vector2 rayOrigin = (directionY == -1)?raycastOrigins.bottomLeft:raycastOrigins.topLeft; rayOrigin += Vector2.right * (verticalRaySpacing * i + moveAmount.x); RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask); Debug.DrawRay(rayOrigin, Vector2.up * directionY,Color.red); if (hit) { if (hit.collider.tag == "Through") { if (directionY == 1 || hit.distance == 0) { continue; } if (collisions.fallingThroughPlatform) { continue; } if (playerInput.y == -1) { collisions.fallingThroughPlatform = true; Invoke("ResetFallingThroughPlatform",.5f); continue; } } moveAmount.y = (hit.distance - skinWidth) * directionY; rayLength = hit.distance; if (collisions.climbingSlope) { moveAmount.x = moveAmount.y / Mathf.Tan(collisions.slopeAngle * Mathf.Deg2Rad) * Mathf.Sign(moveAmount.x); } collisions.below = directionY == -1; collisions.above = directionY == 1; } } if (collisions.climbingSlope) { float directionX = Mathf.Sign(moveAmount.x); rayLength = Mathf.Abs(moveAmount.x) + skinWidth; Vector2 rayOrigin = ((directionX == -1)?raycastOrigins.bottomLeft:raycastOrigins.bottomRight) + Vector2.up * moveAmount.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) { moveAmount.x = (hit.distance - skinWidth) * directionX; collisions.slopeAngle = slopeAngle; collisions.slopeNormal = hit.normal; } } } } void ClimbSlope(ref Vector2 moveAmount, float slopeAngle, Vector2 slopeNormal) { float moveDistance = Mathf.Abs (moveAmount.x); float climbmoveAmountY = Mathf.Sin (slopeAngle * Mathf.Deg2Rad) * moveDistance; if (moveAmount.y <= climbmoveAmountY) { moveAmount.y = climbmoveAmountY; moveAmount.x = Mathf.Cos (slopeAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign (moveAmount.x); collisions.below = true; collisions.climbingSlope = true; collisions.slopeAngle = slopeAngle; collisions.slopeNormal = slopeNormal; } } void DescendSlope(ref Vector2 moveAmount) { RaycastHit2D maxSlopeHitLeft = Physics2D.Raycast (raycastOrigins.bottomLeft, Vector2.down, Mathf.Abs (moveAmount.y) + skinWidth, collisionMask); RaycastHit2D maxSlopeHitRight = Physics2D.Raycast (raycastOrigins.bottomRight, Vector2.down, Mathf.Abs (moveAmount.y) + skinWidth, collisionMask); if (maxSlopeHitLeft ^ maxSlopeHitRight) { SlideDownMaxSlope (maxSlopeHitLeft, ref moveAmount); SlideDownMaxSlope (maxSlopeHitRight, ref moveAmount); } if (!collisions.slidingDownMaxSlope) { float directionX = Mathf.Sign (moveAmount.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 <= maxSlopeAngle) { if (Mathf.Sign (hit.normal.x) == directionX) { if (hit.distance - skinWidth <= Mathf.Tan (slopeAngle * Mathf.Deg2Rad) * Mathf.Abs (moveAmount.x)) { float moveDistance = Mathf.Abs (moveAmount.x); float descendmoveAmountY = Mathf.Sin (slopeAngle * Mathf.Deg2Rad) * moveDistance; moveAmount.x = Mathf.Cos (slopeAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign (moveAmount.x); moveAmount.y -= descendmoveAmountY; collisions.slopeAngle = slopeAngle; collisions.descendingSlope = true; collisions.below = true; collisions.slopeNormal = hit.normal; } } } } } } void SlideDownMaxSlope(RaycastHit2D hit, ref Vector2 moveAmount) { if (hit) { float slopeAngle = Vector2.Angle(hit.normal, Vector2.up); if (slopeAngle > maxSlopeAngle) { moveAmount.x = Mathf.Sign(hit.normal.x) * (Mathf.Abs (moveAmount.y) - hit.distance) / Mathf.Tan (slopeAngle * Mathf.Deg2Rad); collisions.slopeAngle = slopeAngle; collisions.slidingDownMaxSlope = true; collisions.slopeNormal = hit.normal; } } } void ResetFallingThroughPlatform() { collisions.fallingThroughPlatform = false; } public struct CollisionInfo { public bool above, below; public bool left, right; public bool climbingSlope; public bool descendingSlope; public bool slidingDownMaxSlope; public float slopeAngle, slopeAngleOld; public Vector2 slopeNormal; public Vector2 moveAmountOld; public int faceDir; public bool fallingThroughPlatform; public void Reset() { above = below = false; left = right = false; climbingSlope = false; descendingSlope = false; slidingDownMaxSlope = false; slopeNormal = Vector2.zero; slopeAngleOld = slopeAngle; slopeAngle = 0; } } }
PlatformController.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; public class PlatformController : RaycastController { public LayerMask passengerMask; public Vector3[] localWaypoints; Vector3[] globalWaypoints; public float speed; public bool cyclic; public float waitTime; [Range(0,2)] public float easeAmount; int fromWaypointIndex; float percentBetweenWaypoints; float nextMoveTime; List<PassengerMovement> passengerMovement; Dictionary<Transform,Controller2D> passengerDictionary = new Dictionary<Transform, Controller2D>(); public override void Start () { base.Start (); globalWaypoints = new Vector3[localWaypoints.Length]; for (int i =0; i < localWaypoints.Length; i++) { globalWaypoints[i] = localWaypoints[i] + transform.position; } } void Update () { UpdateRaycastOrigins (); Vector3 velocity = CalculatePlatformMovement(); CalculatePassengerMovement(velocity); MovePassengers (true); transform.Translate (velocity); MovePassengers (false); } float Ease(float x) { float a = easeAmount + 1; return Mathf.Pow(x,a) / (Mathf.Pow(x,a) + Mathf.Pow(1-x,a)); } Vector3 CalculatePlatformMovement() { if (Time.time < nextMoveTime) { return Vector3.zero; } fromWaypointIndex %= globalWaypoints.Length; int toWaypointIndex = (fromWaypointIndex + 1) % globalWaypoints.Length; float distanceBetweenWaypoints = Vector3.Distance (globalWaypoints [fromWaypointIndex], globalWaypoints [toWaypointIndex]); percentBetweenWaypoints += Time.deltaTime * speed/distanceBetweenWaypoints; percentBetweenWaypoints = Mathf.Clamp01 (percentBetweenWaypoints); float easedPercentBetweenWaypoints = Ease (percentBetweenWaypoints); Vector3 newPos = Vector3.Lerp (globalWaypoints [fromWaypointIndex], globalWaypoints [toWaypointIndex], easedPercentBetweenWaypoints); if (percentBetweenWaypoints >= 1) { percentBetweenWaypoints = 0; fromWaypointIndex ++; if (!cyclic) { if (fromWaypointIndex >= globalWaypoints.Length-1) { fromWaypointIndex = 0; System.Array.Reverse(globalWaypoints); } } nextMoveTime = Time.time + waitTime; } return newPos - transform.position; } 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 && hit.distance != 0) { 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 && hit.distance != 0) { 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 && hit.distance != 0) { 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; } } void OnDrawGizmos() { if (localWaypoints != null) { Gizmos.color = Color.red; float size = .3f; for (int i =0; i < localWaypoints.Length; i ++) { Vector3 globalWaypointPos = (Application.isPlaying)?globalWaypoints[i] : localWaypoints[i] + transform.position; Gizmos.DrawLine(globalWaypointPos - Vector3.up * size, globalWaypointPos + Vector3.up * size); Gizmos.DrawLine(globalWaypointPos - Vector3.left * size, globalWaypointPos + Vector3.left * size); } } } }
Player.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof (Controller2D))] public class Player : MonoBehaviour { public float maxJumpHeight = 4; public float minJumpHeight = 1; public float timeToJumpApex = .4f; float accelerationTimeAirborne = .2f; float accelerationTimeGrounded = .1f; float moveSpeed = 6; public Vector2 wallJumpClimb; public Vector2 wallJumpOff; public Vector2 wallLeap; public float wallSlideSpeedMax = 3; public float wallStickTime = .25f; float timeToWallUnstick; float gravity; float maxJumpVelocity; float minJumpVelocity; Vector3 velocity; float velocityXSmoothing; Controller2D controller; Vector2 directionalInput; bool wallSliding; int wallDirX; void Start() { controller = GetComponent<Controller2D> (); gravity = -(2 * maxJumpHeight) / Mathf.Pow (timeToJumpApex, 2); maxJumpVelocity = Mathf.Abs(gravity) * timeToJumpApex; minJumpVelocity = Mathf.Sqrt (2 * Mathf.Abs (gravity) * minJumpHeight); } void Update() { CalculateVelocity (); HandleWallSliding (); controller.Move (velocity * Time.deltaTime, directionalInput); if (controller.collisions.above || controller.collisions.below) { if (controller.collisions.slidingDownMaxSlope) { velocity.y += controller.collisions.slopeNormal.y * -gravity * Time.deltaTime; } else { velocity.y = 0; } } } public void SetDirectionalInput (Vector2 input) { directionalInput = input; } public void OnJumpInputDown() { if (wallSliding) { if (wallDirX == directionalInput.x) { velocity.x = -wallDirX * wallJumpClimb.x; velocity.y = wallJumpClimb.y; } else if (directionalInput.x == 0) { velocity.x = -wallDirX * wallJumpOff.x; velocity.y = wallJumpOff.y; } else { velocity.x = -wallDirX * wallLeap.x; velocity.y = wallLeap.y; } } if (controller.collisions.below) { if (controller.collisions.slidingDownMaxSlope) { if (directionalInput.x != -Mathf.Sign (controller.collisions.slopeNormal.x)) { // not jumping against max slope velocity.y = maxJumpVelocity * controller.collisions.slopeNormal.y; velocity.x = maxJumpVelocity * controller.collisions.slopeNormal.x; } } else { velocity.y = maxJumpVelocity; } } } public void OnJumpInputUp() { if (velocity.y > minJumpVelocity) { velocity.y = minJumpVelocity; } } void HandleWallSliding() { wallDirX = (controller.collisions.left) ? -1 : 1; wallSliding = false; if ((controller.collisions.left || controller.collisions.right) && !controller.collisions.below && velocity.y < 0) { wallSliding = true; if (velocity.y < -wallSlideSpeedMax) { velocity.y = -wallSlideSpeedMax; } if (timeToWallUnstick > 0) { velocityXSmoothing = 0; velocity.x = 0; if (directionalInput.x != wallDirX && directionalInput.x != 0) { timeToWallUnstick -= Time.deltaTime; } else { timeToWallUnstick = wallStickTime; } } else { timeToWallUnstick = wallStickTime; } } } void CalculateVelocity() { float targetVelocityX = directionalInput.x * moveSpeed; velocity.x = Mathf.SmoothDamp (velocity.x, targetVelocityX, ref velocityXSmoothing, (controller.collisions.below)?accelerationTimeGrounded:accelerationTimeAirborne); velocity.y += gravity * Time.deltaTime; } }
PlayerInput.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof (Player))] public class PlayerInput : MonoBehaviour { Player player; void Start () { player = GetComponent<Player> (); } void Update () { Vector2 directionalInput = new Vector2 (Input.GetAxisRaw ("Horizontal"), Input.GetAxisRaw ("Vertical")); player.SetDirectionalInput (directionalInput); if (Input.GetKeyDown (KeyCode.Space)) { player.OnJumpInputDown (); } if (Input.GetKeyUp (KeyCode.Space)) { player.OnJumpInputUp (); } } }
RaycastController.cs
using UnityEngine; using System.Collections; [RequireComponent (typeof (BoxCollider2D))] public class RaycastController : MonoBehaviour { public LayerMask collisionMask; public const float skinWidth = .015f; const float dstBetweenRays = .25f; [HideInInspector] public int horizontalRayCount; [HideInInspector] public int verticalRayCount; [HideInInspector] public float horizontalRaySpacing; [HideInInspector] public float verticalRaySpacing; [HideInInspector] public BoxCollider2D collider; public RaycastOrigins raycastOrigins; public virtual void Awake() { collider = GetComponent<BoxCollider2D> (); } public virtual void Start() { 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); float boundsWidth = bounds.size.x; float boundsHeight = bounds.size.y; horizontalRayCount = Mathf.RoundToInt (boundsHeight / dstBetweenRays); verticalRayCount = Mathf.RoundToInt (boundsWidth / dstBetweenRays); horizontalRaySpacing = bounds.size.y / (horizontalRayCount - 1); verticalRaySpacing = bounds.size.x / (verticalRayCount - 1); } public struct RaycastOrigins { public Vector2 topLeft, topRight; public Vector2 bottomLeft, bottomRight; } }
プレイヤーの設定
作成した「PlayerInput」スクリプトを「Player」オブジェクトにドラッグします
そして、「Player」オブジェクトを選択した状態で
「Max Slope Angle」に「55」と入力します
「Max Slope Angle」は、登ることができる坂道の角度になります
そして、シーンに適当に床を配置してこのようにパラメータを設定します
これでゲームを再生してプレイヤーを動かすと
急斜面の坂道は滑り落ちるようになったことが確認できます