• 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画


    系列目录

    【Unity3D基础】让物体动起来①--基于UGUI的鼠标点击移动

    【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动

    时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现

    时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画

    时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率

    原理

    看过前篇的朋友,一定能猜到这篇的内容了,2D人物动画,这是一个老生常谈的话题,很多人都写过或者提供过类似的代码,本文还是遵守着重原理,代码次之的原则。下面是根据以前自己学习的时候学习“深蓝色右手”WPF游戏教程的“WPF/Silverlight动画及游戏系列教程”,先结合Unity3d技术改编的原理文字

    动态实现2D人物角色动画目前有两种主流方法,下面我会分别进行介绍。
          第一种方法我称之为图片切换法,准备工作:首先通过3DMAX等工具3D渲染2D的方法制作出角色,然后将角色每个动作均导出8个方向每方向若干帧的系列图片(如果是有方向的魔法图片,很多2D-MMORPG往往会导出16个方向的系列帧图片以求更为逼真),即将每个人物每个动作的各方向的每帧均存成一张图片,如下图仅以从破天一剑游戏中提取的素材为例:

    (特别申明:本系列教程所使用的如有注明归属权的图片素材均来源于网络,请勿用于商业用途,否则造成的一切后果均与本人无关。)

    1

    从上图可以看到,我将人物向右方跑步共8帧图片通过Photoshop分别将画布等比例扩大成150*150象素图片(因为是提取的素材,初始宽和高是不均衡值,所以必须扩大成自己的需求,这样人物会在图片中居中,并且为后期加入武器或坐骑留好余地。稍微的偏离也可以在后期进行微调),并将他们从开始到结束分别命名为0.png,1.png,2.png,3.png,4.png,5.png,6.png,7.png,然后将这8张图片保存到相关目录下,到此准备工作终于结束了

    这里在WPF中有一个UI线程级别的定时器DispatcherTimer,而Unity中没有提供类似的机制(或许是我不知道),Unity主要是心跳来控制的也就是Update函数了,但是这里的原理就是帧动画,每个多少帧变化一下player的动作图片即可,但我们知道帧就是和时间相关的。

    简单的说:就是定义一个图片数组,然后实现一个定时器,时间到了就获取数组里的一张图,替换精灵的背景图片。

    1(1)

    实现

    这里我们把问题分解主要是两个子问题,一、定时获取图片替换精灵背景,简称定时器;二、数组的图片循环获取,简称数组顺序遍历

    先从软柿子开始,二比较简单,一个数组,加一个全局基数器变量 搞定

    private int currentTexture = 0;
    public Sprite[] textureArray;
    private SpriteRenderer spriteRenderer;

    //遍历数组 到数组未重新回到0索引

    void NextTexture()
    {
        currentTexture++;
        if (currentTexture >= textureArray.Length)
        {
            currentTexture = 0;
        }

        spriteRenderer.sprite = textureArray[currentTexture];
    }

    一、定时器,稍微麻烦点,Unity3d并没有提供像样的UI定时器封装,这里为了验证 这种定帧动画的原理,我用几种Unity3d中定时器机制分别实现了动画功能,实际开发中用的A和D方法比较多,至少我查了不少教程基本是A和D

    首先是变量

    private float animationDeltaTime;
    private float animationDelay = 5 / 60f;

    A、Update 心跳延时定时器

    void Update()
    {
        animationDeltaTime += Time.deltaTime;
        // Debug.Log(animationDeltaTime);
        if (animationDeltaTime >= animationDelay)
        {
            animationDeltaTime = 0;

            NextTexture();
        }
    }

    B、协程递归定时器

    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>() as SpriteRenderer;
        StartCoroutine(TextureChanger()); 
    }

    IEnumerator TextureChanger()
    {
        yield return new WaitForSeconds(animationDelay);
        if (true)
        {
            //Debug.Log(animationDeltaTime);
            NextTexture();
            StartCoroutine(TextureChanger());
        }
    }

    C、InvokeRepeating定时器

    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>() as SpriteRenderer;
        InvokeRepeating("NextTexture", 1, 0.1f);//1秒后调用LaunchProjectile () 函数,之后每5秒调用一次     
    }

    D、时长求余法(我自己起的名字,比较巧妙可能也是用的比较多的方法)

    using UnityEngine;
    using System.Collections;
    
    public class PlayerAnimator : MonoBehaviour {
    
        public Sprite[] sprites;
        public float framesPerSecond;
    
        private SpriteRenderer spriteRenderer;
        // Use this for initialization
    	void Start () {
            spriteRenderer = GetComponent<Renderer>() as SpriteRenderer;
    	}
    	
    	// Update is called once per frame
    	void Update () {
            int timeIndex = (int)(Time.timeSinceLevelLoad * framesPerSecond);
            int index = timeIndex % sprites.Length;
            spriteRenderer.sprite = sprites[index];
    
    	}
    }
    

      

    原理的代码分析和代码展示完毕,下面是自己在网上找的前人分享的一些代码,自测可以运行,主要的问题还是一句老话,“原理很简单,现实很残酷”,实际一个简单的2d动画涉及的东西很多,比如性能效率,状态控制,封装合理性等等吧。

    A、Unity3d UGUI序列帧动画 实现 (原文地址:http://www.cnblogs.com/mrblue/p/5191183.html)

    using UnityEngine;
    
    using System.Collections;
    
    using System.Collections.Generic;
    
    using UnityEngine.UI;
    
    using System;
    
    [RequireComponent(typeof(Image))]
    
    public class UGUISpriteAnimation : MonoBehaviour
    
    {
    
        private Image ImageSource;
    
        private int mCurFrame = 0;
    
        private float mDelta = 0;
    
        public float FPS = 5;
    
        public List<Sprite> SpriteFrames;
    
        public bool IsPlaying = false;
    
        public bool Foward = true;
    
        public bool AutoPlay = false;
    
        public bool Loop = false;
    
        public int FrameCount
    
        {
    
            get
    
            {
    
                return SpriteFrames.Count;
    
            }
    
        }
    
        void Awake()
    
        {
    
            ImageSource = GetComponent<Image>();
    
        }
    
        void Start()
    
        {
    
            if (AutoPlay)
    
            {
    
                Play();
    
            }
    
            else
    
            {
    
                IsPlaying = false;
    
            }
    
        }
    
        private void SetSprite(int idx)
    
        {
    
            ImageSource.sprite = SpriteFrames[idx];
    
            ImageSource.SetNativeSize();
    
        }
    
        public void Play()
    
        {
    
            IsPlaying = true;
    
            Foward = true;
    
        }
    
        public void PlayReverse()
    
        {
    
            IsPlaying = true;
    
            Foward = false;
    
        }
    
        void Update()
    
        {
    
            if (!IsPlaying || 0 == FrameCount)
    
            {
    
                return;
    
            }
    
            mDelta += Time.deltaTime;
    
            if (mDelta > 1 / FPS)
    
            {
    
                mDelta = 0;
    
                if(Foward)
    
                {
    
                    mCurFrame++;
    
                }
    
                else
    
                {
    
                    mCurFrame--;
    
                }
    
                if (mCurFrame >= FrameCount)
    
                {
    
                    if (Loop)
    
                    {
    
                        mCurFrame = 0;
    
                    }
    
                    else
    
                    {
    
                        IsPlaying = false;
    
                        return;
    
                    }
    
                }
    
                else if (mCurFrame<0)
    
                {
    
                    if (Loop)
    
                    {
    
                        mCurFrame = FrameCount-1;
    
                    }
    
                    else
    
                    {
    
                        IsPlaying = false;
    
                        return;
    
                    }        
    
                }
    
                SetSprite(mCurFrame);
    
            }
    
        }
    
        public void Pause()
    
        {
    
            IsPlaying = false;
    
        }
    
        public void Resume()
    
        {
    
            if (!IsPlaying)
    
            {
    
                IsPlaying = true;
    
            }
    
        }
    
        public void Stop()
    
        {
    
            mCurFrame = 0;
    
            SetSprite(mCurFrame);
    
            IsPlaying = false;
    
        }
    
        public void Rewind()
    
        {
    
            mCurFrame = 0;
    
            SetSprite(mCurFrame);
    
            Play();
    
        }
    
    }
    

      

    B、Native2D 序列帧动画 实现

    这部分代码已经在上文“D、时长求余法(我自己起的名字,比较巧妙可能也是用的比较多的方法)”中贴出,这里不再重复

    总结

    实际上“序列帧动画”的实现原理很简单,就是一个定时器,但是Unity3d偏偏没有封装定时器,所以就需要我们深刻了解其的特性,然后选最优的方式(虽然前人已经栽树了),万里长征第一步继续吧。

  • 相关阅读:
    Gitkraken使用教程
    request.getHeader中区分大小写参数
    MySql 中查询列表中添加序号
    解决windows 下mysql 表名自动转成小写的问题
    MYSQL服务无法启动,服务没有任何错误;解决方法
    idea中设置一键生成方法注释和类注释
    Windows10下安装MySQL8.0.21-64
    navicat连接mysql出现2059错误的解决方法
    解决tomca在eclipse中正常启动,在bin下启动闪退问题
    QT线程的结束
  • 原文地址:https://www.cnblogs.com/IlidanStormRage/p/6043642.html
Copyright © 2020-2023  润新知