《学Unity的猫》——第十三章:Unity使用Animator控制动画播放,皮皮猫打字机游戏

简介:我是一名Unity游戏开发工程师,皮皮是我养的猫,会讲人话,它接到了喵星的特殊任务:学习编程,学习Unity游戏开发。
于是,发生了一系列有趣的故事。
在这里插入图片描述

13.1 皮皮猫打字机游戏

皮皮:“铲屎官,你为什么打字速度这么快?”
我:“一个字,练。”
皮皮:“教教我,怎么连打字速度。”
我:“来,我给你做一个打字练习游戏吧。”
我:“当你可以一分钟连击180次的时候,你就可以出山了。”
皮皮:“作为猫族,不能被速度打败。”
游戏画面如下:
在这里插入图片描述
模块设计如下
在这里插入图片描述

本工程使用的Unity版本为2020.1.14f1c1 (64-bit),工程已上传到GitHub,感兴趣的同学可以下载下来学习。
GitHub地址:https://github.com/linxinfa/Unity-TypeWriting-Game
在这里插入图片描述

13.2 场景制作
13.2.1 入口场景

EntryScene.unity
在这里插入图片描述
在这里插入图片描述
一个标题文本(Text组件),一个开始按钮(Button组件),一个难度选择勾选(ToggleGroupToggle组件)。
背景图使用SpriteRender组件在3D摄像机中渲染。
在这里插入图片描述
难度等级的勾选使用了ToggleGroup组件,用来给Toggle分组。
在这里插入图片描述
子节点中的Toggle需要指明相同的ToggleGroup
在这里插入图片描述
实现单选的效果。
在这里插入图片描述

13.2.2 游戏场景

GameScene.unity
在这里插入图片描述
在这里插入图片描述
一个血条(Slider组件),一个得分(Text组件),一个字母盘(GridLayoutGroupText组件)、一个连击(Text组件),一个角色(SpriteRendererAnimator组件)、一个背景图(SpriteRenderer组件)。
其中字母盘只做一个字母,游戏中进行动态克隆。
在这里插入图片描述
再做游戏结束面板,提供一个返回和重来的按钮。
在这里插入图片描述

13.2.3 场景切换

点击菜单File - Build Settings...
在这里插入图片描述
将场景添加到Scenes In Build中。
在这里插入图片描述
代码中,通过SceneManager.LoadScene切换场景,如下

// 进入GameScene场景
UnityEngine.SceneManagement.SceneManager.LoadScene(1);
13.3 游戏管理器

游戏管理器GameMgr,它需要包括数据和逻辑,如下。
在这里插入图片描述

13.3.1 数据定义
	/// <summary>
	/// 难度等级
	/// </summary>
	public int hardLevel { get; set; }
	
	
	/// <summary>
	/// 得分
	/// </summary>
	public int score { get; set; }
	
	/// <summary>
	/// 最大血量
	/// </summary>
	private const int MAX_BLOOD = 1500;
	
	/// <summary>
	/// 血量
	/// </summary>
	public int blood
	{
	    get { return m_blood; }
	    set
	    {
	        m_blood = value;
	        if (m_blood <= 0)
	        {
	            gameOver = true;
				// TODO 抛出事件
	        }
	    }
	}
	private int m_blood = 0;
	
	/// <summary>
	/// 连击数量
	/// </summary>
	public int comboCnt { get; set; }
	/// <summary>
	/// 连击定时器
	/// </summary>
	public float comboTimer { get; set; }
	
	/// <summary>
	/// 游戏结束
	/// </summary>
	public bool gameOver { get; private set; }
	
	/// <summary>
	/// 按键列表
	/// </summary>
	public List<KeyCode> keyList { get { return m_keyList; } }
	private List<KeyCode> m_keyList = new List<KeyCode>();
13.3.2 生成字母盘

生成字母盘(16个字母),要求每个字母都不重复,生成的字母存到m_keyList中。
在这里插入图片描述

 	/// <summary>
    /// 生成字母盘
    /// </summary>
    private void GenKeys()
    {
        for (int i = 0; i < 16; ++i)
        {
            m_keyList.Add(GenOneKey());
        }
    }

    /// <summary>
    /// 生成一个字母
    /// </summary>
    /// <returns></returns>
    private KeyCode GenOneKey()
    {
        var key = (KeyCode)UnityEngine.Random.Range((int)KeyCode.A, (int)KeyCode.Z);
        for(int i=0,cnt=m_keyList.Count;i<cnt;++i)
        {
            if(m_keyList[i] == key)
            {
                // 如果生成的字母已存在,则递归生成
                return GenOneKey();
            }
        }
        return key;
    }
