请选择 进入手机版 | 继续访问电脑版
切换风格

Sunset glow Snow NewYear London Flowers Wizard California Cloud Sky Lavender City Black Beige Dragon

307

主题

312

帖子

2142

积分

管理员

Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

积分
2142

论坛新星

unity 时光倒流[复制链接]
发表于 2020-7-27 02:30:30 | 显示全部楼层 |阅读模式
效果图:
unity 时光倒流-1.jpg 可以通过时光倒流看到发生过的事情,在有的游戏项目中已经有使用过有光倒流,那么在开发过程这个功能是如何实现的呢,下面就给大家介绍下在unity中的时光倒流效果实现。

简介

时光倒流,这个再熟悉不过的词往往都会出现在各种电影、游戏、动漫的背景设定中。
透过回顾已发生过的事件及事物,可以更加清楚的了解事情所发生的经过。
这次就来尝试实作时光倒流,这个好玩又有趣的效果。

机制核心


时光倒流机制的主要核心浅显易懂,不依赖游戏引擎所提供的时间轴,而依赖于自订时间轴,使物体的移动、旋转等透过自订时间轴而产生变化。
[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public float Time
{
    get { return _time; }
    set
    {
        if (value != _time)
        {
            float deltaTime = value - _time;

            UpdateTime(deltaTime);

            _time = value;
        }
    }
}
private float _time;



ITimeMachine


在最初,先透过一个简单的介面来定义时间轴的变化。
任何需要与自订时间轴互动的行为,都需要继承并实作这个介面,使物体能够随时间而变化。

[url=]?[/url]
1
2
3
4
public interface ITimeMachine
{
    void UpdateTime(float deltaTime);
}




RewindAction


接著定义时光倒流时,所需要的回朔事件结构。

[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
using System;

public class RewindAction
{
    public float m_time;
    public Action m_action;

    public RewindAction(float time, Action action)
    {
        m_time = time;
        m_action = action;
    }
}




TimeMachineManager


透过这个唯一的时间轴管理器来注册、反注册所有物件、回朔事件以及时间轴更新。
只需要调用 TimeMachineManager.Instance.Time 就可以相当简单的改变所有已注册物件的时间轴。

[url=]?[/url]
1
2
3
4
5
6
7
8
9
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Collections.Generic;

public class TimeMachineManager
{
    public static TimeMachineManager Instance
    {
        get
        {
            if (null == _instance)
            {
                _instance = new TimeMachineManager();
            }

            return _instance;
        }
    }
    private static TimeMachineManager _instance;

    public float Time
    {
        get { return _time; }
        set
        {
            if (value != _time)
            {
                float deltaTime = value - _time;

                if (deltaTime < 0)
                {
                    while(_rewindActions.Count > 0 && _rewindActions.Peek().m_time >= _time + deltaTime)
                    {
                        float curDeltaTime = _rewindActions.Peek().m_time - _time;
                        UpdateTime(curDeltaTime);

                        _rewindActions.Pop().m_action();
                        deltaTime -= curDeltaTime;
                    }
                }

                UpdateTime(deltaTime);

                _time = value;
            }
        }
    }
    private float _time;

    private List _timeMachineList;
    private Stack _rewindActions;


    public TimeMachineManager()
    {
        _timeMachineList = new List();
        _rewindActions = new Stack();
    }


    public void RegistertimeMachine(ITimeMachine timeMachine)
    {
        _timeMachineList.Add(timeMachine);
    }


    public void UnregisterTimeMachine(ITimeMachine timeMachine)
    {
        _timeMachineList.Remove(timeMachine);
    }


    public void AddRewindAction(Action action)
    {
        _rewindActions.Push(new RewindAction(Time, action));
    }


    private void UpdateTime(float deltaTime)
    {
        foreach (ITimeMachine timeMachine in _timeMachineList)
        {
            timeMachine.UpdateTime(deltaTime);
        }
    }
}



BaseTimeMachine


在这个范例中,所有被监控的行为都继承了 BaseTimeMachine,透过继承 BaseTimeMachine 来处理注册及反注册物件。
接著就可以处理任何想要透过自订时间轴而产生变化的行为了。

[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;

public abstract class BaseTimeMachine : MonoBehaviour, ITimeMachine
{
    private void Awake()
    {
        TimeMachineManager.Instance.RegistertimeMachine(this);

        Initialize();
    }


    private void OnDestroy()
    {
        TimeMachineManager.Instance.UnregisterTimeMachine(this);
    }


    public virtual void Initialize(){}
    public abstract void UpdateTime(float deltaTime);
}



TimeMachine – Line Movement
unity 时光倒流-2.jpg
[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using UnityEngine;

public class LineMovementTimeMachine : BaseTimeMachine
{
    public Vector3 Direction
    {
        get { return _direction; }
        set { _direction = value; }
    }

    public float Speed
    {
        get { return _speed; }
        set { _speed = value; }
    }

    [SerializeField] private bool _local;
    [SerializeField] private Vector3 _direction;
    [SerializeField] private float _speed;

    public override void UpdateTime(float deltaTime)
    {
        if (_local)
        {
            transform.localPosition += _direction.normalized * _speed * deltaTime;
        }
        else
        {
            transform.position += _direction.normalized * _speed * deltaTime;
        }
    }
}



TimeMachine – Rotate
unity 时光倒流-3.jpg


[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
using UnityEngine;

public class RotateTimeMachine : BaseTimeMachine
{
    [SerializeField] private Vector3 _direction;
    [SerializeField] private float _speed;

    public override void UpdateTime(float deltaTime)
    {
        transform.Rotate(_direction.normalized * _speed * deltaTime);
    }
}



TimeMachine – Particle
unity 时光倒流-4.jpg
[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParticleSystemTimeMachine : BaseTimeMachine
{
    private ParticleSystem _particleSystem;
    private float _time;

    public override void Initialize()
    {
        _particleSystem = GetComponent();
        _particleSystem.randomSeed = (uint)(new System.Random().Next());
    }


    public override void UpdateTime(float deltaTime)
    {
        _time += deltaTime;

        _particleSystem.Simulate(_time, true, true);
    }
}



BaseAction


在上面的行为中,已经完成了很有趣的效果。但是在游戏中,我们往往会有一些例外状况需要处理,例如:颜色修改、实例化物件、隐藏物件…等。
为了注册这些例外状况,并产生相对应的处理,我们需要複写并继承抽象化类别。

[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;

public abstract class BaseAction : MonoBehaviour
{
    private void Awake()
    {
        Initialize();
    }

    protected abstract void Initialize();
    public abstract void Action();
    public abstract void RewindAction();
}



Action – Color
unity 时光倒流-5.jpg
[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;

public class ColorAction : BaseAction
{
    [SerializeField] private Color _color;
    private Color _preColor;
    private Material _material;

    protected override void Initialize()
    {
        _material = GetComponent().sharedMaterial;
    }

    public override void Action()
    {
        if (_material.color == _color)
            return;

        _preColor = _material.color;
        _material.color = _color;

        TimeMachineManager.Instance.AddRewindAction(RewindAction);
    }

    public override void RewindAction()
    {
        _material.color = _preColor;
    }
}



Action – Invisible
[url=]?[/url]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;

public class InvisibleAction : BaseAction
{
    private Renderer _renderer;

    protected override void Initialize()
    {
        _renderer = GetComponent();
    }

    public override void Action()
    {
        if (null == _renderer)
            return;

        if (!_renderer.enabled)
            return;

        _renderer.enabled = false;

        TimeMachineManager.Instance.AddRewindAction(RewindAction);
    }

    public override void RewindAction()
    {
        _renderer.enabled = true;
    }
}



Action – Instantiate
[url=]?[/url]
1
2
3
4
5
6
7
8
9
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using UnityEngine;
using System.Collections.Generic;

public class InstantiateAction : BaseAction
{
    [SerializeField] private GameObject _prefab;
    [SerializeField] private int _instantiateNumber = 1;

    private List _instances;

    protected override void Initialize()
    {
        _instances = new List();
    }


    public override void Action()
    {
        GameObject cacheObj = null;
        LineMovementTimeMachine cacheTimeline = null;
        LineMovementTimeMachine thisTimeline = null;
        thisTimeline = GetComponent();

        for (int count = 0; count < _instantiateNumber; count++)
        {
            cacheObj = Instantiate(_prefab);
            cacheObj.transform.SetParent(transform.parent);
            cacheObj.transform.position = GetRandomPosition(transform.position);

            cacheTimeline = cacheObj.GetComponent();

            if (null != cacheTimeline && null != thisTimeline)
            {
                cacheTimeline.Speed = thisTimeline.Speed;
                cacheTimeline.Direction = thisTimeline.Direction;
            }

            _instances.Add(cacheObj);
        }

        TimeMachineManager.Instance.AddRewindAction(RewindAction);
    }

    public override void RewindAction()
    {
        foreach (GameObject go in _instances)
        {
            Destroy(go);
        }
    }

    private Vector3 GetRandomPosition(Vector3 position)
    {
        return position + new Vector3(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), 0);
    }
}





Image Effect – Gray Scale


最后还可以再时光倒流时加入画面滤镜效果,让倒流的效果更加明确。
这边实作了基本的灰阶滤镜效果。

unity 时光倒流-6.jpg
[url=]?[/url]
1
2
3
4
5
6
7
8
9
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Shader "Hidden/GrayScaleImageEffectShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }
            
            sampler2D _MainTex;
            float _saturation;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float3 intensity = dot(col.rgb, float3(0.39, 0.59, 0.11));
                col.rgb = lerp(intensity, col.rgb, _saturation);
                return col;
            }
            ENDCG
        }
    }
}




最終效果
结语

透过这个简单的实作,可以了解到自订时间轴所带来的可控性,然而可控性的提升却会造成便利性大幅下降的情况。
任何需要与时间轴互动的事件、物件,都需要额外实作功能及行为的脚本,没办法很方便及快速的进行功能扩充。
取而代之,若是将所有需要纪录的物件,透过纪录快照功能,将每一个时间点的物件行为纪录并存取下来,或许就能够相当真正的达到时间控制。



回复

使用道具 举报

发表于 2020-7-27 04:01:52 来自手机 | 显示全部楼层
我看不错噢 谢谢楼主!
回复

使用道具 举报

点击右侧快捷回复 【吾爱分享www.52fenxiang.top】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|吾爱分享-享你所想 ( 黔ICP备18007665号-2 )|网站地图

GMT+8, 2020-8-4 01:42 , Processed in 0.117643 second(s), 44 queries .

Powered by 52Fenxiang.Top

© 2001-2020 Comsenz Inc.

返回顶部