처리 언어에 대한 궁극적인 가이드 2부: 간단한 게임 만들기

게시 됨: 2022-03-11

이것은 프로세싱 언어에 대한 궁극적인 가이드의 두 번째 부분입니다. 첫 번째 부분에서는 기본적인 처리 언어 연습을 제공했습니다. Processing을 배우기 위한 다음 단계는 더 많은 실습 프로그래밍입니다.

이 기사에서는 Processing을 사용하여 자신의 게임을 단계별로 구현하는 방법을 보여 드리겠습니다. 각 단계에 대해 자세히 설명합니다. 그런 다음 게임을 웹으로 이식합니다.

Processing 언어로 간단한 게임을 만드십시오.

처리 튜토리얼을 시작하기 전에 이전 부분의 DVD 로고 연습 코드가 있습니다. 질문이 있으시면 반드시 댓글을 남겨주세요.

처리 튜토리얼: 간단한 게임

이 처리 튜토리얼에서 빌드할 게임은 일종의 Flappy Bird, Pong 및 Brick Breaker의 조합입니다. 제가 이런 게임을 선택한 이유는 초보자들이 게임 개발을 배울 때 어려워하는 개념이 대부분 들어있기 때문입니다. 이것은 내가 조교로 있을 때의 경험을 바탕으로 새 프로그래머가 Processing을 사용하는 방법을 배울 수 있도록 돕습니다. 이러한 개념에는 중력, 충돌, 점수 유지, 다양한 화면 처리 및 키보드/마우스 상호 작용이 포함됩니다. Flappy Pong에는 모든 것이 들어 있습니다.

지금 게임을 플레이하세요!

객체 지향 프로그래밍(OOP) 개념을 사용하지 않고는 여러 레벨, 플레이어, 엔터티 등의 플랫폼 게임과 같은 복잡한 게임을 빌드하기가 쉽지 않습니다. 계속 진행하면서 코드가 정말 빨리 복잡해지는 것을 보게 될 것입니다. 이 처리 튜토리얼을 체계적이고 간단하게 유지하기 위해 최선을 다했습니다.

기사를 따라가서 전체 코드를 가져와서 스스로 플레이하고 가능한 한 빨리 자신의 게임에 대해 생각하고 구현을 시작하는 것이 좋습니다.

시작하겠습니다.

플래피퐁 만들기

처리 자습서 단계 #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() 에서 정의했다는 것입니다. 그렇게 한 이유는 공이 왼쪽에서 1/4, 위쪽에서 1/5 지점에서 시작하기를 원했기 때문입니다. 우리가 그것을 원하는 특별한 이유는 없지만 그것이 공을 시작하기에 좋은 포인트입니다. 따라서 스케치의 widthheight 를 동적으로 가져와야 했습니다. 스케치 크기는 첫 번째 줄 다음에 setup() 에서 정의됩니다. widthheight setup() 이 실행되기 전에 설정되지 않기 때문에 맨 위에 변수를 정의하면 이를 달성할 수 없습니다.

중력

이제 중력을 구현하는 것은 실제로 쉬운 부분입니다. 몇 가지 트릭만 있습니다. 먼저 구현은 다음과 같습니다.

 ... 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 ( + radius) 가 height 보다 작은지 확인하고 마찬가지로 ballY ( - radius) 가 0 보다 큰지 확인합니다. 조건이 충족되지 않으면 makeBounceBottom()makeBounceTop() 메서드를 사용하여 공이 바운스(하단 또는 상단에서)되도록 합니다. 공을 바운스하려면 공을 바운스해야 하는 정확한 위치로 이동하고 수직 속도( ballSpeedVert )에 -1 을 곱하면 됩니다(-1을 곱하면 부호가 변경됨). 속도 값에 마이너스 기호가 있을 때 Y 좌표를 추가하면 속도가 ballY + (-ballSpeedVert) , 즉 ballY - ballSpeedVert 가 됩니다. 따라서 공은 즉시 같은 속도로 방향을 바꿉니다. 그런 다음 ballSpeedVertgravity 을 추가하고 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 를 중앙으로 설정하여 라켓이 커서의 중앙에 정렬되도록 합니다.

