GameMaker Studio 之中的攻击与受击判定盒
什么是hitboxes和hurtboxes?
注:這里我還是用原文,原意是攻擊判定盒 & 受擊判定盒,如果你常玩FTG、ACT或者FPS類型的游戲,對hitbox這玩意兒肯定了解很多。不了解的朋友還請仔細閱讀。
基本上,hitbox和hurtbox只是專門的碰撞檢測(碰撞檢測允許您確定對象何時接觸或重疊)。hitbox通常與某種形式的攻擊相關聯,并描述該攻擊的有效范圍。hurtbox通常與角色(或游戲中的任何其他“可擊中”對象)關聯。每當他們倆碰撞時,我們認為攻擊已“達成”,我們將其效果應用于目標。下面的內容我會用FTG類型游戲做主要的例子。在我看來,格斗游戲提供了最明顯的hitbox和hurtbox示例,使它們非常容易理解。 我們來看看街霸4,如下:
?
上圖里,我們看到Makoto表演了她的一個特殊動作,吹上攻擊。這招兒就是向上出拳,通常用來防空,可以擊中向你跳躍的對手。紅色矩形是hitbox,而綠色矩形是hurtbox。如果Makoto用她的hitbox碰到別人的hurtbox,那么另一個玩家將被“擊中”。
現在,默念“box”一千遍,好了,咱們開始設置。
Hurtbox 設置
首先!我們需要一個精靈圖用于我們的hurtbox。創建一個新的sprite,命名為sprHurtbox,1 x 1像素,并將其著色為綠色。我們只需要一個像素,因為我們將在實例化hurtbox時將其縮放到我們需要的任意大小。另一種方法是為每個可能需要hurtbox的游戲對象創建一個自定義大小的精靈圖,這樣很……浪費資源,也很……無聊。
現在我們創建一個object(對象),命名為oHurtbox,精靈圖指定為sprHurtbox。添加create事件,敲下面這些。
?
現在我們需要創建一個hurtbox并給它一個持有者。
創建一個script(腳本),命名為hurtbox_create。敲入下面這些代碼。(咳咳,哥們兒你別復制粘貼啊……)
?
如果你以前沒怎么寫過script(腳本)的話,可能覺得這個看起來有點兒多啊,但其實很簡單。首先,我們創建一個oHurtbox對象,并將該對象的ID存儲在_hurtbox的owner變量中。然后,使用_hurtbox的變量,我們傳入所有者(調用此腳本的任意對象),接著定義了hurtbox的大小和偏移量。現在腳本已經寫好了,我們可以來調用一下試試看。打開oPlayer對象把下面的代碼加到create事件里。
?
使用我們剛剛創建的hurtbox_create腳本,我們可以很方便的地設置比例和偏移量,并將oHurtbox對象的ID存儲在oPlayer對象可以使用的變量中。腳本中使用的數字以像素為單位。我們創建的hurtbox是18像素寬x24像素高,偏移玩家精靈左側9像素,并偏移玩家精靈上方24像素(注:這說的可夠真詳細的 =_=)。好了,現在運行游戲看看,hurtbox好像沒有跟隨你的角色。
我們得解決這個問題。在oPlayer對象中打開end step事件并添加以下代碼。如果你看了本系列的前幾篇教程,我把這些代碼加到了animation code底下。
?
with和other如果你還沒用過的話,我在這里簡單解釋一下(如果還是不明白的話,還是去仔細看一下F1比較好)。當你使用with后跟對象名稱(或特定對象ID)時,花括號里的代碼將執行,就像該對象正在執行它一樣。so,當我們寫with(hurtbox)時,我們正在更新存儲在hurtbox變量中的特定oHurtbox對象的x和y位置。
由于我們使用with,我們也可以使用other。這段代碼用到other時,它將引用此代碼運行的原始對象。在這種情況下,就是我們的oPlayer對象。
好了,現在hurtbox跟隨玩家了。
?
Hitbox設置
現在我們有hurbox了,我們得打它啊! hitbox所需的設置跟hurtbox差不多,但它還有更多功能。 簡單理解一下hitbox,首先我們來檢測碰撞,要是碰撞了,然后決定接下來要做什么。(哎,我車還沒碰到你,你怎么倒了呢?面對一些老年碰瓷者,有可能是咱們的車hitbox出毛病了,要么就是他們的hurtbox的offset或者scale出毛病了吧……這時候可能就需要交警和行車記錄儀來debug了 =_=)
就像hurtbox一樣,我們需要創建一個精靈和一個對象。創建一個名為sprHitbox的單像素精靈,紅色。然后創建oHitbox對象并指定sprHitbox精靈。添加create,step,end step和destroy事件,打開create事件并敲入以下代碼。
?
與我們的hurtbox一樣,我們需要設置所有者和偏移量。然而,與受傷害的盒子不同,hitbox并不是一直存在的,它只存在于攻擊期間。life變量將用于確定數據框將存在多少幀并保持活動狀態。 xHit和yHit是我們的擊退變量。hitStun確定我們擊中的角色被打中后眩暈的時間。最后,ignore變量和ignoreList列表將用于確保我們不會多次擊中一個角色。后面你會看到它是如何工作的。
擊中眩暈是一個角色在被擊中后被擊暈的時長。如果玩家被擊暈,除了等著被揍或者祈禱,他們什么都做不了(當然你也可以寫成瘋狂按鍵可以稍微減少眩暈時長)!格斗游戲里這玩意兒很常見。你要是把對手打暈了的話,嗯……先來一個挑釁動作,然后一套連招KO好了~ (或者…你也可以點一個輕攻擊讓對方恢復正常,接著繼續干死他…有點兒更藐視對手,是的,我跑題了 : p)
打開destroy事件并加上下面的代碼。
?
這可以確保hitbox在銷毀后,其所有者停止嘗試與其進行交互,并在不再需要時刪除ignoreList。如果列表未被刪除,則可能導致內存泄漏。
之后打開step事件,加入下面代碼:
?
這將在hitbox處于活動狀態時從生命周期中減去(就是計時器)。當life變量達到0時,刪除hitbox。最后到end step事件,加入下面這一小段:
?
當一個對象被破壞時,就像我們上面所做的那樣,將調用destroy事件(如果存在)。OK,hitbox設置已經完成了, 但對于實際對象!還有很多事情要做。就像hurtbox一樣,接著我們要干嘛?對了,腳本。創建一個新腳本,命名為hitbox_create,然后敲入以下代碼(上面的我加了注釋,下面的注釋我就不加了,作者講的很細)。
?
跟hurtbox那個差不多,多了幾樣東西,life,xHit和hitStun。 完事兒了嗎?我們差不多已經完成了一半。回到oPlayer對象的end step事件,在hurtbox代碼段下面加上
這些:
?
這與hurtbox代碼略有不同,我們要先在攻擊那一刻確認此時是否已經有hitbox存在,也就是檢查我們的hitbox變量是否不等于-1。
現在,最后一步,我們需要在攻擊期間的正確時間實際創建hitbox。但在我們這樣做之前,我需要簡要介紹一下格斗游戲中攻擊的實際構成。所有攻擊都分為三個部分。啟動(Start up),活躍(Active)和恢復(Recovery)。每一部分都會持續一定的幀數。看看下面的圖表會理解的更清晰。
?
啟動是攻擊變為活動所需的時間,然后銜接到出拳。二手手機買賣活躍是hitbox能夠實際擊中敵人的時間。恢復是角色完成攻擊并返回中立狀態(譯注:這里對狀態不太熟悉的話,請先詳細了解一下狀態機)所需的時間,之后才可以再執行其他操作。讓我們看看我們的角色精靈,以確定我們的啟動,活動和恢復幀應該在哪里。
?
我們的啟動幀是0-2幀。相當于攻擊動作的發條。活躍幀為3-4幀,恢復5-7幀。我們需要在第3幀創建我們的hitbox,它需要在第5幀開始之前一直處于活動狀態。在我的項目中,我的frameSpeed變量為0.15并且游戲以60 fps運行,我的精靈動畫以大約每秒四幀。所以,我的hitbox的生命需要為8幀。
打開attack_state腳本并添加以下行(譯注:這個腳本在之前的教程中)。
?
我們要檢查我們是否在正確的幀上,并且hitbox不存在,再使用hitbox_create腳本創建hitbox。在創建hitbox時,我們需要將水平值(xscale和xOffset)乘以角色面向的方向。這確保了hitbox始終與角色的方向對齊。然后我們設置了8幀的存活時間,然后是水平擊退和擊暈。現在運行游戲并按下攻擊,你應該會看到hitbox出現并按預期消失。現在我們得讓它能打東西了!
TIPS:hitbox越大,它就越強大。生活也一樣。 hitbox活動的時間越長,它就越強。在格斗和動作游戲中,巨大的,持續時間長的hitbox總是非常強大(想想那些惡心人的BOSS吧)。在設計攻擊時請記住這一點!
敵人設置
拳擊手需要沙袋,而我們,需要一個敵人。這將非常簡單,因為敵人將使用與我們的玩家相同的許多代碼(譯注:通常玩家和敵人會隸屬于一個Entity的父類對象,這樣就不用重寫類似的代碼了)。現在,我們需要添加一些新的精靈。你可以使用你想要的任何精靈,或者用我正在使用的相同精靈(譯注:效果可能沒那么好,比如受擊、跳)。
以與創建玩家精靈相同的方式創建精靈。確保精靈原點是(16,32),就像上次一樣!你應該有兩個精靈:sprEnemy_Idle和sprEnemy_Hurt。 復制oPlayer對象并將其命名為oEnemy(譯注:如果你有一個oEntity的對象的話,就可以更方便了)。將sprEnemey_Idle sprite分配給對象,然后打開create事件。我們需要添加一些新變量:
?
hit是一個簡單的布爾值,我們將在應用命中效果時使用到它。接下來,hitStun是被擊中后敵人在hitStun中停留的時間。最后,hitBy將是擊中它們的對象的ID。
接著打開step事件。刪除與player按鍵和狀態切換有關的代碼段(譯注:如果你有一個oEntity的對象的話,沒必要這么麻煩了)。當我們按下按鈕時,我們不希望敵人執行動作,我們需要重寫狀態切換。加入以下代碼。
由于我們的敵人只會站著或被擊中,我們現在不需要任何其他狀態。但是我們確實需要創建hit_state腳本。立即執行此操作并添加以下代碼。
?
如果你已經讀到這里了,那這對你來說應該很熟悉。首先,我們降低敵人的水平速度,直到達到零。接下來,我們讓hitStun倒計時,并在hitStun達到零時將敵人恢復到默認正常狀態。很簡單吧! 再打開end step事件。首先,把animation_control()改成 animation_control_enemy();然后在hurtbox代碼下面添加這個。
?
這是我們應用命中效果的地方,如擊退,擠壓和拉伸,屏震(如果你想要這種效果的話),等等。它還將敵人狀態更改為受擊狀態,這會阻止他們在擊中昏迷時執行任何其他操作。 現在,我們要創建animation_control_enemy腳本。這是玩家使用的相同類型的腳本,但是簡化了,因為敵人的動畫和行為比玩家少很多。加入下面的代碼(注意精靈名是否與你的資源匹配):
?
這里沒什么好說的。我們所做的就是根據狀態設置精靈,就像我們對玩家一樣。 OK,敵人設置完成!放在房間里一兩個敵人。下面到了比較難的部分了...檢查hitbox / hurtbox碰撞(重疊),并解決該碰撞。
擊中檢測和確定攻擊
這部分有點兒繞。還記得with和other么?嗯...我們還要用到它們,但嵌套在自己內部。告訴對象在另一個對象內部的另一個對象內部做什么!對象開始!好吧也許它并不復雜,但有時讀起來就有點兒費勁...... 不管怎樣,咱們先回到oPlayer對象并打開end step事件,你可以在其中更新一下hitbox代碼段,讓它看起來像這樣。
?
快速回顧一下這里發生的事情。我們檢查當時是否確實有一個hitbox,如果有,我們會檢查所有的hurtbox對象,看看它們是否與這個特定的hitbox實例發生碰撞。使用with時請務必注意,如果您只使用對象的名稱(如oHurtbox)而不是對象的實例ID,則將從該對象的所有實例中運行代碼。現在我們是兩層深,并且正在檢查來自hurtbox的碰撞,所以當我們使用other時,它不再引用運行所有這些代碼的主對象(oPlayer對象),而是作為一個層的對象在這一個之上(oHitbox對象)。 查看下面的圖表,可以直觀地了解正在發生的事情。
?
oPlayer用于與oHitbox通信,然后oHitbox使用with與oHurtbox進行通信。每次調用都會為代碼創建一個新層。當一個對象正在使用其他對象時,它會引用它上面的層。必須了解這些層以及with/other,才能完全理解這些碰撞檢測將如何工作。
最后,我們需要解決碰撞。我們已經檢查了hitbox和hurtbox是否發生了碰撞,現在我們需要決定接下來會發生什么。好了,我們的ignore、ignoreList登場啦。首先,我們需要檢測,看看hitbox是否已經擊中了hurtbox。
?
好多花括號......甭擔心。在確定我們的hitbox與一個hurtbox相撞后,我們不得不再做一個功能,并且這兩個判定盒有不同的Owner(持有判定盒的對象)。Owner檢查可防止hitbox與屬于玩家的hurtbox碰撞,從而阻止玩家自己打到自己。
接下來檢測我們要忽略的敵人列表。如果你之前從未使用過for循環,這可能會讓人感到有些困惑,但它看起來要簡單得多。 for循環執行一定代碼塊一定次數。在這兒,它執行的次數與ignoreList中的數據實例一樣多。它會檢查列表中的每個位置,并將其與剛剛碰撞的hurtbox的owner進行比較。如果列表中的任何數據與hurtbox的owner匹配,則忽略owner,并且不會被命中,我們將使用break停止檢查列表。我們這樣做是為了防止同一個敵人在我們的攻擊活躍的每一幀被擊中。如果這個忽略檢測不存在,則敵人將在8幀中被擊中8次。
你可能想知道ignoreList 如何填充數據,我們后面再說。如果我們的第一次檢查失敗,也就是說,如果不應忽略敵人,我們可以擊打它們并將其數據添加到列表中。對你的代碼進行以下更改。
?
如果ignore為false,那么hurtbox(other.owner)的持有者就會被擊中!我們需要告訴它被擊中的對象(other.owner.hit = true)以及擊中它們的對象(other.owner.hitBy = id)。然后將它們添加到忽略列表中,這樣我們就不會在下一幀再次點擊它們(ds_list_add(ignoreList, other.owner)。現在運行游戲,去揍你的敵人們吧!他們應該會被擊中、擊退、并被擊暈(譯注:當然,在動作或格斗游戲中,單一的普通攻擊是不會擊暈敵人很長時間的,這里的敵人硬直時間可能稍長,擊退距離也略顯長,這里是教程作者為了便于理解有意為之)。
最后的想法
哇。好累啊!很高興你能看完,真棒~ 當我開始寫這篇文章時,我并沒有預料到需要這么長時間(譯注:嗯,別說寫了,我都沒想到要花這么長時間來翻譯,真的很累……)。我很高興能夠展示很多有趣的概念,例如with / other,for循環,ds_lists和簡單的碰撞檢測。
總結
以上是生活随笔為你收集整理的GameMaker Studio 之中的攻击与受击判定盒的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 9个让2D游戏创作更轻松的工具
- 下一篇: 创造开放世界——《看火人》游戏场景设计