返回 登录
0

Unity3D实战之残影技术

我们使用Unity3D开发动作类游戏或者RPG游戏时,为了增加战斗的效果,经常会使用残影技术,该技术可以使角色的动作更具有观赏性,可以使游戏品质更上一个台阶。本章实现了游戏中经常使用的残影技术,从原理到技术实现,完整的一个残影架构系统,方便移植到各种类型游戏开发中去。下面先给大家展示一下阴影实现的效果如下图所示。
图片描述
3D残影与2D残影技术实现是截然不同的,2D残影的实现直接可以做成序列帧,然后放在程序里播放动画即可。而3D残影需要实现Mesh的克隆,克隆Mesh时需要注意的是残影每一帧都要去渲染,大家不要担心其帧数,在手机上跑没有任何问题,因为它只是生成几个Mesh,Mesh 的数量可以控制,Mesh身上的材质渲染也可以通过Shader去改变。它的原理就是在设定的时间内克隆出几个Mesh,因为残影需要一个淡入淡出效果,需要设置一个消失时间间隔,如果超出规定的时间就将其破坏掉,运行效果给人的感觉就好像一个人的速度快的只看到一个影子一样。这种效果在武侠小说里面经常会看到,它的实现跟以前实现的刀光拖尾原理非常类似,下面开始讲解残影的技术实现。
残影实现原理搞清楚了,接下来需要设计编码实现了,首先的问题是克隆出Mesh,这个需要每帧进行的,因为在实现残影的过程中,动作是一直播放的,这样才更具有观赏性,下面开始代码的编写,我们代码的名字是Canying.cs,先把完整的代码展示如下所示:

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

public class CanYing : MonoBehaviour {

    public float interval = 0.1f;
    public float lifeCycle = 2.0f;

    float lastCombinedTime = 0.0f;

    MeshFilter[] meshFilters = null;

    MeshRenderer[] meshRenderers = null;

    SkinnedMeshRenderer[] skinedMeshRenderers = null;

    List<GameObject> objs = new List<GameObject>();

    // Use this for initialization
    void Start () 
    {
        meshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
        skinedMeshRenderers = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
    }

    void OnDisable()
    {
        foreach (GameObject go in objs)
        {
            DestroyImmediate(go);
        }
        objs.Clear();
        objs = null;
    }
    // 每帧更新
    void Update () 
    {
        if (Time.time - lastCombinedTime > interval)
        {
            lastCombinedTime = Time.time;

            for (int i = 0; skinedMeshRenderers != null && i < skinedMeshRenderers.Length; ++i)
            {
                Mesh mesh = new Mesh();

                skinedMeshRenderers[i].BakeMesh(mesh);

                GameObject go = new GameObject();

                go.hideFlags = HideFlags.HideAndDontSave;
                MeshFilter meshFilter = go.AddComponent<MeshFilter>();
                meshFilter.mesh = mesh;

                MeshRenderer meshRenderer = go.AddComponent<MeshRenderer>();
                meshRenderer.material = skinedMeshRenderers[i].material;

                InitFadeInObj(go, skinedMeshRenderers[i].transform.position,
                    skinedMeshRenderers[i].transform.rotation, lifeCycle);
            }
            for (int i = 0; meshFilters != null && i < meshFilters.Length; ++i)
            {
                GameObject go = Instantiate(meshFilters[i].gameObject) as GameObject;
                InitFadeInObj(go, meshFilters[i].transform.position, meshFilters[i].transform.rotation, lifeCycle);
            }
        }
    }

    private void InitFadeInObj(GameObject go, Vector3 position, Quaternion rotation, float lifeCycle)
    {
        go.hideFlags = HideFlags.HideAndDontSave;
        go.transform.position = position;
        go.transform.rotation = rotation;

        FadInOut fi = go.AddComponent<FadInOut>();
        fi.lifeCycle = lifeCycle;
        objs.Add(go);
    }
}

说一下代码编写思路,在代码中声明了几个数组变量:

MeshFilter[] meshFilters = null;
    SkinnedMeshRenderer[] skinedMeshRenderers = null;

用于存放已有对象子类的meshFilters组件网格和SkinnedMeshRender组件蒙皮网格渲染,这个可以在Start初始化函数中有对其赋值,函数代码如下所示:

void Start () 
    {
        meshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
        skinedMeshRenderers = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
    }

残影的生成是在Update函数中,残影是有生命周期的,加了一个时间条件判断语句如下:

Time.time - lastCombinedTime > interval

接下来遍历蒙皮网格渲染组件SkinnedMeshRender,因为残影的生成是根据动作而变化的,每帧都要获取到角色动作,遍历skinedMeshRenderers组件代码如下:

for (int i = 0; skinedMeshRenderers != null && i < skinedMeshRenderers.Length; ++i)
{}

在遍历的过程中要自己创建生成网格,而且要把对应的网格根据SkinnedMeshRender组件的函数BaekMesh把动作蒙皮实现出来,再通过new GameObject重新生成一个对象,把其对应的组件逐步赋值给它也包括材质,这样就完成了残影的绘制,核心代码如下:

Mesh mesh = new Mesh();
                skinedMeshRenderers[i].BakeMesh(mesh);
                GameObject go = new GameObject();
                go.hideFlags = HideFlags.HideAndDontSave;
                MeshFilter meshFilter = go.AddComponent<MeshFilter>();
        meshFilter.mesh = mesh;
                MeshRenderer meshRenderer = go.AddComponent<MeshRenderer>();
                meshRenderer.material = skinedMeshRenderers[i].material;

在代码的最后调用了函数:

InitFadeInObj(go, skinedMeshRenderers[i].transform.position,
                    skinedMeshRenderers[i].transform.rotation, lifeCycle);

该函数主要作用是实现残影的淡入淡出效果。因为角色的生成的残影位置和旋转都是不同的,而且它们都有自己的生命周期,函数内容如下:

 go.hideFlags = HideFlags.HideAndDontSave;
        go.transform.position = position;
        go.transform.rotation = rotation;

        FadInOut fi = go.AddComponent<FadInOut>();
        fi.lifeCycle = lifeCycle;
        objs.Add(go);

在上面语句中增加了组件FadInOut用于淡入淡出效果,这个在后面会把代码给出。下面再把思路总结一下:在Update函数里面主要的作用是每一帧生成MeshFilter,同时捕捉每一帧的动作实时绘制Mesh。InitFadeInObj函数主要的作用是处理绘制出来的Mesh,并且按照一定的时间间隔将其销毁掉,同时为了表现出好的效果,可以用自己的Shader替换原有角色的Shader,详情查看笔者已出版书籍《Unity3D实战核心技术详解》一书。实现的效果:
图片描述
效果非常绚丽,该技术可以运行在移动端游戏中,目前该技术已应用到了项目开发中,其中残影的效果可以通过参数进行调整,比如残影绘制的时间间隔,残影的材质设置等。

作者简介:姜雪伟个人网页

评论