这一章节将在 Unity 编辑器中的插件商城下载两个包:Input System 和 Cinemachine,并关掉 Reload Domain 开关,从而允许我们能在编辑和游玩模式间快速切换。
在 Window 菜单下打开 Package Manager,在窗口左上角把“Package: In Project”切换到“Package: Unity Registry”,在右边的搜索框分别搜索要下载的两个包并点击下载。其中 Input System 在下载完成后会要求重启编辑器。下载完成后应该可以在“Package: In Project”找到它们。
在 Edit 菜单下打开 Project Setting,窗口左侧选择 Editor,下滑到底部,勾选 Enter Play Mode Options 选项和 Reload Scene 选项,选项的详细解释可以通过鼠标悬停进行查看。
using System.Collections; using System.Collections.Generic; using UnityEngine;
publicclassParallaxEffect : MonoBehaviour { public Camera cam; public Transform followTarget;
// Starting position for the parallax game object Vector2 startingPosition;
// Start Z value of the parallax game object // Z alue is the distance into the background float startingZ;
// Distance that the camera has moved from the starting position of the parallax game object // => means it updates on every frame Vector2 camMoveSinceStart => (Vector2)cam.transform.position - startingPosition;
// If the parallax game object is in front of the player, use nearClipPlane, otherwise use farClipPlane float clippingPlane => (cam.transform.position.z + (zDistanceFromTarget > 0 ? cam.farClipPlane : cam.nearClipPlane));
// The futher the object from the player, the faster the parallax game object will move // Drag it's Z value closer to hte target to make it move slower float parallaxFactor => Mathf.Abs(zDistanceFromTarget) / clippingPlane;
// Start is called before the first frame update voidStart() { // transform.position is Vector3, but the z-axis will be shaved off automatically startingPosition = transform.position; startingZ = transform.position.z; }
// Update is called once per frame voidUpdate() { Vector2 newPosition = startingPosition + camMoveSinceStart * parallaxFactor; transform.position = new Vector3(newPosition.x, newPosition.y, startingZ); } }
之后选择 BG1、BG2 和 BG3,为它们添加 ParallaxEffect 组件,并设置 Cam 和 Follow Target 分别为 Main Camera 和 Player 物体。再分别修改 BG1、BG2 和 BG3 的 Z 坐标为 -1,-0.8 和 -0.5(值适当即可)。
Animations
这一节将给游戏角色添加移动时的动画。
首先我们添加动画元素:
在 Player 的预设体上新加一个组件 Animator
在 Player 文件夹下创建 Animator Controller 文件,命名为 AC_Player
privatebool _isFacingRight = true; publicbool IsFacingRight { get { return _isFacingRight; } privateset { if (_isFacingRight != value) { // Flip the local scale to make the player face the opposite direction transform.localScale *= new Vector2(-1, 1); } _isFacingRight = value; } }
using System.Collections; using System.Collections.Generic; using UnityEngine;
// The following script is used to check if a game object is grounded by casting a 2D ray downwards using a CapsuleCollider2D component. // It also updates an Animator's bool parameter "isGrounded" based on the grounded status. publicclassTouchingDirections : MonoBehaviour { // A ContactFilter2D used to determine which colliders should be considered when casting the ray. public ContactFilter2D castFilter; // The distance of the ray casted from the object's CapsuleCollider2D. publicfloat groundDistance = 0.05f; publicfloat wallDistance = 0.2f; publicfloat ceilingDistance = 0.05f;
// Array to store RaycastHit2D results when casting the ray. RaycastHit2D[] groundHits = new RaycastHit2D[5]; RaycastHit2D[] wallHits = new RaycastHit2D[5]; RaycastHit2D[] ceilingHits = new RaycastHit2D[5];
[SerializeField] privatebool _isGrounded; publicbool IsGrounded { get { return _isGrounded; } set { _isGrounded = value; animator.SetBool(AnimationStrings.isGrounded, value); } }
[SerializeField] privatebool _isOnWall; publicbool IsOnWall { get { return _isOnWall; } set { _isOnWall = value; animator.SetBool(AnimationStrings.isOnWall, value); } }
[SerializeField] privatebool _isOnCeiling; publicbool IsOnCeiling { get { return _isOnCeiling; } set { _isOnCeiling = value; animator.SetBool(AnimationStrings.isOnCeiling, value); } }
// Awake is called when the script instance is being loaded. privatevoidAwake() { touchingCollider = GetComponent<CapsuleCollider2D>(); animator = GetComponent<Animator>(); }
// FixedUpdate is called at fixed intervals and is commonly used for physics-related updates. voidFixedUpdate() { // Cast a 2D ray downwards from the CapsuleCollider2D. // If the ray hits any colliders within the given groundDistance, consider the object as grounded. IsGrounded = touchingCollider.Cast(Vector2.down, castFilter, groundHits, groundDistance) > 0; IsOnWall = touchingCollider.Cast(wallCheckDirection, castFilter, wallHits, wallDistance) > 0; IsOnCeiling = touchingCollider.Cast(Vector2.up, castFilter, ceilingHits, ceilingDistance) > 0; } }
privatebool _isFacingRight = true; publicbool IsFacingRight { get { return _isFacingRight; } privateset { if (_isFacingRight != value) { // Flip the local scale to make the player face the opposite direction transform.localScale *= new Vector2(-1, 1); } _isFacingRight = value; } }
using System.Collections; using System.Collections.Generic; using UnityEngine;
publicclassSetBoolBehaviour : StateMachineBehaviour { publicstring boolName; publicbool updateOnStateMachine; publicbool updateOnState; publicbool valueOnEnter, valueOnExit; // OnStateEnter is called before OnStateEnter is called on any state inside this state machine overridepublicvoidOnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (updateOnState) { animator.SetBool(boolName, valueOnEnter); } }
// OnStateUpdate is called before OnStateUpdate is called on any state inside this state machine //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) //{ // //}
// OnStateExit is called before OnStateExit is called on any state inside this state machine overridepublicvoidOnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { if (updateOnState) { animator.SetBool(boolName, valueOnExit); } }
// OnStateMove is called before OnStateMove is called on any state inside this state machine //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) //{ // //}
// OnStateIK is called before OnStateIK is called on any state inside this state machine //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) //{ // //}
// OnStateMachineEnter is called when entering a state machine via its Entry Node overridepublicvoidOnStateMachineEnter(Animator animator, int stateMachinePathHash) { if (updateOnStateMachine) { animator.SetBool(boolName, valueOnEnter); } }
// OnStateMachineExit is called when exiting a state machine via its Exit Node overridepublicvoidOnStateMachineExit(Animator animator, int stateMachinePathHash) { if (updateOnStateMachine) { animator.SetBool(boolName, valueOnExit); } } }
privatebool _isFacingRight = true; publicbool IsFacingRight { get { return _isFacingRight; } privateset { if (_isFacingRight != value) { // Flip the local scale to make the player face the opposite direction transform.localScale *= new Vector2(-1, 1); } _isFacingRight = value; } }
publicbool CanMove { get { return animator.GetBool(AnimationStrings.canMove); } }
public WalkableDirection WalkDirection { get { return _walkDirection; } set { if (_walkDirection != value) { // Direction flipped gameObject.transform.localScale = new Vector2(-1 * gameObject.transform.localScale.x, gameObject.transform.localScale.y);
privatevoidFixedUpdate() { if (touchingDirections.IsGrounded && touchingDirections.IsOnWall) { FlipDirections(); } rb.velocity = new Vector2(walkSpeed * walkDirectionVector.x, rb.velocity.y); }
privatevoidFlipDirections() { if (WalkDirection == WalkableDirection.Right) { WalkDirection = WalkableDirection.Left; } elseif (WalkDirection == WalkableDirection.Left) { WalkDirection = WalkableDirection.Right; } else { Debug.LogError("Current walkable direction is not set to legal values of right or left"); } }
// Start is called before the first frame update voidStart() { }
// Update is called once per frame voidUpdate() { } }