【游戏面包屑】时间回溯·逆流吧

学习链接

【Creator3】实现时间倒放效果,游戏开发时间倒流的技术实现方案,时间回溯在游戏开发中如何实现,来个tenet信条玩玩_哔哩哔哩_bilibili

早上看了这个学习视频,心里挺痒的,正好有一些灵感。提前写了一些学习心得分享。


实现思路

    1.使用数据结构存储状态(类似 堆栈,可根据游戏自定义设计特定数据结构)。
           使用多个栈存储所需的状态(类)。
    2.根据游戏设计记录时间轴状态规则:记录频率、记录长度、记录的内容。
    3.需要时间回溯时,不断出栈 获取上一个时间轴的数据进行重置。


如何做到只记录最近5秒内的回溯?
    1.记录一个下标值,以此作为回溯索引,这么的话是不是栈结构也无所谓了。如5秒每0.1秒分为一个下标,共有50个空间存储。当记录下标为6秒时,即60P = 10,该存储的位置为第10个位置。当读取时也是从第6秒开始逆序-1的读取,59P = 9、49P = 49······直到逆序访问到第一个空null的值(坐标为10)或者算法手动计算固定周期的长度(50个)。当记录时间为固定周期的长度2倍时(100),则索引减去前一个周期的长度(100-50)来完全使新周期取代旧周期。


如何做到长周期的回溯
    系统每隔一定的周期自动存档。当玩家需要长周期回溯时,直接读取系统的自动存档即可。


缺点:

    必须针对游戏设计特定的时间回溯算法,并且回溯规则越复杂,性能消耗越大。
    如果只是针对玩家的个体进行时间回溯,那么性能消耗完全是可以接受的。但是如果想让整个场景进行回溯,算法会变得复杂与大量的性能消耗。而且这可能会因为算法的遗漏导致许多隐藏的bug。虽然有几个简单的优化策略,如只在玩家可视范围内进行信息记录或一定距离内记录回溯信息;只对特定目标如敌人、施法效果等进行距离;将需要记录的目标放在同一节点下并添加是否记录的标签(如死亡超过一定时常则不必记录)·····这些算法优化了性能,但是与时间回溯原则已经不符(游戏时间完全回溯,而不是重要的对象回溯)。
    回溯规则越复杂,性能消耗越大,这是不可避免的。具体的回溯规则还是得根据游戏需求进行定义。如果规则设定的合理,那么游戏就会显得自然。比如游戏的回溯规则比较复杂,希望做到真正意义上的回溯。如果是3D游戏,则尽量让场景简洁(有意识的不设计复杂场景,从根本上减低性能消耗),2D游戏,相对3D不会太过复杂,那么真正意义上的回溯导致的性能消耗是可以接受。设定一个规则,如玩家的时间回溯能力如果周围的对象越多,则越弱。周围对象越少,则越强(总的记录消耗控制在开发者手中,性能消耗=对象*记录消耗。根据实际场景调节这2个参数,但是总的性能消耗仍在预期内。这也是相对好些的解决方案)。需要记录的对象越多,则给每个对象记录回溯的信息空间就减少,反之,则增多。例如,玩家被允许有500点的时间回溯性能消耗(假设记录一个对象一秒钟的信息需要消耗1点的性能)。此时玩家周围有50个对象需要记录回溯信息,每个对象则可以记录最多10秒的回溯信息(50*10 = 500),那么玩家使用时间回溯能力时,可以最多回溯10秒。如果玩家周围有5个对象需要记录回溯信息,则每个对象可以记录最多100秒的回溯信息(5*100 = 500),那么玩家使用时间回溯能力时,可以最多回溯100秒。
    具体的可允许最大时间回溯性能消耗,取决于具体游戏的性能优化情况。每个固定周期更新回溯信息会增加这一时间段的系统性能压力。
    也许可以设计好系统的时间轴,将不同的性能消耗任务分配到不同的时间轴上进行。这样时间回溯可以独享固定时间片断的系统全力支持运算。其次,一个时间片断中,系统完成了该片断的任务,则剩余的运算时间可用于其他的任务(未完成的任务可按照优先级进行排序到一个存储结构中,当有剩余运算时间时则开始优先级高的任务,但任务完成时从这个存储结构中移除该任务),这样最大化利用系统。
    
    玩家角色个体时间回溯能力给人族使用比较合适,肉体比较羸弱,时间回溯能量消耗会更小。也能作为对角色死亡,空间发动的复活功能


简单的实现效果

   PS:只进行了位置信息的回溯,没有对旋转信息进行记录,所有演示视频,碰撞到物体导致旋转改变了出现bug。


