作者:QXYO
前言
控制時間相信幾乎是每個人都想擁有的能力,也為眾多影視、遊戲等提供了靈感,荒木老師在jojo的奇妙冒險中幾乎把控制時間的能力玩了個遍。而在遊戲領域,令筆者印象最深的就是本次的主角——時空幻境(Braid),一款把橫板跳躍與時間回溯完美結合的遊戲。
注意!由於本教程主要實現時間回溯效果,橫板過關類遊戲的場景搭建、移動、動畫等不在本次內容範圍內,有興趣的同學可以先從B站專欄開始學起:
【簡明UNITY教程】教你迅速實現2D角色的移動和跳躍:https://www.bilibili.com/video/BV1jJ41147WM
本次教程也會以該系列視頻的工程為基礎,在原項目上進行修改實現時間回溯的功能。
項目來源:【簡明Unity教程】2D跳躍遊戲的踩怪功能
https://www.bilibili.com/video/BV1v7411E7qz
基礎工程: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上的,用於獲取和修改某一幀角色的動作;用於在時間倒流時暫停動畫的播放;,原工程的角色控制代碼,用於修改角色臉的朝向;,獲取、修改速度和時間倒流時關閉物理引擎。
void Start()
{
TimeBackData = new Stack();
SpriteRenderer = GetComponent();
animator = GetComponent();
cc2D = GetComponent();
m_Rigidbody2D = GetComponent();
}
首先是保存數據,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
對線下游戲開發學習感興趣的盆友,歡迎訪問:http://levelpp.com/
同時,也歡迎加入遊戲開發群攪基:610475807