이제 라켓을 만들었으므로 공이 바운스되도록 해야 합니다.

 ... 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 로 계산되는 변수 overhead 를 눈치채셨나요? pmouseXpmouseY 변수는 이전 프레임의 마우스 좌표를 저장합니다. 마우스는 매우 빠르게 움직일 수 있으므로 마우스가 공을 향해 충분히 빠르게 움직이는 경우 프레임 사이에서 공과 라켓 사이의 거리를 올바르게 감지하지 못할 가능성이 높습니다. 따라서 프레임 간 마우스 좌표의 차이를 고려하여 거리를 감지합니다. 마우스가 더 빨리 움직일수록 더 먼 거리가 허용됩니다.

우리는 또한 다른 이유로 overhead 를 사용합니다. overhead 의 부호를 확인하여 마우스가 움직이는 방향을 감지합니다. 오버헤드가 음수이면 마우스가 이전 프레임 아래 어딘가에 있었기 때문에 마우스(라켓)가 위로 이동합니다. 이 경우 공에 속도를 추가하고 일반 바운스보다 조금 더 이동하여 라켓으로 공을 치는 효과를 시뮬레이션하려고 합니다. overhead0 보다 작으면 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 에 수평 속도를 적용하고 공기 마찰을 제거하는 방법을 만들었습니다. 화면의 왼쪽과 오른쪽 가장자리에 공을 치는 것을 감시하는 두 개의 if 문을 keepInScreen() 메서드에 추가했습니다. 마지막으로 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 를 사용하여 라켓 중앙에서 공이 치는 지점의 거리를 결정한 것입니다. 그런 다음 수평 속도로 만듭니다. 실제 차이가 너무 커서 몇 번 해보고 그 값의 10분의 1이 가장 자연스럽다고 생각했습니다.

처리 튜토리얼 단계 #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 가 무엇인지 모르는 분들을 위해 Array처럼 작동하는 목록의 구현일 뿐이지만 몇 가지 장점이 있습니다. 크기를 조정할 수 있으며 list.add(index) , list.get(index)list.remove(index) ) 와 같은 유용한 메서드가 있습니다. 우리는 arraylist 내에서 벽 데이터를 정수 배열로 유지합니다. 배열에 보관하는 데이터는 두 벽 사이의 간격에 대한 것입니다. 배열에는 다음 값이 포함됩니다.

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

실제 벽은 간격 벽 값을 기반으로 그려집니다. 이 모든 것은 클래스를 사용하여 더 좋고 깔끔하게 처리될 수 있지만 객체 지향 프로그래밍(OOP)의 사용은 이 처리 자습서의 범위에 없기 때문에 이것이 처리하는 방법입니다. 우리는 벽을 관리하는 두 가지 기본 메소드, wallAdder()wallHandler 있습니다.

wallAdder() 메서드는 단순히 모든 wallInterval 밀리초마다 새 벽을 arraylist에 추가합니다. 마지막 벽이 추가 된 시간(밀리초) 을 저장하는 전역 변수 lastAddTime 이 있습니다. 현재 밀리초 millis() 에서 마지막으로 추가된 밀리초 lastAddTime 을 뺀 값이 간격 값 wallInterval 보다 크면 이제 새 벽을 추가할 시간임을 의미합니다. 그런 다음 맨 위에 정의된 전역 변수를 기반으로 랜덤 갭 변수가 생성됩니다. 그런 다음 새 벽(갭 벽 데이터를 저장하는 정수 배열)이 arraylist에 추가되고 lastAddTime 이 현재 밀리초 millis() 로 설정됩니다.