代码:

   使用了装饰器模式,便于后续扩展,并用一个类统一管理这些装饰器。

   接口

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

public interface IFunction
{
    void Ues();
}

接口的实体类

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

/// <summary>
/// 基类
/// 说明:
///     包含“使用”
/// </summary>
public class FunTime : IFunction
{
    public void Ues()
    {
        Debug.Log("FunTime使用能力");
    }
}

实现了接口的抽象装饰类

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

/// <summary>
/// IFunction接口的抽象装饰类(保存接口的实体类,并在构造函数中进行赋值)
/// </summary>
public abstract class DecTime : IFunction
{
    protected IFunction decTime;

    public  DecTime(IFunction func)
    {
        this.decTime = func;
    }
    public void Ues()
    {
        decTime.Ues();
    }
}

扩展了抽象装饰类的实体装饰类

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

//扩展了抽象装饰类的实体装饰类
//时间回溯能力
public class DecTimeBack : DecTime
{
    //栈·角色位置信息
    public Stack<Vector3> stackPos = new Stack<Vector3>();
    public DecTimeBack(IFunction decFun) : base(decFun)
    {

    }
    
    public void Use(GameObject go)
    {
        //调用父类的方法
        base.Ues();
        //调用装饰器·时间回溯方法
        TimeBack(go);
    }

    public void TimeBack(GameObject go)
    {
        Debug.Log("时间回溯");
        go.transform.position = stackPos.Pop();
    }

}

扩展了抽象装饰类的实体装饰类

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

//扩展了抽象装饰类的实体装饰类
//时间暂停能力
public class DecTimeStop : DecTimeBack
{
    public bool stop = false;
    public DecTimeStop(IFunction decFun) : base(decFun)
    {

    }
    public void Use()
    {
        //调用父类的方法
        base.Ues();
        //调用装饰器·时间暂停方法
        TimeStop();
    }

    //时停
    public void TimeStop()
    {
        if (stop)
        {
            Time.timeScale = 0;
            Debug.Log("时间静止");
        }
        else
        {
            Time.timeScale = 1;
            Debug.Log("时间开始流动");
        }
    }
}

实体装饰类Mgr

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

/// <summary>
/// 时间能力的控制总类(实体装饰类Mgr)
/// 说明:
///     时间系的每多一种变化则多一个时间装饰器来扩展
/// </summary>
public class FunTimeControl 
{
    //时间回溯能力
    public DecTimeBack timeBack = new DecTimeBack(new FunTime());
    //时间暂停能力
    public DecTimeStop timeStop = new DecTimeStop(new FunTime());

}

执行程序类

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

public class Testss : MonoBehaviour
{
    private float speed = 5;
    public GameObject player;
    private FunTimeControl fun = new FunTimeControl();
    // Start is called before the first frame update
    void Start()
    {

    }
    
    // Update is called once per frame
    void Update()
    {
        Crtl();
        Move();
    }

    private void Crtl()
    {
        //空格·时间回溯
        //R·时间静止(开/关)
        if (Input.GetKey(KeyCode.Space))
        {
            if (fun.timeBack.stackPos.Count>0)
            {
                fun.timeBack.Use(player);
            }
        }else if (Input.GetKeyDown(KeyCode.R))
        {
            Debug.Log("砸挖了朵");
            if (fun.timeStop.stop)
            {
                fun.timeStop.stop = false;
                fun.timeStop.Use();
            }
            else
            {
                fun.timeStop.stop = true;
                fun.timeStop.Use();
            }
        }

    }

    private void Move()
    {
        //移动时记录位置信息,加入时间回溯类的栈中
        if (Input.GetKey(KeyCode.W))
        {
            player.transform.Translate(Vector3.forward * Time.deltaTime * speed, Space.Self);
            fun.timeBack.stackPos.Push(player.transform.position);
        }
        else if (Input.GetKey(KeyCode.D))
        {
            player.transform.Translate(Vector3.right * Time.deltaTime * speed, Space.Self);
            fun.timeBack.stackPos.Push(player.transform.position);
        }
        else if (Input.GetKey(KeyCode.A))
        {
            player.transform.Translate(Vector3.left * Time.deltaTime * speed, Space.Self);
            fun.timeBack.stackPos.Push(player.transform.position);
        }
        else if (Input.GetKey(KeyCode.S))
        {
            player.transform.Translate(Vector3.back * Time.deltaTime * speed, Space.Self);
            fun.timeBack.stackPos.Push(player.transform.position);
        }
    }

}


今天又对暂时用不到的功能进行了头脑风暴,接下来的内容就留到下一次再写吧

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值