Guia definitivo para a linguagem de processamento Parte II: Construindo um jogo simples
Publicados: 2022-03-11Esta é a segunda parte do guia definitivo para a linguagem Processing. Na primeira parte, dei um passo a passo básico da linguagem Processing. O próximo passo para você aprender Processamento é simplesmente mais programação prática.
Neste artigo, mostrarei como usar o Processing para implementar seu próprio jogo, passo a passo. Cada etapa será explicada em detalhes. Então, vamos portar o jogo para a web.
Antes de começarmos o tutorial de Processamento, aqui está o código do exercício do logotipo do DVD da parte anterior. Se você tiver alguma dúvida, não deixe de deixar um comentário.
Tutorial de Processamento: Um Jogo Simples
O jogo que vamos construir neste tutorial de Processamento é uma espécie de combinação de Flappy Bird, Pong e Brick Breaker. A razão pela qual escolhi um jogo como este é que ele tem a maioria dos conceitos com os quais os iniciantes lutam ao aprender o desenvolvimento de jogos. Isso é baseado na minha experiência de quando eu era assistente de ensino, ajudando novos programadores a aprender a usar o Processing. Esses conceitos incluem gravidade, colisões, manutenção de pontuações, manipulação de diferentes telas e interações teclado/mouse. Flappy Pong tem todos eles nele.
Jogue agora!
Sem usar conceitos de programação orientada a objetos (OOP), não é fácil construir jogos complexos, como jogos de plataforma com vários níveis, jogadores, entidades etc. À medida que avançamos, você verá como o código fica complicado muito rápido. Fiz o meu melhor para manter este tutorial de Processamento organizado e simples.
Eu aconselho você a seguir o artigo, pegar o código completo, jogar com ele por conta própria, começar a pensar em seu próprio jogo o mais rápido possível e começar a implementá-lo.
Então vamos começar.
Construindo Flappy Pong
Etapa 1 do tutorial de processamento: inicializar e lidar com telas diferentes
O primeiro passo é inicializar nosso projeto. Para começar, vamos escrever nossa configuração e desenhar blocos como de costume, nada extravagante ou novo. Em seguida, lidaremos com diferentes telas (tela inicial, tela de jogo, tela de game over etc.). Então surge a pergunta, como fazemos para que o Processing mostre a página correta no momento correto?
A realização desta tarefa é bastante simples. Teremos uma variável global que armazena as informações da tela atualmente ativa. Em seguida, desenhamos o conteúdo da tela correta dependendo da variável. No bloco draw, teremos uma instrução if que verifica a variável e exibe o conteúdo da tela de acordo. Sempre que quisermos mudar a tela, mudaremos essa variável para o identificador da tela que queremos que ela exiba. Com isso dito, aqui está como nosso código de esqueleto se parece:
/********* 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; }Isso pode parecer assustador no começo, mas tudo o que fizemos foi construir a estrutura básica e separar diferentes partes com blocos de comentários.
Como você pode ver, definimos um método diferente para cada tela a ser exibida. Em nosso bloco de desenho, simplesmente verificamos o valor de nossa variável gameScreen e chamamos o método correspondente.
Na parte void mousePressed(){...} , estamos ouvindo os cliques do mouse e se a tela ativa for 0, a tela inicial, chamamos o método startGame() que inicia o jogo como esperado. A primeira linha deste método altera a variável gameScreen para 1, a tela do jogo.
Se isso for entendido, o próximo passo é implementar nossa tela inicial. Para fazer isso, estaremos editando o método initScreen() . Aqui vai:
void initScreen() { background(0); textAlign(CENTER); text("Click to start", height/2, width/2); } Agora nossa tela inicial tem um fundo preto e um texto simples, “Clique para iniciar”, localizado no meio e alinhado ao centro. Mas quando clicamos, nada acontece. Ainda não especificamos nenhum conteúdo para nossa tela de jogo. O método gameScreen() não tem nada nele, então não estamos cobrindo o conteúdo anterior extraído da última tela (o texto) tendo background() como a primeira linha do desenho. É por isso que o texto ainda está lá, mesmo que a linha text() não esteja mais sendo chamada (assim como o exemplo da bola em movimento da última parte que estava deixando um rastro para trás) . O fundo ainda é preto pelo mesmo motivo. Então vamos em frente e começar a implementar a tela do jogo.
void gameScreen() { background(255); }Após essa alteração, você notará que o fundo fica branco e o texto desaparece.
Tutorial de Processamento Etapa #2: Criando a Bola e Implementando a Gravidade
Agora, vamos começar a trabalhar na tela do jogo. Vamos primeiro criar nossa bola. Devemos definir variáveis para suas coordenadas, cor e tamanho porque podemos querer alterar esses valores mais tarde. Por exemplo, se queremos aumentar o tamanho da bola à medida que o jogador pontua mais alto, para que o jogo seja mais difícil. Precisaremos alterar seu tamanho, portanto, deve ser uma variável. Também definiremos a velocidade da bola, depois de implementarmos a gravidade.
Primeiro, vamos adicionar o seguinte:
... 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); } Definimos as coordenadas como variáveis globais, criamos um método que desenha a bola, chamado do método gameScreen . A única coisa a prestar atenção aqui é que inicializamos as coordenadas, mas as definimos em setup() . A razão pela qual fizemos isso é que queríamos que a bola começasse a um quarto da esquerda e um quinto de cima. Não há nenhuma razão específica para querermos isso, mas esse é um bom ponto para a bola começar. Portanto, precisávamos obter a width e a height do esboço dinamicamente. O tamanho do sketch é definido em setup() , após a primeira linha. width e height não são definidas antes da execução de setup() , é por isso que não poderíamos conseguir isso se definimos as variáveis no topo.
Gravidade
Agora, implementar a gravidade é realmente a parte mais fácil. Existem apenas alguns truques. Aqui está a implementação primeiro:
... 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); } }E o resultado é:
Segure seus cavalos, físico. Eu sei que não é assim que a gravidade funciona na vida real. Em vez disso, isso é mais um processo de animação do que qualquer outra coisa. A variável que definimos como gravity é apenas um valor numérico - um float para que possamos usar valores decimais, não apenas inteiros - que adicionamos a ballSpeedVert em cada loop. E ballSpeedVert é a velocidade vertical da bola, que é adicionada à coordenada Y da bola ( ballY ) em cada loop. Observamos as coordenadas da bola e garantimos que ela permaneça na tela. Se não o fizéssemos, a bola cairia ao infinito. Por enquanto, nossa bola só se move na vertical. Então, observamos os limites do piso e do teto da tela. Com o método keepInScreen() , verificamos se ballY ( + o raio) é menor que height , e da mesma forma ballY ( - o raio) é maior que 0 . Se as condições não atenderem, fazemos a bola quicar (de baixo ou de cima) com makeBounceBottom() e makeBounceTop() . Para fazer a bola quicar, simplesmente movemos a bola para o local exato onde ela deveria quicar e multiplicamos a velocidade vertical ( ballSpeedVert ) por -1 (multiplicando por -1 muda o sinal). Quando o valor da velocidade tem um sinal de menos, adicionando a coordenada Y a velocidade se torna ballY + (-ballSpeedVert) , que é ballY - ballSpeedVert . Assim, a bola muda imediatamente de direção com a mesma velocidade. Então, à medida que adicionamos gravity a ballSpeedVert e ballSpeedVert tem um valor negativo, ele começa a se aproximar de 0 , eventualmente se torna 0 e começa a aumentar novamente. Isso faz a bola subir, subir mais devagar, parar e começar a cair.
No entanto, há um problema com nosso processo de animação - a bola continua quicando. Se este fosse um cenário do mundo real, a bola teria enfrentado resistência do ar e atrito toda vez que tocasse uma superfície. Esse é o comportamento que queremos para o processo de animação do nosso jogo, então implementar isso é fácil. Adicionamos o seguinte:
... 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); }E agora nosso processo de animação produz isso:
Como o nome sugere, o friction é o atrito da superfície e o airfriction do ar é o atrito do ar. Então, obviamente, o friction tem que ser aplicado cada vez que a bola toca qualquer superfície. a airfriction do ar, no entanto, deve ser aplicada constantemente. Então foi isso que fizemos. O método applyGravity() é executado em cada loop, então retiramos 0.0001 por cento de seu valor atual de ballSpeedVert em cada loop. makeBounceBottom() e makeBounceTop() são executados quando a bola toca qualquer superfície. Então, nesses métodos, fizemos a mesma coisa, só que desta vez com friction .
Tutorial de Processamento Etapa #3: Criando a Raquete
Agora precisamos de uma raquete para a bola quicar. Deveríamos estar controlando a raquete. Vamos torná-lo controlável com o mouse. Aqui está o código:
... color racketColor = color(0); float racketWidth = 100; float racketHeight = 10; ... void gameScreen() { ... drawRacket(); ... } ... void drawRacket(){ fill(racketColor); rectMode(CENTER); rect(mouseX, mouseY, racketWidth, racketHeight); } Definimos a cor, largura e altura da raquete como uma variável global, podemos querer que elas mudem durante o jogo. Implementamos um método drawRacket() que faz o que seu nome sugere. Colocamos o rectMode no centro, para que nossa raquete fique alinhada ao centro do nosso cursor.
Agora que criamos a raquete, temos que fazer a bola quicar nela.
... 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; } } } }E aqui está o resultado:
Então, o que watchRacketBounce() faz é garantir que a raquete e a bola colidam. Há duas coisas para verificar aqui, que é se a bola e a raquete estão alinhadas vertical e horizontalmente. A primeira instrução if verifica se a coordenada X do lado direito da bola é maior que a coordenada X do lado esquerdo da raquete (e vice-versa). Se for, a segunda instrução verifica se a distância entre a bola e a raquete é menor ou igual ao raio da bola (o que significa que estão colidindo) . Então, se essas condições forem atendidas, o método makeBounceBottom() é chamado e a bola quica em nossa raquete (em mouseY , onde a raquete está).
Você notou a overhead variável que é calculada por mouseY - pmouseY ? As variáveis pmouseX e pmouseY armazenam as coordenadas do mouse no quadro anterior. Como o mouse pode se mover muito rápido, há uma boa chance de não detectarmos a distância entre a bola e a raquete corretamente entre os quadros se o mouse estiver se movendo em direção à bola com rapidez suficiente. Então, tomamos a diferença das coordenadas do mouse entre os quadros e levamos isso em consideração ao detectar a distância. Quanto mais rápido o mouse estiver se movendo, maior será a distância aceitável.
Também usamos overhead por outro motivo. Detectamos para que lado o mouse está se movendo verificando o sinal de overhead . Se a sobrecarga for negativa, o mouse estava em algum lugar abaixo do quadro anterior, então nosso mouse (raquete) está se movendo para cima. Nesse caso, queremos adicionar uma velocidade extra à bola e movê-la um pouco além do salto normal para simular o efeito de bater na bola com a raquete. Se o overhead for menor que 0 , nós o adicionamos a ballY e ballSpeedVert para fazer a bola subir mais alto e mais rápido. Portanto, quanto mais rápido a raquete atingir a bola, mais alto e mais rápido ela subirá.
Tutorial de Processamento Etapa #4: Movimento Horizontal e Controle da Bola
Nesta seção, adicionaremos movimento horizontal à bola. Então, possibilitaremos controlar a bola horizontalmente com nossa raquete. Aqui vamos nós:
... // 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); } }E o resultado é:
A ideia aqui é a mesma que fizemos para o movimento vertical. Criamos uma variável de velocidade horizontal, ballSpeedHorizon . Criamos um método para aplicar velocidade horizontal à ballX e eliminar o atrito do ar. Adicionamos mais duas instruções if ao método keepInScreen() que observará a bola para acertar as bordas esquerda e direita da tela. Finalmente, criamos makeBounceLeft() e makeBounceRight() para lidar com os saltos da esquerda e da direita.

Agora que adicionamos velocidade horizontal ao jogo, queremos controlar a bola com a raquete. Como no famoso jogo do Atari Breakout e em todos os outros jogos de quebra de tijolos, a bola deve ir para a esquerda ou para a direita de acordo com o ponto da raquete que atinge. As bordas da raquete devem dar à bola mais velocidade horizontal, enquanto o meio não deve ter nenhum efeito. Codifique primeiro:
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; ... } } }O resultado é:
Adicionar essa linha simples ao watchRacketBounce() fez o trabalho. O que fizemos foi determinar a distância do ponto que a bola atinge do centro da raquete com ballX - mouseX . Então, fazemos a velocidade horizontal. A diferença real era demais, então tentei algumas vezes e percebi que um décimo do valor parece o mais natural.
Tutorial de Processamento Etapa #5: Criando as Paredes
Nosso esboço está começando a parecer mais um jogo a cada passo. Nesta etapa, adicionaremos paredes se movendo para a esquerda, assim como no 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); } }E isso resultou em:
Mesmo que o código pareça longo e intimidador, prometo que não há nada difícil de entender. A primeira coisa a notar é ArrayList . Para quem não sabe o que é um ArrayList , é apenas uma implementação de list que funciona como um Array, mas tem algumas vantagens sobre ele. É redimensionável, possui métodos úteis como list.add(index) , list.get(index) e list.remove(index) . Mantemos os dados da parede como arrays inteiros dentro da arraylist. Os dados que mantemos nas matrizes são para o espaço entre duas paredes. As matrizes contêm os seguintes valores:
[gap wall X, gap wall Y, gap wall width, gap wall height] As paredes reais são desenhadas com base nos valores das paredes de folga. Observe que tudo isso poderia ser tratado melhor e mais limpo usando classes, mas como o uso de Programação Orientada a Objetos (POO) não está no escopo deste tutorial de Processamento, é assim que vamos lidar com isso. Temos dois métodos básicos para gerenciar as paredes, wallAdder() e wallHandler .
O método wallAdder() simplesmente adiciona novas paredes em cada milissegundo wallInterval ao arraylist. Temos uma variável global lastAddTime que armazena a hora em que a última parede foi adicionada (em milissegundos) . Se o milissegundo atual millis() menos o último milissegundo adicionado lastAddTime for maior que nosso valor de intervalo wallInterval , significa que agora é hora de adicionar uma nova parede. As variáveis de intervalo aleatório são então geradas com base nas variáveis globais definidas no topo. Em seguida, uma nova parede (matriz de inteiros que armazena os dados da parede de intervalo) é adicionada à lista de matrizes e o lastAddTime é definido como o milissegundo milissegundos atual millis() .
wallHandler() percorre as paredes atuais que estão na lista de matrizes. E para cada item em cada loop, ele chama wallRemover(i) , wallMover(i) e wallDrawer(i) pelo valor do índice do arraylist. Esses métodos fazem o que seu nome sugere. wallDrawer() desenha as paredes reais com base nos dados da parede do intervalo. Ele pega a matriz de dados da parede da lista de matrizes e chama o método rect() para desenhar as paredes para onde elas deveriam estar. O método wallMover() pega o elemento da lista de matrizes, altera sua localização X com base na variável global wallSpeed . Finalmente, wallRemover() remove as paredes da lista de arrays que estão fora da tela. Se não fizéssemos isso, o Processing os trataria como ainda estão na tela. E isso teria sido uma enorme perda de desempenho. Portanto, quando uma parede é removida da lista de matrizes, ela não é desenhada nos loops subsequentes.
O último desafio que resta a fazer é detectar colisões entre a bola e as paredes.
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() é chamado para cada parede em cada loop. Pegamos as coordenadas da parede do vão, calculamos as coordenadas das paredes reais (superior e inferior) e verificamos se as coordenadas da bola colidem com as paredes.
Etapa 6 do tutorial de processamento: integridade e pontuação
Agora que podemos detectar as colisões da bola e as paredes, podemos decidir sobre a mecânica do jogo. Depois de alguns ajustes no jogo, consegui torná-lo um pouco jogável. Mas mesmo assim foi muito difícil. Meu primeiro pensamento sobre o jogo foi fazê-lo como Flappy Bird, quando a bola toca as paredes, o jogo termina. Mas então percebi que seria impossível jogar. Então aqui está o que eu pensei:
Deve haver uma barra de saúde em cima da bola. A bola deve perder saúde enquanto estiver tocando as paredes. Com essa lógica, não faz sentido fazer a bola quicar nas paredes. Então, quando a saúde for 0, o jogo deve terminar e devemos mudar para a tela de fim de jogo. Aqui vamos nos:
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(); } }E aqui está uma execução simples:
Criamos uma variável global health para manter a saúde da bola. E então criou um método drawHealthBar() que desenha dois retângulos em cima da bola. A primeira é a barra de saúde básica, a outra é a ativa que mostra a saúde atual. A largura do segundo é dinâmica e é calculada com healthBarWidth*(health/maxHealth) , a proporção de nossa saúde atual em relação à largura da barra de saúde. Por fim, as cores de preenchimento são definidas de acordo com o valor da integridade. Por último, mas não menos importante, pontuações :
... 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); } Precisávamos marcar quando a bola passasse por uma parede. Mas precisamos adicionar no máximo 1 pontuação por parede. Ou seja, se a bola passar por uma parede e voltar e passar novamente, outra pontuação não deve ser adicionada. Para conseguir isso, adicionamos outra variável ao array gap wall dentro do arraylist. A nova variável armazena 0 se a bola ainda não passou por aquela parede e 1 se passou. Em seguida, modificamos o método watchWallCollision() . Adicionamos uma condição que dispara o método score() e marca a parede como passada quando a bola passa por uma parede que não passou antes.
Estamos agora muito perto do fim. A última coisa a fazer é implementar click to restart na tela de game over. Precisamos definir todas as variáveis que usamos para seu valor inicial e reiniciar o jogo. Aqui está:
... 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; }Vamos adicionar mais cores.
Voilá! Temos Flappy Pong!
O código completo do jogo Processing pode ser encontrado aqui.
Portando o código do jogo de processamento para a Web usando p5.js
p5.js é uma biblioteca JavaScript com uma sintaxe muito semelhante à da linguagem de programação Processing. Não é uma biblioteca capaz de simplesmente executar o código Processing existente; em vez disso, o p5.js requer a escrita de código JavaScript real—semelhante à porta JavaScript do Processing conhecida como Processing.js. Nossa tarefa é converter o código do Processing em JavaScript usando a API p5.js. A biblioteca tem um conjunto de funções e uma sintaxe semelhante ao Processing, e temos que fazer algumas alterações em nosso código para fazê-los funcionar em JavaScript - mas como Processing e JavaScript compartilham semelhanças com Java, é menos um salto do que parece . Mesmo que você não seja um desenvolvedor JavaScript, as mudanças são muito triviais e você deve conseguir acompanhar bem.
Antes de tudo, precisamos criar um index.html simples e adicionar p5.min.js ao nosso cabeçalho. Também precisamos criar outro arquivo chamado flappy_pong.js , que abrigará nosso código convertido.
<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> Nossa estratégia ao converter o código deve ser copiar e colar todo o nosso código em flappy_pong.js e depois fazer todas as alterações. E é isso que eu fiz. E aqui estão os passos que tomei para atualizar o código:
Javascript é uma linguagem não tipada (não há declarações de tipo como
intefloat). Portanto, precisamos alterar todas as declarações de tipo paravar.Não há
voidem Javascript. Devemos mudar tudo parafunction.Precisamos remover as declarações de tipo de argumentos de assinaturas de função. (ou seja
void wallMover(var index) {tofunction wallMover(index) {)Não há
ArrayListem JavaScript. Mas podemos conseguir a mesma coisa usando arrays JavaScript. Fazemos as seguintes alterações:-
ArrayList<int[]> walls = new ArrayList<int[]>();paravar walls = []; -
walls.clear();parawalls = []; -
walls.add(randWall);parawalls.push(randWall); -
walls.remove(index);parawalls.splice(index,1); -
walls.get(index);parawalls[index] -
walls.size()parawalls.length
-
Altere a declaração do array
var randWall = {width, randY, wallWidth, randHeight, 0};tovar randWall = [width, randY, wallWidth, randHeight, 0];Remova todas as palavras-chave
public.Mova todas as declarações de
color(0)para afunction setup()porquecolor()não será definida antes da chamada desetup().Alterar
size(500, 500);paracreateCanvas(500, 500);Renomeie
function gameScreen(){para algo comofunction gamePlayScreen(){porque já temos uma variável global chamadagameScreen. Quando estávamos trabalhando com Processing, uma era uma função e a outra uma variávelint. Mas o JavaScript os confunde, pois não são tipados.A mesma coisa vale para
score(). Eu o renomeei paraaddScore().
O código JavaScript completo que cobre tudo neste tutorial de Processamento pode ser encontrado aqui.
Processando o código do jogo: você também pode fazer isso
Neste tutorial de Processing, tentei explicar como construir um jogo muito simples. No entanto, o que fizemos neste artigo é apenas a ponta do iceberg. Com a linguagem de programação Processing, praticamente qualquer coisa pode ser alcançada. Na minha opinião, é a melhor ferramenta para programar o que você está imaginando. Minha intenção real com este tutorial de Processing foi ao invés de ensinar Processing e construir um jogo, provar que programar não é tão difícil. Construir seu próprio jogo não é apenas um sonho. Eu queria mostrar a você que com um pouco de esforço e entusiasmo, você pode fazer isso. Eu realmente espero que esses dois artigos inspirem todos a dar uma chance à programação.
