2015年10月27日 星期二

GameMaker:Studio 學習筆記10

1、event_inherited() 

當一個物件有父物件時,預設是會繼承父物件的所有event,但當子物件自己也建立和父物件同樣的event時,例如create event,則子物件的instance只會執行子物件新建的create event而不會執行父物件的create event,除非以event_inherited function告訴子物件的instance必須執行父物件的create event,需注意父物件的create event內容會先執行完之後才接續執行子物件的create event內容

2、 merge_colour(col1,col2,amount):

混和指定的兩個顏色產生新的顏色。amount是混和比例,0表示完全為col1,1表示完全為col2,0.5則是以1半1半混和,0.2則是以col1 20% col2 80%的比例混和,以紅、藍兩色混和為例如下圖




3、draw_sprite_stretched(sprite,subimg,x,y,w,h):

在指定的座標(此座標做為左上角)繪製指定sprite的指定subimg填滿指定的寬高面積

4、place_free(x,y):

檢查在指定座標是否會與標記為solid的物件發生碰撞,若有碰撞則傳回false,反之傳回true

GameMaker:Studio 學習筆記9

11、在GMS中不能在Room Editor介面直接將Vector Sprite設置為背景,而必須透過以controller object執行draw sprite function的方式達成以vector sprite當背景

12、指定subimage時若給定的數字非整數,則取無條件捨去後的整數,這個特性可用來控制動畫速度,在每個step加一個數值,數值愈大動畫速度愈快

13、Vector Sprite注意事項:

使用stage size做為collision mask,若stage size很大,且有很多frame,可能會佔用大量記憶體,若真的有需要collision mask,儘量使用precise collision masks以降低記憶體用量

尚有其他需注意事項,使用上若有問題可查閱說明文件

14、ini檔案編輯注意事項:

原本不存在的section或key,初次寫入順序與排列順序相反,原本已存在則寫入順序與排列順序一樣,故在寫入前先清空舊資料才能掌握內容排列順序

15、關於Application_Surface_Scaling:

主要就是以下元素的size與相對位置關係,需要時再仔細研讀即可:

view(想在畫面顯示的room內容)、viewport(用來顯示view內容的display範圍)、application_surface(遊戲的顯示畫面)、window_get_width,height(遊戲視窗的大小)、display_get_width,height(顯示器螢幕大小)、display_get_gui_width,height(gui layer大小)、application_get_position(取得遊戲畫面的位置,據以計算其他layer與其相對位置關係)

16、window_set_colour(colour):設定在視窗或螢幕內,但在application surface範圍外的區域顏色

17、當啟用"keep aspect ratio"選項時,若未勾選room editor的"clear display buffer with window colour"選項,則在視窗或螢幕中未被application surface覆蓋的範圍會出現不受控制的怪異(odd)畫面,因"clear display buffer with window colour"選項沒有code,所以要建立一個基礎room勾選該選項,然後以room_duplicate的方式建立新room(參數會和來源room一樣)

除了application suface範圍外可能因未清除display buffer而有odd畫面之外,在application surface範圍內未繪製任何東西的區域也可能有殘留影像,一般會以繪製背景色(Draw background color)的方式持續清除殘像

18、切換room時並不會destroy既存物件,而是deactivate他們,所以要注意一些使用記憶體的功能像是surface、particle、fixture等要在room結束時清除,或是直接以指令destroy物件,destroy的情況下會清除資源佔用

2015年10月26日 星期一

像素繪圖的6個技巧學習

覺得不錯的教學,可以參考

6 Pixel Art Drawing Techniques

GameMaker:Studio 學習筆記8

6、draw_set_blend_mode_ext(src,dest):設定繪製動作的延伸blend mode

重點1:

GMS在draw1個pixel時考慮兩個面相,一為source colour(我們打算繪製的顏色),二為destination colour(該pixel既存的顏色),混色計算方式是以pixel為單位,公式如下:

