天天看點

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part7– 第18至第20天

第18天:外星人圖形與圓形沖突、完美的子彈軌迹

今天我受夠了“射擊月亮”bug。有時候外星人即使在螢幕中出現,也可能射不中。我做了大量測試,在螢幕上布滿外星人并且設定月亮半透明以定位這個bug的原因。我發現測試擊中區域的坐标偏移了一個bit位,但即使解決了這個問題原先的bug依然存在。外星人圖形不能簡單用圓形覆寫,否則玩家要麼射不到外星人,要麼會射到隐蔽在月亮下的外星人。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part7– 第18至第20天

是以我決定使用圓形檢查。由于月亮比外星人大很多,能夠很容易地檢查外星人圖形邊緣的四個點是否都在圓形月亮内。為了測試,我使用libGDX内置的ShapeRender類,具體的實作代碼如下:

1 2 3 4 5

shapeRenderer.setProjectionMatrix(camera.combined);

shapeRenderer.begin(ShapeType.Circle);

shapeRenderer.setColor(

1

,

1

,

1

,

1

);

shapeRenderer.circle(sMoon.getX() +

119

, sMoon.getY() +

116

,

167

);

shapeRenderer.end();

上面的代碼加在SpriteBatch完成以後,沿着月亮表面畫白色的圓圈。類似地,給外星人邊界畫上長方形。

測試一個點是否在圓内的高效方法不是計算平方根(速度較慢)而是比較距離的平方。libGDX的内置函數Circle.contains(x,y)恰好實作了這個功能,是以我使用了這個函數進行檢查。事實證明這個方法非常有效。我為半徑長度增加了一些像素值,因為所有外星人之間會有一些間隔。改動後的結果令我非常滿意。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part7– 第18至第20天

完美的子彈軌迹

在這個遊戲中,子彈是從距離螢幕下方50像素值的地方發射的。我使用了函數atan2讓子彈旋轉着擊中目标,但我的代碼中有一些錯誤,在沒有射中目标時錯誤會經常出現。為了了解這部分内容,請注意在這個遊戲所有的射擊都采用了HitScan政策。

譯注:HitScan與射擊目标相對,指的是射擊出的子彈不針對任何目标而是摧毀子彈運作軌迹上的任何物體。

在沒有射中目标時,現在的代碼将子彈軌迹延伸到螢幕盡頭,而以前的代碼把盡頭設定得太遠。由于子彈的飛行使用了中間位置,結果看上去有很大的跳躍并且在子彈射出螢幕之前隻能看到2、3個點。通過把結束點設定到螢幕的邊緣來解決了這個問題,現在你能清楚地看到子彈在飛行。

這時又暴露出另外一個問題:子彈有時候距離玩家接觸的螢幕點隻有10到20個像素點。導緻這個問題有三個原因。第一個問題,我使用了子彈的X坐标和Y坐标。由于這個坐标位于螢幕底部的角落。通過把子彈的中心坐标加上一半的寬和高解決了這個問題。但仍有一些子彈沒有射中。第二個問題,我忘記設定原點,是以子彈圍繞着左下角進行旋轉。這個問題也解決了,但仍有一些朝螢幕左邊射射出的子彈沒有射中。

第三個問題,我意識到當子彈旋轉時寬度和高度是在變化的,是以子彈的中心點需要在旋轉後需要重新計算。解決了這個問題,子彈就能正确地從玩家觸摸的地方射擊。修改後的代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12

// 子彈飛行

LaserBullet lb =

new

LaserBullet(tUI,

65

,

64

,

20

,

40

);

lb.setPosition(

, -

450

);

lb.setOrigin(

10

,

20

);

lb.setRotation( (

float

)(Math.atan2(-x, 450f+y) * 180f / Math.PI) );

Rectangle r = lb.getBoundingRectangle();

x = (

int

)(x - r.width *

.5f);

y = (

int

)(y - r.height *

.5f);

lb.target.set(x, y);

bullets.add(lb);

Tween.to(lb, SpriteTweenAccessor.POSITION_XY, delay)