wallHandler() 는 arraylist에 있는 현재 벽을 반복합니다. 그리고 각 루프의 각 항목에 대해 arraylist의 인덱스 값으로 wallRemover wallRemover(i) , wallMover(i)wallDrawer(i) 를 호출합니다. 이러한 메서드는 이름에서 알 수 있는 작업을 수행합니다. wallDrawer() 는 간격 벽 데이터를 기반으로 실제 벽을 그립니다. arraylist에서 벽 데이터 배열을 가져오고 rect() 메서드를 호출하여 벽이 실제로 있어야 할 위치에 그립니다. wallMover() 메소드는 arraylist에서 요소를 잡고 wallSpeed ​​전역 변수를 기반으로 X 위치를 변경합니다. 마지막으로 wallRemover() 는 화면 밖에 있는 배열 목록에서 벽을 제거합니다. 그렇게 하지 않았다면 Processing은 화면에 있는 그대로 처리했을 것입니다. 그리고 그것은 성능면에서 큰 손실을 입었을 것입니다. 따라서 벽이 arraylist에서 제거되면 후속 루프에서 그려지지 않습니다.

마지막으로 남은 과제는 공과 벽 사이의 충돌을 감지하는 것입니다.

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

색상을 더 추가해 보겠습니다.

풀 컬러로 완성된 플래피퐁.

짜잔! 플래피퐁이 있습니다!

전체 프로세싱 게임 코드는 여기에서 찾을 수 있습니다.

p5.js를 사용하여 처리 게임 코드를 웹으로 이식

p5.js는 Processing 프로그래밍 언어와 구문이 매우 유사한 JavaScript 라이브러리입니다. 단순히 기존 처리 코드를 실행할 수 있는 라이브러리가 아닙니다. 대신, p5.js는 Processing.js로 알려진 Processing의 JavaScript 포트와 유사한 실제 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 로 변경해야 합니다.

  • 자바스크립트에는 void 이 없습니다. 모두 function 으로 변경해야 합니다.

  • 함수 서명에서 인수의 유형 선언을 제거해야 합니다. (즉, void wallMover(var index) { 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() to walls.length
  • 배열 선언 변경 var randWall = {width, randY, wallWidth, randHeight, 0}; var randWall = [width, randY, wallWidth, randHeight, 0];

  • 모든 public 키워드를 제거하십시오.

  • color()setup() 호출 전에 정의되지 않으므로 모든 color(0) 선언을 function setup() 로 이동합니다.

  • size(500, 500); 생성하기 위해 createCanvas(500, 500);

  • function gameScreen(){ 이름을 function gamePlayScreen() function gamePlayScreen(){ 과 같은 다른 이름으로 바꿉니다. 이미 gameScreen 이라는 전역 변수가 있기 때문입니다. Processing으로 작업할 때 하나는 함수이고 다른 하나는 int 변수였습니다. 그러나 JavaScript는 유형이 지정되지 않았기 때문에 이들을 혼동합니다.

  • score() 도 마찬가지입니다. addScore() 로 이름을 변경했습니다.

이 처리 튜토리얼의 모든 내용을 다루는 전체 JavaScript 코드는 여기에서 찾을 수 있습니다.

게임 코드 처리: 당신도 할 수 있습니다

이 프로세싱 튜토리얼에서는 아주 간단한 게임을 만드는 방법을 설명하려고 했습니다. 그러나 이 기사에서 우리가 한 일은 빙산의 일각에 불과합니다. Processing 프로그래밍 언어를 사용하면 거의 모든 것을 달성할 수 있습니다. 제 생각에는 상상하는 것을 프로그래밍하는 가장 좋은 도구입니다. 이 프로세싱 튜토리얼의 실제 의도는 프로세싱을 가르치고 게임을 만드는 것이 아니라 프로그래밍이 그렇게 어렵지 않다는 것을 증명하는 것이었습니다. 나만의 게임을 만드는 것은 꿈이 아닙니다. 약간의 노력과 열정만 있으면 할 수 있다는 것을 보여드리고 싶었습니다. 이 두 기사가 모든 사람이 프로그래밍에 도전할 수 있도록 영감을 주기를 바랍니다.