R.s*R.sf+R.d*R.df=R.outcome

G.s*G.sf+G.d*G.df=G.outcome

B.s*B.sf+B.d*B.df=B.outcome

A.s*A.sf+A.d*A.df=A.outcome

(R.outcome,G.outcome,B.outcome,A.outcome)即為該pixel顯示在畫面的色彩,而blend mode影響的是sf(source factor)與df(destination factor),各blend mode計算sf與df的方式可參照draw_set_blend_mode_ext function的說明文件

重點2:在混色計算時,位於不同surface的顏色是不會納入計算的

重點3:透明的點其RGB值不一定是0,在計算混色時要考慮其RGB值,例如RGBA為0.5,0.6,0.7,0和0,0,0,0看起來都是完全透明,但在計算混色時會有完全不同的結果,故以不透明的黑色(0,0,0,0)做為底色對後續的色彩控制會比較簡單,只要使用不透明的純白(1,1,1,1)以subtract模式就可以還原特定區域為底色,每次繪製的基礎都會相同

重點4:透明度對混色影響很大,其讓混色多了很多變化,但相對的也讓混色計算變得比較複雜,完全不透明的混色結果很容易掌握,有透明度的混色就相對複雜許多,例如想在已有色彩的區域繪製圖案,且希望結果和來源完全相同而不受目標點既有色彩影響,在來源無透明度或來源有透明度但目標與來源顏色完全相同時,使用bm_normal即可,若來源有透明度且目標顏色和來源不完全相同時,就需先將目標以純白subtract還原成(0,0,0,0)之後,再以ext(bm_one,bm_one)模式繪製以得到和來源完全相同的結果

關於blend mode的詳細說明可參照以下網址:

Explaining Blend Modes - Part 2 https://www.yoyogames.com/tech_blog/69

draw_set_blend_mode(bm_normal):

預設模式,效果為直接覆蓋,需注意若欲繪製的圖案本身有透明度且目標區域底色非透明無色,則繪製出來的圖案顏色會因與底色混合而與來源有差異,差異程度依來源與目標色彩及透明度而定

draw_set_blend_mode_ext(bm_one,bm_inv_src_alpha):

一般情況下當在同1個區域繪製2次且分別使用不同的alpha值,結果將會覆蓋而非累積,若希望以0.5的alpha值在相同位置繪製同1個image2次時能得到約alpha0.75的累積效果而不是直接覆蓋為0.5,則可用draw_set_blend_mode_ext(bm_one,bm_inv_src_alpha)這個blend mode,此mode適合用於繪製陰影(shadows)以及灰階的圖像

draw_set_blend_mode(bm_add):

適合用於明亮效果(bright effects)的混色,例如爆炸、雷射、光源等

draw_set_blend_mode(bm_subtract):

減除明暗度並依明暗度高低設定透明度,越暗的pixels畫出來的透明度愈高,適合用於從黑暗或陰影中挖出一個可視區

draw_set_blend_mode_ext(bm_normal,bm_zero):

直接取代該區域的色彩,畫同一個sprite在同一個位置會疊加而有愈來愈深的狀況,用此模式則不會疊加,可用以修復被破壞的形狀而不用先清除剩餘的部份

7、在上視角場景營造投擲物體飛越敵人的錯覺並適時發生反彈的方法:

設定速度快時不反彈,且depth小於其他物件(depth小的顯示在上層,會有飛越其上的錯覺),速度設定遞減,depth設定遞增,在速度下降到指定值之後讓反彈enable,此時depth也遞增到大於其他物件而在重疊時會被覆蓋(會有在地上被跨過的錯覺)

8、在上視角場景營造投擲物體在一定距離後落地的錯覺的方法:

類似營造投擲物體飛越敵人的錯覺之方式,以移動速度做為是否落地的判定依據,並在設定為落地的時機點設計適當的sprite移動方式,以模擬落地彈跳效果,同時播放落地音效增加擬真度

