TimeBake:part1

来源:互联网 发布:win10仿linux主题 编辑:程序博客网 时间:2024/05/16 08:44

原文链接: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 =newArrayList();
}


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

首先,我们存一下数据:

[C#] 纯文本查看 复制代码
?
 
1
2
3
voidFixedUpdate()
{
    playerPositions.Add (player.transform.position);
}


在FixedUpdate()函数中,我们存了数据。FixedUpdate被每秒被调用至少50次(或者任何你设置的周期),这个函数允许设一个固定的调用间隔来记录数据。Updat()函数的执行依赖于CPU的处理,在Update中处理会更困难一些。

这代码保存player每一帧的位置。现在我们需要应用它。

我们增加一个检查项查看倒回按钮是否被按下。像这样,我们需要一个boolean变量:

[C#] 纯文本查看 复制代码
?
 
publicboolisReversing =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;
    publicboolisReversing =false;
  
    voidStart()
    {
        playerPositions =newArrayList();
    }
  
    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))asTimeController;
    }
      
    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;
    publicboolisReversing =false;
      
    voidStart()
    {
        playerPositions =newArrayList();
        playerRotations =newArrayList();
    }
      
    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帧记录一次,解决巨大的数据量
  • 只保存最后75Player的位置和旋转数据,以保正数据不会变得巨大,而引起崩溃。

之前只有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
0 0
原创粉丝点击