Panduan Utama untuk Bahasa Pemrosesan Bagian II: Membangun Game Sederhana

Diterbitkan: 2022-03-11

Ini adalah bagian kedua dari panduan utama untuk bahasa Pemrosesan. Di bagian pertama, saya memberikan panduan bahasa Pemrosesan dasar. Langkah selanjutnya bagi Anda untuk mempelajari Pemrosesan adalah pemrograman yang lebih praktis.

Pada artikel ini, saya akan menunjukkan cara menggunakan Processing untuk mengimplementasikan game Anda sendiri, langkah demi langkah. Setiap langkah akan dijelaskan secara rinci. Kemudian, kami akan port game ke web.

Bangun game sederhana dengan bahasa Pemrosesan.

Sebelum kita memulai tutorial Processing, berikut adalah kode latihan logo DVD dari bagian sebelumnya. Jika Anda memiliki pertanyaan, pastikan untuk meninggalkan komentar.

Tutorial Pemrosesan: Game Sederhana

Game yang akan kita buat dalam tutorial Processing ini adalah semacam kombinasi dari Flappy Bird, Pong dan Brick Breaker. Alasan saya memilih game seperti ini adalah karena sebagian besar konsepnya sulit dipahami oleh pemula saat mempelajari pengembangan game. Ini berdasarkan pengalaman saya sejak saya menjadi asisten pengajar, membantu programmer baru mempelajari cara menggunakan Processing. Konsep-konsep ini termasuk gravitasi, tabrakan, menjaga skor, menangani layar yang berbeda dan interaksi keyboard/mouse. Flappy Pong memiliki semuanya di dalamnya.

Mainkan Game Sekarang!

Tanpa menggunakan konsep pemrograman berorientasi objek (OOP), tidak mudah untuk membangun game yang kompleks, seperti game platform dengan banyak level, pemain, entitas, dll. Saat kita melanjutkan, Anda akan melihat bagaimana kode menjadi rumit dengan sangat cepat. Saya melakukan yang terbaik untuk menjaga tutorial Pemrosesan ini tetap teratur dan sederhana.

Saya menyarankan Anda untuk mengikuti artikelnya, ambil kode lengkapnya, mainkan sendiri, mulailah memikirkan permainan Anda sendiri secepat mungkin, dan mulai menerapkannya.

Jadi mari kita mulai.

Membangun Flappy Pong

Tutorial Pemrosesan Langkah #1: Inisialisasi & Tangani Layar Berbeda

Langkah pertama adalah menginisialisasi proyek kita. Sebagai permulaan, kami akan menulis pengaturan kami dan menggambar blok seperti biasa, tidak ada yang mewah atau baru. Kemudian, kami akan menangani layar yang berbeda (layar awal, layar game, layar game over, dll.). Jadi timbul pertanyaan, bagaimana caranya agar Processing menampilkan halaman yang benar pada waktu yang tepat?

