処理言語の究極のガイドパートII:シンプルなゲームの構築
公開: 2022-03-11これは、Processing言語の究極のガイドの2番目の部分です。 最初の部分では、基本的な処理言語のウォークスルーを行いました。 処理を学ぶための次のステップは、単により実践的なプログラミングです。
この記事では、Processingを使用して独自のゲームを実装する方法を段階的に説明します。 各ステップについて詳しく説明します。 次に、ゲームをWebに移植します。
処理チュートリアルを開始する前に、前のパートのDVDロゴ演習のコードを次に示します。 ご不明な点がございましたら、必ずコメントを残してください。
処理チュートリアル:簡単なゲーム
この処理チュートリアルで作成するゲームは、Flappy Bird、Pong、BrickBreakerの組み合わせのようなものです。 私がこのようなゲームを選んだ理由は、初心者がゲーム開発を学ぶときに苦労する概念のほとんどを持っているからです。 これは、私がティーチングアシスタントをしていたときの経験に基づいており、新しいプログラマーがProcessingの使用方法を学ぶのに役立ちます。 これらの概念には、重力、衝突、スコアの保持、さまざまな画面の処理、およびキーボードとマウスの相互作用が含まれます。 ゆるいポンにはそれらすべてが含まれています。
今すぐゲームをプレイ!
オブジェクト指向プログラミング(OOP)の概念を使用しないと、複数のレベル、プレーヤー、エンティティなどのプラットフォームゲームなど、複雑なゲームを構築するのは簡単ではありません。進むにつれて、コードが非常に速く複雑になることがわかります。 この処理チュートリアルを整理してシンプルに保つために最善を尽くしました。
記事に従い、完全なコードを入手し、自分で遊んで、できるだけ早く自分のゲームについて考え始め、実装を開始することをお勧めします。
それでは始めましょう。
ゆるいポンの構築
チュートリアルの処理ステップ#1:さまざまな画面の初期化と処理
最初のステップは、プロジェクトを初期化することです。 手始めに、セットアップを作成し、通常どおりブロックを描画します。派手なものや新しいものはありません。 次に、さまざまな画面(初期画面、ゲーム画面、ゲームオーバー画面など)を処理します。 では、どのようにしてProcessingに正しいページを正しい時間に表示させるのかという疑問が生じます。
このタスクを実行するのはかなり簡単です。 現在アクティブな画面の情報を格納するグローバル変数があります。 次に、変数に応じて正しい画面の内容を描画します。 drawブロックには、変数をチェックし、それに応じて画面の内容を表示するifステートメントがあります。 画面を変更したいときはいつでも、その変数を表示したい画面の識別子に変更します。 そうは言っても、スケルトンコードは次のようになります。
/********* VARIABLES *********/ // We control which screen is active by settings / updating // gameScreen variable. We display the correct screen according // to the value of this variable. // // 0: Initial Screen // 1: Game Screen // 2: Game-over Screen int gameScreen = 0; /********* SETUP BLOCK *********/ void setup() { size(500, 500); } /********* DRAW BLOCK *********/ void draw() { // Display the contents of the current screen if (gameScreen == 0) { initScreen(); } else if (gameScreen == 1) { gameScreen(); } else if (gameScreen == 2) { gameOverScreen(); } } /********* SCREEN CONTENTS *********/ void initScreen() { // codes of initial screen } void gameScreen() { // codes of game screen } void gameOverScreen() { // codes for game over screen } /********* INPUTS *********/ public void mousePressed() { // if we are on the initial screen when clicked, start the game if (gameScreen==0) { startGame(); } } /********* OTHER FUNCTIONS *********/ // This method sets the necessary variables to start the game void startGame() { gameScreen=1; }
これは最初は怖いように見えるかもしれませんが、基本的な構造を構築し、コメントブロックでさまざまな部分を分離するだけです。
ご覧のとおり、表示する画面ごとに異なるメソッドを定義しています。 ドローブロックでは、 gameScreen
変数の値を確認し、対応するメソッドを呼び出すだけです。
void mousePressed(){...}
の部分では、マウスのクリックをリッスンしています。アクティブな画面が0(初期画面)の場合、 startGame()
メソッドを呼び出して、期待どおりにゲームを開始します。 このメソッドの最初の行は、 gameScreen
変数を1、ゲーム画面に変更します。
これを理解したら、次のステップは初期画面を実装することです。 そのために、 initScreen()
メソッドを編集します。 ここに行きます:
void initScreen() { background(0); textAlign(CENTER); text("Click to start", height/2, width/2); }
これで、最初の画面の背景が黒になり、「クリックして開始」という単純なテキストが中央に配置され、中央に配置されました。 しかし、クリックしても何も起こりません。 ゲーム画面のコンテンツはまだ指定していません。 gameScreen()
メソッドには何も含まれていないため、最初の描画行としてbackground()
を使用して、最後の画面(テキスト)から描画された前のコンテンツをカバーしていません。 そのため、 text()
行が呼び出されなくなっても、テキストはまだそこにあります(トレースを残していた最後の部分の移動するボールの例のように) 。 同じ理由で背景はまだ黒です。 それでは、先に進んでゲーム画面の実装を始めましょう。
void gameScreen() { background(255); }
この変更後、背景が白くなり、テキストが消えることに気付くでしょう。
チュートリアルの処理ステップ#2:ボールの作成と重力の実装
それでは、ゲーム画面の作業を開始します。 まず、ボールを作成します。 後でこれらの値を変更したい場合があるため、座標、色、サイズの変数を定義する必要があります。 たとえば、プレーヤーの得点が高くなるにつれてボールのサイズを大きくして、ゲームを難しくしたい場合です。 サイズを変更する必要があるので、変数にする必要があります。 重力を実装した後、ボールの速度も定義します。
まず、以下を追加しましょう。
... int ballX, ballY; int ballSize = 20; int ballColor = color(0); ... void setup() { ... ballX=width/4; ballY=height/5; } ... void gameScreen() { ... drawBall(); } ... void drawBall() { fill(ballColor); ellipse(ballX, ballY, ballSize, ballSize); }
座標をグローバル変数として定義し、 gameScreenメソッドから呼び出されるボールを描画するメソッドを作成しました。 ここで注意しなければならないのは、座標を初期化したことですが、 setup()
で定義しました。 その理由は、ボールを左から4分の1、上から5分の1から開始したかったからです。 それが欲しい理由は特にありませんが、それがボールをスタートさせる良いポイントです。 そのため、スケッチのwidth
とheight
を動的に取得する必要がありました。 スケッチサイズは、 setup()
の最初の行の後に定義されます。 setup()
を実行する前にwidth
とheight
を設定しないため、変数を上に定義した場合、これを実現できませんでした。
重力
現在、重力の実装は実際には簡単な部分です。 いくつかのトリックがあります。 最初の実装は次のとおりです。
... float gravity = 1; float ballSpeedVert = 0; ... void gameScreen() { ... applyGravity(); keepInScreen(); } ... void applyGravity() { ballSpeedVert += gravity; ballY += ballSpeedVert; } void makeBounceBottom(float surface) { ballY = surface-(ballSize/2); ballSpeedVert*=-1; } void makeBounceTop(float surface) { ballY = surface+(ballSize/2); ballSpeedVert*=-1; } // keep ball in the screen void keepInScreen() { // ball hits floor if (ballY+(ballSize/2) > height) { makeBounceBottom(height); } // ball hits ceiling if (ballY-(ballSize/2) < 0) { makeBounceTop(0); } }
そして結果は次のとおりです。
物理学者、あなたの馬を抱きしめてください。 私はそれが実際の生活で重力がどのように機能するかではないことを知っています。 代わりに、これは何よりもアニメーションプロセスです。 gravity
として定義した変数は単なる数値であり、整数だけでなく10進値を使用できるようにするためのfloat
小数点数であり、すべてのループでballSpeedVert
に追加します。 また、 ballSpeedVert
はボールの垂直速度であり、各ループのボール( ballY
)のY座標に追加されます。 ボールの座標を監視し、ボールが画面に表示されていることを確認します。 そうしないと、ボールは無限に落ちてしまいます。 今のところ、私たちのボールは垂直方向にしか動きません。 そのため、画面の床と天井の境界を監視します。 keepInScreen()
メソッドを使用して、 ballY
( +半径)がheight
より小さいかどうかを確認し、同様にballY
( -半径)が0
より大きいかどうかを確認します。 条件が満たされない場合は、makeBounceBottom()メソッドとmakeBounceBottom()
makeBounceTop()
メソッドを使用して、ボールを(下または上から)バウンスさせます。 ボールをバウンスさせるには、ボールをバウンドする必要のある正確な位置に移動し、垂直速度( ballSpeedVert
)に-1
を掛けます(-1を掛けると符号が変わります)。 速度値にマイナス記号がある場合、Y座標を追加すると、速度はballY + (-ballSpeedVert)
になります。これはballY - ballSpeedVert
です。 したがって、ボールはすぐに同じ速度で方向を変えます。 次に、 ballSpeedVert
にgravity
を追加し、 ballSpeedVert
の値が負になると、 0
0
なり、再び増加し始めます。 これにより、ボールが上昇し、ゆっくりと上昇し、停止して落下を開始します。
ただし、アニメーションプロセスには問題があります。ボールがバウンドし続けます。 これが実際のシナリオである場合、ボールは表面に触れるたびに空気抵抗と摩擦に直面していました。 これがゲームのアニメーションプロセスに必要な動作であるため、これを実装するのは簡単です。 以下を追加します。
... float airfriction = 0.0001; float friction = 0.1; ... void applyGravity() { ... ballSpeedVert -= (ballSpeedVert * airfriction); } void makeBounceBottom(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); } void makeBounceTop(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); }
そして今、私たちのアニメーションプロセスはこれを生成します:
名前が示すように、 friction
は表面摩擦であり、空気airfriction
は空気の摩擦です。 したがって、明らかに、ボールが任意の表面に接触するたびにfriction
が適用される必要があります。 ただし、 airfriction
は常に適用する必要があります。 それが私たちがしたことです。 applyGravity()
メソッドは各ループで実行されるため、すべてのループで現在の値の0.0001
パーセントをballSpeedVert
から取得します。 makeBounceBottom()
)メソッドとmakeBounceTop()
メソッドは、ボールが任意のサーフェスに接触したときに実行されます。 したがって、これらの方法では、同じことを行いましたが、今回はfriction
を使用しました。
チュートリアルの処理ステップ#3:ラケットの作成
次に、ボールが跳ね返るラケットが必要です。 ラケットをコントロールする必要があります。 マウスで操作できるようにしましょう。 コードは次のとおりです。
... color racketColor = color(0); float racketWidth = 100; float racketHeight = 10; ... void gameScreen() { ... drawRacket(); ... } ... void drawRacket(){ fill(racketColor); rectMode(CENTER); rect(mouseX, mouseY, racketWidth, racketHeight); }
ラケットの色、幅、高さをグローバル変数として定義しました。ゲームプレイ中に変更したい場合があります。 名前が示すとおりに実行するメソッドdrawRacket()
を実装しました。 rectMode
をcenterに設定して、ラケットがカーソルの中央に揃えられるようにします。
ラケットを作成したので、ボールをバウンドさせる必要があります。
... int racketBounceRate = 20; ... void gameScreen() { ... watchRacketBounce(); ... } ... void watchRacketBounce() { float overhead = mouseY - pmouseY; if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { makeBounceBottom(mouseY); // racket moving up if (overhead<0) { ballY+=overhead; ballSpeedVert+=overhead; } } } }
そしてここに結果があります:
つまり、 watchRacketBounce()
が行うことは、ラケットとボールが衝突することを確認することです。 ここで確認することが2つあります。それは、ボールとラケットが垂直方向と水平方向の両方に並んでいるかどうかです。 最初のifステートメントは、ボールの右側のX座標がラケットの左側のX座標よりも大きいかどうか(およびその逆)をチェックします。 そうである場合、2番目のステートメントは、ボールとラケットの間の距離がボールの半径以下であるかどうかをチェックします(つまり、ボールが衝突していることを意味します) 。 したがって、これらの条件が満たされると、 makeBounceBottom()
メソッドが呼び出され、ボールがラケット上でバウンドします(ラケットがあるmouseY
で)。
mouseY - pmouseY
によって計算される可変overhead
に気づきましたか? pmouseX
変数とpmouseY
変数は、前のフレームでのマウスの座標を格納します。 マウスは非常に速く動くことができるので、マウスがボールに向かって十分速く動いている場合、フレーム間のボールとラケットの間の距離を正しく検出できない可能性があります。 そのため、フレーム間のマウス座標の違いを考慮して、距離を検出します。 マウスの動きが速いほど、許容できる距離は長くなります。
また、別の理由でoverhead
を使用します。 overhead
の符号をチェックすることにより、マウスがどちらの方向に動いているかを検出します。 オーバーヘッドが負の場合、マウスは前のフレームのどこか下にあったため、マウス(ラケット)は上に移動しています。 その場合、ボールに速度を追加し、通常のバウンスよりも少し遠くに移動して、ラケットでボールを打つ効果をシミュレートします。 overhead
が0
未満の場合は、 ballY
とballSpeedVert
に追加して、ボールをより高く、より速く移動させます。 したがって、ラケットがボールに当たる速度が速いほど、ラケットはより高く、より速く移動します。
チュートリアルの処理ステップ#4:水平方向の動きとボールの制御
このセクションでは、ボールに水平方向の動きを追加します。 そして、ラケットでボールを水平にコントロールできるようにします。 どうぞ:
... // we will start with 0, but for we give 10 just for testing float ballSpeedHorizon = 10; ... void gameScreen() { ... applyHorizontalSpeed(); ... } ... void applyHorizontalSpeed(){ ballX += ballSpeedHorizon; ballSpeedHorizon -= (ballSpeedHorizon * airfriction); } void makeBounceLeft(float surface){ ballX = surface+(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } void makeBounceRight(float surface){ ballX = surface-(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } ... void keepInScreen() { ... if (ballX-(ballSize/2) < 0){ makeBounceLeft(0); } if (ballX+(ballSize/2) > width){ makeBounceRight(width); } }
そして結果は次のとおりです。
ここでの考え方は、垂直移動で行ったのと同じです。 水平速度変数ballSpeedHorizon
を作成しました。 ballX
に水平速度を適用し、空気摩擦を取り除く方法を作成しました。 さらに2つのifステートメントをkeepInScreen()
メソッドに追加しました。このメソッドは、ボールが画面の左端と右端に当たるのを監視します。 最後に、左右からのバウンスを処理するmakeBounceLeft()メソッドとmakeBounceLeft()
makeBounceRight()
メソッドを作成しました。
ゲームに水平速度を追加したので、ラケットでボールを制御します。 有名なアタリゲームのブレイクアウトや他のすべてのブリックブレイクゲームと同様に、ボールはラケットのヒットポイントに応じて左または右に移動する必要があります。 ラケットのエッジはボールの水平方向の速度を上げる必要がありますが、中央は効果がありません。 最初のコード:
void watchRacketBounce() { ... if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { ... ballSpeedHorizon = (ballX - mouseX)/5; ... } } }
結果は次のとおりです。

その単純な行をwatchRacketBounce()
に追加することでうまくいきました。 私たちが行ったのは、 ballX - mouseX
を使用して、ラケットの中心からボールが当たるポイントの距離を決定することです。 次に、それを水平速度にします。 実際の違いが大きすぎたので、何度か試してみたところ、10分の1の値が最も自然に感じられると思いました。
チュートリアルの処理ステップ#5:壁の作成
私たちのスケッチは、各ステップでゲームのように見え始めています。 このステップでは、Flappy Birdの場合と同様に、左に向かって移動する壁を追加します。
... int wallSpeed = 5; int wallInterval = 1000; float lastAddTime = 0; int minGapHeight = 200; int maxGapHeight = 300; int wallWidth = 80; color wallColors = color(0); // This arraylist stores data of the gaps between the walls. Actuals walls are drawn accordingly. // [gapWallX, gapWallY, gapWallWidth, gapWallHeight] ArrayList<int[]> walls = new ArrayList<int[]>(); ... void gameScreen() { ... wallAdder(); wallHandler(); } ... void wallAdder() { if (millis()-lastAddTime > wallInterval) { int randHeight = round(random(minGapHeight, maxGapHeight)); int randY = round(random(0, height-randHeight)); // {gapWallX, gapWallY, gapWallWidth, gapWallHeight} int[] randWall = {width, randY, wallWidth, randHeight}; walls.add(randWall); lastAddTime = millis(); } } void wallHandler() { for (int i = 0; i < walls.size(); i++) { wallRemover(i); wallMover(i); wallDrawer(i); } } void wallDrawer(int index) { int[] wall = walls.get(index); // get gap wall settings int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; // draw actual walls rectMode(CORNER); fill(wallColors); rect(gapWallX, 0, gapWallWidth, gapWallY); rect(gapWallX, gapWallY+gapWallHeight, gapWallWidth, height-(gapWallY+gapWallHeight)); } void wallMover(int index) { int[] wall = walls.get(index); wall[0] -= wallSpeed; } void wallRemover(int index) { int[] wall = walls.get(index); if (wall[0]+wall[2] <= 0) { walls.remove(index); } }
そしてこれは結果として:
コードは長くて威圧的に見えますが、理解するのが難しいことは何もないと私は約束します。 最初に気付くのはArrayList
。 ArrayList
が何であるかを知らない人にとっては、それは配列のように機能するリストの単なる実装ですが、それに比べていくつかの利点があります。 サイズ変更可能で、 list.add(index)
、 list.get(index)
)、 list.remove(index)
(index)などの便利なメソッドがあります。 壁データは、arraylist内の整数配列として保持されます。 アレイに保持するデータは、2つの壁の間のギャップに関するものです。 配列には次の値が含まれています。
[gap wall X, gap wall Y, gap wall width, gap wall height]
実際の壁は、ギャップ壁の値に基づいて描画されます。 これらはすべて、クラスを使用してより適切かつクリーンに処理できることに注意してください。ただし、オブジェクト指向プログラミング(OOP)の使用はこの処理チュートリアルの範囲外であるため、これが処理方法です。 壁を管理するための2つの基本メソッド、 wallAdder()
とwallHandler
あります。
wallAdder()
メソッドは、 wallInterval
ミリ秒ごとに新しい壁をarraylistに追加するだけです。 最後の壁が追加された時刻(ミリ秒単位)を格納するグローバル変数lastAddTime
があります。 現在のミリ秒millis()
から最後に追加されたミリ秒lastAddTime
を引いた値が、間隔値wallInterval
より大きい場合は、新しい壁を追加するときが来たことを意味します。 次に、最上部で定義されたグローバル変数に基づいてランダムギャップ変数が生成されます。 次に、新しい壁(ギャップ壁データを格納する整数配列)がarraylistに追加され、 lastAddTime
が現在のミリ秒millis()
に設定されます。
wallHandler()
は、arraylistにある現在の壁をループします。 また、各ループのアイテムごとに、arraylistのインデックス値によってwallDrawer(i)
wallRemover(i)
、 wallMover(i)
、wallDrawer(i)を呼び出します。 これらのメソッドは、その名前が示すとおりに機能します。 wallDrawer()
は、ギャップ壁データに基づいて実際の壁を描画します。 壁のデータ配列をarraylistから取得し、 rect()
メソッドを呼び出して、壁を実際の場所に描画します。 wallMover()
メソッドは、arraylistから要素を取得し、 wallSpeed
グローバル変数に基づいてXの位置を変更します。 最後に、 wallRemover()
は、画面の外にある壁を配列リストから削除します。 そうしなかった場合、Processingはそれらをまだ画面に表示されているものとして扱います。 そして、それはパフォーマンスの大きな損失だったでしょう。 したがって、壁が配列リストから削除されても、後続のループには描画されません。
残された最後の挑戦的なことは、ボールと壁の間の衝突を検出することです。
void wallHandler() { for (int i = 0; i < walls.size(); i++) { ... watchWallCollision(i); } } ... void watchWallCollision(int index) { int[] wall = walls.get(index); // get gap wall settings int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; int wallTopX = gapWallX; int wallTopY = 0; int wallTopWidth = gapWallWidth; int wallTopHeight = gapWallY; int wallBottomX = gapWallX; int wallBottomY = gapWallY+gapWallHeight; int wallBottomWidth = gapWallWidth; int wallBottomHeight = height-(gapWallY+gapWallHeight); if ( (ballX+(ballSize/2)>wallTopX) && (ballX-(ballSize/2)<wallTopX+wallTopWidth) && (ballY+(ballSize/2)>wallTopY) && (ballY-(ballSize/2)<wallTopY+wallTopHeight) ) { // collides with upper wall } if ( (ballX+(ballSize/2)>wallBottomX) && (ballX-(ballSize/2)<wallBottomX+wallBottomWidth) && (ballY+(ballSize/2)>wallBottomY) && (ballY-(ballSize/2)<wallBottomY+wallBottomHeight) ) { // collides with lower wall } }
watchWallCollision()
メソッドは、各ループの各壁に対して呼び出されます。 ギャップ壁の座標を取得し、実際の壁(上下)の座標を計算して、ボールの座標が壁と衝突するかどうかを確認します。
チュートリアルの処理ステップ#6:ヘルスとスコア
ボールと壁の衝突を検出できるようになったので、ゲームの仕組みを決定できます。 ゲームを少し調整した後、私はなんとかゲームをいくらかプレイ可能にすることができました。 しかし、それでも、それは非常に困難でした。 ゲームで最初に考えたのは、ボールが壁に触れるとゲームが終了する、ゆるい鳥のようにすることでした。 でも、プレイするのは無理だと気づきました。 だからここに私が思ったことはあります:
ボールの上にヘルスバーがあるはずです。 ボールが壁に触れている間、ボールは健康を失うはずです。 このロジックでは、ボールを壁から跳ね返らせるのは意味がありません。 したがって、ヘルスが0の場合、ゲームは終了し、ゲームオーバー画面に切り替える必要があります。 だからここに行きます:
int maxHealth = 100; float health = 100; float healthDecrease = 1; int healthBarWidth = 60; ... void gameScreen() { ... drawHealthBar(); ... } ... void drawHealthBar() { // Make it borderless: noStroke(); fill(236, 240, 241); rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth, 5); if (health > 60) { fill(46, 204, 113); } else if (health > 30) { fill(230, 126, 34); } else { fill(231, 76, 60); } rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth*(health/maxHealth), 5); } void decreaseHealth(){ health -= healthDecrease; if (health <= 0){ gameOver(); } }
そして、ここに簡単な実行があります:
ボールのヘルスを維持するために、グローバル変数health
を作成しました。 次に、ボールの上に2つの長方形を描画するメソッドdrawHealthBar()
を作成しました。 1つは基本ヘルスバーで、もう1つは現在のヘルスを示すアクティブバーです。 2番目の幅は動的であり、ヘルスバーの幅に対する現在のヘルスの比率であるhealthBarWidth*(health/maxHealth)
を使用して計算されます。 最後に、健康の価値に応じて塗りつぶしの色が設定されます。 最後になりましたが、スコア:
... void gameOverScreen() { background(0); textAlign(CENTER); fill(255); textSize(30); text("Game Over", height/2, width/2 - 20); textSize(15); text("Click to Restart", height/2, width/2 + 10); } ... void wallAdder() { if (millis()-lastAddTime > wallInterval) { ... // added another value at the end of the array int[] randWall = {width, randY, wallWidth, randHeight, 0}; ... } } void watchWallCollision(int index) { ... int wallScored = wall[4]; ... if (ballX > gapWallX+(gapWallWidth/2) && wallScored==0) { wallScored=1; wall[4]=1; score(); } } void score() { score++; } void printScore(){ textAlign(CENTER); fill(0); textSize(30); text(score, height/2, 50); }
ボールが壁を通過したときに得点する必要がありました。 ただし、壁ごとに最大1つのスコアを追加する必要があります。 つまり、ボールが戻って再び通過するよりも壁を通過した場合、別のスコアを追加するべきではありません。 これを実現するために、arraylist内のギャップウォール配列に別の変数を追加しました。 新しい変数は、ボールがまだその壁を通過していない場合は0
を格納し、通過した場合は1
を格納します。 次に、 watchWallCollision()
メソッドを変更しました。 score()
メソッドを起動し、ボールが前に通過していない壁を通過したときに壁を通過としてマークする条件を追加しました。
私たちは今、非常に終わりに近づいています。 最後に行うことは、ゲームオーバー画面でclick to restart
することを実装することです。 使用したすべての変数を初期値に設定し、ゲームを再開する必要があります。 ここにあります:
... public void mousePressed() { ... if (gameScreen==2){ restart(); } } ... void restart() { score = 0; health = maxHealth; ballX=width/4; ballY=height/5; lastAddTime = 0; walls.clear(); gameScreen = 0; }
さらに色を追加しましょう。
出来上がり! ゆるいポンがあります!
完全なProcessingゲームコードはここにあります。
p5.jsを使用した処理ゲームコードのWebへの移植
p5.jsは、Processingプログラミング言語と非常によく似た構文を持つJavaScriptライブラリです。 これは、既存の処理コードを単純に実行できるライブラリではありません。 代わりに、p5.jsでは実際のJavaScriptコードを記述する必要があります。これは、Processing.jsとして知られるProcessingのJavaScriptポートに似ています。 私たちのタスクは、p5.jsAPIを使用して処理コードをJavaScriptに変換することです。 ライブラリには、Processingと同様の一連の関数と構文があり、JavaScriptで機能させるには、コードに特定の変更を加える必要があります。ただし、ProcessingとJavaScriptはどちらもJavaと類似しているため、思ったよりも飛躍的ではありません。 。 JavaScript開発者でなくても、変更は非常に簡単であり、問題なく実行できるはずです。
まず、単純なindex.html
を作成し、ヘッダーにp5.min.js
を追加する必要があります。 また、変換されたコードを格納するflappy_pong.js
という別のファイルを作成する必要があります。
<html> <head> <title>Flappy Pong</title> <script tyle="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.19/p5.min.js"></script> <script tyle="text/javascript" src="flappy_pong.js"></script> <style> canvas { box-shadow: 0 0 20px lightgray; } </style> </head> <body> </body> </html>
コードを変換する際の戦略は、すべてのコードをコピーしてflappy_pong.js
に貼り付けてから、すべての変更を加えることです。 そしてそれが私がしたことです。 そして、これが私がコードを更新するために取ったステップです:
Javascriptは型指定されていない言語です(
int
やfloat
のような型宣言はありません)。 したがって、すべての型宣言をvar
に変更する必要があります。Javascriptには
void
はありません。 すべてをfunction
に変更する必要があります。関数のシグネチャから引数の型宣言を削除する必要があります。 (つまり
void wallMover(var index) {
tofunction wallMover(index) {
)JavaScriptには
ArrayList
はありません。 しかし、JavaScript配列を使用して同じことを達成できます。 次の変更を行います。-
ArrayList<int[]> walls = new ArrayList<int[]>();
var walls = [];
-
walls.clear();
walls = [];
-
walls.add(randWall);
壁にwalls.push(randWall);
-
walls.remove(index);
壁にwalls.splice(index,1);
-
walls.get(index);
walls[index]
-
walls.size()
からwalls.length
-
配列の宣言を変更し
var randWall = {width, randY, wallWidth, randHeight, 0};
tovar randWall = [width, randY, wallWidth, randHeight, 0];
すべての
public
キーワードを削除します。setup()
を呼び出す前にcolor()
が定義されないため、すべてのcolor(0)
宣言をfunction setup()
に移動します。size(500, 500);
createCanvas(500, 500);
function gameScreen(){
の名前をfunction gamePlayScreen()function gamePlayScreen(){
のような名前に変更します。これは、gameScreen
という名前のグローバル変数がすでにあるためです。 Processingを使用していたとき、1つは関数で、もう1つはint
変数でした。 しかし、JavaScriptは型指定されていないため、これらを混乱させます。同じことが
score()
にも当てはまります。 名前をaddScore()
に変更しました。
この処理チュートリアルのすべてをカバーする完全なJavaScriptコードは、ここにあります。
ゲームコードの処理:あなたもそれを行うことができます
この処理チュートリアルでは、非常に単純なゲームを作成する方法を説明しようとしました。 ただし、この記事で行ったことは、氷山の一角にすぎません。 処理プログラミング言語を使用すると、ほぼすべてを実現できます。 私の意見では、それはあなたが想像しているものをプログラムするための最良のツールです。 このProcessingチュートリアルでの私の実際の意図は、プログラミングを教えてゲームを構築することではなく、プログラミングがそれほど難しくないことを証明することでした。 独自のゲームを構築することは、単なる夢ではありません。 少しの努力と熱意でそれができることをお見せしたいと思います。 これらの2つの記事が、プログラミングを試してみるきっかけになることを心から願っています。