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

前言

  为了便于测试,我们会先使用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<Rigidbody2D>();
    }

    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<AudioSource>();
    }

    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<Rigidbody2D>();
        m_AudioSource = GetComponent<AudioSource>();
    }

    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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容