9、若指定父物件做為碰撞檢查的目標物件,例如collision_line,則會檢查所有子物件的instance是否有在此線上發生碰撞,若有則傳回其ID(有多個碰撞發生時,則傳回最接近起始點的instance ID),若無則傳回"noone"

10、路徑functions:

mp_potential_step(xgoal,ygoal,stepsize,checkall):朝目標點(xgoal,ygoal)移動instance,過程中迴避障礙物,stpesize為移動速度,checkall為false則只迴避實心(solid)物件,為true則全會迴避全部物件

mp_potential_step_object(xgoal,ygoal,stepsize,obj):往目標點移動,過程中迴避指定obj

mp_potential_path(path,xgoal,ygoal,stepsize,factor,checkall):計算instance本身與目標點之間的路徑,並產生1個路徑,類似於mp_potential_step,但並不移動,而是產生一個路徑,其中path是已存在的路徑index,將會被此function產生的路徑覆蓋,factor為運算時間,需大於1以避免無窮計算,時間內無法得出有效路徑則會傳回false,找到有效路徑則傳回true,即使false也會產生路徑,但只指向大概的方向而不會到達目標點

mp_potential_path_object(path,xgoal,ygoal,stepsize,factor,obj):同mp_potential_path,但只迴避指定obj

mp_potential_settings(maxrot,rotstep,ahead,onspot):設定mp_potential相同functions運作的方式

maxrot:設定每step允許instance從當下面對的方向轉向的角度上限,值愈大,愈容易找到有效路徑,但路徑愈不自然,值愈小,路徑愈平滑,但可能繞比較遠,甚至無法找到有效路徑,例如設為30,當下面對90度方向,每次轉5度檢查直線前方是否有碰撞,檢查到125度方向時找到可行路線,但因125-90=35超過上限30,因此此路徑不採用,換個角度講就是設定檢查有效路徑的範圍,例如設為90度就是檢查前方180度(左右各90度)的範圍內是否有有效路徑

rotstep:當前方有障礙物時,轉向進行下一個直線確認的單位轉向角度,角度愈小,找到路徑的可能性愈高,但需要的計算也愈多,速度相對較慢,例如設為10,當下面對90度方向且前方有障礙物,便會轉向10度確認100度方向直線前方是否有障礙物

ahead:設定檢查碰撞的範圍(可理解為視野,愈早看到障礙,愈早轉向),預設為3,代表3step內的碰撞都會被檢查到,並轉向,這個值愈小,愈晚轉向,值愈大,愈早轉向迴避

onspot:當可行範圍皆已檢查完畢且無有效路徑可行,則接下來的動作就依據此參數,設為true表示instance自當下面對方向旋轉"maxrot"設定角度後再開始檢查,設為false則不做任何動作,結束尋找路徑的動作,設為false很實用,像是車輛就適合用false,但設為false會降低找到路徑的機會

GameMaker:Studio 學習筆記7

1、Sound:

1.1、音樂檔案適合用壓縮格式,音效檔案不適合用壓縮格式,因為音效通常需要在很短的時間內播放,且重複播放頻率很高,採壓縮格式會增加CPU的loading,且可能會有延遲,而音樂檔案通常是長時間播放,只需在初始化時解壓1次,後續就不會再對CPU造成太大負荷

Target Options則應儘量和符合檔案原本的設定,若非必要不要去修改,用預設(default)值即可

1.2、audio_sound_gain(index,volume,time):設定聲音檔的音量volume,值可介於0(靜音)~1(原音量),time為音量變化時程長度,單位毫秒(千分之1秒),可以營造音量漸增或漸減效果,若要直接改變音量,time設置為0即可,注意若要直接改變音量,最好在播放前先改再播放,播放中改可能會有雜音出現,index若為聲音資源名稱則影響所有播放此聲音資源的instance都會受影響,若index使用的是特定instance聲音id,例如從audio_play_sound function傳回的id,就只影響其本身

