処理言語の究極のガイドパート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から開始したかったからです。 それが欲しい理由は特にありませんが、それがボールをスタートさせる良いポイントです。 そのため、スケッチのwidthheightを動的に取得する必要がありました。 スケッチサイズは、 setup()の最初の行の後に定義されます。 setup()を実行する前にwidthheightを設定しないため、変数を上に定義した場合、これを実現できませんでした。

重力

現在、重力の実装は実際には簡単な部分です。 いくつかのトリックがあります。 最初の実装は次のとおりです。

 ... 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です。 したがって、ボールはすぐに同じ速度で方向を変えます。 次に、 ballSpeedVertgravityを追加し、 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の符号をチェックすることにより、マウスがどちらの方向に動いているかを検出します。 オーバーヘッドが負の場合、マウスは前のフレームのどこか下にあったため、マウス(ラケット)は上に移動しています。 その場合、ボールに速度を追加し、通常のバウンスよりも少し遠くに移動して、ラケットでボールを打つ効果をシミュレートします。 overhead0未満の場合は、 ballYballSpeedVertに追加して、ボールをより高く、より速く移動させます。 したがって、ラケットがボールに当たる速度が速いほど、ラケットはより高く、より速く移動します。

チュートリアルの処理ステップ#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); } }

そしてこれは結果として:

壁のあるレベルを跳ね返るボール。

コードは長くて威圧的に見えますが、理解するのが難しいことは何もないと私は約束します。 最初に気付くのはArrayListArrayListが何であるかを知らない人にとっては、それは配列のように機能するリストの単なる実装ですが、それに比べていくつかの利点があります。 サイズ変更可能で、 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.j​​sを使用した処理ゲームコードのWebへの移植

p5.j​​sは、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は型指定されていない言語です( intfloatのような型宣言はありません)。 したがって、すべての型宣言をvarに変更する必要があります。

  • Javascriptにはvoidはありません。 すべてをfunctionに変更する必要があります。

  • 関数のシグネチャから引数の型宣言を削除する必要があります。 (つまりvoid wallMover(var index) { to function 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}; to var 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つの記事が、プログラミングを試してみるきっかけになることを心から願っています。