13.3.3 按键判断

我们需要先判断按键类型,封装一个接口GetKeyDownCode

    /// <summary>
    /// 获取按键类型
    /// </summary>
    /// <returns></returns>
    public KeyCode GetKeyDownCode()
    {
        if (Input.anyKeyDown)
        {
            foreach (KeyCode keyCode in Enum.GetValues(typeof(KeyCode)))
            {
                if (Input.GetKeyDown(keyCode))
                {
                    return keyCode;
                }
            }
        }
        return KeyCode.None;
    }

然后判断按下的按键是否在字母盘中,返回对应的索引,如果不在字母盘中,则返回-1。

    /// <summary>
    /// 判断按键是否在字母盘中
    /// </summary>
    /// <param name="key">按键</param>
    /// <returns></returns>
    private int IsKeyBingo(KeyCode key)
    {
        for (int i = 0, cnt = m_keyList.Count; i < cnt; ++i)
        {
            if (m_keyList[i] == key)
                return i;
        }
        return -1;
    }

按键正确的时候,执行连击计算,加血加分,生成新的字母,抛事件更新ui

    /// <summary>
    /// 按键正确
    /// </summary>
    private void OnKeyBingo(int bingoIndex)
    {
        // 加连击
        ++comboCnt;

        if (comboCnt >= 3)
        {
            // 加血加分,连击加持
            blood += 150;
            if (blood > MAX_BLOOD)
                blood = MAX_BLOOD;
            score += 20;
        }
        else
        {
            // 加血加分
            blood += 50;
            score += 10;
        }
		// 生成新的字母
        var oldKey = m_keyList[bingoIndex];
        var newKey = GenOneKey();
        m_keyList[bingoIndex] = newKey;

        // TODO 抛事件,更新ui
    }

按键错误的时候,连击中断,扣血,抛事件更新ui

    /// <summary>
    /// 按键错误
    /// </summary>
    private void OnKeyError()
    {
        // 连击中断
        comboCnt = 0;
        // 扣血
        blood -= 30;
        // TODO 抛事件,更新ui
    }
13.3.4 连击定时器

如下,其中Time.deltaTime是一帧的间隔时间。每帧调用UpdateComboTimer,对comboTimer进行帧间隔时间递减,通过comboTimer判断是否超过时间限制,超过则中断连击。

    /// <summary>
    /// 连击定时器
    /// </summary>
    public void UpdateComboTimer()
    {
        if (comboTimer > 0)
        {
            comboTimer -= Time.deltaTime;
            // 超过时间限制,连击断开
            if (comboTimer <= 0)
            {
                comboCnt = 0;
                // TODO 抛事件更新连击ui
            }
        }
    }
13.4 动画控制器Animator

Unity可以用两种方式控制动画
1 Animation,这种方式简单,直接 Play(“Idle”)或者CorssFade(“Idle”)就可以播放动画;
2 AnimatorUnity5.x之后推荐使用这种方式,因为里面可以加上混合动画,让动画切换更加平滑。

13.4.1 添加Animator

点击菜单Window - Animation - Animation,可以打开Animation窗口,快捷键是Ctrl+6
在这里插入图片描述
选中某个物体后,可以为该物体添加或编辑动画,比如选中一个空物体,由于没有动画,会出现一个Create按钮。在这里插入图片描述
点击Create按钮,会弹出窗口设置文件保存路劲。
在这里插入图片描述
创建成功后,物体上会出现一个Animator组件。
在这里插入图片描述

并且我们可以在目录中看到生成了两个文件。
在这里插入图片描述
.controller文件是一个动画状态机,在Unity中双击它会打开Animator窗口,即可看到里面的内容,我们可以在这个窗口中组织各个动画文件。
在这里插入图片描述
.anim是动画文件,在Unity中双击它会打开Animation窗口,我们可以在这个窗口中制作动画。
在这里插入图片描述

