处理语言终极指南第二部分:构建一个简单的游戏

已发表: 2022-03-11

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

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

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

在我们开始处理教程之前,这里是上一部分的 DVD 徽标练习的代码。 如果您有任何问题,请务必发表评论。

处理教程:一个简单的游戏

我们将在本处理教程中构建的游戏是 Flappy Bird、Pong 和 Brick Breaker 的组合。 我之所以选择这样一款游戏,是因为它包含了初学者在学习游戏开发时难以理解的大部分概念。 这是基于我当助教时的经验,帮助新程序员学习如何使用 Processing。 这些概念包括重力、碰撞、记分、处理不同的屏幕和键盘/鼠标交互。 Flappy Pong 里面都有。

现在玩游戏!

如果不使用面向对象编程 (OOP) 概念,构建复杂的游戏并不容易,例如具有多个关卡、玩家、实体等的平台游戏。随着我们的深入,您将看到代码是如何迅速变得复杂的。 我尽我所能保持这个处理教程的组织和简单。

我建议你按照这篇文章,获取完整的代码,自己玩,尽快开始思考你自己的游戏,然后开始实现它。

那么让我们开始吧。

建造 Flappy Pong

处理教程步骤#1:初始化和处理不同的屏幕

第一步是初始化我们的项目。 对于初学者,我们将像往常一样编写设置和绘制块,没有什么花哨的或新的。 然后,我们将处理不同的屏幕(初始屏幕、游戏屏幕、游戏结束屏幕等)。 那么问题来了,我们如何让 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); }

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

处理教程步骤#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()中定义了它们。 我们这样做的原因是我们希望球从左四分之一和上五分之一开始。 我们没有特别想要这样的理由,但这是一个很好的开始点。 所以我们需要动态获取草图的widthheight 。 草图大小在setup()中定义,在第一行之后。 在setup()运行之前没有设置widthheight ,这就是为什么我们在顶部定义变量时无法实现这一点的原因。

重力

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

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

结果是:

一个在伪重力下无限弹跳的球。

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

球在球拍上无限弹跳。

但是,我们的动画过程存在一个问题——球一直在弹跳。 如果这是真实世界的场景,球每次接触表面时都会面临空气阻力和摩擦。 这就是我们想要的游戏动画过程的行为,所以实现它很容易。 我们添加以下内容:

 ... float airfriction = 0.0001; float friction = 0.1; ... void applyGravity() { ... ballSpeedVert -= (ballSpeedVert * airfriction); } void makeBounceBottom(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); } void makeBounceTop(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); }

现在我们的动画过程产生了这个:

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

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

处理教程步骤#3:创建球拍

现在我们需要一个球拍让球弹起来。 我们应该控制球拍。 让我们用鼠标控制它。 这是代码:

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

