原文链接:http://www.manew.com/thread-98803-1-1.html

如何建立一个波斯王子风格的时间倒回系统 Part1

在这个教程中,我们在中建一个小游戏,游戏中玩家可以倒回(它也可以修改适用于其他系统中)。第一部分我们学习这个系统的基本部分,下一部分,我们完善它,并让它有更多功能。

首先,我们看一下用到的游戏的样子,然后,在创建最终的小游戏之前,我们再来看使用到的技术,给你打个基础。

基本功能的演示 你需要用到Unity最新的版本,并有一些相关经验。如果你想核对自己的代码,源代码可以下载。 准备好了吧,现在开始。

以前是如何使用的时间倒回的?
波斯王子:时之沙 是第一款真正把时间倒回机制溶入到其游戏性中的游戏。当你死亡之后,你不能马上重新加载,而是倒退到你死亡前几秒,马上再试一次。
波斯王子:遗忘之沙, 时之沙系列完美地整合了时间倒回机制到游戏中,避免了破坏沉侵的快速重载。

这种倒回机制不仅仅整合到了游戏中,在故事和游戏世界也同样适用,整个故事中它也被反复提及。

一个有这种系统的游戏是《时空幻境(Braid)》,它也是围绕着时间的游戏。在Overwatch中,主人公Tracer有一种可以把自己重置到几秒种之前的位置的能力,即使是多人游戏,她也可以倒回她的时间。

如果你玩《超级房车赛(GRID)》中发生了崩溃,你有机会回到崩溃之前的某个时间点。
其他的用法

这个系统不仅仅只用于及时存储,另一种用法是用在赛车游戏和异步多人游戏游戏中的幽灵。

Replays 重播
重播是这个功能另一个比较有趣的用法。这可以在《燥热(SUPERHOT)》,Worms series系列,和大多数运动游戏中看到。
运动回放的工作方式和电视上呈现的方式是相同的,一个动作可能从不同的角度被重复播放。要做到这种,不是记录视频,而是要记录用户的动作,允许重播采用不同的相机角度和地点。Wormsseries系列是以一种幽默的方式使用它的,在实时的重播中呈现得有些滑稽或者有特效的杀人者。

《燥热(SUPERHOT)》也是记录你的动作。当你完成游戏后,你的整个进度会被回放并显示几秒钟内发生的实际动作。

《超级食肉男孩(Super Meat Boy)》使用的方式更有趣。当你完成一个关卡,你会看到之前你所有的尝试一个挨着一个,最后是你的最终的一次。

《超级食肉男孩(Super Meat Boy)》的关卡最后,你之前所有的尝试都会被同时回放。
Time-Trial Ghosts 时间偿试幽灵

当你在空跑道上,为了最好的成绩赛车时,会使用到赛车幽灵(Race-Ghosting)技术。同一时间,你在跟一个幽灵比赛,它是一辆像幽灵一样透明的车,总是你在前面挡住你的。你不能撞上它,这样你可以集中精力得到好的成绩。

比起你一个人进行自我挑战,这种方式要有趣得多。从极品飞车(Need for Speed )到大金刚赛车(Diddy Kong Racing)大部分赛车游戏都有这个功能。
Trackmania Nations 上跟幽灵赛车的比赛。这个游戏有一个银牌难度,就是如果我打败了它,就可以得到银牌。注意这个重叠的汽车模型不是并不是实体,可以穿过它。
Multiplayer-Ghosts 多人游戏中的幽灵

异步的多人游戏中有另一种用法。这是一种非常不常见的用法,通过记录玩家的数据并发送给其他玩家,来进行多人匹配,随后可以挑战第一个玩家进行战斗。数据就是应用的“时间偿试幽灵(time-trial-ghost)”的方式,只有这样,你才可能跟其他的玩家进行挑战。

Trackmania-games(一种反重力赛车体验游戏)中体现了这种形式,游戏中可以挑战一些难度。记录的赛事,会给你一个挑战对手,并得到一定的奖励。

Movie-Editing 电影编辑
少数游戏提供这类的功能,使用得好这是个有趣的工具。《军团要塞2(Team Fortress 2 )》就内置了重播编辑器,这样你可以创建你自己的剪辑。
《军团要塞2(Team Fortress 2 )》的重播编辑器。记录了一个赛事就可以从任何角度来看它,并不限于玩家视线。

