欢迎光临散文网 会员登陆 & 注册

时间回溯——用Unity实现时空幻境(Braid)中的控制时间效果

2020-04-01 17:43 作者:皮皮关做游戏  | 我要投稿

作者:QXYO


前言

控制时间相信几乎是每个人都想拥有的能力,也为众多影视、游戏等提供了灵感,荒木老师在jojo的奇妙冒险中几乎把控制时间的能力玩了个遍。而在游戏领域,令笔者印象最深的就是本次的主角——时空幻境(Braid),一款把横板跳跃与时间回溯完美结合的游戏。


注意!由于本教程主要实现时间回溯效果,横板过关类游戏的场景搭建、移动、动画等不在本次内容范围内,有兴趣的同学可以先从B站专栏开始学起:
【简明UNITY教程】教你迅速实现2D角色的移动和跳跃

本次教程也会以该系列视频的工程为基础,在原项目上进行修改实现时间回溯的功能。

项目来源:【简明Unity教程】2D跳跃游戏的踩怪功能

基础工程:https://pan.baidu.com/share/init?surl=HISQizt0NvCHo8U0KgSCCA
提取码:jlbx


一、时间回溯实现原理

我们知道视频是能够倒放的,那游戏可不可以也像视频那样把每一帧记录下来,需要时再倒着输出实现时间倒流呢?答案当然是可以的,这种方法称为“备忘录模式”。事实上时空幻境的作者也说过该游戏主要是用该方法制作,有兴趣、英语好的同学可以看看作者的解释:https://news.ycombinator.com/item?id=9484197


二、用Unity实现时间回溯

下载好前言中提到的基础工程,打开之后可能会有几个不影响的警告,Clear即可。打开Scene文件夹下的SampleScene场景,运行一下,试试操作人物移动、跳跃,应该不会有什么问题。

主要实现操控角色的时间倒流效果,所以先把怪物(opossum-1)从场景中删除。

1.设置保存数据类型

首先要确定每帧保存什么数据,位置数据、起跳后的速度数据,由于是2d动画所以还需要记录每帧所用的Sprite和脸的朝向。在Scripts文件夹里新建一个c#脚本,取名为ObjectStage。

public class ObjectStage

{

    public Vector3 Position { get; set; }

    public Vector3 Velocity { get; set; }

    public Sprite Sprite { get; set; }

    public bool IsRight { get; set; }

}

 

2.保存角色状态

接下来就要实现时间倒流的效果了,新建脚本TimeBack挂到Player上。由于读取数据是从后往前读取,所以可以使用stack(栈)一个后进先出的容器来保存数据。同时也需要获取到player上的<SpriteRenderer>,用于获取和修改某一帧角色的动作;<Animator>用于在时间倒流时暂停动画的播放;<CharacterController2D>,原工程的角色控制代码,用于修改角色脸的朝向;<Rigidbody2D>,获取、修改速度和时间倒流时关闭物理引擎。

    void Start()

    {

        TimeBackData = new Stack();

        SpriteRenderer = GetComponent<SpriteRenderer>();

        animator = GetComponent<Animator>();

        cc2D = GetComponent<CharacterController2D>();

        m_Rigidbody2D = GetComponent<Rigidbody2D>();

    }

 

首先是保存数据,cc2D.m_FacingRight在原工程里受保护的(private)这里我们需要公开(public)。



    void SaveData()

    {

        ObjectStage stage = new ObjectStage();

        stage.Position = transform.position;

        stage.Sprite = SpriteRenderer.sprite;

        stage.IsRight = cc2D.m_FacingRight;

        stage.Velocity = m_Rigidbody2D.velocity;

        TimeBackData.Push(stage);

    }

 

3.读取和显示状态

接下来是读取数据,读取后的数据就可以删除了,可以用Stack.Pop(),但是最后一个读取的数据,也就是第一个保存的数据不能删,可以用 Stack.Peek()。

    ObjectStage LoadData()

    {

        if (TimeBackData.Count > 1)

        {

            return (ObjectStage)TimeBackData.Pop();

        }

        else

        {

            return (ObjectStage)TimeBackData.Peek();

        }

    }

 

然后就是把读取的数据反映到Player身上,也就是时间倒流的过程,要注意在这期间角色应该是不受物理引擎的影响,并且不能播放动画,要在代码中关闭。

    void ShowData(ObjectStage stage)

    {

        animator.enabled = false;

        transform.position = stage.Position;

        SpriteRenderer.sprite = stage.Sprite;

        transform.localScale = new Vector3(stage.IsRight ? 1 : -1, 1, 1);

        m_Rigidbody2D.simulated = false;

        m_Rigidbody2D.velocity = stage.Velocity;

    }

 

4.调用代码实现时间回溯

方法写好了,接下来就是调用了,我们知道update在一秒内执行的次数是不固定的,所以我们保存数据和读取数据只能放在FixedUpdate里。并且只有在按下时间倒流的按键时才能读取数据,其他时间保存数据,按键抬起的时候要把之前关闭的物理引擎和动画开启。

  ObjectStage LoadStageData = new ObjectStage(); 

  private void FixedUpdate()

    {

        if (CheckKey)

        {

            LoadStageData = LoadData();

            if (LoadStageData != null)

            {

                ShowData(LoadStageData);

            }

        }

        else

        {

            SaveData();

        }

    }

 

(注意,按键检测仍然要放在UpDate里。)

    private void Update()

    {

        CheckKey = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);

        CheckKeyUp = Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKeyUp(KeyCode.RightShift);

        if (CheckKeyUp)

        {

            cc2D.m_FacingRight = LoadStageData.IsRight;

            animator.enabled = true;

            m_Rigidbody2D.simulated = true;

        }

    }

 

运行游戏操作一会,再按下Shift看看你的角色是不是已经是一个无敌的存在,毕竟一个可以无限时间倒流的人是不可能会输的吧。(某平凡的上班族点了个赞!)

如果追求细节的话能发现,时间回溯到在空中时结束回溯,角色会垂直落下,这时候只需要把CharacterController2D脚本上的canAirControl勾选为false即可继续跳跃。但这样修改也有个问题,角色不能在空中移动了,为了模拟Braid原版游戏的手感,我们可以尝试修改基础工程的move方法。

首先把canAirControl勾选为false。

        if (m_Grounded || canAirControl)

        {

            // 输入变量move决定横向速度

            m_Rigidbody2D.velocity = new Vector2(move, m_Rigidbody2D.velocity.y);

        }

        else if (!m_Grounded)

        {

            if (move > 0 && m_FacingRight)

            {

                m_Rigidbody2D.velocity = new Vector2(Mathf.Max(move, m_Rigidbody2D.velocity.x), m_Rigidbody2D.velocity.y);

            }

            else if (move < 0 && !m_FacingRight)

            {

                m_Rigidbody2D.velocity = new Vector2(Mathf.Min(move, m_Rigidbody2D.velocity.x), m_Rigidbody2D.velocity.y);

            }//如果在空中有相反方向的操作则修改水平速度

        }

 

修改后的手感就和Braid里面非常相似了。

另外,如果想在时间回溯时音频也跟着倒放,可以修改AudioSource组件的Pitch参数为-1。

修改后的工程:https://pan.baidu.com/share/init?surl=tmPDt9Ebq814cbasffOhlA

提取码: m4pg


对线下游戏开发学习感兴趣的盆友,欢迎访问:levelpp.com/

同时,也欢迎加入游戏开发群搅基:610475807


时间回溯——用Unity实现时空幻境(Braid)中的控制时间效果的评论 (共 条)

分享到微博请遵守国家法律