13.4.2 Animator状态机

每个Animator Controller都会自带三个状态:Any State, EntryExit
在这里插入图片描述

13.4.2.1 Any State状态

表示任意状态的特殊状态。例如我们如果希望角色在任何状态下都有可能切换到死亡状态,那么Any State就可以帮我们做到。当你发现某个状态可以从任何状态以相同的条件跳转到时,那么你就可以用Any State来简化过渡关系。

13.4.2.2 Entry状态

表示状态机的入口状态。当我们为某个GameObject添加上Animator组件时,这个组件就会开始发挥它的作用。
如果Animator Controller控制多个Animation的播放,那么默认情况下Animator组件会播放哪个动画呢? 由Entry来决定的。
但是Entry本身并不包含动画,而是指向某个带有动画的状态,并设置其为默认状态。被设置为默认状态的状态会显示为 橘黄色。
在这里插入图片描述
当然,你可以随时在任意一个状态上通过 鼠标右键->Set as Layer Default State更改默认状态。
在这里插入图片描述

记住, EntryAnimator组件被激活后 无条件 跳转到默认状态,并且每个Layer有且仅有一个默认状态。

13.4.2.3 Exit状态

表示状态机的出口状态,以红色标识。如果你的动画控制器只有一层,那么这个状态可能并没有什么卵用。但是当你需要从子状态机中返回到上一层(Layer)时,把状态指向Exit就可以了。
在这里插入图片描述

13.4.3 动画状态的属性

我们可以选中某个自定义状态,并在Inspector窗口下观察它具有的属性
在这里插入图片描述

属性名描述
Motion状态对应的动画。每个状态的基本属性,直接选择已定义好的动画(Animation Clip)即可
Speed动画播放的速度。默认值为1,表示速度为原动画的1.0倍。
Mutiplier勾选右侧的Parameter后可用,即在计算Speed的时考虑 区域1 中定义的某个参数。若选择的参数为smooth, 则动画播放速度的计算公式为 smooth * speed * fps(animation clip中指定)
Mirror仅适用于humanoid animation(人型机动画)
Cycle Offset周期偏移,取值范围为0-1.0,用于控制动画起始的偏移量。把它和正弦函数的offset进行对比就能够理解了,只会影响起始动画的播放位置。
Foot IK仅适用于humanoid animation(人型机动画)
Write Default最好保持默认,感兴趣可以参考官方手册
Transitions该状态向其他状态发起的过渡列表,包含了Solo和Mute两个参数,在预览状态机的效果时起作用
Add Behaviour用于向状态添加“行为
13.4.4 状态间的过渡关系(Transitions)

状态间的过渡关系,直观上说它们就是连接不同状态的有向箭头。

在这里插入图片描述
要创建一个从状态A状态B的过渡,直接在状态A上 鼠标右键 - Make Transition并把出现的箭头拖拽到状态B上点击鼠标左边即可。
在这里插入图片描述

13.4.5 添加状态控制参数

参数有FloatIntBoolTrigger
在这里插入图片描述
FloatInt用来控制一个动画状态的参数,比如速度方向等可以用数值量化的东西,
Bool用来控制动画状态的转变,比如从走路转变到跑步,
Trigger本质上也是bool类型,但它默认为false,且当程序设置为true后,它会自动变回false

如下这里创建一个Int类型的参数AnimState
在这里插入图片描述

13.4.6 编辑切换状态的条件

点击连线,在Inspecter窗口中可以进行设置,在Conditions栏下可以添加条件,如下图表示当参数
AnimState0时会执行这个动画Any StateNew Animation2的过渡

必须在Parameters面板中添加了参数才可以在这里查看到,其次添加的条件为&& ”与” 关系,即必须同时满足。

在这里插入图片描述

13.5 猫娘动画状态机
13.5.1 模型资源下载

Assets Store上下载猫娘模型,资源地址:https://assetstore.unity.com/packages/2d/characters/fancydoll-c000-little-cat-girl-112776
在这里插入图片描述

13.5.2 动画循环设置

模型自带了一些动画
在这里插入图片描述
idle(站立)、walk(走路)、run(跑)需要循环播放,勾选Loop Time
在这里插入图片描述
get_hit(受击)、die(阵亡)不需要循环播放,不勾选Loop Time
在这里插入图片描述

