《土豆荣耀》重构笔记(七)控制角色移动并添加音效

前言

  为了便于测试,我们会先使用PC端的键盘和鼠标输入来控制人物的移动,等到功能测试完成之后,再将PC端的键盘和鼠标输入换成移动端的虚拟摇杆和按钮输入。这里,我们首先使用PC端的键盘和鼠标输入来实现控制角色进行移动的功能。


为角色添加Collider和Rigidbody

  为了让角色具有物理属性,我们需要为角色添加Collider和Rigidbody。此外,为了避免角色出现翻滚的问题,让角色一直保持直立,因此我们需要在Rigidbody2DConstraints属性里设置勾选Freeze Rotation Z,不让角色在进行物理模拟时,绕Z轴进行旋转。

角色添加的组件信息

控制角色移动

  接下来,我们开始编写脚本来实现让怪物在场景中移动的功能。我们在Assets\Scripts文件夹下创建一个名为Player的文件夹用于保存和角色相关的脚本。创建完毕后,我们在Player文件夹下创建一个名为PlayerController的C#脚本,然后添加以下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController : MonoBehaviour {
    [Tooltip("角色初始朝向是否朝向右边")]
    public bool FacingRight = true;
    [Tooltip("移动时角色加速的力大小")]
    public float MoveForce = 365f;
    [Tooltip("角色移动的最大速度")]
    public float MaxSpeed = 5f;
    [Tooltip("跳跃时向上加速的力大小")]
    public float JumpForce = 1000f;
    [Tooltip("检测角色是否落地")]
    public Transform GroundCheck;

    // 记录角色当前是否处于准备跳跃状态
    private bool m_IsReadyToJump;
    // 记录角色当前是否正处于跳跃状态
    private bool m_IsJumping;
    // 记录角色当前是否处于着地状态
    private bool m_GroundedStatus;

    // 组件引用变量
    private Rigidbody2D m_Rigidbody2D;

    private void Awake() {
        // 获取组件引用
        m_Rigidbody2D = GetComponent();
    }

    private void Start() {
        // 监测变量是否正确赋值
        if(GroundCheck == null) {
            Debug.LogError("请先设置GroundCheck");
        }

        // 初始化变量
        m_IsReadyToJump = false;
        m_IsJumping = false;
        m_GroundedStatus = false;
    }

    private void Update() {
        // 通过检测角色和groundCheck之间是否存在Ground层的物体来判断当前是否落地
        m_GroundedStatus = Physics2D.Linecast(
            transform.position,
            GroundCheck.position,
            LayerMask.GetMask("Obstacle")
        );
        
        // 着地时,如果当前不处于跳跃状态且按下了跳跃键,进入准备跳跃状态
        if(m_GroundedStatus && !m_IsJumping && Input.GetButtonDown("Jump")) {
            m_IsReadyToJump = true;
        }

        // 刚刚落地,退出跳跃状态
        if(m_GroundedStatus && m_IsJumping) {
            m_IsJumping = false;
        }
    }

    private void FixedUpdate() {
        //获取水平输入
        float h = Input.GetAxis("Horizontal");

        // 若h * m_Rigidbody2D.velocity.x为正数且小于MaxSpeed,表示需要继续加速
        // 若h * m_Rigidbody2D.velocity.x为负数,则表示需要反向加速
        if(h * m_Rigidbody2D.velocity.x < MaxSpeed) {
            m_Rigidbody2D.AddForce(Vector2.right * h * MoveForce);
        }

        //设置物体速度的阈值
        if(Mathf.Abs(m_Rigidbody2D.velocity.x) > MaxSpeed) {
            m_Rigidbody2D.velocity = new Vector2(
                Mathf.Sign(m_Rigidbody2D.velocity.x) * MaxSpeed,
                m_Rigidbody2D.velocity.y
            );
        }

        //判断当前是否需要转向
        if(h > 0 && !FacingRight) {
            Flip();
        }else if(h < 0 && FacingRight) {
            Flip();
        }

        // 跳跃
        if(m_IsReadyToJump) {
            Jump();
        }
    }

    private void Jump() {
        // 进入跳跃状态
        m_IsJumping = true;

        // 设置一个竖直向上的力
        m_Rigidbody2D.AddForce(new Vector2(0f, JumpForce));

        // 退出准备跳跃状态,避免重复跳跃
        m_IsReadyToJump = false;
    }

    private void Flip() {
        // 修改当前朝向
        FacingRight = !FacingRight;

        // 修改scale的x分量实现转向
        this.transform.localScale = Vector3.Scale(
            new Vector3(-1, 1, 1),
            this.transform.localScale
        );
    }
}