1.3、audio_play_sound_at(index,x,y,z,falloff_ref,falloff_max,falloff_factor,loop,prioriity):以3D模式播放聲音,可很精確的指定聲音的位置和變化,參數的詳細用途以及相關functions可參閱語法說明


2、Application Surface:

為讓設計者能獲得對draw功能的全面控制能力而建立的surface,不能從遊戲中被刪除或移除

GMS並不是直接描繪畫面在顯示器上,而是在Application Surface上,再將Application Surface描繪在視窗或是顯示器畫面上,這樣可以讓遊戲在不同設備的不同螢幕大小上顯示變得單純,先將所有東西在AS上完成,再來只需要處理AS和設備螢幕大小的比例調整而省去不同顯示器大小對遊戲內容如座標等的影響,而一般情況下GMS會協助處理AS和螢幕大小比例問題。

當想自行控制AS的繪製時,要考量對象設備的螢幕大小據以計算相關位置,例如:

draw_surface_ext(AS,x,y,xscale,yscale,colour,alpha)

除AS之外其他處理是在ROOM的架構下完成,與螢幕大小無關,而AS的處理是相對於螢幕大小

以上述function為例,要手動繪制AS,其座標x,y是基於設備螢幕座標,而xscale,yscale卻是基於room大小,x,y的處理可透過appllication_get_position來取得AS在設備上的座標(需注意若有做surface_resize的動作,數值在下一個step才會變更)再依需求計算,而xscale,yscale的處理則要先取得AS在設備上的縮放比率後再據以調整大小,例如ROOM大小是1024*768(無View作用時AS的大小和Room一樣),而手機的顯示器是854*480,等比例縮放後(在global game setting 有勾選"keep aspect ratio"選項)AS在手機上的比例是640*480,xscale=640/1024=0.625,yscale=480/768=0.625

注意除了AS之外,其他自定義Surface相對關係一樣是在ROOM架構下,例如自定義surface_temp,draw_surface(surf_temp,0,0)此處的0,0會是指room的0,0而非設備螢幕座標

瞭解上述諸規則才能正確輸出想要的結果

視窗大小等同於設備螢幕大小,非全螢幕情況底下的視窗長寬不計其上方title bar和邊框

繪製順序


surface_resize(index,w,h):重新設定指定surface的大小,注意這將會crop或stretch此surface的內容,更精確的說這是destroy現在的surface,並以新的尺寸以及同樣的index重新建立它,這意味它必須先被清除以及重新繪製,除非此surface是application_surface,則GMS會自動清除及重畫

如果改變application_surface的尺寸,這些改變在下一輪的draw event才會開始生效,意思是在同一個event或step中使用application_get_position(),surface_get_width()或surface_get_height()傳回的值會是改變前的尺寸

application_surface:global variable,代表application_surface

當沒有"view"正在作用,application_surface的size會設定為和第1個room的大小相同,當room切換,而新的room和第1個room大小不同,則必須改變application_surface的大小以符合新room的大小

view:

view in room:要在screen上秀出來的room區域

port on screen:要顯示view內容的螢幕區域

例如把room裡面的一塊200*150的區域在螢幕的一塊指定區域(0,0,200,150形成的方形區域)顯示,和在以另一個指定區域(0,0,400,300)比較,後者就像是把遊戲畫面放大2倍

application_surface_draw_enable(boolean):若要手動繪製,為避免double drawing,應設定為false

draw_surface_ext(index,x,y,xscale,yscale,rot,colour,alpha):和draw_surface一樣會繪製指定的surface,但可以給定比例、旋轉角度、混色、透明度,需注意surface(除了application_surface之外)可能在任何時間被清除,故在參照之前應先以surface_exists function確認其存在,避免參照錯誤發生

注意:當手動移動application surface之後,mouse events將不會正確登錄,mouse clicks和position依然會被偵測(keyboard不受影響),但需自行計算正確的值

