《土豆荣耀》重构笔记(八)给角色添加动画

前言

  为了提高玩家的游戏体验,我们常常需要在角色运动的时候,根据它的运动状态播放对应的动画。为了能够控制当前播放什么动画,我们首先需要制作动画状态机,再用代码去控制动画状态机切换当前的状态,从而切换当前播放的动画。


制作角色的动画状态机

  我们在给角色制作动画状态机来控制我们之前制作好的角色动画之前,我们需要确定我们有哪些状态,以及控制这些状态转移的条件是什么。

各个角色动画之间的状态转换规则如下:

  1. 当角色不着地时,不播放任何状态
  2. 角色着地时播放Idle播放动画
  3. 当角色着地水平移动时,播放Walk动画
  4. 当角色触发跳跃事件时,播放Jump动画
  5. Jump动画播放完之后,着地播放Idle动画
  6. 当角色触发射击事件时,播放Shoot动画
  7. Shoot动画播放完之后,着地播放Idle动画
  8. 当角色触发死亡事件时,播放Death动画
  9. Death动画播放完之后,自动播放Falling动画

  根据状态转换规则,我们可以得出控制状态转换的所有参数

状态转换参数:

  • Grounded:Bool
  • Speed:Float
  • Jump:Trigger
  • Death:Trigger
  • Shoot:Trigger

  接着,我们在Unity顶部菜单栏选中Window->Animator打开动画状态机编辑窗口,然后在Hierarchy窗口中选中Player编辑角色的动画状态机。在Unity中,一个状态控制一个动画。根据上面状态转换规则,我们还需要创建一个没有任何动画的空状态,并将其作为整个状态机的默认状态。我们在Animator窗口中点击鼠标右键选择Empty创建一个空状态,将其命名为Empty之后,点击鼠标右键编辑这个状态,然后选择Set as Layer Default State将其设置为默认状态。最后,我们根据上面得到的控制状态转换的所有参数来创建状态转换参数,并构建角色的动画状态机如下:

创建状态机

  创建完动画状态机之后,我们为状态之间的转移边设置条件。点击状态之间的转移边,然后在右侧的Inspector面板进行设置。

设置转移边

每条转移边的具体设置如下:

  • Empty -> Idle:
    • Has Exit Time: false
    • Interruption Source": Next State
    • Conditions: Grounded(true)
  • Idle -> Empty:
    • Has Exit Time: false
    • Conditions: Grounded(false)
  • Idle -> Walk:
    • Has Exit Time: false
    • Conditions: Speed(Greater 0.1)
  • Walk -> Idle:
    • Has Exit Time: false
    • Interruption Source": Next State
    • Conditions: Speed(Less 0.1)
  • Walk -> Empty:
    • Has Exit Time: false
    • Conditions: Grounded(false)
  • AnyState -> Jump:
    • Has Exit Time: false
    • Can Transition To Self: false
    • Conditions: Jump(Trigger)
  • Jump -> Empty:
    • Has Exit Time: true
    • Interruption Source": Next State
    • Conditions: List is Empty
  • AnyState -> Shoot:
    • Has Exit Time: false
    • Can Transition To Self: True
    • Conditions: Shoot(Trigger)
  • Shoot -> Empty:
    • Has Exit Time: true
    • Interruption Source": Current State then Next State
    • Conditions: List is Empty
  • AnyState -> Death:
    • Has Exit Time: false
    • Can Transition To Self: false
    • Conditions: Death(Trigger)
  • Death -> Falling:
    • Has Exit Time: true
    • Conditions: List is Empty

加入控制代码

  角色的动画状态机编辑完成之后,我们还需要使用代码来控制动画状态机的状态转移。打开PlayerController.cs,插入动画控制代码:

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

[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(AudioSource))]
[RequireComponent(typeof(Animator))]
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 Animator m_Animator;

    private void Awake() {
        // 获取组件引用
        m_Rigidbody2D = GetComponent<Rigidbody2D>();
        m_AudioSource = GetComponent<AudioSource>();
        m_Animator = GetComponent<Animator>();
    }

    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")
        );
        
        // 设置动画状态机控制参数
        m_Animator.SetBool("Grounded", m_GroundedStatus);

        // 着地时,如果当前不处于跳跃状态且按下了跳跃键,进入准备跳跃状态
        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");

        // 设置动画状态机控制参数
        m_Animator.SetFloat("Speed", Mathf.Abs(h));

        // 若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_Animator.SetTrigger("Jump");

        // 退出准备跳跃状态,避免重复跳跃
        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
        );
    }
}

修改动画和状态设置

  点击运行游戏,可以看到角色已经有了动画。但是,我们发现角色跳跃的时候,一直在重复播放Jump动画。这是因为我们在制作动画的时候,为了便于预览,Unity默认将我们的动画设置为循环动画。我们需要修改一下JumpShootDeath这三个动画的动画设置。打开Assets\Animation\Player,分别选中这三个动画,然后取消Loop Time的勾选。

修改动画设置

  此外,为了让优化动画效果,我们还可以在Animator设置各个状态的动画播放速度。这里,我们将JumpShoot这两个状态的Speed分别设置为2.54

修改状态设置

  修改完成之后,再次运行游戏,可以看到Jump动画的播放速度变快了。


后言

  至此,我们已经制作好了角色的动画状态机,并使用代码控制IdleWalkJump这三个动画的切换,剩下的动画控制将在后面提到。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay6分支下看到,读者可以clone这个仓库到本地进行查看。


参考链接

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

推荐阅读更多精彩内容