Menyelesaikan tugas ini cukup sederhana. Kami akan memiliki variabel global yang menyimpan informasi dari layar yang sedang aktif. Kami kemudian menggambar isi layar yang benar tergantung pada variabel. Di blok draw, kita akan memiliki pernyataan if yang memeriksa variabel dan menampilkan isi layar yang sesuai. Kapan pun kita ingin mengubah layar, kita akan mengubah variabel itu menjadi pengidentifikasi layar yang ingin kita tampilkan. Dengan itu, inilah tampilan kode kerangka kami:

 /********* 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; }

Ini mungkin terlihat menakutkan pada awalnya, tetapi yang kami lakukan hanyalah membangun struktur dasar dan memisahkan bagian-bagian yang berbeda dengan blok komentar.

Seperti yang Anda lihat, kami mendefinisikan metode yang berbeda untuk setiap layar yang akan ditampilkan. Di blok draw kami, kami cukup memeriksa nilai variabel gameScreen kami, dan memanggil metode yang sesuai.

Di bagian void mousePressed(){...} , kita mendengarkan klik mouse dan jika layar aktif adalah 0, layar awal, kita memanggil metode startGame() yang memulai permainan seperti yang Anda harapkan. Baris pertama dari metode ini mengubah variabel gameScreen menjadi 1, layar game.

Jika sudah dipahami, langkah selanjutnya adalah mengimplementasikan layar awal kita. Untuk melakukan itu, kita akan mengedit metode initScreen() . Ini dia:

 void initScreen() { background(0); textAlign(CENTER); text("Click to start", height/2, width/2); }

Sekarang layar awal kami memiliki latar belakang hitam dan teks sederhana, "Klik untuk memulai", terletak di tengah dan sejajar dengan tengah. Tapi ketika kita klik, tidak ada yang terjadi. Kami belum menentukan konten apa pun untuk layar game kami. Metode gameScreen() tidak memiliki apa pun di dalamnya, jadi kami tidak menutupi konten sebelumnya yang diambil dari layar terakhir (teks) dengan memiliki background() sebagai baris pertama undian. Itu sebabnya teksnya tetap ada, meskipun baris text() tidak dipanggil lagi (seperti contoh bola bergerak dari bagian terakhir yang meninggalkan jejak) . Latar belakang masih hitam untuk alasan yang sama. Jadi mari kita lanjutkan dan mulai mengimplementasikan layar game.

 void gameScreen() { background(255); }

Setelah perubahan ini, Anda akan melihat bahwa latar belakang berubah menjadi putih & teks menghilang.

Tutorial Pemrosesan Langkah #2: Membuat Bola & Menerapkan Gravitasi

Sekarang, kita akan mulai mengerjakan layar game. Pertama-tama kita akan membuat bola kita. Kita harus mendefinisikan variabel untuk koordinat, warna, dan ukurannya karena kita mungkin ingin mengubah nilai tersebut nanti. Misalnya, jika kita ingin memperbesar ukuran bola karena skor pemain lebih tinggi sehingga permainan akan lebih sulit. Kita perlu mengubah ukurannya, jadi itu harus berupa variabel. Kami akan menentukan kecepatan bola juga, setelah kami menerapkan gravitasi.

Pertama, mari tambahkan yang berikut ini:

 ... 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); }

Kami mendefinisikan koordinat sebagai variabel global, membuat metode yang menarik bola, yang disebut dari metode gameScreen . Satu-satunya hal yang perlu diperhatikan di sini adalah kita menginisialisasi koordinat, tetapi kita mendefinisikannya di setup() . Alasan kami melakukan itu adalah kami ingin bola dimulai pada seperempat dari kiri dan seperlima dari atas. Tidak ada alasan khusus kami menginginkan itu, tetapi itu adalah poin yang bagus untuk bola dimulai. Jadi kita perlu mendapatkan width dan height sketsa secara dinamis. Ukuran sketsa ditentukan dalam setup() , setelah baris pertama. width dan height tidak disetel sebelum setup() berjalan, itu sebabnya kami tidak dapat mencapai ini jika kami mendefinisikan variabel di atas.

Gravitasi

Sekarang menerapkan gravitasi sebenarnya adalah bagian yang mudah. Hanya ada beberapa trik. Berikut implementasinya terlebih dahulu:

 ... 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); } }

Dan hasilnya adalah:

Sebuah bola memantul tanpa batas dengan gravitasi semu.

Pegang kudamu, fisikawan. Saya tahu itu bukan cara kerja gravitasi dalam kehidupan nyata. Sebaliknya, ini lebih merupakan proses animasi daripada apa pun. Variabel yang kita definisikan sebagai gravity hanyalah nilai numerik— float sehingga kita dapat menggunakan nilai desimal, bukan hanya bilangan bulat—yang kita tambahkan ke ballSpeedVert di setiap loop. Dan ballSpeedVert adalah kecepatan vertikal bola, yang ditambahkan ke koordinat Y bola ( ballY ) pada setiap loop. Kami mengamati koordinat bola dan memastikannya tetap berada di layar. Jika tidak, bola akan jatuh hingga tak terhingga. Untuk saat ini, bola kami hanya bergerak vertikal. Jadi kami melihat batas lantai dan langit-langit layar. Dengan metode keepInScreen() , kami memeriksa apakah ballY ( + radius) kurang dari height , dan juga ballY ( - radius) lebih dari 0 . Jika kondisi tidak terpenuhi, kami membuat bola memantul (dari bawah atau atas) dengan makeBounceBottom() dan makeBounceTop() . Untuk membuat bola memantul, kita cukup memindahkan bola ke lokasi yang tepat di mana bola harus memantul dan mengalikan kecepatan vertikal ( ballSpeedVert ) dengan -1 ( mengalikan dengan -1 akan mengubah tanda). Ketika nilai kecepatan memiliki tanda minus, penambahan koordinat Y kecepatan menjadi ballY + (-ballSpeedVert) , yaitu ballY - ballSpeedVert . Jadi bola segera berubah arah dengan kecepatan yang sama. Kemudian, saat kita menambahkan gravity ke ballSpeedVert dan ballSpeedVert memiliki nilai negatif, ia mulai mendekati 0 , akhirnya menjadi 0 , dan mulai meningkat lagi. Itu membuat bola naik, naik lebih lambat, berhenti dan mulai jatuh.

Sebuah bola memantul tanpa batas pada raket.

Namun, ada masalah dengan proses animasi kami—bola terus memantul. Jika ini adalah skenario dunia nyata, bola akan menghadapi hambatan udara dan gesekan setiap kali menyentuh permukaan. Itulah perilaku yang kami inginkan untuk proses animasi game kami, jadi menerapkan ini mudah. Kami menambahkan yang berikut ini:

 ... 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); }

Dan sekarang proses animasi kami menghasilkan ini:

Sebuah bola memantul tetapi berhenti karena gesekan.

Seperti namanya, friction adalah gesekan permukaan dan airfriction adalah gesekan udara. Jadi jelas, friction harus diterapkan setiap kali bola menyentuh permukaan apa pun. airfriction namun harus diterapkan terus-menerus. Jadi itulah yang kami lakukan. metode applyGravity() berjalan pada setiap loop, jadi kami mengambil 0.0001 persen dari nilainya saat ini dari ballSpeedVert pada setiap loop. makeBounceBottom() dan makeBounceTop() dijalankan saat bola menyentuh permukaan apa pun. Jadi dalam metode itu, kami melakukan hal yang sama, hanya kali ini dengan friction .

Tutorial Pemrosesan Langkah #3: Membuat Raket

Sekarang kami membutuhkan raket untuk memantulkan bola. Kita harus mengendalikan raket. Mari kita membuatnya dapat dikontrol dengan mouse. Berikut kodenya:

 ... color racketColor = color(0); float racketWidth = 100; float racketHeight = 10; ... void gameScreen() { ... drawRacket(); ... } ... void drawRacket(){ fill(racketColor); rectMode(CENTER); rect(mouseX, mouseY, racketWidth, racketHeight); }

Kami mendefinisikan warna, lebar dan tinggi raket sebagai variabel global, kami mungkin ingin mereka berubah selama bermain game. Kami menerapkan metode drawRacket() yang melakukan apa yang disarankan namanya. Kami mengatur rectMode ke tengah, sehingga raket kami sejajar dengan pusat kursor kami.

Sekarang setelah kita membuat raket, kita harus membuat bola memantul di atasnya.

 ... 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; } } } }

Dan inilah hasilnya:

Sebuah bola memantul pada raket tetapi berhenti karena gesekan.

Jadi yang dilakukan watchRacketBounce() adalah memastikan bahwa raket dan bola bertabrakan. Ada dua hal yang harus diperiksa di sini, yaitu apakah bola dan raket berjajar baik secara vertikal maupun horizontal. Pernyataan if pertama memeriksa apakah koordinat X sisi kanan bola lebih besar dari koordinat X sisi kiri raket (dan sebaliknya). Jika ya, pernyataan kedua memeriksa apakah jarak antara bola dan raket lebih kecil dari atau sama dengan jari-jari bola (yang berarti keduanya bertabrakan) . Jadi jika kondisi ini terpenuhi, metode makeBounceBottom() dipanggil dan bola memantul di raket kita (di mouseY , tempat raket berada).

Pernahkah Anda memperhatikan variabel overhead yang dihitung dengan mouseY - pmouseY ? pmouseX dan pmouseY menyimpan koordinat mouse pada frame sebelumnya. Karena mouse dapat bergerak sangat cepat, ada kemungkinan besar kita tidak dapat mendeteksi jarak antara bola dan raket dengan benar di antara frame jika mouse bergerak ke arah bola dengan cukup cepat. Jadi, kami memperhitungkan perbedaan koordinat mouse di antara bingkai dan memperhitungkannya saat mendeteksi jarak. Semakin cepat mouse bergerak, semakin jauh jarak yang dapat diterima.

Kami juga menggunakan overhead untuk alasan lain. Kami mendeteksi ke arah mana mouse bergerak dengan memeriksa tanda di overhead . Jika overhead negatif, mouse berada di suatu tempat di bawah frame sebelumnya sehingga mouse (raket) kita bergerak ke atas. Dalam hal ini, kami ingin menambahkan kecepatan ekstra pada bola dan memindahkannya sedikit lebih jauh dari pantulan biasa untuk mensimulasikan efek memukul bola dengan raket. Jika overhead kurang dari 0 , kami menambahkannya ke ballY dan ballSpeedVert untuk membuat bola lebih tinggi dan lebih cepat. Jadi semakin cepat raket memukul bola, semakin tinggi dan cepat ia akan bergerak ke atas.

Tutorial Pemrosesan Langkah #4: Gerakan Horizontal & Mengontrol Bola

Pada bagian ini, kita akan menambahkan gerakan horizontal pada bola. Kemudian, kami akan memungkinkan untuk mengontrol bola secara horizontal dengan raket kami. Ini dia:

 ... // 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); } }

Dan hasilnya adalah:

Sebuah bola memantul sekarang secara horizontal juga.

Idenya di sini sama dengan apa yang kami lakukan untuk gerakan vertikal. Kami membuat variabel kecepatan horizontal, ballSpeedHorizon . Kami menciptakan metode untuk menerapkan kecepatan horizontal ke ballX dan menghilangkan gesekan udara. Kami menambahkan dua pernyataan if lagi ke metode keepInScreen() yang akan mengawasi bola untuk memukul tepi kiri dan kanan layar. Akhirnya kami membuat makeBounceLeft() dan makeBounceRight() untuk menangani pantulan dari kiri dan kanan.

Sekarang setelah kami menambahkan kecepatan horizontal ke permainan, kami ingin mengontrol bola dengan raket. Seperti dalam game Atari Breakout yang terkenal dan di semua game memecahkan batu bata lainnya, bola harus bergerak ke kiri atau ke kanan sesuai dengan titik pada raket yang dipukul. Tepi raket harus memberikan kecepatan bola yang lebih horizontal sedangkan bagian tengah seharusnya tidak memiliki efek apa pun. Kode terlebih dahulu:

 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; ... } } }

Hasilnya adalah:

Fisika horizontal gaya breakout.

Menambahkan baris sederhana itu ke watchRacketBounce() berhasil. Apa yang kami lakukan adalah kami menentukan jarak titik yang ditendang bola dari pusat raket dengan ballX - mouseX . Kemudian, kita membuatnya menjadi kecepatan horizontal. Perbedaan sebenarnya terlalu banyak, jadi saya mencobanya beberapa kali dan berpikir bahwa sepersepuluh dari nilai terasa paling alami.

Tutorial Pemrosesan Langkah #5: Membuat Dinding

Sketsa kami mulai terlihat lebih seperti permainan dengan setiap langkah. Pada langkah ini, kita akan menambahkan dinding yang bergerak ke arah kiri, seperti pada 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); } }

Dan ini mengakibatkan:

Sebuah bola memantul melalui tingkat dengan dinding.

Meskipun kodenya terlihat panjang dan menakutkan, saya berjanji tidak ada yang sulit untuk dipahami. Hal pertama yang harus diperhatikan adalah ArrayList . Bagi Anda yang belum tahu apa itu ArrayList , ini hanyalah implementasi dari daftar yang berfungsi seperti Array, tetapi memiliki beberapa kelebihan. Ini dapat diubah ukurannya, memiliki metode yang berguna seperti list.add(index) , list.get(index) dan list.remove(index) . Kami menyimpan data dinding sebagai array integer dalam daftar array. Data yang kami simpan dalam array adalah untuk celah antara dua dinding. Array berisi nilai-nilai berikut:

 [gap wall X, gap wall Y, gap wall width, gap wall height]

Dinding sebenarnya digambar berdasarkan nilai celah dinding. Perhatikan bahwa semua ini dapat ditangani dengan lebih baik dan lebih bersih menggunakan kelas, tetapi karena penggunaan Pemrograman Berorientasi Objek (OOP) tidak dalam cakupan tutorial Pemrosesan ini, inilah cara kami akan menanganinya. Kami memiliki dua metode dasar untuk mengelola dinding, wallAdder() dan wallHandler .

wallAdder() metode hanya menambahkan dinding baru di setiap milidetik wallInterval ke daftar array. Kami memiliki variabel global lastAddTime yang menyimpan waktu ketika dinding terakhir ditambahkan (dalam milidetik) . Jika milidetik saat ini millis() dikurangi milidetik terakhir yang ditambahkan lastAddTime lebih besar dari nilai interval wallInterval , itu berarti sekarang saatnya untuk menambahkan dinding baru. Variabel gap acak kemudian dihasilkan berdasarkan variabel global yang didefinisikan di bagian paling atas. Kemudian dinding baru (array bilangan bulat yang menyimpan data dinding celah) ditambahkan ke dalam daftar array dan lastAddTime diatur ke milidetik saat ini millis() .

wallHandler() loop melalui dinding saat ini yang ada di daftar array. Dan untuk setiap item di setiap loop, ia memanggil wallRemover(i) , wallMover(i) dan wallDrawer(i) dengan nilai indeks daftar array. Metode ini melakukan apa yang disarankan oleh namanya. wallDrawer() menggambar dinding sebenarnya berdasarkan data dinding celah. Ini mengambil array data dinding dari daftar array, dan memanggil metode rect() untuk menggambar dinding ke tempat yang seharusnya. metode wallMover() mengambil elemen dari daftar array, mengubah lokasi X-nya berdasarkan variabel global wallSpeed . Terakhir, wallRemover() menghapus dinding dari daftar array yang berada di luar layar. Jika kami tidak melakukan itu, Processing akan memperlakukan mereka seperti mereka masih di layar. Dan itu akan menjadi kerugian besar dalam kinerja. Jadi ketika sebuah dinding dihapus dari daftar array, itu tidak tergambar pada loop berikutnya.

Tantangan terakhir yang harus dilakukan adalah mendeteksi benturan antara bola dan dinding.

 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() metode dipanggil untuk setiap dinding di setiap loop. Kami mengambil koordinat dinding celah, menghitung koordinat dinding yang sebenarnya (atas dan bawah) dan kami memeriksa apakah koordinat bola bertabrakan dengan dinding.

Tutorial Pemrosesan Langkah #6: Kesehatan dan Skor

Sekarang setelah kita dapat mendeteksi tabrakan bola dan dinding, kita dapat memutuskan mekanisme permainannya. Setelah beberapa penyetelan ke permainan, saya berhasil membuat permainan agak bisa dimainkan. Tapi tetap saja, itu sangat sulit. Pikiran pertama saya tentang game ini adalah membuatnya seperti Flappy Bird, ketika bola menyentuh dinding, permainan berakhir. Tapi kemudian saya menyadari itu tidak mungkin untuk dimainkan. Jadi inilah yang saya pikirkan:

Harus ada bar kesehatan di atas bola. Bola harus kehilangan kesehatan saat menyentuh dinding. Dengan logika ini, tidak masuk akal untuk membuat bola memantul dari dinding. Jadi ketika kesehatannya 0, game harus berakhir dan kita harus beralih ke layar game over. Jadi di sini kita pergi:

 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(); } }

Dan ini adalah lari sederhana:

Sebuah bola dengan bar kesehatan memantul melalui tingkat, kehilangan kesehatan setiap kali bertabrakan dengan dinding.

Kami menciptakan health variabel global untuk menjaga kesehatan bola. Dan kemudian buat metode drawHealthBar() yang menggambar dua persegi panjang di atas bola. Yang pertama adalah bar kesehatan dasar, yang lain adalah yang aktif yang menunjukkan kesehatan saat ini. Lebar yang kedua adalah dinamis, dan dihitung dengan healthBarWidth*(health/maxHealth) , rasio kesehatan kita saat ini sehubungan dengan lebar bilah kesehatan. Terakhir, warna isian diatur sesuai dengan nilai kesehatan. Terakhir, skor :

 ... 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); }

Kami harus mencetak gol saat bola melewati tembok. Tapi kita perlu menambahkan maksimal 1 skor per dinding. Artinya, jika bola melewati tembok daripada kembali dan mengopernya lagi, skor lain tidak boleh ditambahkan. Untuk mencapai ini, kami menambahkan variabel lain ke array dinding celah di dalam daftar array. Variabel baru menyimpan 0 jika bola belum melewati tembok itu dan 1 jika berhasil. Kemudian, kami memodifikasi metode watchWallCollision() . Kami menambahkan kondisi yang menembakkan metode score() dan menandai tembok yang dilewati ketika bola melewati tembok yang belum pernah dilewati sebelumnya.

Kami sekarang sangat dekat dengan akhir. Hal terakhir yang harus dilakukan adalah menerapkan click to restart pada layar game over. Kita perlu mengatur semua variabel yang kita gunakan ke nilai awalnya, dan memulai kembali permainan. Ini dia:

 ... 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; }

Mari tambahkan beberapa warna lagi.

Flappy Pong lengkap dalam warna.

Voila! Kami memiliki Flappy Pong!

Kode game Processing lengkap dapat ditemukan di sini.

Memindahkan Kode Game Pemrosesan ke Web Menggunakan p5.js

p5.js adalah library JavaScript dengan sintaks yang sangat mirip dengan bahasa pemrograman Processing. Ini bukan perpustakaan yang hanya mampu menjalankan kode Pemrosesan yang ada; sebagai gantinya, p5.js memerlukan penulisan kode JavaScript yang sebenarnya—mirip dengan port JavaScript Pemrosesan yang dikenal sebagai Processing.js. Tugas kita adalah mengubah kode Pemrosesan menjadi JavaScript menggunakan p5.js API. Pustaka memiliki serangkaian fungsi dan sintaks yang mirip dengan Pemrosesan, dan kami harus melakukan perubahan tertentu pada kode kami untuk membuatnya berfungsi di JavaScript—namun karena Pemrosesan dan JavaScript memiliki kesamaan dengan Java, ini tidak terlalu jauh dari kedengarannya. . Bahkan jika Anda bukan pengembang JavaScript, perubahannya sangat sepele dan Anda harus dapat mengikutinya dengan baik.

Pertama-tama, kita perlu membuat index.html sederhana dan menambahkan p5.min.js ke header kita. Kita juga perlu membuat file lain bernama flappy_pong.js yang akan menampung kode konversi kita.

 <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>

Strategi kami saat mengonversi kode harus menyalin dan menempelkan semua kode kami ke flappy_pong.js dan kemudian membuat semua perubahan. Dan itulah yang saya lakukan. Dan berikut adalah langkah-langkah yang saya ambil untuk memperbarui kode:

  • Javascript adalah bahasa yang tidak diketik (tidak ada deklarasi tipe seperti int dan float ). Jadi kita perlu mengubah semua deklarasi tipe menjadi var .

  • Tidak ada void dalam Javascript. Kita harus mengubah semua function .

  • Kita perlu menghapus deklarasi tipe argumen dari tanda tangan fungsi. (mis. void wallMover(var index) { function wallMover(index) { )

  • Tidak ada ArrayList di JavaScript. Tapi kita bisa mencapai hal yang sama menggunakan array JavaScript. Kami membuat perubahan berikut:

    • ArrayList<int[]> walls = new ArrayList<int[]>(); ke var walls = [];
    • walls.clear(); ke walls = [];
    • walls.add(randWall); ke walls.push(randWall);
    • walls.remove(index); ke walls.splice(index,1);
    • walls.get(index); ke walls[index]
    • walls.size() ke walls.length
  • Ubah deklarasi array var randWall = {width, randY, wallWidth, randHeight, 0}; to var randWall = [width, randY, wallWidth, randHeight, 0];

  • Hapus semua kata kunci public .

  • Pindahkan semua deklarasi color(0) ke dalam function setup() karena color() tidak akan didefinisikan sebelum panggilan setup() .

  • Ubah size(500, 500); untuk createCanvas(500, 500);

  • Ganti nama function gameScreen(){ menjadi sesuatu yang lain seperti function gamePlayScreen(){ karena kita sudah memiliki variabel global bernama gameScreen . Ketika kami bekerja dengan Processing, satu adalah fungsi dan yang lainnya adalah variabel int . Tetapi JavaScript membingungkan ini karena tidak diketik.

  • Hal yang sama berlaku untuk score() . Saya menamainya menjadi addScore() .

Kode JavaScript lengkap yang mencakup semuanya dalam tutorial Pemrosesan ini dapat ditemukan di sini.

Memproses Kode Game: Anda Juga Bisa

Dalam tutorial Processing ini, saya mencoba menjelaskan bagaimana membangun sebuah game yang sangat sederhana. Namun, apa yang kami lakukan dalam artikel ini hanyalah puncak gunung es. Dengan bahasa pemrograman Processing, apa saja bisa dicapai. Menurut pendapat saya, ini adalah alat terbaik untuk memprogram apa yang Anda bayangkan. Niat saya yang sebenarnya dengan tutorial Pemrosesan ini adalah daripada mengajar Pemrosesan dan membuat game, untuk membuktikan bahwa pemrograman tidak terlalu sulit. Membangun game Anda sendiri bukan hanya mimpi. Saya ingin menunjukkan kepada Anda bahwa dengan sedikit usaha dan antusiasme, Anda bisa melakukannya. Saya sangat berharap kedua artikel ini menginspirasi semua orang untuk mencoba pemrograman.