繪製在Application_Surface的東西會自動隨顯示器的畫面大小縮放,而畫在GUI Layer上的則不會,或是需經過其他設定

3、Coding:

關於維護的心得:

以變數的方式取代直接給定值的方式可避免在參數改變時修改太多程式碼,變數搭配適當的名稱可提高效率,以下例子說明

if gamestate=1 //1代表玩家勝利
{
...
}

if gamestate=Player_Win //很明顯的是代表玩家勝利
{
...
}

"Don't Repeat Yourself" principle(DRY):重複的程式碼以寫成function呼叫,不要重複編寫

"Single Responsibility" principle(SR):將需要處理的項目切割,1段程式區塊處理1個項目,清楚區分功能,易管理

關於效率的心得:

二維區間搜尋法:

var low=0;up=mapheight-1;midd=0;

while up>=low//迴圈直到收斂至剩單點為止
midd=round((up+low)/2);//取上下限中點 

if (mouse_y > (ypos[midd,1] + blank_halfheight)) low=midd+1;//未命中且大於中點範圍上限,下限點改為中點+1

else if (mouse_y < (ypos[midd,1] - blank_halfheight)) up=midd-1;//目標小於中點範圍下限,上限點改為中點-1

else break;//命中則結束
}

tb_row=midd;

讓sprite跟隨指定目標轉向的程式碼寫法:

var dd;

dd = ((((point_direction(x, y, global.Player_x, global.Player_y) - image_angle) mod 360) + 540) mod 360) - 180;

image_angle += min(abs(dd), 5) * sign(dd);

效率原則:

a.容易命中的條件先判斷,順序擺在前

b.比對速度快的條件先執行

c.正向舉證與逆向舉證:有黑白兩色,當白色較少時,以白色為搜尋目標會比用黑色好,因為把白色找出來剩下的就是黑色,而白色比較少,需要進行的搜尋動作也相對較少

d.任何動作都是需要時間的,包含變數宣告,故以效率為重時,在create時先宣告變數,會比在使用前才宣告有效率,使用前才宣告的好處是能以local變數即用即釋放,降低記憶體的佔用,應視需求調整宣告方式

e.很多條件是很少會成立的,建立巢狀關係,讓發生機率高的優先判斷,進入後就不用進行後面的判斷,省去不必要的判斷,例如:

整體考量a.b.兩點,則"條件命中率之反比" * "條件比對動作數" 值愈小者順序應愈前面

例如:

if a=5和if a=9兩個條件,已知a=5的機率高於=9的機率,則a=5的條件應列在前先做判斷,可減少條件確認次數

if a>10 and a<20和if a>20兩個條件式,a>20條件式需比對的動作較少,則a>20條件應先進行判斷

正向舉證與逆向舉證:

可以從一堆目標裡比對不要的以篩選要的,也可以直接比對要的,端看何者效率高,例如SHUTU裡的關卡結束判斷條件,可以很麻煩的一格一格確認其格線是否已連,亦可直接比對敵人受攻擊次數達到目的,但效率天壤之別,同樣的可以一格一格判斷是否是不可逆連結,是的不加總以求出可動總數,亦可以一開始就紀錄總數,然後在攻擊敵人後將攻擊數扣除,程式碼編寫簡單且執行效率更好

關於語法的心得:

要將文字與變數組合需使用"+"符號,且要注意要組合的元素必須是同形態,文字只能跟文字組合

例:

m=5;

combine="abc"+string(m);

用real()將get_string()的結果轉為數字可避免大部份的輸入錯誤,例如按到空白鍵

real(get_string())

用string()將數字轉文字可讓輸出結果不帶多餘的小數位數,例如寫入ini檔或是get_string預設值

get_string("test",100)畫面會顯示預設值100.00

get_string("test",string(100))畫面會顯示預設值100

重要:

將規則以圖形表現出來能大大幫助編程思考

遞廻語法範例(參照SHUTU):

///scr_6IsFree(ar0,ar1)

