您當前的位置:首頁 > 遊戲

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

作者:由 陳逸飛 發表于 遊戲時間:2017-08-06

格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?Thinkraft2017-08-06 21:09:57

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

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

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

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

體型碰撞是怎麼做的?

——實現方式有很多。最普通的一種處理是在兩個角色的體型Hitbox發生碰撞時,給雙方各新增一個「下一幀向後彈開一定距離」的水平位移修正,讓角色互相推開。大部分傳統2D遊戲都是這麼做的。

拿你的問題來說,1P判定框下降與2P判定框接觸的那一幀,程式就會根據當時的侵入量修正雙方的水平位移,不會讓1P摞在2P頭頂上。

PS:我順手翻了翻你之前的問題,在這裡提醒一下,如果你沒有「幀」的概念,是做不好格鬥遊戲的。

格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?blue sky2017-08-06 21:37:10

明明有啊,街霸裡面將軍就很喜歡站著踩別人頭,雖然耗血不多,但姿勢很帥啊

格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?匿名使用者2017-08-06 22:22:56

駁回BUG單並附評論:

設計如此

給跪了。jpg

格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?匿名使用者2017-08-07 12:56:28

據我所知,大部分遊戲引擎character的膠囊體有個選項叫others can walk on,開啟則不會滑落可以在別人頭頂走啊走,反之就會滑落。

至於矩形碰撞體也是同理,比如位於頭頂前半部分的時候給個向後的加速度,位於後半部分的時候給個向後的。

格鬥遊戲為什麼人物不會站到另一個人物頭上?體型碰撞是怎麼做的?猴與花果山2017-08-07 21:12:26

謝邀。

題主的問題在於搞混了“碰撞框”的概念,在動作遊戲中,一個角色身上的“碰撞框”通常有多組:

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\top\width\height等資訊,還有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裡面去看看:

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

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

標簽: entity  角色  position  Motion  add