上一篇我们把Sunny Land素材从商店中下载并导入到Unity中,并且完善了场景的位置关系以及简单制作了人物的移动脚本。

开始第二天的制作!

这一篇我们主要来完善角色的动画控制以及添加背景的移动功能,来实现角色移动时相机跟随以及背景带来的一些视觉效果。

制作Player动画

首先我们先创建一个 Animator 命名为Player,并将其添加到 Player 上。

unity 搭建2D动画场景 unity制作2d素材_unity

然后我们点击 Windows 选项卡,依次点击 Windows -> Animation -> Animator/Animation 打开 Animator 和 Animation窗口,并将其放在合适位置。

我们进入到 Animation 窗口,点击图示位置下拉选项卡,点击 Create New Clip 创建一个新的 Clip,然后将其保存在合适的位置。

unity 搭建2D动画场景 unity制作2d素材_个人开发_02

本次我们主要制作三个动画,分别是 Player_Idle,Player_Run,Player_Jump,即空闲,跑步,跳跃这三个动画。

unity 搭建2D动画场景 unity制作2d素材_unity_03

0:60为一秒,我们空闲状态总共有四幅图像,我们间隔10帧放置一幅,最后在第40帧的时候再放一个第一幅,使得小狐狸空闲状态的动画流畅。

剩下的Run和Jump动画制作方式也是相同,我将Run状态的每隔5帧放置一幅,是小狐狸奔跑时的动画不会显得缓慢。Jump状态我只保留了第一幅,因为我觉得这样的跳跃动画更加符合我的预期。

unity 搭建2D动画场景 unity制作2d素材_c#_04

unity 搭建2D动画场景 unity制作2d素材_c#_05

在以上步骤完成之后,我们进入到 Animator窗口,根据图示关系设置好每个动画之间的关系。

要求Player在空闲状态和跑步状态都可以进入跳跃状态。

unity 搭建2D动画场景 unity制作2d素材_unity_06

完成上述关系的连接之后,各个动画的制作以及它们之间的关系就算是完成了。

设置动画进入条件

完成动画制作之后,我们要来设置每个动画进入的条件和结束的条件。

首先我们在 Animator 窗口中,点击 Parameter,创建一个 float 类型的 moveSpeed 和 bool 类型的 isGrounded用来作为动画进入以及退出的条件。

unity 搭建2D动画场景 unity制作2d素材_c#_07


unity 搭建2D动画场景 unity制作2d素材_unity 搭建2D动画场景_08

unity 搭建2D动画场景 unity制作2d素材_c#_09

将每个动画的 Has Exit Time 关掉,并且在 Settings 里将 Transition Duration 设置为0,使动画之间可以直接切换。

添加动画控制部分的代码

在上一模块我们定义了一个bool类型的 isGrounded,这个变量与上一篇的isGrounded的值是同步的,用来控制跳跃。

而float类型的 moveSpeed 是用来获取 刚体水平速度,只要它的值大于0.1,我们就判定Player进入了跑步状态,而小于0.1则是停止了跑步。但是需要注意的一点是,我们获取的 刚体水平速度 的值会存在的,所以我们对其取绝对值。

接下来上代码。

//首先我们先定义一个Animator来获取动画组件
private Animator anim;

//这里为了提高效率,我们直接定义两个int类型的变量来存储(可不写)
private static readonly int IsGrounded=Animator.StringToHash("isGrounded");
private static readonly int MoveSpeed = Animator.StringToHash("moveSpeed");

//在脚本开始运行时获取Player上的Animator组件
void Start()
{
	anim = GetComponent<Animator>();
}

//Update函数
void Update(){
    //将isGrounded的值和刚体水平速度实时赋值给变量
    anim.SetBool(IsGrounded,isGrounded);
    anim.SetFloat(MoveSpeed,Mathf.Abs(rBody.velocity.x));
    
    //如果上面没有定义,那就在这么写
    //anim.SetBool("isGrounded",isGrounded);
    //anim.SetFloat("moveSpeed",Mathf.Abs(rBody.velocity.x));
}

接下来,将本篇的动画控制代码与上一篇的角色移动控制代码组成一个完整的PlayerController脚本。

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Serialization;

public class PlayerController : MonoBehaviour
{
    //Player移动
    private const float playerMoveSpeed = 6f;
    private const float playerJumpForce = 8f;
    private Rigidbody2D rBody;

    //Player跳跃
    private int playerJumpCount = 1;//二段跳
    private bool isGrounded = true;
    private Transform groundPoint;
    public LayerMask groundLayerMask;
    
    //Player动画
    private Animator anim;
    private static readonly int IsGrounded = Animator.StringToHash("isGrounded");
    private static readonly int MoveSpeed = Animator.StringToHash("moveSpeed");