13.5.3 状态过渡设置

我们需要通过Animator将这些动画进行合理的组织,如下
在这里插入图片描述
添加变量Action,过渡条件根据Action的值进行判断。
在这里插入图片描述
过渡条件如下

状态1状态2条件
Any StateidleAction == 1
Any Stateget_hitAction == 4
get_hitidle
Any StatedieAction == 5
13.6 角色动画控制器

角色动画控制器CharacterAniCtrler,它需要包括数据和逻辑,如下。
在这里插入图片描述
运行中的状态过渡
在这里插入图片描述

13.6.1 数据定义
	/// <summary>
	/// 状态定义,默认为Idle状态。
	/// </summary>
	public enum CharacterAniId
	{
	    Idle = 1,
	    Walk = 2,
	    Run = 3,
	
	
	    Hit = 4,
	    Death = 5,
	}
	
    /// <summary>
    /// 角色动画Animator组件
    /// </summary>
	private Animator m_animator;
	
	/// <summary>
    /// 动画队列
    /// </summary>
    private Queue<int> m_animQueue = new Queue<int>();
13.6.2 直接播放动画接口
    /// <summary>
    /// 立即播放某个动画
    /// </summary>
    /// <param name="name">动画名称</param>
    private void PlayAniImmediately(string name)
    {
        if (IsDeath) return;
        m_animator.CrossFade(name, 0.1f, 0);
    }

如立即播放走路动画

    public void PlayWalk()
    {
        PlayAniImmediately("walk");
    }
13.6.3 设置变量值
// 设置Action变量值为4
 m_animator.SetInteger("Action", 4);

封装成接口

	private const string STR_ACTION = "Action";
	
    /// <summary>
    /// 播放不同动作ID
    /// </summary>
    /// <param name="isJump"></param>
    /// 
    public void PlayAnimation(int actionID)
    {
        if (IsDeath) return;
        if (m_animator == null)
            return;
        if (!m_animator.isInitialized || m_animator.IsInTransition(0))
        {
            // 如果正在过渡,则先塞到队列中
            m_animQueue.Enqueue(actionID);
            return;
        }
        m_animator.SetInteger(STR_ACTION, actionID);
    }
13.6.4 根据状态队列设置状态

提供一个LateUpdate接口每帧调用,设置了Action值需要在下一帧的时候重置为0,然后从队列中取下一个状态进行处理。

    /// <summary>
    /// 每帧调用
    /// </summary>
    public void LateUpdate()
    {
        if (m_animator == null)
        {
            return;
        }
        if (!m_animator.isInitialized || m_animator.IsInTransition(0))
        {
            return;
        }
        if (null == mClips)
            mClips = m_animator.GetCurrentAnimatorClipInfo(0);
        if (null == mClips || mClips.Length == 0)
            return;

        int actionID = m_animator.GetInteger(STR_ACTION);
        if (actionID > 0)
        {
            //将Action复位
            m_animator.SetInteger(STR_ACTION, 0);
        }

        //将剩余队列的动作重新拿出来播放
        PlayRemainAction();
    }

    /// <summary>
    /// 将剩余队列的动作重新拿出来播放
    /// </summary>
    void PlayRemainAction()
    {
        if (m_animQueue.Count > 0)
        {
            PlayAnimation(m_animQueue.Dequeue());
        }
    }
13.7 入口场景脚本

入口场景脚本EntryScene.cs挂在Canvas上,设置Start Game BtnTgl Group
在这里插入图片描述
代码如下

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class EntryScene : MonoBehaviour
{
    public Button startGameBtn;
    public ToggleGroup tglGroup;

    void Start()
    {
        startGameBtn.onClick.AddListener(() =>
        {
            // 根据勾选,缓存难度等级
            foreach (var item in tglGroup.ActiveToggles())
            {
                GameMgr.Instance.hardLevel = int.Parse(item.name);
                break;
            }

            // 进入Game场景
            SceneManager.LoadScene(1);
        });
    }
}
13.8 游戏场景脚本

游戏场景脚本GameScene.cs挂在Canvas上,设置公开的成员对象。
在这里插入图片描述
主要根据各种事件更新ui

