在Unity中如何让角色死的更漂亮 VR资源

manew_JR 2017-08-18 15:45:34
 
 
你好,我们是一个小型的独立游戏工作室,被称为“醉酒星期一”。
 
我们最近在上做了一个游戏,你在一个竞技场里跑来跑去,用巨大的斧头绕着你自己旋转,并试图攻击其他玩家。Good smash - good kill.
 
我们使用基于物理的ragdoll动画来让死亡看起来更逼真。开始的时候一切都很好。
 
然而,当人物和计算的数量开始增长时,游戏开始缓慢而落后于旧手机。禁用所有物理计算给了我们50 - 60个fps和绝对平滑度,但我们不想放弃角色的酷炫死亡画面。
 
 
其中一个解决方案是强迫animator制作一组死亡动画。但是我们有一个好主意,把一些布娃娃的死亡直接记录在Unity中,然后显示出想要的动画?死亡将会变得多样化,不需要占据animator,最重要的是——一切都会很快,美丽和现实。
 
我们最终的效果
 
 
 
实现
 
Unity中的动画剪辑由Clip类提供,该类包含一个AnimationCurve数组。AnimationCurve为特定对象的某一特定属性的更改定义了曲线,例如,localPosition.x。时间轴上的值变化由一些关键帧结构描述。
 
这个想法很简单:对于每个字符对象的每个属性,我们创建一个AnimationCurve并将该属性的值存储在每个框架的曲线上。生成的AnimationClip通过AssetDatabase导出。最后生成CreateAsset。
 
 
让我们创建类AnimationRecorderItem来跟踪每个字符对象。监视对象的所有属性都将通过字典来描述,其中键是属性的名称,值是动画曲线。
 
 
01
02
03
04
05
06
07
08
09
10
11
12
13
Properties = new Dictionary<string, AnimationCurve> ();
  
Properties.Add ( "localPosition.x", new AnimationCurve () );
Properties.Add ( "localPosition.y", new AnimationCurve () );
Properties.Add ( "localPosition.z", new AnimationCurve () );
  
Properties.Add ( "localRotation.x", new AnimationCurve () );
Properties.Add ( "localRotation.y", new AnimationCurve () );
Properties.Add ( "localRotation.z", new AnimationCurve () );
Properties.Add ( "localRotation.w", new AnimationCurve () );
  
Properties.Add ( "localScale.x", new AnimationCurve () );
Properties.Add ( "localScale.y", new AnimationCurve () );
Properties.Add ( "localScale.z", new AnimationCurve () );

对于每个框架中的所有对象属性,都将设置它们的当前值:
 
01
02
03
04
05
06
07
08
09
10
11
Properties["localPosition.x"].AddKey (new Keyframe (time, _animObj.localPosition.x, 0.0f, 0.0f));
Properties["localPosition.y"].AddKey (new Keyframe (time, _animObj.localPosition.y, 0.0f, 0.0f));
Properties["localPosition.z"].AddKey (new Keyframe (time, _animObj.localPosition.z, 0.0f, 0.0f));
 
Properties["localRotation.x"].AddKey (new Keyframe (time, _animObj.localRotation.x, 0.0f, 0.0f));
Properties["localRotation.y"].AddKey (new Keyframe (time, _animObj.localRotation.y, 0.0f, 0.0f));
Properties["localRotation.z"].AddKey (new Keyframe (time, _animObj.localRotation.z, 0.0f, 0.0f));
Properties["localRotation.w"].AddKey (new Keyframe (time, _animObj.localRotation.w, 0.0f, 0.0f));
 
Properties["localScale.x"].AddKey (new Keyframe (time, _animObj.localScale.x, 0.0f, 0.0f));
Properties["localScale.y"].AddKey (new Keyframe (time, _animObj.localScale.y, 0.0f, 0.0f));
Properties["localScale.z"].AddKey (new Keyframe (time, _animObj.localScale.z, 0.0f, 0.0f));
 
但是,如果您为每个对象的每个属性记录每个帧的所有值,那么动画的输出文件将会变得太大。让我们介绍一下限制最小变化的条件与前面的框架相比。如果对象移动了,增加和转动了一点,我们将不会记录这些变化。
 
另外,我们还需要创建一个manager类 —AnimationRecorder.。
 
这个脚本应该通过动画对象的所有子元素执行,并为每个对象创建一个animation记录器的实例。
同时,它还会立即生成并记住它将被保存在AnimationClip中的相对路径。
 