代码说明:

  • Input.GetAxis("Horizontal"):Unity提供的默认输入项,能获取键盘上A\D键和<-\->方向键的输入
  • Input.GetButtonDown("Jump"):Unity提供的默认输入项,能获取键盘上空格键的输入
  • Physics2D.Linecast:来获取场景里两个点之间属于某个Layer的所有Collider
  • 因为和角色物理模拟有关的操作都应该在FixedUpdate里执行,所以Jump函数需要在FixedUpdate里调用
  • 我们为了避免出现多次按跳跃键导致Jump函数被多次调用的问题,我们使用变量m_IsJumping来判断当前是否处于跳跃状态

添加Physics Material

  添加完成后,将PlayerController.cs添加到Hierarchy窗口的Player上,运行游戏,我们发现角色在运动时,会出现很明显的滑动现象。这是因为我们没有为角色和平台上的Collider设置Physics Material,导致角色和平台之间的摩擦力为0。

添加Physics Material的步骤

  1. Assets目录下新建一个名为Physics Material的文件夹
  2. Physics Material的文件夹创建两个Physics Material 2D,并将它们分别命名为PlatformPlayer
  3. PlatformPlayer这两个Physics Material 2DFriction属性都设置为1
  4. PlatformPlayer这两个Physics Material 2D分别设置到所有角色可移动的平台角色的Rigidbody2D

  因为平台数量较多,我们可以批量操作。按住shift键选择多个GameObject,右侧Inspector就会显示它们同时都具备的组件,我们可以同时对它们同时都具备的组件进行编辑。

批量操作

  此外,角色在跳跃时,我们发现角色跳跃的高度很高,且下落的速度很慢,游戏体验较差。为了提高游戏体验,我们需要增大游戏角色的重力加速度。将PlayerRigidbody2D组件的Gravity Scale设置为3.1,表示将角色的重力加大至原有的3.1倍。

设置Gravity Scale

  从上面的图片可以看到,我将Player这一Physics Material 2D设置到角色的Rigidbody2D上,这表示角色的所有没设置Physics Material 2D的Collider都会使用Player这一Physics Material 2D

  设置完成之后,运行游戏,可以看到之前的滑动和下落缓慢的问题都消失了,但是出现了一个新的问题,那就是当我们按住方向键不动的时候,角色会贴在平台边缘。之所以会出现这个问题,是因为角色和平台之间有摩擦力,为了避免这一情况,我们需要创建一个名为WallPhysics Material 2D,将其Friction属性都设置为0,然后在各个平台的末端处创建一个使用Wall这一Physics Material 2D的Collider。

Foreground添加Collider的情况如下:

  • env_TowerFull和env_TowerFull (1):
    • 不添加新的Collider
    • 设置原有Collider的Physics Material 2DWall
  • env_PlatformBridge和env_PlatformBridge (1):
    • 新添加一个BoxCollider2D
    • Material:Wall
    • Offset:(0.8, 0.8)
    • Size: (15.5, 0.6)
  • env_PlatformTop和env_PlatformTop (1):
    • 新添加一个BoxCollider2D
    • Material:Wall
    • Offset:(4.7, 0.12)
    • Size: (0.5, 2.6)
  • env_PlatformUfo:
    • 新添加两个CircleCollider2D
    • Material:都为Wall
    • Offset:(-15.3, -0.65)和(15.3, -0.65)
    • Radius: 都为0.5

  添加完毕之后,再次运行游戏,可以看到不再出现角色贴在平台边缘的问题。


添加音效

  接着,我们需要为角色添加音效。首先,在PlayerController.cs内加入以下代码,其中...表示原有的代码:

...
[RequireComponent(typeof(AudioSource))]
public class PlayerController : MonoBehaviour {
    ...
    [Tooltip("跳跃音效")]
    public AudioClip[] JumpClips;

    ...
    private AudioSource m_AudioSource;

    ...

    private void Awake() {
        ...
        m_AudioSource = GetComponent();
    }

    private void Jump() {
        ...

        //随机在角色当前所处的位置播放一个跳跃的音频
        if(JumpClips.Length > 0) {
            int i = Random.Range(0, JumpClips.Length);
            AudioSource.PlayClipAtPoint(JumpClips[i], transform.position);
        }
    }
}

  添加完毕之后,我们给Player添加AudioSource组件,并将Assets\Audio\Player\Jumps添加到JumpClips属性上。接着运行游戏,可以听到角色在跳跃的时候已经有了音效。最后,将我们在Player上做的修改Apply到Prefab上,并保存场景产生的修改即可。

