İşlem Dili Bölüm II için Nihai Kılavuz: Basit Bir Oyun Oluşturma
Yayınlanan: 2022-03-11Bu, İşleme diline ilişkin nihai kılavuzun ikinci kısmıdır. İlk bölümde, temel bir İşleme dili adım adım anlattım. İşleme öğrenmeniz için bir sonraki adım, sadece daha fazla uygulamalı programlamadır.
Bu yazıda, kendi oyununuzu adım adım uygulamak için İşleme'yi nasıl kullanacağınızı göstereceğim. Her adım ayrıntılı olarak açıklanacaktır. Ardından, oyunu web'e taşıyacağız.
İşleme eğitimine başlamadan önce, önceki bölümdeki DVD logosu alıştırmasının kodunu burada bulabilirsiniz. Herhangi bir sorunuz varsa, yorum bıraktığınızdan emin olun.
İşleme Eğitimi: Basit Bir Oyun
Bu İşleme eğitiminde kuracağımız oyun, bir çeşit Flappy Bird, Pong ve Brick Breaker kombinasyonudur. Böyle bir oyun seçmemin nedeni, yeni başlayanların oyun geliştirmeyi öğrenirken zorlandıkları kavramların çoğuna sahip olması. Bu, yeni programcıların İşleme'yi nasıl kullanacaklarını öğrenmelerine yardımcı olan bir öğretim asistanıyken edindiğim deneyimime dayanmaktadır. Bu kavramlar yerçekimi, çarpışmalar, puan tutma, farklı ekranları kullanma ve klavye/fare etkileşimlerini içerir. Flappy Pong'un içinde hepsi var.
Şimdi Oyunu Oyna!
Nesne yönelimli programlama (OOP) kavramlarını kullanmadan, birden çok seviyeli platform oyunları, oyuncular, varlıklar vb. gibi karmaşık oyunlar oluşturmak kolay değildir. İlerledikçe, kodun nasıl gerçekten hızlı bir şekilde karmaşıklaştığını göreceksiniz. Bu İşleme eğitimini düzenli ve basit tutmak için elimden gelenin en iyisini yaptım.
Makaleyi takip etmenizi, tam kodu almanızı, onunla kendi başınıza oynamanızı, mümkün olduğunca çabuk kendi oyununuzu düşünmeye başlamanızı ve uygulamaya başlamanızı tavsiye ederim.
Öyleyse başlayalım.
Flappy Pong Yapımı
İşleme Eğitimi Adım 1: Farklı Ekranları Başlatın ve İşleyin
İlk adım projemizi başlatmak. Yeni başlayanlar için kurulumumuzu yazacağız ve blokları her zamanki gibi çizeceğiz, süslü veya yeni bir şey değil. Ardından, farklı ekranları ele alacağız (ilk ekran, oyun ekranı, ekran üstü oyun vb.). Öyleyse soru ortaya çıkıyor, İşlemenin doğru sayfayı doğru zamanda göstermesini nasıl sağlarız?
Bu görevi gerçekleştirmek oldukça basittir. Şu anda aktif olan ekranın bilgilerini saklayan global bir değişkenimiz olacak. Daha sonra değişkene bağlı olarak doğru ekranın içeriğini çiziyoruz. Draw bloğunda, değişkeni kontrol eden ve buna göre ekranın içeriğini görüntüleyen bir if ifadesine sahip olacağız. Ekranı ne zaman değiştirmek istersek, o değişkeni, görüntülenmesini istediğimiz ekranın tanımlayıcısına değiştireceğiz. Bununla birlikte, iskelet kodumuz şöyle görünür:
/********* 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; }
Bu ilk başta korkutucu görünebilir, ancak tek yaptığımız temel yapıyı oluşturmak ve farklı bölümleri yorum bloklarıyla ayırmak.
Gördüğünüz gibi, her ekranın görüntülenmesi için farklı bir yöntem tanımlıyoruz. Beraberlik gameScreen
değişkenimizin değerini kontrol ediyoruz ve ilgili yöntemi çağırıyoruz.
void mousePressed(){...}
kısmında fare tıklamalarını dinliyoruz ve aktif ekran 0 ise başlangıç ekranı, oyunu beklediğiniz gibi başlatan startGame()
yöntemini çağırıyoruz. Bu yöntemin ilk satırı gameScreen
değişkenini oyun ekranı olan 1 olarak değiştirir.
Bu anlaşılırsa, bir sonraki adım başlangıç ekranımızı uygulamaktır. Bunu yapmak için initScreen()
yöntemini düzenleyeceğiz. İşte gidiyor:
void initScreen() { background(0); textAlign(CENTER); text("Click to start", height/2, width/2); }
Şimdi ilk ekranımız siyah bir arka plana ve ortada bulunan ve merkeze hizalanmış basit bir “Başlamak için tıklayın” metnine sahip. Ama tıkladığımızda hiçbir şey olmuyor. Oyun ekranımız için henüz herhangi bir içerik belirtmedik. gameScreen()
yönteminde hiçbir şey yoktur, bu nedenle ilk çizim satırı olarak background()
'u kullanarak son ekrandan (metin) çizilen önceki içeriği kapsamıyoruz. Bu nedenle, text()
satırı artık çağrılmasa da metin hala oradadır (tıpkı son bölümden geriye bir iz bırakan hareketli top örneğinde olduğu gibi) . Arka plan aynı nedenden dolayı hala siyah. Öyleyse devam edelim ve oyun ekranını uygulamaya başlayalım.
void gameScreen() { background(255); }
Bu değişiklikten sonra arka planın beyaza döndüğünü ve metnin kaybolduğunu fark edeceksiniz.
İşleme Eğitimi Adım #2: Topu Oluşturma ve Yerçekimini Uygulama
Şimdi oyun ekranında çalışmaya başlayacağız. İlk önce topumuzu oluşturacağız. Koordinatları, rengi ve boyutu için değişkenler tanımlamalıyız çünkü bu değerleri daha sonra değiştirmek isteyebiliriz. Örneğin, oyuncu daha yüksek puan aldıkça topun boyutunu artırmak istiyorsak, oyun daha zor olacaktır. Boyutunu değiştirmemiz gerekecek, bu yüzden bir değişken olmalı. Yerçekimini uyguladıktan sonra topun hızını da tanımlayacağız.
Öncelikle şunları ekleyelim:
... 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); }
Koordinatları global değişkenler olarak tanımladık, gameScreen metodundan çağrılan, topu çeken bir metot yarattık. Burada dikkat edilmesi gereken tek şey, koordinatları başlatmış olmamız, ancak onları setup()
içinde tanımlamış olmamızdır. Bunu yapmamızın nedeni, topun soldan dörtte bir ve üstten beşte birinden başlamasını istedik. Bunu istememizin özel bir nedeni yok ama bu topun başlaması için iyi bir nokta. Bu yüzden çizimin width
ve height
dinamik olarak elde etmemiz gerekiyordu. Çizim boyutu, ilk satırdan sonra setup()
içinde tanımlanır. width
ve height
setup()
çalıştırılmadan önce ayarlanmaz, bu yüzden değişkenleri üstte tanımlasaydık bunu başaramazdık.
Yer çekimi
Şimdi yerçekimini uygulamak aslında kolay kısım. Sadece birkaç hile var. İşte ilk uygulama:
... 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); } }
Ve sonuç:
Atlarınızı tutun fizikçi. Yerçekiminin gerçek hayatta böyle çalışmadığını biliyorum. Bunun yerine, bu her şeyden çok bir animasyon sürecidir. gravity
olarak tanımladığımız değişken, her döngüde ballSpeedVert
eklediğimiz yalnızca sayısal bir değerdir—yalnızca tam sayıları değil ondalık değerleri kullanabilmemiz için bir float
noktadır. Ve ballSpeedVert
, her döngüde topun Y koordinatına ( ballY
) eklenen topun dikey hızıdır. Topun koordinatlarını izliyoruz ve ekranda kalmasını sağlıyoruz. Eğer yapmasaydık, top sonsuza kadar düşecekti. Şimdilik topumuz sadece dikey hareket ediyor. Böylece ekranın taban ve tavan sınırlarını izliyoruz. keepInScreen()
yöntemi ile ballY
( + yarıçapın) height
öğesinden küçük olup olmadığını ve benzer şekilde ballY
( - yarıçap) 0
büyük olup olmadığını kontrol ederiz. Koşullar karşılanmıyorsa makeBounceBottom()
ve makeBounceTop()
yöntemleriyle topun (alttan veya üstten) sekmesini sağlıyoruz. Topu zıplatmak için, topu tam olarak zıplaması gereken yere hareket ettiririz ve dikey hızı ( ballSpeedVert
) -1
ile çarparız (-1 ile çarpmak işareti değiştirir). Hız değeri eksi işaretine sahip olduğunda, Y koordinatı eklendiğinde hız ballY + (-ballSpeedVert)
olur, bu da ballY - ballSpeedVert
. Böylece top hemen aynı hızla yönünü değiştirir. Daha sonra ballSpeedVert
gravity
eklediğimizde ve ballSpeedVert
negatif bir değere sahip olduğunda, 0
0
ve tekrar artmaya başlar. Bu, topun yükselmesini, daha yavaş yükselmesini, durmasını ve düşmeye başlamasını sağlar.
Ancak animasyon sürecimizde bir sorun var - top zıplamaya devam ediyor. Bu gerçek bir dünya senaryosu olsaydı, top bir yüzeye her dokunduğunda hava direnci ve sürtünme ile karşı karşıya kalırdı. Oyunumuzun animasyon süreci için istediğimiz davranış budur, bu yüzden bunu uygulamak kolaydır. Aşağıdakileri ekliyoruz:
... 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); }
Ve şimdi animasyon sürecimiz şunu üretiyor:
Adından da anlaşılacağı gibi, friction
yüzey sürtünmesidir ve hava airfriction
havanın sürtünmesidir. Açıktır ki, top herhangi bir yüzeye dokunduğunda friction
uygulanmalıdır. Ancak airfriction
sürekli olarak uygulanmalıdır. Biz de öyle yaptık. applyGravity()
yöntemi her döngüde çalışır, bu nedenle her döngüde mevcut değerinin yüzde ballSpeedVert
0.0001
top herhangi bir yüzeye değdiğinde makeBounceBottom()
ve makeBounceTop()
yöntemleri çalışır. Yani bu yöntemlerde aynı şeyi yaptık, ancak bu sefer friction
ile.
İşleme Eğitimi Adım #3: Raket Oluşturma
Şimdi topun zıplayabilmesi için bir rakete ihtiyacımız var. Raketi kontrol ediyor olmalıyız. Mouse ile kontrol edilebilir hale getirelim. İşte kod:
... color racketColor = color(0); float racketWidth = 100; float racketHeight = 10; ... void gameScreen() { ... drawRacket(); ... } ... void drawRacket(){ fill(racketColor); rectMode(CENTER); rect(mouseX, mouseY, racketWidth, racketHeight); }
Raketin rengini, genişliğini ve yüksekliğini global bir değişken olarak tanımladık, oyun sırasında değişmesini isteyebiliriz. Adından da anlaşılacağı gibi bir drawRacket()
yöntemini uyguladık. rectMode
merkeze ayarlıyoruz, böylece raketimiz imlecimizin merkezine hizalı.
Şimdi raketi yarattığımıza göre, topun üzerinde zıplamasını sağlamalıyız.
... 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; } } } }
Ve işte sonuç:
Yani watchRacketBounce()
'ın yaptığı şey, raketin ve topun çarpışmasını sağlamaktır. Burada kontrol edilmesi gereken iki şey var, o da top ve raketin hem dikey hem de yatay olarak sıralanıp dizilmediğidir. İlk if ifadesi, topun sağ tarafının X koordinatının, raketin sol tarafının X koordinatından (ve tam tersi) büyük olup olmadığını kontrol eder. Eğer öyleyse, ikinci ifade top ile raket arasındaki mesafenin topun yarıçapından küçük veya eşit olup olmadığını kontrol eder (yani çarpışıyorlar demektir) . Dolayısıyla, bu koşullar karşılanırsa, makeBounceBottom()
yöntemi çağrılır ve top raketimizin üzerinde seker (raketin olduğu mouseY
).
mouseY - pmouseY
tarafından hesaplanan değişken overhead
fark ettiniz mi? pmouseX
ve pmouseY
değişkenleri, farenin koordinatlarını önceki karede saklar. Fare çok hızlı hareket edebildiğinden, fare topa doğru yeterince hızlı hareket ediyorsa, çerçeveler arasında top ile raket arasındaki mesafeyi doğru bir şekilde algılayamayabiliriz. Bu nedenle, kareler arasındaki fare koordinatlarının farkını alıyoruz ve mesafeyi tespit ederken bunu dikkate alıyoruz. Fare ne kadar hızlı hareket ederse, o kadar büyük mesafe kabul edilebilir.
Ayrıca başka bir nedenle overhead
kullanıyoruz. overhead
işaretini kontrol ederek farenin hangi yöne hareket ettiğini tespit ederiz. Tepegöz negatifse, fare önceki karede aşağıda bir yerdeydi, bu yüzden faremiz (raket) yukarı hareket ediyor. Bu durumda, topa raketle vurmanın etkisini simüle etmek için topa ekstra bir hız eklemek ve normal sekmeden biraz daha ileri taşımak istiyoruz. Ek overhead
0
küçükse, topun daha yükseğe ve daha hızlı gitmesini sağlamak için onu ballY
ve ballSpeedVert
. Yani raket topa ne kadar hızlı vurursa, o kadar yüksek ve hızlı hareket eder.
İşleme Eğitimi Adım #4: Yatay Hareket ve Topu Kontrol Etme
Bu bölümde topa yatay hareket ekleyeceğiz. Ardından raketimiz ile yatay olarak topu kontrol etmeyi mümkün kılacağız. İşte başlıyoruz:
... // 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); } }
Ve sonuç:
Buradaki fikir, dikey hareket için yaptığımızla aynı. Yatay bir hız değişkeni yarattık, ballSpeedHorizon
. ballX
yatay hız uygulamak ve hava sürtünmesini ortadan kaldırmak için bir yöntem oluşturduk. Topun ekranın sol ve sağ kenarlarına çarpmasını izleyecek olan keepInScreen()
yöntemine iki if ifadesi daha ekledik. Son olarak, soldan ve sağdan sekmeleri işlemek için makeBounceLeft()
ve makeBounceRight()
yöntemlerini yarattık.

Artık oyuna yatay hız eklediğimize göre raket ile topu kontrol etmek istiyoruz. Ünlü Atari oyunu Breakout'ta ve diğer tüm tuğla kırma oyunlarında olduğu gibi, top raket üzerinde çarptığı noktaya göre sola veya sağa gitmelidir. Raketin kenarları topa daha fazla yatay hız vermeli, orta kısım ise herhangi bir etki yaratmamalıdır. Önce kod:
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; ... } } }
Sonuç:
Bu basit satırı watchRacketBounce()
eklemek işi yaptı. Yaptığımız şey ballX - mouseX
ile topun çarptığı noktanın raketin merkezinden uzaklığını belirledik. Daha sonra yatay hız yapıyoruz. Gerçek fark çok fazlaydı, bu yüzden birkaç deneme yaptım ve değerin onda birinin en doğal olduğunu düşündüm.
İşleme Eğitimi Adım #5: Duvarları Oluşturma
Çizimimiz her adımda daha çok bir oyun gibi görünmeye başlıyor. Bu adımda tıpkı Flappy Bird'deki gibi sola doğru hareket eden duvarlar ekleyeceğiz:
... 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); } }
Ve bunun sonucu:
Kod uzun ve ürkütücü görünse de, anlaşılması zor bir şey olmadığına söz veriyorum. İlk fark edilecek şey ArrayList
. ArrayList
ne olduğunu bilmeyenler için, sadece bir Array gibi davranan bir list uygulamasıdır, ancak ona göre bazı avantajları vardır. Yeniden boyutlandırılabilir, list.add( list.add(index)
, list.get(index)
ve list.remove(index)
gibi faydalı yöntemlere sahiptir. Duvar verilerini tamsayı dizileri olarak arraylist içinde tutuyoruz. Dizilerde tuttuğumuz veriler iki duvar arasındaki boşluk içindir. Diziler aşağıdaki değerleri içerir:
[gap wall X, gap wall Y, gap wall width, gap wall height]
Gerçek duvarlar, boşluk duvar değerlerine göre çizilir. Tüm bunların sınıflar kullanılarak daha iyi ve daha temiz bir şekilde ele alınabileceğini unutmayın, ancak Nesne Yönelimli Programlama (OOP) kullanımı bu İşleme öğreticisinin kapsamında olmadığı için, bunu bu şekilde ele alacağız. Duvarları yönetmek için iki temel yöntemimiz var, wallAdder()
ve wallHandler
.
wallAdder()
yöntemi, her wallInterval
milisaniyesinde dizi listesine yeni duvarlar ekler. Son duvarın eklendiği zamanı (milisaniye cinsinden) saklayan global bir lastAddTime
değişkenimiz var. Geçerli milisaniye millis()
eksi son eklenen milisaniye lastAddTime
, wallInterval
aralık değerimizden büyükse, bu, şimdi yeni bir duvar ekleme zamanının geldiği anlamına gelir. Rastgele boşluk değişkenleri daha sonra en üstte tanımlanan global değişkenlere dayalı olarak oluşturulur. Ardından dizi listesine yeni bir duvar (boşluk duvarı verilerini depolayan tamsayı dizisi) eklenir ve lastAddTime
geçerli milisaniyeye millis()
.
wallHandler()
, dizi listesindeki geçerli duvarlar arasında döngü yapar. Ve her döngüdeki her öğe için, dizi listesinin indeks değerine göre wallRemover(i)
, wallMover(i)
ve wallDrawer(i)
'yi çağırır. Bu yöntemler, adlarının önerdiği şeyi yapar. wallDrawer()
, boşluk duvarı verilerine dayalı olarak gerçek duvarları çizer. Dizi listesinden duvar veri dizisini alır ve duvarları gerçekte olması gereken yere çizmek için rect()
yöntemini çağırır. wallMover()
yöntemi, öğeyi dizi listesinden alır, wallSpeed
global değişkenine göre X konumunu değiştirir. Son olarak wallRemover()
, ekran dışında kalan duvarları dizi listesinden kaldırır. Bunu yapmasaydık, İşleme onlara hala ekranda oldukları gibi davranırdı. Ve bu performansta büyük bir kayıp olurdu. Böylece, dizi listesinden bir duvar kaldırıldığında, sonraki döngülerde çizilmez.
Yapılması gereken son zorlu şey, top ve duvarlar arasındaki çarpışmaları tespit etmektir.
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()
yöntemi, her döngüdeki her duvar için çağrılır. Boşluk duvarının koordinatlarını alıyoruz, gerçek duvarların (üst ve alt) koordinatlarını hesaplıyoruz ve topun koordinatlarının duvarlarla çarpışıp çarpışmadığını kontrol ediyoruz.
İşleme Eğitimi Adım #6: Sağlık ve Puan
Artık topun ve duvarların çarpışmalarını tespit edebildiğimize göre oyun mekaniklerine karar verebiliriz. Oyunu biraz ayarladıktan sonra oyunu biraz oynanabilir hale getirmeyi başardım. Ama yine de çok zordu. Oyunla ilgili ilk düşüncem Flappy Bird gibi yapmaktı, top duvarlara değdiğinde oyun biter. Ama sonra oynamanın imkansız olduğunu anladım. İşte düşündüğüm şey:
Topun üstünde bir sağlık çubuğu olmalıdır. Top duvarlara dokunurken sağlığını kaybetmelidir. Bu mantıkla topun duvarlardan geri sektirilmesi mantıklı değil. Yani sağlık 0 olduğunda oyun bitmeli ve ekran üzerinden oyuna geçmeliyiz. İşte başlıyoruz:
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(); } }
Ve işte basit bir koşu:
Topun sağlığını korumak için küresel bir değişken health
yarattık. Ardından, topun üzerine iki dikdörtgen çizen bir drawHealthBar()
yöntemi yarattı. Birincisi temel sağlık çubuğu, diğeri ise mevcut sağlığı gösteren aktif çubuktur. İkincisinin genişliği dinamiktir ve mevcut sağlığımızın sağlık çubuğunun genişliğine oranı olan healthBarWidth*(health/maxHealth)
ile hesaplanır. Son olarak, dolgu renkleri sağlık değerine göre ayarlanır. Son fakat en az değil, puanlar :
... 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); }
Top duvarı geçtiğinde gol atmamız gerekiyordu. Ancak duvar başına maksimum 1 puan eklememiz gerekiyor. Yani, top bir duvarı geçerse geri dönüp tekrar geçerse, başka bir puan eklenmemelidir. Bunu başarmak için, dizi listesindeki boşluk duvarı dizisine başka bir değişken ekledik. Yeni değişken, top henüz o duvarı geçmediyse 0
ve geçtiyse 1
kaydeder. Ardından watchWallCollision()
yöntemini değiştirdik. Top daha önce geçmediği bir duvarı geçtiğinde, score()
yöntemini tetikleyen ve duvarı geçti olarak işaretleyen bir koşul ekledik.
Artık sona çok yaklaştık. Yapılacak son şey, oyunu ekranda click to restart
uygulamaktır. Kullandığımız tüm değişkenleri başlangıç değerlerine ayarlayıp oyunu yeniden başlatmamız gerekiyor. İşte burada:
... 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; }
Biraz daha renk ekleyelim.
işte! Flappy Pong'umuz var!
Tam İşleme oyun kodu burada bulunabilir.
p5.js Kullanarak Oyun Kodunu İşleme Web'e Taşıma
p5.js, Processing programlama dilinin sözdizimine çok benzeyen bir JavaScript kitaplığıdır. Mevcut İşleme kodunu basitçe çalıştırabilen bir kitaplık değildir; bunun yerine p5.js, Processing.js olarak bilinen Processing JavaScript bağlantı noktasına benzer şekilde gerçek JavaScript kodunun yazılmasını gerektirir. Görevimiz, p5.js API'sini kullanarak İşleme kodunu JavaScript'e dönüştürmektir. Kitaplığın bir dizi işlevi ve İşleme'ye benzer bir sözdizimi vardır ve bunların JavaScript'te çalışmasını sağlamak için kodumuzda belirli değişiklikler yapmamız gerekir - ancak hem İşleme hem de JavaScript Java ile benzerlikler paylaştığından, göründüğünden daha az bir sıçramadır. . JavaScript geliştiricisi olmasanız bile, değişiklikler çok önemsizdir ve gayet iyi takip edebilmeniz gerekir.
Öncelikle basit bir index.html
oluşturup p5.min.js
gerekiyor. Ayrıca, dönüştürülmüş kodumuzu barındıracak flappy_pong.js
adlı başka bir dosya oluşturmamız gerekiyor.
<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>
Kodu dönüştürürken stratejimiz, tüm flappy_pong.js
kopyalayıp yapıştırmak ve ardından tüm değişiklikleri yapmak olmalıdır. Ben de öyle yaptım. İşte kodu güncellemek için attığım adımlar:
Javascript yazılmamış bir dildir (
int
vefloat
gibi tür bildirimleri yoktur). Bu nedenle, tüm tür bildirimlerinivar
olarak değiştirmemiz gerekiyor.Javascript'te
void
yoktur. Hepsinifunction
göre değiştirmeliyiz.İşlev imzalarından argümanların tür bildirimlerini kaldırmamız gerekiyor. (ör.
void wallMover(var index) {
function wallMover(index) {
)JavaScript'te
ArrayList
yok. Ama aynı şeyi JavaScript dizilerini kullanarak da başarabiliriz. Aşağıdaki değişiklikleri yapıyoruz:-
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()
'denwalls.length
-
Dizinin bildirimini değiştirin
var randWall = {width, randY, wallWidth, randHeight, 0};
var randWall = [width, randY, wallWidth, randHeight, 0];
Tüm
public
anahtar kelimeleri kaldırın.color()
,setup()
çağrısından önce tanımlanmayacağından, tümcolor(0)
bildirimlerinifunction setup()
taşıyın.size(500, 500);
createCanvas(500, 500);
function gameScreen(){
function gamePlayScreen(){
gibi başka bir şeyle yeniden adlandırın çünkü zatengameScreen
adında bir global değişkenimiz var. Processing ile çalışırken biri fonksiyon, diğeri iseint
değişkeniydi. Ancak JavaScript, türlenmemiş oldukları için bunları karıştırır.Aynı şey
score()
için de geçerli.addScore()
olarak yeniden adlandırdım.
Bu İşleme eğitimindeki her şeyi kapsayan tam JavaScript kodu burada bulunabilir.
Oyun Kodu İşleniyor: Siz de Yapabilirsiniz
Bu İşleme eğitiminde çok basit bir oyun nasıl yapılır onu anlatmaya çalıştım. Ancak bu yazıda yaptıklarımız buzdağının sadece görünen kısmı. Processing programlama dili ile hemen hemen her şey başarılabilir. Bence, hayal ettiğiniz şeyi programlamak için en iyi araçtır. Bu İşleme eğitimindeki asıl amacım İşleme öğretmek ve bir oyun oluşturmak yerine programlamanın o kadar da zor olmadığını kanıtlamaktı. Kendi oyununuzu oluşturmak sadece bir rüya değildir. Biraz çaba ve hevesle yapabileceğinizi göstermek istedim. Umarım bu iki makale herkese programlamaya bir şans vermeleri için ilham verir.