一旦创建了这个功能,你就可以记录,观看之前的比赛了。重要的一点就是一切都要记录下来,而不仅仅是你的视野中的物体。这意味着,你可以移动记录的游戏世界,看到每个物体,从头到尾对他们进行控制。

如何创建

为了测试系统,我们需要建一个简单的游戏来测试,现在创建一个。

The Player 玩家

在场景中创建一个立方体,作为我们的角色。再创建一个名为Player.cs的C#脚本,加一个如下的Update()函数:

[C#] 纯文本查看 复制代码
?
1
2
3
4
voidUpdate()
{
    transform.Translate (Vector3.forward * 3.0f * Time.deltaTime * Input.GetAxis ("Vertical"));
    transform.Rotate (Vector3.up * 200.0f * Time.deltaTime * Input.GetAxis ("Horizontal"));
}

   
这个脚本通过健盘上的上下健产生简单的移动,把这个脚本绑定到立方体上。当你点击Play运行,你就能够移动。
倾斜相机,当我们移动时,以便可以从上面观察立方体。最后,创建一个Floor的面,两个物体使不用同的材质,这样,我们不至于在一个虚空中移动它。看上去像这样:
试一试,你可以用WSAD或是上下健移动你的立方体。
时间控制TimeController

现在创建一个新的C#脚本,命名为TimeController.cs,创建一个空的GameObject,绑定脚本。脚本处理实时记录和随时倒回游戏。


为了让它运行,我们会记录玩家的所有移动。当我们按下倒回按钮时,可以修正玩家的坐标。为此,首先要创建一个变量来保存玩家,像这样:
[C#] 纯文本查看 复制代码
?
publicGameObject player;
把 player 对象 设到 TimeController 的属性槽上去,这样可以访问玩家对象和它的数据。
然后,我们需要创建一个数组来保存玩家的数据:
[C#] 纯文本查看 复制代码
?
1
2
3
4
5
publicArrayList playerPositions;
  
void Start()
{
    playerPositions =new ArrayList();
}

按下来我们需要持续地记录player的位置。我们会存下player最后一帧的位置,6帧前的位置,8秒前的位置(不论多长时间都会被记录下来)。当我们点击按钮想要倒回去时,通过我们的数据,可以一帧一帧地设置之前的位置,从而形成时间倒回的功能。

首先,我们存一下数据:
[C#] 纯文本查看 复制代码
?
1
2
3
voidFixedUpdate()
{
    playerPositions.Add (player.transform.position);
}

在FixedUpdate()函数中,我们存了数据。FixedUpdate被每秒被调用至少50次(或者任何你设置的周期),这个函数允许设一个固定的调用间隔来记录数据。Updat()函数的执行依赖于CPU的处理,在Update中处理会更困难一些。
这代码保存player每一帧的位置。现在我们需要应用它。
我们增加一个检查项查看倒回按钮是否被按下。像这样,我们需要一个boolean变量:
[C#] 纯文本查看 复制代码
?
publicbool isReversing =false;
增加一个检查在Update函数中,设 置我们是否需要对游戏进行倒回操作:
[C#] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
voidUpdate()
{
    if(Input.GetKey(KeyCode.Space))
    {
        isReversing =true;
    }
    else
    {
        isReversing =false;
    }
}

当游戏执行倒回操作时,我们应用数据而不再记录数据。记录数据和应用player位置的代码,像下面这样:
[C#] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
voidFixedUpdate()
{
    if(!isReversing)
    {
        playerPositions.Add (player.transform.position);
    }
    else
    {
        player.transform.position = (Vector3) playerPositions[playerPositions.Count - 1];
        playerPositions.RemoveAt(playerPositions.Count - 1);
    }
}

整个TimeController脚本,如下:
[C#] 纯文本查看 复制代码
?
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
36
37
38
usingUnityEngine;
using System.Collections;
  
public class TimeController: MonoBehaviour
{
    publicGameObject player;
    publicArrayList playerPositions;
    publicbool isReversing =false;
  
    voidStart()
    {
        playerPositions =new ArrayList();
    }
  
    voidUpdate()
    {
        if(Input.GetKey(KeyCode.Space))
        {
            isReversing =true;
        }
        else
        {
            isReversing =false;
        }
    }
      
    voidFixedUpdate()
    {
        if(!isReversing)
        {
            playerPositions.Add (player.transform.position);
        }
        else
        {
            player.transform.position = (Vector3) playerPositions[playerPositions.Count - 1];
            playerPositions.RemoveAt(playerPositions.Count - 1);
        }
    }
}

另外,不要忘记给player脚本增加一个检查,检查TimeController当前是倒回状态还是记录,只有当它不是倒回状态时,才可能移动立方体,否则可能产生BUG:
[C#] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
usingUnityEngine;
using System.Collections;
  
public class Player: MonoBehaviour
{
    privateTimeController timeController;
  
    voidStart()
    {
        timeController = FindObjectOfType(typeof(TimeController))as TimeController;
    }
      
    voidUpdate()
    {
        if(!timeController.isReversing)
        {
            transform.Translate (Vector3.forward * 3.0f * Time.deltaTime * Input.GetAxis ("Vertical"));
            transform.Rotate (Vector3.up * 200.0f * Time.deltaTime * Input.GetAxis ("Horizontal"));
        }
    }
}

在启动的时候,新加的代码自动在场景中到TimeController对象,在整个运行过程中检查它是在倒回还是在移动。只有当不在倒回状态下,我们才可以控制移动物体。

现在,你可以在世界中移动,按下空格健进行回放你的动作。如果你下载下本贴的附件中已经建好的包,你可以打开TimeRewindingFunctionality01 ,试一试。

(测试的时候,要一直按着空格健)
等一下,为什么我们简单的立方体总是保持着它最后的方向?因为我们没有记录它的旋转!

你需要另一个数据来存储旋转值,在最开始的时候初始化它,在处理位置数据的地方保存和应用数据。

[C#] 纯文本查看 复制代码
?
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
36
37
38
39
40
41
42
43
44
usingUnityEngine;
using System.Collections;
  
public class TimeController: MonoBehaviour
{
    publicGameObject player;
    publicArrayList playerPositions;
    publicArrayList playerRotations;
    publicbool isReversing =false;
      
    voidStart()
    {
        playerPositions =new ArrayList();
        playerRotations =new ArrayList();
    }
      
    voidUpdate()
    {
        if(Input.GetKey(KeyCode.Space))
        {
            isReversing =true;
        }
        else
        {
            isReversing =false;
        }
    }
      
    voidFixedUpdate()
    {
        if(!isReversing)
        {
            playerPositions.Add (player.transform.position);
            playerRotations.Add (player.transform.localEulerAngles);
        }
        else
        {
            player.transform.position = (Vector3) playerPositions[playerPositions.Count - 1];
            playerPositions.RemoveAt(playerPositions.Count - 1);
      
            player.transform.localEulerAngles = (Vector3) playerRotations[playerRotations.Count - 1];
            playerRotations.RemoveAt(playerRotations.Count - 1);
        }
    }
}

再试一下。TimeRewindingFunctionality02  是修改后的版本,现在我们的Player立方体可以回退了,并且看上去跟当时的状态一模一样。
总结

我们建立了一个可用的时间倒回系统的简单的原型,但它还没有完成。这个系列的下一部分,我们会让它更稳定和多样,再加一些好的效果。

下面是我们还需要做的:
  • 每12帧记录一次,解决巨大的数据量
  • 只保存最后75个Player的位置和旋转数据,以保正数据不会变得巨大,而引起崩溃。

之前只有player的属性,后面我们还会扩展一下这个系统:
  • 记录更多的数据,而不是仅仅是player
  • 增加特效显示回放正在进行
  • 使用一个自定义类保存玩家位置和旋转数据,而不是用数组。
原文标题:How to Build a Prince-of-Persia-Style Time-Rewind System, Part 1
原文链接:https://gamedevelopment.tutsplus.com/tutorials/how-to-build-a-prince-of-persia-style-time-rewind-system-part-1--cms-26090

TimeBake:part1相关推荐

  1. Storefront与NetScaler的集成配置 - part1

    Storefront与NetScaler的集成配置 - part1 http://kaiqian.blog.51cto.com/blog/236001/1344447 Storefront与NetSc ...

  2. Lync Server 2010标准版系列PART1:基础构建

    可能环境准备已经是老生常谈的东西了,但我们搭建环境做测试.评估,或者说在客户生产环境做部署,非常需要重视的就是细节,力求做到一丝不苟,并且快速完成部署任务.所以在测试环境的搭建中,基础环境的准备是非常 ...

  3. 从零开始学Win32平台缓冲区溢出(Part1)

    原文:Stack Based Buffer Overflow in Win 32 Platform: The Basics 译者:鸢尾 来源:从零开始学Win32平台缓冲区溢出(Part1) 缓冲区溢 ...

  4. Cocos2dx-demo演示项目:Part1

    这个项目,我主要是用来积累.记录自己在利用cocos2dx引擎进行项目开发.学习实践中的开发经验.每天的开发任务.查看别人分享的内容,总是能够收获到可取的东西,将这些可取的东西自己再着手开发一次,能够 ...

  5. CS231n 学习笔记(1)——神经网络 part1 :图像分类与数据驱动方法

    *此系列为斯坦福李飞飞团队的系列公开课"cs231n convolutional neural network for visual recognition "的学习笔记.本文主要 ...

  6. 写给大家看的机器学习书【Part1】什么是机器学习?机器学到的到底是什么?

     写给大家看的机器学习书[Part1]什么是机器学习?机器学到的到底是什么? 机器学习 深度学习 神经网络 人工智能 阅读1390

  7. 黑客与画家 part1 版权声明 part2 O'Reilly Media,Ina.介绍

    part1 版权声明 page 11 版权声明 英文原版O'Reilly Media,Ina.出版社2004. 简体中文版由人民邮电出版社出版,2011.英文原版的翻译得到O'Reilly Media ...

  8. 用PHP写一个最简单的解释器Part1

    偶然间在朋友圈发现有人在看一本<两周自制脚本语言>,觉得写个脚本语言挺不错的,方便自己对语言本身进一步了解.于是乎,买了下来看了看,写的挺通俗易懂,但是不便的是,采用的语言是Java,PH ...

  9. Lync Server 2010迁移至Lync Server 2013部署系列 Part1: 扩展AD架构

    由于最近直在忙Lync 升级,好久没有更新博客了,今天开始将对最近做的Lync Server 2010迁移至Lync Server 2013项目做一个系列的部署操作更新,希望能给即将在企业中部署的兄弟 ...

  10. std string与线程安全_C++标准库多线程简介Part1

    Part1:线程与互斥量 本篇文章将简单的介绍一下C++的标准线程库,本篇内容十分基础,如果你有C++多线程相关的使用经验或者知识,就不必在这篇文章上浪费时间了... 如果你认为本篇文章对你有帮助,请 ...

最新文章

  1. oracle Hash Join及三种连接方式
  2. Android艺术——性能优化问题
  3. c#下实现GUI编程_程序员会懂的冷笑话:各大编程语言的内心独白
  4. 排序算法系列:插入排序算法
  5. 关于软件项目工作量估算的若干问题
  6. traceroute程序_来!程序猿教你们玩微信代码~
  7. 赞!Google 资深软件工程师 LeetCode 刷题笔记首次公开
  8. ms sql server 添加列,删除列。
  9. java this() super()_java中的this和super
  10. CentOS Linux 系统镜像文件(M1 Mac虚拟机专用)
  11. 【题解】Inspection UVa 1440 LA 4597 NEERC 2009
  12. 【Codeforces 1051D】Bicolorings
  13. jep-java-4.0-trial.jar 下载
  14. 状态模式,懂你的另一半
  15. 微信公众平台接口API
  16. 安全管理体系升级 迈动互联获得ISO国际认证
  17. 使用python创建学员管理系统
  18. THINKPHP6 运行出现Malformed UTF-8 characters, possibly incorrectly encoded
  19. Android 实现人脸识别检测时的扫描动画效果(二维码扫描动画效果同理)
  20. 计算机课各种造型靠图形教案,三年级信息技术课老师教案参照三篇

热门文章

  1. vb改变字形的代码是什么_VB怎么改变字体?
  2. linux脚本自动 输入命令,Linux脚本自动输入密码
  3. Java 反编译工具包(.class -> .java) 及其在 Minecraft 中相关应用
  4. python将经纬度标注在地图上_Python 给定的经纬度标注在地图上的实现方法
  5. Linux虚拟镜像下载
  6. 【避坑指南】GD32 KEIL中SW Device没有识别芯片,jlink下载不进去的问题
  7. 基于Fisher准则线性分类器设计
  8. C4D四视图切换及基本操作
  9. 总纲篇:产品结构设计指导VII(本博客指引章节)
  10. 《深入浅出通信原理》连载1-562合集