设置音效

PlayerController.cs完整代码

  此时,PlayerController.cs完整代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(AudioSource))]
public class PlayerController : MonoBehaviour {
    [Tooltip("角色初始朝向是否朝向右边")]
    public bool FacingRight = true;
    [Tooltip("移动时角色加速的力大小")]
    public float MoveForce = 365f;
    [Tooltip("角色移动的最大速度")]
    public float MaxSpeed = 5f;
    [Tooltip("跳跃时向上加速的力大小")]
    public float JumpForce = 1000f;
    [Tooltip("检测角色是否落地")]
    public Transform GroundCheck;

    [Tooltip("跳跃音效")]
    public AudioClip[] JumpClips;

    // 记录角色当前是否处于准备跳跃状态
    private bool m_IsReadyToJump;
    // 记录角色当前是否正处于跳跃状态
    private bool m_IsJumping;
    // 记录角色当前是否处于着地状态
    private bool m_GroundedStatus;

    // 组件引用变量
    private Rigidbody2D m_Rigidbody2D;
    private AudioSource m_AudioSource;

    private void Awake() {
        // 获取组件引用
        m_Rigidbody2D = GetComponent();
        m_AudioSource = GetComponent();
    }

    private void Start() {
        // 监测变量是否正确赋值
        if(GroundCheck == null) {
            Debug.LogError("请先设置GroundCheck");
        }

        // 初始化变量
        m_IsReadyToJump = false;
        m_IsJumping = false;
        m_GroundedStatus = false;
    }

    private void Update() {
        // 通过检测角色和groundCheck之间是否存在Ground层的物体来判断当前是否落地
        m_GroundedStatus = Physics2D.Linecast(
            transform.position,
            GroundCheck.position,
            LayerMask.GetMask("Obstacle")
        );
        
        // 着地时,如果当前不处于跳跃状态且按下了跳跃键,进入准备跳跃状态
        if(m_GroundedStatus && !m_IsJumping && Input.GetButtonDown("Jump")) {
            m_IsReadyToJump = true;
        }

        // 刚刚落地,退出跳跃状态
        if(m_GroundedStatus && m_IsJumping) {
            m_IsJumping = false;
        }
    }

    private void FixedUpdate() {
        //获取水平输入
        float h = Input.GetAxis("Horizontal");

        // 若h * m_Rigidbody2D.velocity.x为正数且小于MaxSpeed,表示需要继续加速
        // 若h * m_Rigidbody2D.velocity.x为负数,则表示需要反向加速
        if(h * m_Rigidbody2D.velocity.x < MaxSpeed) {
            m_Rigidbody2D.AddForce(Vector2.right * h * MoveForce);
        }

        //设置物体速度的阈值
        if(Mathf.Abs(m_Rigidbody2D.velocity.x) > MaxSpeed) {
            m_Rigidbody2D.velocity = new Vector2(
                Mathf.Sign(m_Rigidbody2D.velocity.x) * MaxSpeed,
                m_Rigidbody2D.velocity.y
            );
        }

        //判断当前是否需要转向
        if(h > 0 && !FacingRight) {
            Flip();
        }else if(h < 0 && FacingRight) {
            Flip();
        }

        // 跳跃
        if(m_IsReadyToJump) {
            Jump();
        }
    }

    private void Jump() {
        // 进入跳跃状态
        m_IsJumping = true;

        // 设置一个竖直向上的力
        m_Rigidbody2D.AddForce(new Vector2(0f, JumpForce));

        // 退出准备跳跃状态,避免重复跳跃
        m_IsReadyToJump = false;

        //随机在角色当前所处的位置播放一个跳跃的音频
        if(JumpClips.Length > 0) {
            int i = Random.Range(0, JumpClips.Length);
            AudioSource.PlayClipAtPoint(JumpClips[i], transform.position);
        }
    }

    private void Flip() {
        // 修改当前朝向
        FacingRight = !FacingRight;

        // 修改scale的x分量实现转向
        this.transform.localScale = Vector3.Scale(
            new Vector3(-1, 1, 1),
            this.transform.localScale
        );
    }
}

后言

  可以看到,目前角色在运动的时候还没有动画。因为给角色加上动画设计到动画状态机的制作,较为复杂,在下一篇文章将会介绍如何给角色添加动画。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay5分支下看到,读者可以clone这个仓库到本地进行查看。


参考链接

  1. Gravity Scale
  2. Conventional Game Input
  3. Physics Material 2D

你可能感兴趣的:(《土豆荣耀》重构笔记(七)控制角色移动并添加音效)