根据文档,相对路径生成如下:
 
Quote
这条曲线适用于游戏对象的路径。相对路径的格式类似于pathname。e.g. "root/spine/leftArm". 如果相对路径为空,则表示动画剪辑所附的游戏对象。
 
代码将会是:
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private List<AnimationRecorderItem> _recorders;
  
void Start ()
{
    Configurate ();
}
  
void Configurate ()
{
    _recorders = new List<AnimationRecorderItem> ();
  
    var allTransforms = gameObject.GetComponentsInChildren< Transform > ();
    for ( int i = 0; i < allTransforms.Length; ++i )
    {
        string path = CreateRelativePathForObject ( transform, allTransforms [ i ] );
        _recorders.Add( new AnimationRecorderItem ( path, allTransforms [ i ] ) );
    }
}
  
private string CreateRelativePathForObject ( Transform root, Transform target )
{
    if ( target == root )
    {
        return string.Empty;
    }
  
    string name = target.name;
    Transform bufferTransform = target;
  
    while ( bufferTransform.parent != root )
    {
        name = string.Format ( "{0}/{1}", bufferTransform.parent.name, name );
        bufferTransform = bufferTransform.parent;
    }
    return name;
}
 
计算当前动画时间并记录每个帧的属性值:
 
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
private float _recordingTimer;
private bool _recording = false;
  
void Update ()
{
  
    if ( _recording )
    {
        for ( int i = 0; i < _recorders.Count; ++i )
        {
            _recorders [ i ].AddFrame ( _recordingTimer );
        }
        _recordingTimer += Time.deltaTime;
    }
}
 
但是更新函数经常被调用,并且记录动画的每一个帧都是冗余的,所以我们限制了记录。每个人都应该有30个fps。
 
我们将通过在空格键上敲打来开始记录。
 
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private const float CAPTURING_INTERVAL = 1.0f / 30.0f;
  
private float _lastCapturedTime;
private float _recordingTimer;
private bool _recording = false;
  
void Update ()
{
    if ( Input.GetKeyDown ( KeyCode.Space ) && !_recording )
    {
        StartRecording ();
        return;
    }
  
    if ( _recording )
    {
        if (_recordingTimer==0.0f||_recordingTimer-_lastCapturedTime>=CAPTURING_INTERVAL)
        {
            for ( int i = 0; i < _recorders.Count; ++i )
            {
                _recorders [ i ].AddFrame ( _recordingTimer );
            }
            _lastCapturedTime = _recordingTimer;
        }
        _recordingTimer += Time.deltaTime;
    }
}
  
public void StartRecording ()
{
    Debug.Log ( "AnimationRecorder recording started" );
    _recording = true;
}
 
让我们实现一个动画导出。我们将创建AnimationClip实例并将其填充为所收集的值。
 
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private void ExportAnimationClip ()
{
    AnimationClip clip = new AnimationClip ();
    for ( int i = 0; i < _recorders.Count; ++i )
    {
        Dictionary<string,AnimationCurve> propertiles = _recorders [ i ].Properties;
        for ( int j = 0; j < propertiles.Count; ++j )
        {
            string name = _recorders [ i ].PropertyName;
            string propery = propertiles.ElementAt ( j ).Key;
            var curve = propertiles.ElementAt ( j ).Value;
            clip.SetCurve ( name, typeof(Transform), propery, curve );
        }
    }
    clip.EnsureQuaternionContinuity ();
  
    string path = "Assets/" + gameObject.name + ".anim";
    AssetDatabase.CreateAsset ( clip, path );
    Debug.Log ( "AnimationRecorder saved to = " + path );
}
 
最后,我们将创建AnimationRecorderRagdollHelper助手类,该函数将停止动画的动画对象,打开所有的碰撞,给物体加速度,开始记录我们的动画。动画录制的结束将由我们自己完成。
 
为了避免场景加载和各种对象的初始化,脚本将开始处理指定的延迟。
 
这就是所有的教程,我们添加AnimationRecorderRagdollHelper性格,冲击力,然后开始现场,观察人物高高兴兴地飞来飞去。
 
当冰冷的尸体在地面上结冰时,按下空格键。
 
脚本将我们的动画输出到项目的根。
 
 
 
99VR视界二维码
热门推荐
Hot Recommended
在线客服