使用processing写一个仿雷电小游戏
Processing編程——仿雷電STG
-
-
- 1.前言
- 2.內容展示
- 3.實現過程
-
- 3.1.背景云彩的隨機生成
- 3.2.飛行尾氣的實現
- 3.3.擊中敵人的粒子效果
- 3.4怪物類
- 3.5.武器系統
- 4.一些問題
- 5.總結
-
非常幸運這個學期在互動媒體技術這門課上深入的了解了Daniel Shiffman的《代碼本色 The Nature of Code》 這本書,在課程最后,老師也是希望我們能利用書中的內容做一個交互應用出來,這就是我們本次博文內容的主題啦。
1.前言
那么怎么才能讓這個交互小應用變得足夠有趣呢,我思考了很多的表現形式,最終還是回到了我比較熟悉的游戲上來。
當然,可供選擇的游戲模式依舊有很多,正好在思考這個問題的幾天里,我的網易云推給了我一首FC游戲的bgm,一下子無數回憶涌上心頭,就想使用processing仿照著去寫一個小時候玩過的《雷電》。
2.內容展示
3.實現過程
首先我們需要實現這個小應用需要什么功能,然后再把這些功能抽象成一個個類,這樣實現起來會方便很多。
然后我們可以想到,我們需要的有:子彈類,主角類,云彩類,怪物類,飛機尾氣類還有子彈消失的粒子效果類。
這些類別共同構成了我們可以進行交互娛樂的游戲應用,接下來我們就幾個關鍵技術看他們的具體實現。
3.1.背景云彩的隨機生成
在這份小游戲中,背景由天藍色的整體顏色以及白色的云朵組成,在這里我們可以設定云彩的數量上限,然后在每次初始化的時候進行隨機生成。
class cloud {int num_cloud;float[] bgptx;float[] bgpty;cloud(int num) {bgptx = new float[num];bgpty = new float[num];num_cloud = num;for (int i = 0; i<num_cloud; i++) {bgptx[i] = random(-width*0.4, boxx+width*0.4);bgpty[i] = random(-height*0.4, boxy+height*0.4);}}void update() {show();}void show() {for (int i = 0; i<num_cloud; i++) {noStroke();fill(255);ellipse(bgptx[i]+anchordist.x, bgpty[i]+anchordist.y, 130, 130);ellipse(bgptx[i]+anchordist.x+80, bgpty[i]+anchordist.y+10, 100, 100);ellipse(bgptx[i]+anchordist.x+130, bgpty[i]+anchordist.y+30, 55, 55);ellipse(bgptx[i]+anchordist.x-80, bgpty[i]+anchordist.y+10, 95, 95);}}
}
在這里我們可以看到,cloud在執行構造函數時,每次都會通過random()函數隨機地獲得一個坐標位置,然后在show()函數中,在這個隨機位置上,繪制云彩的圖案。
然后我們在背景類中實體化云彩類,最后在主頁sketch中調用實體化的背景類——層層調用。
// 背景類的構造函數
background() {bds = new bullet_die_particle ();cl = new cloud(20);// 每次生成20朵云}
// sketch主頁上的調用
public background bg;
bg = new background();
void draw() {bg.show();}
3.2.飛行尾氣的實現
要實現每次按動方向鍵就能出現飛行尾氣,我們首先需要能產生“一條”尾氣。
所以我們這里建立兩個類,一個onefire,一個powerFire,雖然二者至今沒有直接的繼承關系,但是由于他們關系密切,我們將他們放在一個標簽頁中。
這里我們可以想到,對于“一條”尾氣來說,他應該有三個參數,位置,速度,以及判斷是否處在“加速”狀態(方向鍵按下)。
接下來我們看一下onefire類的構造函數。
onefire(PVector loc0, PVector vel0, boolean powerup) {loc = new PVector(loc0.x, loc0.y);float anc;if (powerup) {anc = random(-an08, an08);velborn*=1.5;} else {anc = random(-an16, an16);}vel = new PVector(vel0.x, vel0.y);vel.normalize();vel = rotateangle(new PVector(vel.x, vel.y), anc);vel.mult(velborn);if (powerup) {float absanc = abs(anc);if (absanc>an32) {dieline = normallife+int(random(-10, 10));c = c2;if (absanc>an16) {dieline = shortlife+int(random(-10, 10));r*=0.6;c = c3;}} else {dieline = lonelife+int(random(-10, 10));c = c1;r*=1.4;}} else {float absanc = abs(anc);if (absanc>an64) {dieline = normallife+int(random(-10, 10));c = c2;if (absanc>an32) {dieline = shortlife+int(random(-10, 10));c = c3;}} else {dieline = lonelife+int(random(-10, 10));c = c1;}}}
這里可以看到,每一條尾氣都是由三部分組成,也就是說,三種長短不一的尾氣組成了一條尾氣。這么介紹下來可能有些繞口,但是應該比較容易理解。
在onefire類中還涉及幾種簡單的類方法,比如show()繪制尾氣,update()在draw()中調用用來更新尾氣。
然后我們來看powerFire類
這個類就是將onefire類實體化,同時增加一個add方法,提供給主角類,主角類在加速時,可以調用add方法,在畫面上增加尾氣。
class powerfire {ArrayList<onefire> fire;powerfire() {fire = new ArrayList<onefire>();}void update() {rectMode(CENTER);noStroke();for (int i =0; i<fire.size(); i++) {onefire ft = fire.get(i);if (ft.timer()) {fire.remove(i);} else {ft.update();ft.show();}}}void addfire(PVector loc, PVector vel, boolean powerup) {vel.normalize(); for (int i = 20; i>0; i--) {fire.add(new onefire(new PVector(loc.x, loc.y), new PVector(vel.x, vel.y), powerup));}}
}
3.3.擊中敵人的粒子效果
關于這個系統的籠統介紹就是,擋子彈碰到敵人或者墻壁后,可以以散射狀綻開。
這里有三個相關類,particleWithoutAcc,bullet_die,bullet_die_particle這三個類。
particleWithoutAcc中主要是一些獲得當前子彈的顏色,速度,以及子彈大小的基礎方法。
然后在bullet_die中
它繼承自particleWithoutAcc類,依靠父類的方法進行初始化,除此之外有一個show()方法,根據子彈大小繪制出子彈碎片。
class bullet_die extends particleWithoutAcc { bullet_die(PVector loc, PVector vel, color c) {this.loc = new PVector(loc.x, loc.y);this.vel = new PVector(vel.x, vel.y);setcolor(c);setlife(int(framerate*1));setrad(4);}void show() {if (!outboder(loc, rad/2)) {pushMatrix();translate(loc.x+anchordist.x, loc.y+anchordist.y);rotate(atan2(-vel.y, -vel.x));strokeWeight(rad*map(age, 0, life, 1, 0.5));stroke(c);line(-8*map(age, 0, life, 1, 0), 0, 0, 0);popMatrix();}}
}
bullet_die_particle中主要是將bullet_die實例化后,根據當前位置和速度用大量的if語句來進行子彈碎片綻開的角度判斷。
這些都放在add方法中
void addbdp(PVector loc, PVector vel, color c, boolean isDieboder, boolean R, boolean L, boolean U, boolean D) {totaladd++;float angleB;float angleE;if (isDieboder) {angleB = 0;angleE = TWO_PI;if (U) {angleB = 0;angleE = PI;} else {if (D) {angleB = PI;angleE = TWO_PI;}}if (R) {angleB = HALF_PI;angleE = HALF_PI*3;if (U) {angleE = PI;}if (D) {angleB = PI;}} else {if (L) {angleB = -HALF_PI;angleE = HALF_PI;if (U) {angleB = 0;} else {if (D) {angleE = 0;}}}}} else {float angle = atan2(vel.y, vel.x);if (angle>=umbrellaAngle||angle<=-umbrellaAngle) {angle+=TWO_PI;}angleB = angle-umbrellaAngle/2;angleE = angle+umbrellaAngle/2;}for (float i = angleB; i<=angleE; i+=borndist) {bdp.add(new bullet_die(new PVector(loc.x, loc.y), new PVector(cos(i)*velnum, sin(i)*velnum), c));}}
3.4怪物類
在monster的實現上,我實現了一個monster系統來管理幾種monster。
所以這里有這么幾種方法,monster類及它的三個子類——具體的怪物,還有一個monstersystem類。
在monster中,主要是初始化一些參數,比如位置,速度,加速度等參數。
在monster子類中,主要是利用他們的show()方法,繪制圖形,還有check()方法進行碰撞檢測。這里展示一下碰撞檢測。
void check() {if (PVector.dist(toc, moe.loc)<moe.r/2+rad/2) {moe.blood-=5;}int i = 0;while (!isDie&&i<moe.bs.bn.size()) {if (PVector.dist(toc, moe.bs.bn.get(i).loc)<=moe.bs.bn.get(i).rad/2+rad/2) {moe.bs.bn.get(i).isDie = true;isDie = true;}i++;}}
其中的moe是主角類的實例。
最后是monstersystem類,其中比較重要的就是add方法,它會在角色周圍自動生成怪物。
用怪物1來舉例
void addm1(int i) {//could code be betterif (M1.size()<5) {for (int c = 0; c<i; c++) {float angle = random(-PI, PI);float disting = random(300, 1000);float btx = disting*cos(angle)+moe.loc.x;float bty = disting*sin(angle)+moe.loc.y;btx = range(btx, ms0DistMoe, boxx-ms0DistMoe);bty = range(bty, ms0DistMoe, boxy-ms0DistMoe);M1.add(new monster_splite(new PVector(btx, bty)));}}}
3.5.武器系統
武器系統主要由鍵盤事件,主角類和子彈類共同實現。
bullet_normal類中有子彈的初始化方法。
void init_normal(color c, int vel, int rad) {velborn = vel;ms = 0.5;damp = 0.99;maxvel = 20;tagaccN = 1;setcolor(c);setrad(rad);}
在速度之外,我還在初始化中添加了顏色和子彈大小。
然后在bullet類中,使用了一個switch-case語句,來進行武器的選擇。
void addbs(PVector loc, PVector vel, int mod, boolean powerup) {switch(mod) {case 1:addbn1(loc, vel, powerup);return;case 2:addbn2(loc, vel, powerup);return;case 3:addbn3(loc, vel, powerup);return;case 4:addUltimateB(loc, vel, powerup);return;}}
可以看到,mod變量不同,提供的子彈類別也不同。
關于變量mod的改變,主要是在鍵盤事件中。
if (key !=CODED) {if (keyCode == '1') {moe.setcolor(color(161, 23, 21));mod = 1;}if (keyCode == '2') {moe.setcolor(color(0, 90, 171));mod = 2;}if (keyCode == '3') {moe.setcolor(color(6, 128, 67));mod = 3;}
我們可以使用數字鍵切換武器,同時調用主角類的方法,改變主角機體的顏色。就像《雷電》里一樣。機體顏色對應著武器。
當然,我也給他添加了一個終極技能,每次游戲開始有三次釋放機會,圖示在我們最開始的一張圖中。
4.一些問題
雖然磕磕碰碰,過程中看了很多的示例,最后的還原度也就到這個程度了。
沒有能夠實現的點有這么幾個:
道具系統——武器應該是根據道具來改變,并且提升強度
卷軸效果——畫面能與角色同步移動并且沒有嚴格意義上的邊界
素材加載——不是使用函數繪制而是通過素材加載展現內容
難度選擇——可以手動選擇難度
這些問題有一些是因為平臺限制實現起來不方便,有一些是還沒有想好怎樣解決,希望以后可以實現優化,自己做一個好玩的stg彈幕射擊游戲。
5.總結
經過這段時間的學習,對processing編程有了更深的理解,學會了更加靈活地使用向量,在程序中使用物理法則等等。
總之,這次的作業到這兒就暫告一段落了,搭建框架和后期微調都花了不少的時間,整個游戲幾遍玩下來,也算有趣,但就是游戲內容不多,容易膩味,希望之后有時間能優化或者用別的工具重新寫一下。
總結
以上是生活随笔為你收集整理的使用processing写一个仿雷电小游戏的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: P4011 孤岛营救问题
- 下一篇: 关于游戏架构设计(二)