這篇內容,主要會講解,關於球的相關設定,包括了如何讓玩家發球、如何讓球移動、如何讓球反彈、如何改變球的大小,等等的相關功能。
本篇教學所使用的系統是Windows 10,如果有按鍵相關的說明,會用Windows的系統來解釋。如果你用的是Mac系統,再麻煩自行換成相對應的按鍵。
在學習本篇內容之前,最好具備以下的前置知識,以免看不懂這篇教學的內容。
本次的版本,會新增以下的功能:
另外,本次的版本,會出現以下的Bug:
由於篇幅的關係,Bug的部分,會留到下一篇的教學,再來講解處理方案。
這邊要先請各位,準備一個寬高為80*80的矩形,以及一個直徑為160的圓形,尺寸的單位是pixel。
我個人是習慣使用PS,來製作2D素材,接著再導入專案當中。當然,你也可以使用Sprite的繪圖功能,來把素材製作出來。
建立一個Script,將他命名成「scr_global_var」,這個腳本(Script)將會放置,所有的全域變數(Global Variable)。
在scr_global_var裡面,放入以下的程式碼:
//遊戲的狀態參數
global.game = {
//遊戲的播放狀態,0=暫停,1=遊戲中
play : 1 ,
}
這邊分享一個簡單的邏輯:「如果有兩個以上的物件,需要使用同一個變數,那就把這個變數,設定成全域變數。」
這個play
的變數,不管是玩家、球、計時器,都會使用到,因此就需要把他,設定成全域變數。
這邊我使用了結構體(Struct)的資料型態(Data Type),來幫全域變數進行命名。
結構體的部分,我之前還沒講解過,各位可以把他理解成「資料夾」的功能,如果用上面的程式碼來解釋,game
就是資料夾(結構體),而play
就是資料夾裡面的變數。
由於目前只有這個變數,所以各位可能還無法理解,結構體的好處,等慢慢增加之後,會比較能理解結構體的用途。
這邊附上官方的說明書,之後如果有機會,我會在寫相關的教學:
建立一個Sprite,將他命名成「spr_out」,接著把80*80的素材匯入(Import)到spr_out裡面。
這個out的功能,是用來偵測球是否出界,如果觸碰到球,球就會被消除。
spr_out的設定,與spr_wall相同,一樣的碰撞遮罩,以及Nine Slice的設定。
建立一個Object,將他命名成「obj_out」,這表示out的物件。接著把Sprite的參數,設定成「spr_out」。
叫出rm_stage_01的房間,並在Instances的圖層上,建立out的實體,方法就跟牆壁一樣,放上去後拉長就行了。
建立一個Sprite,將他命名成「spr_ball_big」,接著把直徑為160的素材匯入到spr_ball_big裡面。
雖然說球有大中小三種尺寸,但在這個專案中,我們會透過改變圖片的Scale(縮放比例),來控制球的尺寸,因此這邊只需要準備,大球的尺寸即可。
碰撞遮罩需要把Type的參數,修改成Ellipse (Slow)。然後把Fps設定成0,確保這個Sprite不會產生動畫。
原點則是設定成Middle Centre,這表示物體的正中心。
建立一個Object,將他命名成「obj_ball」,這表示球的物件。接著把Sprite的參數,設定成「obj_ball」。
在obj_ball的物件裡,加入Create事件,並放入以下的程式碼:
//移動速度
move_speed = 30 ;
//設定移動速度
id.speed = move_speed ;
id.speed
是內建變數(Built-in Variable),可以用來控制,實體的移動速度。
至於移動的方向,我們會透過玩家的物件,來進行控制,因此這邊不用加入。
除了移動速度之外,我們還需要改變球的大小,因此打開scr_global_var,放入以下的程式碼:
//球的參數
global.ball = {
//球的大小,"s"=小球,"n"=正常,"b"=大球
size : "n" ,
}
加入程式碼後的參考圖:
在obj_ball的Create事件裡,放入以下的程式碼:
//設定大小
if (global.ball.size == "n") {
id.image_xscale = 0.5 ;
id.image_yscale = 0.5 ;
} else if (global.ball.size == "b") {
id.image_xscale = 1 ;
id.image_yscale = 1 ;
} else if (global.ball.size == "s") {
id.image_xscale = 0.25 ;
id.image_yscale = 0.25 ;
}
在obj_ball的物件裡,加入Step事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
id.speed = move_speed ;
//設定大小
if (global.ball.size == "n") {
id.image_xscale = 0.5 ;
id.image_yscale = 0.5 ;
} else if (global.ball.size == "b") {
id.image_xscale = 1 ;
id.image_yscale = 1 ;
} else if (global.ball.size == "s") {
id.image_xscale = 0.25 ;
id.image_yscale = 0.25 ;
}
//除了遊戲中的狀態之外,其他狀態一律停止動作
} else {
id.speed = 0 ;
}
加入程式碼後的參考圖:
id.image_xscale
和id.image_yscale
是內建變數,可以用來改變,實體的圖片比例。
依照程式碼的設定,普通球("n"
)的直徑是80,大球("b"
)的直徑是160,小球("s"
)的直徑是40。
用來改變global.ball.size
的程式碼,將會放在玩家的物件裡面。在玩家還沒改變大小的狀況下,應該會產生普通球,因為global.ball.size
的初始值是"n"
。
補充說明一下,其實在Step事件裡面,撰寫改變大小的程式碼,並不是最優解,因為這樣每個step,都會去改變球的大小,如果球的數量太多,效能就會下降。
比較好的做法,應該是在「碰撞」到道具的時候,執行「一次性」的程式碼,來改變所有球的大小。
這邊是為了方便測試,所以才將他放在Step事件裡面。這在之後的版本中,會在進行修正。
接著要來設定,關於反彈的程式碼。
在obj_ball的物件裡,加入與obj_wall碰撞的事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
//微調反彈的角度,不然球的路徑,會一直保持不變
id.direction += irandom_range(-5 , 5) ;
//反彈
move_bounce_all(false) ;
}
在obj_ball的物件裡,加入與obj_player碰撞的事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
//微調反彈的角度,不然球的路徑,會一直保持不變
id.direction += irandom_range(-5 , 5) ;
//反彈
move_bounce_all(false) ;
}
加入程式碼後的參考圖:
當實體碰撞到實體時,就會觸發碰撞(Collision)事件。上面的設定,表示碰撞到obj_wall和obj_player時,就會觸發事件裡面的程式碼。
id.direction
是內建變數,可以用來改變,實體的移動方向。irandom_range(-5 , 5)
的用途,是隨機產生-5~5之間的整數。
id.direction += irandom_range(-5 , 5)
的意思是:讓移動的方向,隨機加上-5~5的整數。
如果不加入這行程式碼,球依然能正常移動,只是路徑會變得「非常固定」,我個人覺得會有點無聊。
因此才加入這行程式碼,讓球路發生些微的改變,這樣遊戲的體驗,會變得更好一點。
呼叫move_bounce_all(false)
的函式(Function),能讓球的移動路徑,變成反彈後的數值。講白話文就是讓球反彈。
接著要來設定,「讓球消失」的相關程式碼。
在obj_ball的物件裡,加入與obj_out碰撞的事件,並放入以下的程式碼:
//銷毀球
instance_destroy() ;
在obj_ball的物件裡,加入Outside Room事件,並放入以下的程式碼:
//防呆裝置,如果球還是不小心飛出房間(沒碰到out),那就自動銷毀
instance_destroy() ;
加入程式碼後的參考圖:
呼叫instance_destroy()
的函式,能夠銷毀呼叫函式的實體,簡單來說就是自毀。
Outside Room的事件,會在實體超出房間後觸發。在「正常」的情況下,球應該只有在接觸到obj_out時,才會消失。
但為了防止意外的發生,所以才會在Outside Room的事件裡面,加入銷毀球的功能。
在obj_player的物件裡,加入Key Press - A事件,並放入以下的程式碼:
if (global.game.play == 0) {
global.game.play = 1 ;
} else if (global.game.play == 1) {
global.game.play = 0 ;
}
加入這段程式碼之後,我們就可以透過鍵盤上的A,來決定遊戲是否要暫停。
接著要修改一下,玩家移動的程式碼,把「暫停」的功能給加進去。
打開obj_player裡的Key Down - Left事件,並把程式碼,修改成以下的樣子:
//遊戲中
if (global.game.play == 1) {
//沒碰到牆就移動
if (!place_meeting(id.x - move_speed , id.y , obj_wall)) {
//往左移動move_speed的距離
id.x -= move_speed ;
} else {
//小於move_speed的距離,會出現隱形牆,把移動條件改成撞到牆為止
while (!place_meeting(id.x - 0.1 , id.y , obj_wall)) {
id.x -= 0.1 ;
}
}
}
打開obj_player裡的Key Down - Right事件,並把程式碼,修改成以下的樣子:
//遊戲中
if (global.game.play == 1) {
//沒碰到牆就移動
if (!place_meeting(id.x + move_speed , id.y , obj_wall)) {
//往右移動move_speed的距離
id.x += move_speed ;
} else {
//小於move_speed的距離,會出現隱形牆,把移動條件改成撞到牆為止
while (!place_meeting(id.x + 0.1 , id.y , obj_wall)) {
id.x += 0.1 ;
}
}
}
修改後的參考圖:
在obj_player的物件裡,加入Key Press - Space事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
//改變球的大小,變化的順序為小中大
if (global.ball.size == "n") {
global.ball.size = "b" ;
} else if (global.ball.size == "b") {
global.ball.size = "s" ;
} else if (global.ball.size == "s") {
global.ball.size = "n" ;
}
}
加入這段程式碼之後,我們就可以透過空白鍵,來改變球的大小。
在obj_player的物件裡,加入Key Press - Z事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
//用球的大小,來決定y座標的偏移量
var _move_y = 0 ;
if (global.ball.size == "n") {
_move_y = 80 ;
} else if (global.ball.size == "b") {
_move_y = 120 ;
} else if (global.ball.size == "s") {
_move_y = 80 ;
}
//向130~140度發球,單發射擊
var _ball = instance_create_depth(id.x , id.y - _move_y , 0 , obj_ball) ;
_ball.direction = 135 ;
_ball.direction += irandom_range(-5 , 5) ;
}
加入程式碼後的參考圖:
instance_create_depth(id.x , id.y - _move_y , 0 , obj_ball)
的意思是:在座標(id.x , id.y-_move_y)的位置上,建立一個obj_ball的實體,深度設定為0。
這個函式在使用的時候,會順便回傳,這個新建實體的ID。因此,我們可以透過這種特性,來改變球的移動方向。
改變方向的方法很簡單,就是先建立一個局部變數(Local Variable):_ball
,並把函式回傳的ID放入_ball
。
接著我們就可以透過_ball
,來改變移動方向的參數,例如:_ball.direction = 135
,就是把球的移動的方向,設定成135度。
(GameMaker是用移動方向的直線,與基準線的夾角,來表示實體的移動方向。0代表右側,90代表上方,180代表左側,270代表下方。)
_move_y
的用途,是讓球生成的位置,變成在玩家的原點(Origin)上方。
原本是直接用120的數值,但後來發現,大球可以用120,但其他的兩個尺寸,用120的間距會太大,看起來並不合適。
因此才決定加入,偏移量的程式碼,讓大球的距離是120,剩下的兩個,則是使用80。
X鍵和C鍵的發球,跟Z鍵的邏輯是一樣的,只是做了一些微調。
X鍵的發射方向是40~50度,而C鍵則是連發模式,按著就能連續發射,發射的方向則是在45~135度之間。
下面是X鍵和C鍵的設定方法。
在obj_player的物件裡,加入Key Press - X事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
//用球的大小,來決定y座標的偏移量
var _move_y = 0 ;
if (global.ball.size == "n") {
_move_y = 80 ;
} else if (global.ball.size == "b") {
_move_y = 120 ;
} else if (global.ball.size == "s") {
_move_y = 80 ;
}
//向40~50度發球,單發射擊
var _ball = instance_create_depth(id.x , id.y - _move_y , 0 , obj_ball) ;
_ball.direction = 45 ;
_ball.direction += irandom_range(-5 , 5) ;
}
在obj_player的物件裡,加入Key Down - C事件,並放入以下的程式碼:
//遊戲中
if (global.game.play == 1) {
//用球的大小,來決定y座標的偏移量
var _move_y = 0 ;
if (global.ball.size == "n") {
_move_y = 80 ;
} else if (global.ball.size == "b") {
_move_y = 120 ;
} else if (global.ball.size == "s") {
_move_y = 80 ;
}
//向45~135度發球,連發射擊
var _ball = instance_create_depth(id.x , id.y - _move_y , 0 , obj_ball) ;
_ball.direction = 90 ;
_ball.direction += irandom_range(-45 , 45) ;
}
加入程式碼後的參考圖:
善心提醒,C鍵是使用「Key Down事件」,這樣子才可以按著連發,而用來單發的X鍵,則是使用Key Press事件,記得不要設定錯誤了。
🔔如果內容對你有幫助,可以按個喜歡,這樣就能讓更多人,接觸到這些棒棒的內容🔔
✨祝各位也能開心的做出好遊戲✨