格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?
眾所周知,格鬥遊戲都是由一堆判定框來運行的。而在格鬥遊戲中,角色永遠不會站到另一個角色的頭上,就算剛好跳在對方頭上的位置,也會瞬間滑落。而目前各種格鬥遊戲的判定框中,角色的體型碰撞框也都是矩形的啊,既然是矩形為什麼站不上去呢?是用程序特意寫的嗎?還是說角色的上半部分是矩形,但腳下的判定框就不一樣了呢?
格鬥遊戲為什麼角色不會站到另一個角色頭上?
——當然是因為遊戲性設計了,換言之,「一個角色能/不能踩到另一個角色頭上站著」是個功能需求。程序都是根據需求寫的。如果製作人覺得能踩頭上比較好玩,當然也可以做成那個樣子。
體型碰撞是怎麼做的?
——實現方式有很多。最普通的一種處理是在兩個角色的體型Hitbox發生碰撞時,給雙方各添加一個「下一幀向後彈開一定距離」的水平位移修正,讓角色互相推開。大部分傳統2D遊戲都是這麼做的。
拿你的問題來說,1P判定框下降與2P判定框接觸的那一幀,程序就會根據當時的侵入量修正雙方的水平位移,不會讓1P摞在2P頭頂上。PS:我順手翻了翻你之前的問題,在這裡提醒一下,如果你沒有「幀」的概念,是做不好格鬥遊戲的。
謝邀。
題主的問題在於搞混了「碰撞框」的概念,在動作遊戲中,一個角色身上的「碰撞框」通常有多組:
1,攻擊碰撞框
2,受擊碰撞框
3,體型碰撞框
4,自己作為地形的碰撞框
其中3和4決定了誰能「站在」誰的腦袋上。
Talk is cheap, show me the code?
ok,最近回答了一個ECS架構的貼,似乎很多人對這個有興趣,我就用ECS來寫一個大概的概念,進一步說明這些「碰撞框」的邏輯關係,Lua偽代碼:
character = {
--角色信息component,記錄了這個角色特有的一些信息,有這個component也可以認為他就是一個角色
[uniqueId] = "" --每一個角色都應該有一個唯一id
[health] = 100 --角色當前的生命值,其他角色屬性相關的略
[side] = 0 --角色所處陣營,不同陣營之間會互相影響
}
position = {
--位置component,僅僅負責所在坐標
[x] = 0 --x坐標
[y] = 0 --y坐標
}
direction = {
--面向信息,橫版動作遊戲只需要左邊或者右邊
[faceToRight] = true --所以面向右側是false的一定是面向左邊的
}
motion = {
--當前做的動作的信息
[anim] = "" --正在做什麼動作,注意默認是沒有動作的
[frameId] = 0 --當前動畫幀
[maxFrame] = 0 --總動畫幀,這個有點magic,但是邏輯上確實新的就是0
}
motionChange = {
--要改變動作的信息
[toAnim] = "stand" --要變成什麼動作
[frame] = 0 --這個動作的第幾幀
[maxFrame] = 1 --這個動作最多多少幀
}
movement = {
--要進行位置移動的力的信息
[x] = 0 --X方向上的移動
[y] = 0 --Y方向上的移動
}
attackRects = {
--攻擊矩形信息,這裡的數據都可以理解為一個offset,而真正的坐標則是position這個component中的
--當然本文中的rect不僅具有left opwidthheight等信息,還有piovtX和pivotY這兩個額外的信息,是矩形的中心點
[rect] = {} --進攻矩形,這是一個數組,用於判斷攻擊命中敵人的
}
defenseRects = {
--挨揍矩形信息,他就是乾淨的挨打信息,雖然看起來和角色身體有關,但他絕不影響和地面的碰撞
[rect] = {} --【這是問題的關鍵之一】挨打矩形,被攻擊矩形捕獲就有可能挨打
}
terrainRects = {
--地形信息,自身可以被站立等的信息,地形肯定有,如果把它加給角色……
[rect] = {} --【這是問題的關鍵之一】地面矩形,決定了哪兒可以站立,當然,他也是根據position的offset
[jumpFromBottom] = true --是否可以從下往上跳不被阻擋,我們經常遇到「天花板」就是false的。
[jumpFromTop] = true --如果角色站在上面,是否允許下跳,很多地形你站在上面按下和跳可以跳下來,這樣的地形這個是true。
[horizontalAcross] = true --並不是所有的阻擋矩形都是牆壁(組織左右移動的這項應該為false)
}
stepPoints = {
--身體信息,這個信息才是決定角色和地形碰撞的
[points] = {} --【這是問題的關鍵之一】由這些點陣組成了一個角色與地形的碰撞。
}
dockedTo = {
--"我"自己停靠在哪些物體身上
dockInfo = {}
}
dockType = {
[dragTop] = false --是否會拖著「頭上」的角色一起動
[dragLeft] = false --是否會拖著左邊的角色一起動
[dragRight] = false --是否會拖著右邊的角色一起動
[dragBottom] = false --是否會拖著「吊著」的角色一起動
}
bringMove = {
--帶動信息,如果有這個信息,就可以帶動一些停靠在「我身上」的角色,即把我的movement值附帶給他們
}
render = {
--這是需要繪製這個角色,當然我們默認是要繪製的,具體數據就不寫了
}
--這就是一個角色,被創建的時候它具有這些components,add函數當然會返回被添加的component
characterEntity = entityFactory.create()
.add(new character())
.add(new position())
.add(new direction())
.add(new render())
.add(new motion())
.add(new motionChange()) --在創建時change到約定的第一個動作
--值得注意的是,角色並沒有一開始就被附上stepPoints等component,其實這並不重要,因為有一個motionSystem很快就會讓他被附上。
--遊戲中的地形component,最普通的就是這種只有被停靠信息的
terrainEntity = entityFactory.create()
.add(new terrainRects())
.add(new position())
.add(new dockType())
--我們當然需要一些utils的函數,寫在一個utils庫里,當然具體我就不實現了,只是討論用(實現也就是體力活)
function getAnimationInfo(anim) --根據動畫名稱獲得對應的信息,當然這是靜態的數據信息,大多來自策劃編輯的內容
function getHorizontalFlipRect(rect) --獲得矩形根據pivotX左右翻轉後的坐標信息,返回一個新的rect數據
--獲得rectA是否」碰到「rectB上(其實這個演算法是有技巧性的,當然不是這裡的重點)
--利用lua的特性,返回4個值,分別是:A碰觸了B的左側、A碰觸了B的右側、A碰觸了B的頂部、A碰觸了B的底部
function isRectDockRect(rectA, rectB)
--現在我們來看需要哪些system
--管理動作變化的system,只負責motion信息的自然變化
motionSystem = {
local entities = follow({motion}) --捕獲所有的具有motion這個component的entity
function run(deltaTime)
for entity in entities do --對捕捉到的entity進行處理
local animInfo = getAnimationInfo(entity.motion.anim)
if animInfo ~= nil and entity.motion.maxFrame &> 0 then
local frameId = (entity.motion.frameId + 1) % entity.motion.maxFrame
entity.motion.frameId = frameId
--因為是動作遊戲,所以每一幀角色的各種碰撞信息都在變化
entity.remove(stepPoints)
if animInfo.frame[frameId].stepPoints ~= nil then
entity.add(new stepPoints(animInfo.frame[frameId].stepPoints))
end
entity.remove(attackRects)
if animInfo.frame[frameId].attackRects ~= nil then
entity.add(new attackRects(animInfo.frame[frameId].attackRects))
end
entity.remove(defenseRects)
if animInfo.frame[frameId].defenseRects ~= nil then
entity.add(new defenseRects(animInfo.frame[frameId].defenseRects))
end
--【重要】動作可以讓角色成為一種地形
entity.remove(terrainRects)
entity.remove(dockType)
if animInfo.frame[frameId].terrainRects ~= nil then
--偷個懶吧,姑且當做角色只有頂部可以拖動人
entity.add(new terrainRects(animInfo.frame[frameId].terrainRects))
entity.add(new dockType({[dragTop] = true}))
end
end
end
end
}
--motionSystem並不管理動作變化,而motionChangeSystem管理這個
motionChangeSystem = {
local entities = follow({motionChange, motion})
function run(deltaTime)
for entity in entities do
if entity.motionChange.anim ~= entity.motion.anim then
entity.motion.anim = entity.motionChange.anim
entity.motion.frameId = entity.motionChange.frame
entity.motion.maxFrame = entity.motionChange.maxFrame
end
end
end
}
--【這裡是一個關鍵點】dockingSystem用來關心角色的停靠關係
dockingSystem = {
--首先我們關心所有具有站位信息(stepPoints),面向信息(direction)和坐標信息(position)的entity,我們假設他們都是角色
local characterEntities = follow({stepPoints, direction, position})
--然後我們關心所有具有地形信息(terrainRects)和坐標信息(position)的entity,我們假設他們都是地圖塊
--【重點來了】但是很顯然,如果一個characterEntity它也具有這兩個東西,那麼他也會被認為是地形
local terrainEntities = follow({terrainRects, position})
for chaEnt in characterEntities do
--lua中的三目運算符是這樣的,所以別誤會……
local posRect = chaEnt.direction.faceToRight == true and
chaEnt.stepPoints.rect or
getHorizontalFlipRect(chaEnt.stepPoints.rect)
posRect.left = posRect.left + chaEnt.position.x
posRect.top = posRect.top + chaEnt.position.y
for terEnt in terrainEntities do
local isCha = terEnt.direction ~= nil --假如地形有direction,我們姑且認為他也是一個角色
local terRect = isCha == true and
(
terEnt.direction.faceToRight == true and
terEnt.terrainRects.rect or
getHorizontalFlipRect(terrainRects.rect)
)or
terEnt.terrainRects.rect
terRect.left = terRect.left + terEnt.position.x
terRect.top = terRect.top + terEnt.position.y
--這裡判斷獲得是否具有停靠關係
local touchLeft, touchRight, touchTop, touchBottom = isRectDockRect(chaEnt, terEnt)
if touchLeft == true or touchRight == true or touchTop == true or touchBottom == true then
--添加一個dock信息
chaEnt.dockedTo = chaEnt.dockedTo or chaEnt.add(new dockedTo())
table.insert(
chaEnt.dockedTo.dockInfo,
{
[entity] = terEnt,
[left] = tuchLeft, [right] = touchRight, [top] = touchTop, [bottom] = touchBottom
}
)
end
end
--到這裡,dockingSystem的工作就完成了,至於之後怎麼處理,跟dockingSystem沒有半毛錢關係了,」我」只負責判斷是否docking,很乾凈。
end
}
--moveOnTerrainSystem的工作是,根據角色與地形的碰撞信息【修正】角色的movement
--【注意】修正,而不是設置,設置應當在另外一個system中。
moveOnTerrainSystem = {
--這裡只關心movement,在邏輯業務中,頂多會關心dockedTo,但是不會關心position
local characterEntities = follow({movement, dockedTo})
for chaEnt in characterEntities do
for terEnt in chaEnt.dockedTo.dockInfo do
... --這裡則是根據碰撞,設置movement的值,省略
end
end
}
--而movingSystem,則是簡單的根據movement對position賦值
movingSystem = {
local entities = follow({movement, position})
for entity in entities do
entity.position.x = entity.position.x + movement.x
entity.position.y = entity.position.y + movement.y
end
}
--[[
【值得注意】的是:
1,並不是只有一個system在改變movement,包括玩家操作等都會改變它。
2,該改變到哪個motion,則由其他system來設置motionChange這個component,而不是移動決定的。
3,所以說,是不是地形加上個movement就也可以移動了?當然是!
--]]
時間有限,暫時先寫到這裡,還得先去自己的項目幫程序修bug……看起來不那麼舒服的話,不方丟到VSCode裡面去看看:
據我所知,大部分遊戲引擎character的膠囊體有個選項叫others can walk on,開啟則不會滑落可以在別人頭頂走啊走,反之就會滑落。至於矩形碰撞體也是同理,比如位於頭頂前半部分的時候給個向後的加速度,位於後半部分的時候給個向後的。
我警察第一個不服
駁回BUG單並附評論:
設計如此給跪了.jpg明明有啊,街霸裡面將軍就很喜歡站著踩別人頭,雖然耗血不多,但姿勢很帥啊
說不定被踩的人頭上的碰撞箱是沒頂的呢
對面一跳頭上就掉下來了
(我瞎扯的我不懂這個玩意瞎扯,kof里有boss能站頭上,還把對手站趴下了
推薦閱讀:
※精簡目前的格鬥遊戲的基本系統的話,可以簡潔到怎樣的地步?(保持遊戲性的情況下)
※僅僅作為資深玩家能否應聘遊戲策劃?
※DNF(地下城與勇士)的 PK 系統有什麼特點?
※怎樣提高自己的原畫設計能力?
※有哪些狗血的遊戲策劃事故?