    private SpriteRenderer spriteRenderer;
    void Start()
    {
        rBody = GetComponent<Rigidbody2D>();
        groundPoint = transform.GetChild(0).gameObject.transform;
        anim = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        //移动
        rBody.velocity = new Vector2(playerMoveSpeed * Input.GetAxis("Horizontal"), rBody.velocity.y);
        
        //判断是否在地面上
        isGrounded = Physics2D.OverlapCircle(groundPoint.position, .2f, groundLayerMask);
        
        //跳跃
        if (Input.GetButtonDown("Jump") && playerJumpCount>0)
        {
            isGrounded = false;
            playerJumpCount--;
            rBody.velocity = new Vector2(rBody.velocity.x, playerJumpForce);
        }
        //重置二段跳
        if (isGrounded)
        {
            playerJumpCount = 1;
        }
        //控制Player转向
        if (rBody.velocity.x < 0)
        {
            spriteRenderer.flipX = true;
        }
        else if (rBody.velocity.x > 0)
        {
            spriteRenderer.flipX = false;
        }
        
        anim.SetBool(IsGrounded,isGrounded);
        anim.SetFloat(MoveSpeed,Mathf.Abs(rBody.velocity.x));
    }
    
}

这样,角色移动跳跃这部分脚本就全部完成了,我们就得到了一个具有动画的并且可以自由移动的Player了。

相机跟随Player移动

首先我们需要创建一个名为CameraFollow的脚本用来控制相机跟随,并将其挂载在 Main Camera上。

要实现相机跟随Player,我们就需要获取主相机Player

//定义相机和Player的Transform变量
public Transform playerTransform;
private Transform cameraTransform;

//定义相机和Player的Vector3变量
private Vector3 playerPosition;
private Vector3 cameraPosition;

//在Start函数里获取相机的Transform组件,由于Player的Transform组件是直接通过Unity拖进去的,所以这里不用获取(相机也可以直接拖)。
private void Start()
{
        cameraTransform = transform;
    
    	//获取相机和Player的位置
        playerPosition = playerTransform.position;
        cameraPosition = cameraTransform.position;
}

//Update函数
private void Update()
{
    	//实时更新位置
        playerPosition = playerTransform.position;
        cameraPosition = cameraTransform.position;
        
        //相机跟随
        cameraTransform.position = new Vector3(playerPosition.x,playerPosition.y,cameraPosition.z);
        
}

这样相机跟随玩家移动的脚本就写好了,但是现在的代码会出现一个问题,在Player到达较高地段进行二段跳或者掉出世界时,主相机内会显示一些不应该给玩家看到的画面,所以我们需要加以限制。

//定义两个变量来控制相机的最高位置和最低位置
private const float maxHeight=2f, minHeight=-0.5f;

//在Update函数中添加以下限制高度值的代码。
var clampedY = Mathf.Clamp(playerPosition.y, minHeight, maxHeight);//限制相机高度
cameraTransform.position = new Vector3(playerPosition.x,clampedY,cameraPosition.z);

这样,完美的相机跟随代码就完成了,最后上完整的代码。

using System;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Serialization;

public class CameraFollow : MonoBehaviour
{
    public Transform playerTransform;
    private Transform cameraTransform;
    private Vector3 playerPosition;
    private Vector3 cameraPosition;
    private const float maxHeight=2f, minHeight=-0.5f;

    
    private void Start()
    {
        cameraTransform = transform;
        playerPosition = playerTransform.position;
        cameraPosition = cameraTransform.position;
    }

    private void Update()
    {
        playerPosition = playerTransform.position;
        cameraPosition = cameraTransform.position;
        
        //相机跟随
        var clampedY = Mathf.Clamp(playerPosition.y, minHeight, maxHeight);//限制相机高度
        
        cameraTransform.position = new Vector3(playerPosition.x,clampedY,cameraPosition.z);
    }

}

背景循环移动

终于到了本篇最后一部分了,就是让背景动起来,但是我们不可能一直创建新背景去达到背景循环的效果,那样会消耗很多性能,所以我们正确的做法应该是让背景跟随Player动起来以达到背景循环。我们创建一个名为BackGroundFollow的脚本,将其挂载在 Main Camera 上。

由于这部分代码比较简单,就不过多讲解了,所以接下来直接上代码!

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Serialization;

public class BackGroundFollow : MonoBehaviour
{
    public Transform farTransform,middleTransform;//获取天空和树林,也就是场景中的far和middle
    private Vector2 lastPos;//上一个位置
    
    private void Start()
    {
        //在start中先把相机位置
        lastPos = transform.position;
    }

    private void Update()
    {
        //背景跟随
        //因为我们要实现far,camera和middle三者速度存在一定差异来达到背景具有深度的效果,所以需要将相机位置减去lastPos得到一个差值。
        var amountToMove = new Vector2(transform.position.x - lastPos.x, transform.position.y - lastPos.y);
        
        farTransform.position+=new Vector3(amountToMove.x,amountToMove.y,0f);
        
        //让middle慢于far
        middleTransform.position += new Vector3(amountToMove.x,amountToMove.y,0f)*.5f;
        
        //最后更新lastPos
        lastPos=transform.position;
    }

}

总结

本篇完成了Player的动画控制,相机跟随以及背景循环移动,这是整个游戏最基本的一些脚本,使整个游戏具有了初步可玩性。