Processing语言的最终指南第二部分:构建简单的游戏

本文概述

这是Processing语言最终指南的第二部分。在第一部分中, 我给出了基本的Processing语言演练。你学习下一步的过程就是更多的动手编程。

在本文中, 我将逐步向你展示如何使用Processing来实现自己的游戏。将详细说明每个步骤。然后, 我们将游戏移植到网络上。

使用Processing语言构建一个简单的游戏。

在开始Processing教程之前, 这是上一部分DVD徽标练习的代码。如有任何疑问, 请务必发表评论。

Processing教程:一个简单的游戏

我们将在此Processing教程中构建的游戏是Flappy Bird, Pong和Brick Breaker的组合。我之所以选择这样的游戏, 是因为它具有初学者在学习游戏开发时会遇到的大多数概念。这是基于我担任助教以来的经验, 帮助新程序员学习如何使用Processing。这些概念包括重力, 碰撞, 保持分数, Processing不同的屏幕以及键盘/鼠标交互。 Flappy Pong拥有所有这些。

立即玩游戏!

如果不使用面向对象编程(OOP)的概念, 就很难构建复杂的游戏, 例如具有多个级别, 玩家, 实体等的平台游戏。随着我们的前进, 你将看到代码如何迅速变得复杂。我尽我所能使此”Processing”教程井井有条且简单。

我建议你阅读本文, 获取完整的代码, 自己使用它, 开始尽快考虑自己的游戏, 并开始实施它。

让我们开始吧。

构建蓬松乒乓球

Processing教程步骤1:初始化和Processing不同的屏幕

第一步是初始化我们的项目。对于初学者, 我们将像往常一样编写设置和绘制块, 没有新奇或幻想。然后, 我们将Processing不同的屏幕(初始屏幕, 游戏屏幕, 游戏上方屏幕等)。因此出现了问题, 我们如何使Processing在正确的时间显示正确的页面?

完成此任务非常简单。我们将有一个全局变量, 用于存储当前活动屏幕的信息。然后, 我们根据变量绘制正确屏幕的内容。在绘制块中, 我们将具有一个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);
}

进行此更改后, 你会注意到背景变成白色并且文本消失。

Processing教程步骤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()中定义了它们。我们这样做的原因是我们希望球从左四分之一开始, 从顶部五分之一开始。我们并不需要特别的理由, 但这是开始发球的好方法。因此, 我们需要动态获取草图的宽度和高度。草图大小在第一行之后的setup()中定义。在运行setup()之前未设置width和height, 这就是为什么如果我们在顶部定义变量则无法实现这一点。

重力

现在实现重力实际上是容易的部分。只有几招。首先是实现:

...
float gravity = 1;
float ballSpeedVert = 0;
...
void gameScreen() {
  ...
  applyGravity();
  keepInScreen();
}
...
void applyGravity() {
  ballSpeedVert += gravity;
  ballY += ballSpeedVert;
}
void makeBounceBottom(float surface) {
  ballY = surface-(ballSize/2);
  ballSpeedVert*=-1;
}
void makeBounceTop(float surface) {
  ballY = surface+(ballSize/2);
  ballSpeedVert*=-1;
}
// keep ball in the screen
void keepInScreen() {
  // ball hits floor
  if (ballY+(ballSize/2) > height) { 
    makeBounceBottom(height);
  }
  // ball hits ceiling
  if (ballY-(ballSize/2) < 0) {
    makeBounceTop(0);
  }
}

结果是:

用假重力无限地弹跳的球。