if cto[ar0,ar1]=0

{
if isactive[ar0,ar1]=1 global.isover=0;
}
else
{
scr_6IsFree(desty[ar0,ar1],destx[ar0,ar1]);//開始遞廻
}

4、 User-Defined Surfaces:

通常情況下,所有drawing都是作用在application surface,但使用者可指定將特定的drawing作用在自定義的surface上,這代表這些東西不會被看見,直到使用draw_surface function把自定義surface繪製出來,這同時代表繪製在其上的東西也將被看見

surface初始左上角座標為0,0,繪製東西於其上時,座標位置與一般情況無異

自定義surface是保存在VRAM(顯示記憶體),這表示它隨時會被"徵收"而消失(當系統需要用到它的VRAM時),故使用上需特別留心,在參照特定surface前要先確認其是否依然存在,若不存在則需先重新建立,同時需儘量最小化surface佔用的VRAM量,因為用盡VRAM會發生其他的問題,surface繪製時是預設在room的0,0位置,所以如果是在一個大room使用views或僅是想要一個小區域做特效,記得調整surface繪製區域,避免浪費VRAM和執行效能

以surface_create function建立surface之後,接著以surface_set_target function設定該surface為drawing作用目標,在開始繪製東西於此新建surface之前,需先用draw_clear_alpha function把畫面清乾淨,然後再開始畫東西,以避免殘留影像造成預期之外的結果,這算是新建surface時的一個歸零動作,對於已使用過的surface則不需要此動作,除非是想清空該surface上的舊圖像

以畫布圖層比喻說明surface運作規則:

surface就像是一張畫布圖層,使用者畫需要的東西在上面,然後暫存在VRAM裡,在需要的時候在screen(application_surface)上描繪(draw_surface)出來,或是要改變上面的圖像時呼叫出來(surface_set_target)修改,妥善運用surface可節省資源,例如繪製陰影,每個step在各個instance執行繪製陰影的動作是非常沒有效率的,可先將陰影全部畫在指定的surface上,然後每個step描繪該surface1次即可有相同的效果,相對於原本的方法,這是非常有效率的方式

運用surface的基本流程如下:

Step1.確認surface是否存在:存在且並不想改變上面的東西則可以draw_surface把上面的東西描繪在screen上顯示出來,若不存在則需先建立surface(surface_create),接著開始在上面繪製想要的東西

Step2.在surface上面繪製東西:指定接下來的繪製目標(預設為application_surface)為該畫布(surface_set_target),若為初次繪製,在繪製前需先清除畫面(draw_clear_alpha),避免殘留影像污染畫布,清除完後開始繪製(各式draw指令),當此畫布想畫的東西都已繪製後,需將繪製目標還原(surface_reset_target)成screen(application_surface),畫面才會正常顯示

Step3.視需求在screen上"描繪"(draw_surface)該畫布,使其可見,或是修改上面的東西,同樣的,參照surface ID前需先確認其是否存在

Step4.當不會再使用特定surface時,記得刪除它以釋放VRAM,確保遊戲運行穩定

一個節省VRAM的技巧是建立較小的surface,然後以較大的倍數繪製surface,例如screen大小為800*600,則可建立1個400*300的surface,然後用draw_surface_ext以2倍的xscale,yscale設定繪製surface,同樣可有full screen的大小,但要調整繪製於surface上的圖像的位置和大小,使放大後的結果和需求一樣,例如若建立400*300的surface,想讓此surface2倍放大繪製後的結果和800*600的surface一樣,則繪製於其上的圖像座標需除以2(這樣相對位置才會相同,因為座標都是從0,0開始),大小也需除以2,放大後才會是同樣的尺寸

5、make_colour_hsv(hue,sat,val):以hue(色彩)、saturated(飽和度)、value(明暗度)三個參數定義顏色

hue:0~255,255種基礎色彩

saturated:0(灰)~255(純色)

value:0(暗)~255(亮)