.target(x, y).start(tweenManager);

第19天:每日挑戰和任務

每日挑戰是收集5個字母,操作方式和道具一樣。一旦收集了所有字母,就可以得到一些用于購買道具的遊戲币。這是一個通過玩遊戲擷取硬币的簡單方法,這個靈感是受到“地鐵跑酷”(Subway Surfers)的啟發。

任務由許多子任務組成,通過完成這些子任務可以賺取硬币。硬币可以用于購買更新道具和消費物質,如盔甲、炸彈等等。每天的任務由三部分組成,你必須完成所有三項子任務才能獲得獎勵。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part7– 第18至第20天

我發現使用内置的文本換行來顯示任務比較簡單。然而行高會顯得過大,而且直接修改代碼沒有辦法減小行高。是以我選擇編輯由BMFont生成的.fnt檔案,進行如下調整:

1

lineHeight=

33

變成

1

lineHeight=

23

在開始生成位圖時,我在字母的四周增加了5個像素的陰影,是以現在需要把高度減少了10像素(上面減少5像素,下面減少5像素)。

在為此查找文檔時,我發現了一些先前遺漏的問題:在為遊戲選擇字型時,可能數字看起來效果不是很好。數字1看起來很修長,而數字11看起來很奇怪。要解決這個問題,可以為圖中的字型設定固定寬度。

1

font.setFixedWidthGlyphs(

"0123456789"

);

這樣效果看起來會非常好。但由于已經決定使用修長字型,因而沒有采用固定寬度。

第20天:周挑戰、使用者資料持久化、Java日期災難

周挑戰是在一周内收集特定數目的星星,進而獲得一些優異的獎勵,如8個原子彈、5個盔甲等等。我用Gimp做了一個很棒的金色星星并在嘗試了不同的閃爍和星光效果,但是這些看上去效果不是特别好。是以我想到了強化道具的粒子效果,對它進行改變直到滿足星星的要求。星星有了自己的閃爍節奏,而且可以在螢幕上同時顯示星星和強化道具。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part7– 第18至第20天

我還添加了玩家資料的加載和儲存。這個比我想象中要簡單。我以為必須學習一些Android的資料存儲API,但libGDX提供了簡單鍵值存儲類。隻要調用以下代碼進行初始化:

1

Preferences prefs = Gdx.app.getPreferences(

"DroneInvaders"

);

然後使用get(“key”, defaultValute)和set(key,value)進行值的讀寫。

我唯一遇到的麻煩是時間問題。為了持續跟蹤天挑戰和周挑戰,必須存儲最後玩遊戲的時間。當玩家開始遊戲,系統比較這個時間并重新設定一些計數器。理論上我可以阻止玩家将系統月曆修改到過去的時間,但是我不想這麼做。當時間復原時,我所做的是設定新的每日挑戰和周挑戰并且重置星星和搜集到的字母個數。

為了實作這個功能,必須擷取上一次玩遊戲的時間并計算與目前的時間差。是否是同一天、一天前或幾天前都會影響計算結果。我在谷歌上搜尋到很多讨論這個問題的網站以及StackOverflow問題。大多數答案很好笑。許多程式員簡單地用相差的秒數來計算時間差,然後除以60*60*24得到天數,完全忽略了夏令時和閏秒。有人會争辯說,對一個遊戲來說這個差别影響不大。但是我不喜歡每年收到2次大量的bug報告。另一些家夥簡單地通過從開始到結束日期一天天累加天數。這些循環看起來是正确的,但是計算結果還是會丢失了部分時間。比如一個對象在1月1号上午5點存儲了,然後你在1月2好晚上23點計算時間差,在第一個時間點上加上1天仍然比第二個時間點少。但是按他們的計算方法,實際增加了2天。

在這種情況下,我使用的一個技巧是總是設定前一次遊戲的日期為早上10點,而設定最後一次遊戲的日期為下午5點。盡管夏令時總是在晚上改變,但是這個設定是安全的。因為即使如果有一天有人決定夏令時的變化發生在中午,在這之間同樣也有7個小時。