抱着你的马, 物理学家。我知道这不是重力在现实生活中的工作方式。相反, 这更多的是动画过程。我们定义为重力的变量只是一个数字值(一个浮点数, 以便我们可以使用十进制值, 而不仅仅是整数), 我们在每个循环中将其添加到ballSpeedVert中。 ballSpeedVert是球的垂直速度, 它在每个循环中被加到球的Y坐标(ballY)上。我们观察球的坐标并确保其停留在屏幕上。如果我们不这样做, 球会掉到无穷远。目前, 我们的球只垂直移动。因此, 我们观察屏幕的地板和天花板边界。使用keepInScreen()方法, 我们检查ballY(+半径)是否小于高度, 并且类似地ballY(-半径)大于0。如果不满足条件, 我们使球反弹(从底部开始)或顶部)与makeBounceBottom()和makeBounceTop()方法。要使球弹起, 我们只需将球移到必须弹起的确切位置, 然后将垂直速度(ballSpeedVert)乘以-1(乘以-1即可更改符号)。当速度值带有负号时, 将Y坐标加起来, 速度将变为ballY +(-ballSpeedVert), 即ballY-ballSpeedVert。因此, 球立即以相同的速度改变方向。然后, 当我们向ballSpeedVert中添加重力并且ballSpeedVert具有负值时, 它开始接近0, 最终变为0, 并再次开始增加。这会使球上升, 上升变慢, 停止并开始下降。

球在球拍上无限地弹跳。

但是, 我们的动画Processing过程存在问题-球不断弹跳。如果这是现实情况, 则每次接触表面时, 球都会面临空气阻力和摩擦。这就是我们在游戏动画过程中想要的行为, 因此实现起来很容易。我们添加以下内容:

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

现在, 我们的动画过程产生了以下结果:

球反弹但由于摩擦而停止。

顾名思义, 摩擦是表面摩擦, 而空气摩擦是空气的摩擦。因此很明显, 每次球接触任何表面时都必须施加摩擦。然而, 空中摩擦必须不断应用。这就是我们所做的。 applyGravity()方法在每个循环上运行, 因此我们在每个循环上从ballSpeedVert中减去其当前值的0.0001%。当球接触任何表面时, makeBounceBottom()和makeBounceTop()方法将运行。因此, 在这些方法中, 我们只是做了一次相同的事情, 只是这次只是摩擦。

Processing教程步骤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设置为居中, 因此我们的球拍与光标的中心对齐。

现在我们创建了球拍, 我们必须在球上弹跳。

...
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()要做的是确保球拍和球发生碰撞。这里有两件事要检查, 即球和球拍在垂直和水平方向上是否都对齐。第一个if语句检查球右侧的X坐标是否大于球拍左侧的X坐标(反之亦然)。如果是, 则第二条语句检查球和球拍之间的距离是否小于或等于球的半径(这意味着它们正在碰撞)。因此, 如果满足这些条件, 则将调用makeBounceBottom()方法, 并且球会在我们的球拍上反弹(在球拍所在的mouseY处)。

你是否注意到mouseY-pmouseY计算出的可变开销? pmouseX和pmouseY变量将鼠标的坐标存储在前一帧。由于鼠标可以非常快速地移动, 因此如果鼠标足够快地朝着球移动, 我们很有可能无法正确检测到帧之间球与球拍之间的距离。因此, 我们考虑了帧之间鼠标坐标的差异, 并在检测距离时将其考虑在内。鼠标移动得越快, 可接受的距离就越大。

我们还出于其他原因使用开销。我们通过检查开销的迹象来检测鼠标的移动方式。如果开销为负, 则鼠标位于前一帧的下方, 因此我们的鼠标(球拍)正在向上移动。在这种情况下, 我们希望为球增加一个额外的速度, 并将其移动到比常规弹跳稍远的位置, 以模拟用球拍击球的效果。如果开销小于0, 则将其添加到ballY和ballSpeedVert中, 以使球变得更高, 更快。因此, 球拍击球的速度越快, 它上升的速度就越高。

Processing教程步骤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);
  }
}

结果是:

现在还有一个水平反弹的球。

这里的想法与我们进行垂直运动的想法相同。我们创建了一个水平速度变量ballSpeedHorizo​​n。我们创建了一种将水平速度应用于ballX并消除空气摩擦的方法。我们向keepInScreen()方法添加了两个if语句, 该语句将观察球触及屏幕的左边缘和右边缘。最后, 我们创建了makeBounceLeft()和makeBounceRight()方法来Processing左右反弹。