using System;
using UnityEngine;
using UnityEngine.UI;

public class GameScene : MonoBehaviour
{

    public Animator anitor;
    public Text comboText;
    public Text scoreText;
    public Slider bloodSlider;
    public Image bloodImage;
    public GameOverDlg gameOverDlg;
    public KeyGrid keyGrid;

    private CharacterAniCtrler m_aniCtrler;


    private void Awake()
    {
        // 注册事件
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_KEY_BINGO_INDEX, OnEventKeyBingoIndex);
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_COMBO, OnEventCombo);
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_PLAY_ANI, OnEventPlayAni);
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_UPDATE_SCORE, OnEventUpdateScore);
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_RESTART_GAME, OnEventRestartGame);
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_GAMEOVER, OnEventGameOver);

        m_aniCtrler = new CharacterAniCtrler();
        m_aniCtrler.Init(anitor);

        // 开始游戏
        StartGame();
    }

    private void OnDestroy()
    {
        // 注销事件
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_KEY_BINGO_INDEX, OnEventKeyBingoIndex);
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_COMBO, OnEventCombo);
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_PLAY_ANI, OnEventPlayAni);
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_UPDATE_SCORE, OnEventUpdateScore);
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_RESTART_GAME, OnEventRestartGame);
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_GAMEOVER, OnEventGameOver);
    }

    /// <summary>
    /// 开始游戏
    /// </summary>
    private void StartGame()
    {
        GameMgr.Instance.Init();
        TextEffect.Init();
        // 初始化血量
        bloodSlider.maxValue = GameMgr.Instance.blood;
        bloodSlider.value = GameMgr.Instance.blood;
        bloodImage.enabled = true;
        // 生成字母盘
        keyGrid.CreateKeyList(GameMgr.Instance.keyList);

        comboText.gameObject.SetActive(false);
        scoreText.text = "0";
        gameOverDlg.Hide();
    }

   

    void Update()
    {
        if (GameMgr.Instance.gameOver) return;
        // 更新连击定时器
        GameMgr.Instance.UpdateComboTimer();

        // 更新血量ui
        bloodSlider.value = GameMgr.Instance.blood;
        GameMgr.Instance.blood -= GameMgr.Instance.hardLevel;

        // 按键判断
        var keyCode = GameMgr.Instance.GetKeyDownCode();
        if (KeyCode.None == keyCode) return;
        GameMgr.Instance.OnKey(keyCode);
    }

    private void LateUpdate()
    {
        // 更新动画控制器
        m_aniCtrler.LateUpdate();
    }

    /// <summary>
    /// 按键正确事件
    /// </summary>
    /// <param name="args"></param>
    private void OnEventKeyBingoIndex(params object[] args)
    {
        int index = (int)args[0];
        KeyCode oldKey = (KeyCode)args[1];
        KeyCode newKey = (KeyCode)args[2];
        keyGrid.UpdateKeyByIndex(index, oldKey, newKey);
    }

    /// <summary>
    /// 连击事件
    /// </summary>
    /// <param name="args"></param>
    private void OnEventCombo(params object[] args)
    {
        var combo = (int)args[0];
        comboText.text = "连击" + combo;
        comboText.gameObject.SetActive(combo >= 3);
    }

    /// <summary>
    /// 播放动画事件
    /// </summary>
    /// <param name="args"></param>
    private void OnEventPlayAni(params object[] args)
    {
        var ani = (string)args[0];
        switch (ani)
        {
            case "idle": m_aniCtrler.PlayAnimation((int)CharacterAniId.Idle); break;
            case "walk": GameMgr.Instance.comboTimer = 0.5f; m_aniCtrler.PlayWalk(); break;
            case "run": GameMgr.Instance.comboTimer = 0.5f; m_aniCtrler.PlayRun(); break;
            case "hit": m_aniCtrler.PlayAnimation((int)CharacterAniId.Hit); break;
            case "die": m_aniCtrler.PlayDieImmediately(); break;
        }
    }

    /// <summary>
    /// 更新得分事件
    /// </summary>
    /// <param name="args"></param>
    private void OnEventUpdateScore(params object[] args)
    {
        var score = (int)args[0];
        scoreText.text = score.ToString();
    }

    /// <summary>
    /// 游戏结束事件
    /// </summary>
    /// <param name="args"></param>
    private void OnEventGameOver(params object[] args)
    {
        bloodImage.enabled = false;
        gameOverDlg.Show(GameMgr.Instance.score);
    }

    /// <summary>
    /// 重新开始游戏事件
    /// </summary>
    /// <param name="args"></param>
    private void OnEventRestartGame(params object[] args)
    {
        m_aniCtrler.PlayReviveImmediately();
        StartGame();
    }
}