我们将球拍的颜色、宽度和高度定义为全局变量,我们可能希望它们在游戏过程中发生变化。 我们实现了一个方法drawRacket() ,正如它的名字所暗示的那样。 我们将rectMode设置为 center,因此我们的球拍与光标的中心对齐。

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

 ... int racketBounceRate = 20; ... void gameScreen() { ... watchRacketBounce(); ... } ... void watchRacketBounce() { float overhead = mouseY - pmouseY; if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { makeBounceBottom(mouseY); // racket moving up if (overhead<0) { ballY+=overhead; ballSpeedVert+=overhead; } } } }

结果如下:

球在球拍上弹跳,但由于摩擦而停止。

所以watchRacketBounce()所做的是确保球拍和球发生碰撞。 这里有两件事要检查,那就是球和球拍是否垂直和水平排列。 第一个 if 语句检查球右侧的 X 坐标是否大于球拍左侧的 X 坐标(反之亦然)。 如果是,第二个语句检查球和球拍之间的距离是否小于或等于球的半径(这意味着它们正在碰撞) 。 因此,如果满足这些条件,将调用makeBounceBottom()方法并且球在我们的球拍上弹跳(在mouseY ,球拍所在的位置)。

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

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

处理教程步骤#4:水平运动和控制球

在本节中,我们将为球添加水平运动。 然后,我们将可以用我们的球拍水平控制球。 开始了:

 ... // we will start with 0, but for we give 10 just for testing float ballSpeedHorizon = 10; ... void gameScreen() { ... applyHorizontalSpeed(); ... } ... void applyHorizontalSpeed(){ ballX += ballSpeedHorizon; ballSpeedHorizon -= (ballSpeedHorizon * airfriction); } void makeBounceLeft(float surface){ ballX = surface+(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } void makeBounceRight(float surface){ ballX = surface-(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } ... void keepInScreen() { ... if (ballX-(ballSize/2) < 0){ makeBounceLeft(0); } if (ballX+(ballSize/2) > width){ makeBounceRight(width); } }

结果是:

一个球现在也水平弹跳。

这里的想法与我们为垂直移动所做的相同。 我们创建了一个水平速度变量ballSpeedHorizon 。 我们创建了一种将水平速度应用于ballX并消除空气摩擦的方法。 我们在keepInScreen()方法中添加了另外两个 if 语句,它们将观察球撞击屏幕的左右边缘。 最后,我们创建了makeBounceLeft()makeBounceRight()方法来处理左右反弹。

现在我们在游戏中添加了水平速度,我们想用球拍控制球。 与著名的 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确定了球击中点到球拍中心的距离。 然后,我们将其设为水平速度。 实际差异太大,所以我试了几次,觉得十分之一的值感觉最自然。

处理教程步骤#5:创建墙壁

我们的草图开始看起来更像是每一步的游戏。 在这一步中,我们将添加向左移动的墙壁,就像在 Flappy Bird 中一样:

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

这导致:

一个球在有墙壁的水平面上弹跳。

尽管代码看起来很长而且令人生畏,但我保证没有什么难以理解的。 首先要注意的是ArrayList 。 对于那些不知道ArrayList是什么的人来说,它只是一个 list 的实现,就像一个 Array,但它比它有一些优势。 它是可调整大小的,它有一些有用的方法,比如list.add(index)list.get(index)list.remove(index) 。 我们将墙壁数据作为整数数组保存在数组列表中。 我们保存在数组中的数据是关于两堵墙之间的差距。 数组包含以下值:

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

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

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

wallHandler()循环遍历数组列表中的当前墙。 对于每个循环中的每个项目,它通过数组列表的索引值调用wallRemover(i)wallMover(i)wallDrawer(i) 。 这些方法正如其名称所暗示的那样。 wallDrawer()根据间隙墙壁数据绘制实际墙壁。 它从 arraylist 中获取墙壁数据数组,并调用rect()方法将墙壁绘制到它们实际应该在的位置。 wallMover()方法从数组列表中获取元素,根据wallSpeed全局变量更改其 X 位置。 最后, wallRemover()从数组列表中移除屏幕外的墙壁。 如果我们不这样做,Processing 就会将它们视为仍在屏幕上。 这将是性能的巨大损失。 因此,当从数组列表中删除一堵墙时,它不会在后续循环中绘制。

剩下要做的最后一项具有挑战性的事情是检测球和墙壁之间的碰撞。

 void wallHandler() { for (int i = 0; i < walls.size(); i++) { ... watchWallCollision(i); } } ... void watchWallCollision(int index) { int[] wall = walls.get(index); // get gap wall settings int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; int wallTopX = gapWallX; int wallTopY = 0; int wallTopWidth = gapWallWidth; int wallTopHeight = gapWallY; int wallBottomX = gapWallX; int wallBottomY = gapWallY+gapWallHeight; int wallBottomWidth = gapWallWidth; int wallBottomHeight = height-(gapWallY+gapWallHeight); if ( (ballX+(ballSize/2)>wallTopX) && (ballX-(ballSize/2)<wallTopX+wallTopWidth) && (ballY+(ballSize/2)>wallTopY) && (ballY-(ballSize/2)<wallTopY+wallTopHeight) ) { // collides with upper wall } if ( (ballX+(ballSize/2)>wallBottomX) && (ballX-(ballSize/2)<wallBottomX+wallBottomWidth) && (ballY+(ballSize/2)>wallBottomY) && (ballY-(ballSize/2)<wallBottomY+wallBottomHeight) ) { // collides with lower wall } }

watchWallCollision()方法在每个循环中为每个墙调用。 我们抓取缝隙墙壁的坐标,计算实际墙壁(顶部和底部)的坐标,然后检查球的坐标是否与墙壁发生碰撞。

处理教程步骤#6:健康和分数

现在我们可以检测到球和墙壁的碰撞,我们可以决定游戏机制。 在对游戏进行了一些调整后,我设法使游戏变得有些可玩。 但是,这仍然非常困难。 我对游戏的第一个想法是让它像 Flappy Bird 一样,当球碰到墙壁时,游戏结束。 但后来我意识到这是不可能玩的。 所以这就是我的想法:

球的顶部应该有一个健康栏。 球在接触墙壁时应该失去健康。 按照这个逻辑,让球从墙上反弹回来是没有意义的。 所以当生命值为0时,游戏应该结束,我们应该切换到游戏结束屏幕。 所以我们开始:

 int maxHealth = 100; float health = 100; float healthDecrease = 1; int healthBarWidth = 60; ... void gameScreen() { ... drawHealthBar(); ... } ... void drawHealthBar() { // Make it borderless: noStroke(); fill(236, 240, 241); rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth, 5); if (health > 60) { fill(46, 204, 113); } else if (health > 30) { fill(230, 126, 34); } else { fill(231, 76, 60); } rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth*(health/maxHealth), 5); } void decreaseHealth(){ health -= healthDecrease; if (health <= 0){ gameOver(); } }

这是一个简单的运行:

一个带有健康条的球在关卡中弹跳,每当它与墙壁碰撞时都会失去健康。

我们创建了一个全局变量health来保持球的健康。 然后创建了一个方法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 中的gap wall 数组中添加了另一个变量。 如果球还没有通过那堵墙,则新变量存储0 ,如果已经通过,则存储1 。 然后,我们修改了watchWallCollision()方法。 我们添加了一个触发score()方法的条件,并在球通过之前未通过的墙时将墙标记为通过。

我们现在非常接近尾声。 最后要做的是在游戏结束屏幕上实现click to restart 。 我们需要将我们使用的所有变量设置为初始值,然后重新开始游戏。 这里是:

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

让我们添加更多颜色。

完整的 Flappy Pong 全彩。

瞧! 我们有 Flappy Pong!

完整的处理游戏代码可以在这里找到。

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

p5.j​​s 是一个 JavaScript 库,其语法与 Processing 编程语言的语法非常相似。 它不是一个能够简单地运行现有处理代码的库; 相反,p5.js 需要编写实际的 JavaScript 代码——类似于被称为 Processing.js 的 Processing 的 JavaScript 端口。 我们的任务是使用 p5.js API 将处理代码转换为 JavaScript。 该库有一组函数和类似于 Processing 的语法,我们必须对我们的代码进行某些更改以使它们在 JavaScript 中工作——但由于 Processing 和 JavaScript 与 Java 有相似之处,它不像听起来那么简单. 即使您不是 JavaScript 开发人员,这些更改也是非常微不足道的,您应该能够很好地跟进。

首先,我们需要创建一个简单的index.html并将p5.min.js添加到我们的标题中。 我们还需要创建另一个名为flappy_pong.js的文件,它将存放我们转换后的代码。

 <html> <head> <title>Flappy Pong</title> <script tyle="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.19/p5.min.js"></script> <script tyle="text/javascript" src="flappy_pong.js"></script> <style> canvas { box-shadow: 0 0 20px lightgray; } </style> </head> <body> </body> </html>

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

  • Javascript 是一种无类型语言(没有像intfloat这样的类型声明)。 所以我们需要将所有类型声明更改为var

  • Javascript 中没有void 。 我们应该改变所有的function

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

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

    • ArrayList<int[]> walls = new ArrayList<int[]>();var walls = [];
    • walls.clear();walls = [];
    • walls.add(randWall);walls.push(randWall);
    • walls.remove(index);walls.splice(index,1);
    • walls.get(index);walls[index]
    • walls.size()walls.length
  • 更改数组的声明var randWall = {width, randY, wallWidth, randHeight, 0};var randWall = [width, randY, wallWidth, randHeight, 0];

  • 删除所有public关键字。

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

  • 改变size(500, 500); createCanvas(500, 500);

  • function gameScreen(){ function gamePlayScreen(){因为我们已经有一个名为gameScreen的全局变量。 当我们使用 Processing 时,一个是函数,另一个是int变量。 但是 JavaScript 混淆了这些,因为它们是无类型的。

  • score()也是如此。 我将其重命名为addScore()

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

处理游戏代码:你也可以

在这个处理教程中,我试图解释如何构建一个非常简单的游戏。 然而,我们在本文中所做的只是冰山一角。 使用 Processing 编程语言,几乎可以实现任何目标。 在我看来,它是对您的想象进行编程的最佳工具。 我对这个处理教程的实际意图不是教处理和构建游戏,而是证明编程并不难。 建立自己的游戏不仅仅是一个梦想。 我想告诉你,只要付出一点努力和热情,你就可以做到。 我真的希望这两篇文章能激励大家尝试编程。