现在我们为游戏增加了水平速度, 我们想用球拍控制球。就像在著名的Atari游戏Breakout和其他所有打砖块游戏中一样, 球应该根据球拍击中的点向左或向右移动。球拍的边缘应使球具有更高的水平速度, 而中间不应该有任何作用。代码优先:

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确定球击中球拍中心的点的距离。然后, 将其设为水平速度。实际的差异太大, 因此我尝试了一下, 并认为十分之一的值是最自然的。

Processing教程步骤5:创建墙

我们的草图开始看起来更像游戏中的每一步。在这一步中, 我们将添加向左移动的墙, 就像在《飞扬的小鸟》中一样:

...
int wallSpeed = 5;
int wallInterval = 1000;
float lastAddTime = 0;
int minGapHeight = 200;
int maxGapHeight = 300;
int wallWidth = 80;
color wallColors = color(0);
// This arraylist stores data of the gaps between the walls. Actuals walls are drawn accordingly.
// [gapWallX, gapWallY, gapWallWidth, gapWallHeight]
ArrayList<int[]> walls = new ArrayList<int[]>();
...
void gameScreen() {
  ...
  wallAdder();
  wallHandler();
}
...
void wallAdder() {
  if (millis()-lastAddTime > wallInterval) {
    int randHeight = round(random(minGapHeight, maxGapHeight));
    int randY = round(random(0, height-randHeight));
    // {gapWallX, gapWallY, gapWallWidth, gapWallHeight}
    int[] randWall = {width, randY, wallWidth, randHeight}; 
    walls.add(randWall);
    lastAddTime = millis();
  }
}
void wallHandler() {
  for (int i = 0; i < walls.size(); i++) {
    wallRemover(i);
    wallMover(i);
    wallDrawer(i);
  }
}
void wallDrawer(int index) {
  int[] wall = walls.get(index);
  // get gap wall settings 
  int gapWallX = wall[0];
  int gapWallY = wall[1];
  int gapWallWidth = wall[2];
  int gapWallHeight = wall[3];
  // draw actual walls
  rectMode(CORNER);
  fill(wallColors);
  rect(gapWallX, 0, gapWallWidth, gapWallY);
  rect(gapWallX, gapWallY+gapWallHeight, gapWallWidth, height-(gapWallY+gapWallHeight));
}
void wallMover(int index) {
  int[] wall = walls.get(index);
  wall[0] -= wallSpeed;
}
void wallRemover(int index) {
  int[] wall = walls.get(index);
  if (wall[0]+wall[2] <= 0) {
    walls.remove(index);
  }
}

结果是:

弹跳通过墙壁的水平球。

即使代码看起来冗长而令人生畏, 我保证没有什么很难理解的。首先要注意的是ArrayList。对于那些不知道ArrayList是什么的人来说, 它只是list的实现, 就像Array一样, 但是相对于它, 它有一些优势。它是可调整大小的, 具有有用的方法, 如list.add(index), list.get(index)和list.remove(index)。我们将墙数据保留为arraylist中的整数数组。我们保留在数组中的数据用于两堵墙之间的间隙。数组包含以下值:

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

实际壁是根据间隙壁值绘制的。请注意, 使用类可以更好, 更简洁地Processing所有这些问题, 但是由于”面向对象编程”(OOP)的使用不在本”Processing”教程的范围之内, 因此我们将采用这种方式进行Processing。我们有两种管理墙壁的基本方法, wallAdder()和wallHandler。

wallAdder()方法仅在每个wallInterval毫秒内将新的墙添加到arraylist中。我们有一个全局变量lastAddTime, 它存储添加最后一堵墙的时间(以毫秒为单位)。如果当前毫秒millis()减去最后添加的毫秒lastAddTime大于我们的间隔值wallInterval, 则意味着现在该添加一个新的墙了。然后根据最顶部定义的全局变量生成随机间隙变量。然后将新的墙(存储间隙墙数据的整数数组)添加到arraylist中, 并将lastAddTime设置为当前毫秒millis()。

