格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?

眾所周知,格鬥遊戲都是由一堆判定框來運行的。而在格鬥遊戲中,角色永遠不會站到另一個角色的頭上,就算剛好跳在對方頭上的位置,也會瞬間滑落。而目前各種格鬥遊戲的判定框中,角色的體型碰撞框也都是矩形的啊,既然是矩形為什麼站不上去呢?是用程序特意寫的嗎?還是說角色的上半部分是矩形,但腳下的判定框就不一樣了呢?


格鬥遊戲為什麼角色不會站到另一個角色頭上?

——當然是因為遊戲性設計了,換言之,「一個角色能/不能踩到另一個角色頭上站著」是個功能需求。程序都是根據需求寫的。如果製作人覺得能踩頭上比較好玩,當然也可以做成那個樣子。

體型碰撞是怎麼做的?

——實現方式有很多。最普通的一種處理是在兩個角色的體型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 系統有什麼特點?
怎樣提高自己的原畫設計能力?
有哪些狗血的遊戲策劃事故?

TAG:遊戲設計 | 遊戲開發 | 格鬥遊戲FTG |