其中事件定义如下

/// <summary>
/// 事件定义
/// </summary>
public class EventNameDef 
{
    /// <summary>
    /// 按键正确事件
    /// </summary>
    public const string EVENT_KEY_BINGO_INDEX = "EVENT_KEY_BINGO_INDEX";
    /// <summary>
    /// 连击事件
    /// </summary>
    public const string EVENT_COMBO = "EVENT_COMBO";
    /// <summary>
    /// 播放动画事件
    /// </summary>
    public const string EVENT_PLAY_ANI = "EVENT_PLAY_ANI";
    /// <summary>
    /// 游戏结束事件
    /// </summary>
    public const string EVENT_GAMEOVER = "EVENT_GAMEOVER";
    /// <summary>
    /// 更新得分事件
    /// </summary>
    public const string EVENT_UPDATE_SCORE = "EVENT_UPDATE_SCORE";
    /// <summary>
    /// 重新开始游戏事件
    /// </summary>
    public const string EVENT_RESTART_GAME = "EVENT_RESTART_GAME";
}
13.9 文字特效动画

在这里插入图片描述
制作一个TextEffect.prefab预设,添加动画如下。
在这里插入图片描述
由于游戏中需要重复显示这个特效,所以采用对象池方式。
特效动画结束时回收到对象池中,这样可以反复利用。为了监听动画结束,在动画的最后一帧添加帧事件。
在这里插入图片描述
创建一个TextEffect.cs脚本,挂到预设上,提供一个OnAnimationEnd共有方法

public void OnAnimationEnd()

这样就可以设置帧事件的响应函数了
在这里插入图片描述
TextEffect.cs脚本如下

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

/// <summary>
/// 文本特效
/// </summary>
public class TextEffect : MonoBehaviour
{

    /// <summary>
    /// 初始化
    /// </summary>
    public static void Init()
    {
        if(null != s_root)
        {
            Destroy(s_root.gameObject);
            s_root = null;
        }
        
        s_objPool.Clear();
        var canvas = GameObject.Find("Canvas");
        if (null != canvas)
        {
            var rootObj = new GameObject("EffectRoot");
            s_root = rootObj.transform;
            s_root.SetParent(canvas.transform, false);
        }
    }


    /// <summary>
    /// 显示特效
    /// </summary>
    /// <param name="text"></param>
    /// <param name="pos"></param>
    public static void Show(string text, Vector3 pos)
    {
        if (null == s_prefab)
        {
            s_prefab = Resources.Load<GameObject>("TextEffect");
        }

        TextEffect bhv = null;
        if (s_objPool.Count > 0)
        {
            // 从对象池中取对象,
            bhv = s_objPool.Dequeue();
        }
        else
        {
            var obj = Instantiate(s_prefab);
            obj.transform.SetParent(s_root, false);
            bhv = obj.GetComponent<TextEffect>();
        }
        bhv.gameObject.SetActive(true);
        bhv.transform.position = pos;
        bhv.keyText.text = text;
    }

    /// <summary>
    /// 动画结束事件的响应函数
    /// </summary>
    public void OnAnimationEnd()
    {
        gameObject.SetActive(false);
        // 对象回收
        s_objPool.Enqueue(this);
    }

    private static GameObject s_prefab;
    /// <summary>
    /// 对象池
    /// </summary>
    private static Queue<TextEffect> s_objPool = new Queue<TextEffect>();
    /// <summary>
    /// 根节点
    /// </summary>
    private static Transform s_root;

    /// <summary>
    /// 文字组件
    /// </summary>
    public Text keyText;
}

完成。
如果有什么疑问,欢迎留言或私信。


《学Unity的猫》——第十四章:Unity实现文件上传下载,支持续传,猫后爪的秘密

  • 12
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值