wallHandler()循环遍历arraylist中的当前墙。对于每个循环中的每个项目, 它通过arraylist的索引值调用wallRemover(i), wallMover(i)和wallDrawer(i)。这些方法如其名称所示。 wallDrawer()根据间隙壁数据绘制实际的壁。它从arraylist中获取墙数据数组, 并调用rect()方法将墙绘制到实际位置。 wallMover()方法从arraylist中获取元素, 并根据wallSpeed全局变量更改其X位置。最后, wallRemover()从arraylist中移除屏幕之外的墙。如果我们不这样做, 那么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()方法在每个循环中的每个墙都被调用。我们获取间隙壁的坐标, 计算实际壁(顶部和底部)的坐标, 然后检查球的坐标是否与壁碰撞。

Processing教程步骤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();
  }
}

这是一个简单的运行:

带有健康棒的球在水平面上弹跳,每当与墙壁碰撞时就会失去健康。

我们创建了一个全局变量健康状况, 以保持健康状态。然后创建方法drawHealthBar()在球的顶部绘制两个矩形。第一个是基本运行状况栏, 第二个是显示当前运行状况的活动栏。第二个控件的宽度是动态的, 并使用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()方法并将墙标记为已通过。

我们现在快结束了。最后要做的是在屏幕上的游戏上单击重新启动。我们需要将所有使用过的变量设置为其初始值, 然后重新启动游戏。这里是:

...
public void mousePressed() {
  ...
  if (gameScreen==2){
    restart();
  }
}
...
void restart() {
  score = 0;
  health = maxHealth;
  ballX=width/4;
  ballY=height/5;
  lastAddTime = 0;
  walls.clear();
  gameScreen = 0;
}

让我们添加更多颜色。

完整的彩色蓬蓬乒乓球。

瞧!我们有蓬松的乒乓球!

完整的《 Processing》游戏代码可在此处找到。

使用p5.js将Processing游戏代码移植到Web

p5.j​​s是一个JavaScript库, 其语法与Processing编程语言非常相似。它不是一个能够简单运行现有Processing代码的库;相反, p5.js需要编写实际的JavaScript代码-类似于Processing的JavaScript端口, 即Processing.js。我们的任务是使用p5.js API将Processing代码转换为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>

转换代码时, 我们的策略应该是将所有代码复制并粘贴到flippy_pong.js中, 然后进行所有更改。这就是我所做的。这是我更新代码所采取的步骤:

Javascript是一种无类型的语言(没有int和float这样的类型声明)。因此, 我们需要将所有类型声明更改为var。

Javascript中没有空白。我们应该改变所有功能。

我们需要从函数签名中删除参数的类型声明。 (即void wallMover(var index){to function wallMover(index){)

JavaScript中没有ArrayList。但是我们可以使用JavaScript数组实现相同的目的。我们进行以下更改:

  • ArrayList <int []>墙=新的ArrayList <int []>();到var墙= [];
  • wall.clear();到墙壁= [];
  • wall.add(randWall);到walls.push(randWall);
  • wall.remove(index);到wall.splice(index, 1);
  • wall.get(index);到墙壁[索引]
  • walls.size()到walls.length

更改数组的声明var randWall = {width, randY, wallWidth, randHeight, 0};到var randWall = [width, randY, wallWidth, randHeight, 0];

删除所有公共关键字。

将所有color(0)声明移到函数setup()中, 因为在调用setup()之前不会定义color()。

更改大小(500, 500);创建canvas(500, 500);

将函数gameScreen(){重命名为其他函数, 例如gamePlayScreen(){, 因为我们已经有一个名为gameScreen的全局变量。当我们使用Processing时, 一个是函数, 另一个是int变量。但是JavaScript会混淆这些类型, 因为它们没有类型。

Score()也一样。我将其重命名为addScore()。

可以在此处找到涵盖此Processing教程中所有内容的完整JavaScript代码。

Processing游戏代码:你也可以做

在此Processing教程中, 我试图解释如何构建一个非常简单的游戏。但是, 我们在本文中所做的只是冰山一角。使用Processing编程语言, 几乎可以实现任何目的。我认为, 这是编程你所想象的最佳工具。我使用此Processing教程的真正目的不是要讲授Processing和开发游戏, 以证明编程并不难。打造自己的游戏不仅仅是梦想。我想向你展示, 只要稍加努力和热情, 你就可以做到。我真的希望这两篇文章能激励大家尝试编程。

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?