|
前言
控制时间相信几乎是每个人都想拥有的能力,也为众多影视、游戏等提供了灵感,荒木老师在jojo的奇妙冒险中几乎把控制时间的能力玩了个遍。而在游戏领域,令笔者印象最深的就是本次的主角——时空幻境(Braid),一款把横板跳跃与时间回溯完美结合的游戏。
注意!由于本教程主要实现时间回溯效果,横板过关类游戏的场景搭建、移动、动画等不在本次内容范围内,有兴趣的同学可以先从https://www.bilibili.com/video/BV1jJ41147WM这里开始学习。本次教程也会以该系列视频的工程为基础,在原项目上进行修改实现时间回溯的功能。
项目来源:https://www.bilibili.com/video/BV1v7411E7qz
基础工程:https://pan.baidu.com/s/1HISQizt0NvCHo8U0KgSCCA 提取码: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/s/1tmPDt9Ebq814cbasffOhlA
提取码: m4pg
对线下游戏开发学习感兴趣的盆友,欢迎访问:http://levelpp.com/
同时,也欢迎加入游戏开发群搅基:610475807 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|