2017年10月22日 星期日

隨手札記:PF2D Controller的Move段落改寫(Unity C#)

因為新增了牆壁跳功能,連帶地原本的Move寫法也要改寫
不過....原本的Move本來就寫得不太好看就是了....


原本的結構是這樣的:

bool 正在移動 = false;
Update(){
如果(允許移動){
Vector 移動方向 = (零向量);
正在移動 = false;
檢查移動;
如果(正在移動)
物件(水平)速度 = 移動方向*移動速度;
否則
物件(水平)速度 = 0;
}
}
檢查移動{
如果(有按按鍵){
正在移動 = true;
移動方向 += 按鍵指定移動方向;
}
}
這個寫法是從Unity的免費套組GhostFreeRoamCamera改寫過來的。
這樣寫有一個好處--當你同時按下左鍵跟右鍵的時候,你的角色會靜止不動。在一些極端場合,這提供了不錯的操作性。
原版的寫法還有一些加速移動的細節,所以才會有正在移動這個奇怪的變數存在。

但是,這麼寫也有一個壞處:結合上一篇提到的牆壁跳設計--當進行牆壁跳的時候,我們期望能賦予角色一個跳躍的初始速度,其中很有可能包含水平速度。而上面的移動操作程式碼結構會導致這個跳躍初始(水平)速度在被賦予的下一刻就沒了(歸零或被替換成水平移動速度的值)。

實際狀況大概像是這樣:

可以看到,牆壁跳賦予的水平速度幾乎在瞬間就歸零(後續幾次牆壁跳有些許移動,那個是本來預期的效果)、甚至完全沒有執行到(以致於一開始直接垂直跳到牆壁邊緣上)。

如果移除掉移動速度 = 0那段程式碼,那麼牆壁跳的效果就可以正確發生:

...可是沒有那行的話,放開左鍵或右鍵時角色會停不下來啊啊啊:
(移動過程中只有同時按下左右鍵或撞到東西,才能讓方塊停止)

為了解決這個bug,我需要一種新型的程式碼結構....


(思考中)

(思考中)

(思考中)

唉,沒什麼好想法。
先鑒往知來一下好了--我會不會在改寫過程中誤會了它原本的意思?
以下是GhostFreeRoamCamera(原始版本)的程式結構:

bool 正在移動 = false;
float 初始速度 = (定值);
float 現在速度 =0;
Update(){
bool 最後移動 = 正在移動;
Vector 移動方向 = (零向量);
如果(正在移動)
現在速度 增加;
正在移動 = false;
檢查移動;
如果(正在移動){
如果(最後移動 != 正在移動)
現在速度 = 初始速度; //參見文章註解
將單位移動(移動方向*現在速度)轉換成座標更新;
}
否則
現在速度 = 0;
}
檢查移動{
如果(有按按鍵){
正在移動 = true;
移動方向 += 按鍵指定移動方向;
}
}
註:中間那段簡單來講,是「如果上一刻沒有移動、這一刻卻動了,則變為初始速度」的程式碼版本。

存在一個中間變數(現在速度),所以相較之下比較調控嗎?
但我因為諸多原因必須用剛體速度,剛體速度能夠這樣寫嗎.......

..............

重新整理一下思緒好了。

現在想要做到的是:

  1. 開始移動時變更速度(提供移動速度)
  2. 移動時能夠讓速度維持在定值
  3. 停止移動時變更速度(取消移動速度)
  4. 牆壁跳躍時變更速度(到牆壁跳速度)且不會被取消速度
  5. 不要每一刻都在重新指派剛體速度(其實就是上一點與效能考量的融合)

照著這樣的脈絡、並參考GFRC的好處,也許可以刻出一個新結構來....?
看著想著刻出來以下結構:

Vector 現在速度;
Update(){
Vector 上次速度 = 現在速度;
如果(開始移動)
現在速度+=移動速度
如果(取消移動)
現在速度-=移動速度
如果(現在速度!=上次速度)
剛體速度 = 現在速度
}
分析一下,好像五個點都有達成(只要牆壁跳的時候不更新現在速度....這聽起來好像有點違和)

把上面的結構再進化/展開一些:

Vector 移動方向;
Update(){
Vector 上次移動方向 = 移動方向;
檢查移動;
如果(移動方向!=上次移動方向)
剛體速度 = 移動方向*移動速度;
}
檢查移動{
如果(按下按鍵)
移動方向+=指定移動方向
如果(放開按鍵)
移動方向-=指定移動方向
}
感覺好像可行,來試試看好了。

....幹,還真的可以。
所以說,有些時候把自己想達成的目標寫下來、再從這些目標發想、寫架構,是很有用的。
再配合現有結構做一點點安插....

Update(){
Vector 上次移動方向 = 移動方向;
如果(已死亡)
回傳啦,不管後面了;
如果(允許移動)
檢查移動;
如果(移動方向!=上次移動方向)
剛體速度 = 移動方向*移動速度;
剛體速度 += 強迫速度;
}
搭拉,完成了。
裡面的已死亡和允許移動是為了程式的其他功能,這裡就不多說了。
雖然我總覺得那個已死亡可能會造成後續的bug.....?

最後是真正的程式碼:

private Vector2 movingDirection = Vector2.zero;
private Vector2 lastMovingDirection = Vector2.zero;
void Update()
{
lastMovingDirection = movingDirection;
//judging......
if (true)
{
CheckMove(rightButton, ref movingDirection, transform.right);
CheckMove(leftButton, ref movingDirection, -transform.right);
}
if (movingDirection != lastMovingDirection)
{
rb.velocity = new Vector2(movingDirection.x * movingSpeed, rb.velocity.y);
}
//......
}
void CheckMove(KeyCode keyCode, ref Vector2 baseVector, Vector2 directionVector)
{
if (Input.GetKeyDown(keyCode))
{
baseVector += directionVector;
}
if (Input.GetKeyUp(keyCode))
{
baseVector -= directionVector;
}
}
謝謝大家,下班去。

沒有留言:

張貼留言