├── screenshot.png
├── assets
├── start.png
├── title.png
├── VENUSAUR.png
├── boy_run.png
├── BLASTOISE.png
├── GENTLEMAN.png
├── databox_thin.png
├── power-clear.ttf
├── background-art.jpeg
├── overlay_message.png
├── battle-background.png
├── databox_thin_foe.png
├── trainer_GENTLEMAN.png
└── Trainer Tower interior.png
├── entities
├── camera.js
├── debugMode.js
├── Collidable.js
├── character.js
├── dialogBox.js
├── npc.js
├── map.js
└── player.js
├── README.MD
├── scenes
├── menu.js
├── world.js
└── battle.js
├── LICENSE
├── index.html
├── main.js
├── utils.js
└── maps
└── world.json
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/screenshot.png
--------------------------------------------------------------------------------
/assets/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/start.png
--------------------------------------------------------------------------------
/assets/title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/title.png
--------------------------------------------------------------------------------
/assets/VENUSAUR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/VENUSAUR.png
--------------------------------------------------------------------------------
/assets/boy_run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/boy_run.png
--------------------------------------------------------------------------------
/assets/BLASTOISE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/BLASTOISE.png
--------------------------------------------------------------------------------
/assets/GENTLEMAN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/GENTLEMAN.png
--------------------------------------------------------------------------------
/assets/databox_thin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/databox_thin.png
--------------------------------------------------------------------------------
/assets/power-clear.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/power-clear.ttf
--------------------------------------------------------------------------------
/assets/background-art.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/background-art.jpeg
--------------------------------------------------------------------------------
/assets/overlay_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/overlay_message.png
--------------------------------------------------------------------------------
/assets/battle-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/battle-background.png
--------------------------------------------------------------------------------
/assets/databox_thin_foe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/databox_thin_foe.png
--------------------------------------------------------------------------------
/assets/trainer_GENTLEMAN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/trainer_GENTLEMAN.png
--------------------------------------------------------------------------------
/assets/Trainer Tower interior.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSLegendDev/Pokemon-p5js/HEAD/assets/Trainer Tower interior.png
--------------------------------------------------------------------------------
/entities/camera.js:
--------------------------------------------------------------------------------
1 | export function makeCamera(p, x, y) {
2 | return {
3 | x,
4 | y,
5 | attachTo(entity) {
6 | this.entity = entity;
7 | },
8 |
9 | update() {
10 | this.x = -this.entity.x + p.width / 2;
11 | this.y = -this.entity.y + p.height / 2;
12 | },
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/entities/debugMode.js:
--------------------------------------------------------------------------------
1 | function makeDebugMode() {
2 | return {
3 | enabled: false,
4 | drawFpsCounter(p) {
5 | if (!this.enabled) return;
6 | p.push();
7 | p.fill("yellow");
8 | p.textSize(24);
9 | p.text(Math.trunc(p.frameRate()), 10, 20);
10 | p.pop();
11 | },
12 |
13 | toggle() {
14 | this.enabled = !this.enabled;
15 | },
16 |
17 | drawHitbox(p, hitbox) {
18 | if (!this.enabled) return;
19 | p.fill(255, 0, 0, 63);
20 | p.rect(hitbox.screenX, hitbox.screenY, hitbox.width, hitbox.height);
21 | },
22 | };
23 | }
24 |
25 | export const debugMode = makeDebugMode();
26 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Pokemon prototype game made with JavaScript + p5.js
2 |
3 | 
4 |
5 | Live demo : https://jslegend.itch.io/p5-pokemon-prototype
6 |
7 | A prototype pokemon game that includes :
8 |
9 | - camera
10 | - scenes
11 | - basic battle system
12 | - basic animation player system
13 | - player controller
14 | - basic collision detection
15 |
16 | Made with JavaScript and the p5.js creative coding library.
17 |
18 | Tutorial on how to build this : https://www.youtube.com/watch?v=WPT2BmkFFyo
19 |
20 | I run a YouTube channel where I make long-form JS gamedev tutorials.
21 | If you're interested you can take a look : https://youtube.com/@jslegenddev
22 |
--------------------------------------------------------------------------------
/entities/Collidable.js:
--------------------------------------------------------------------------------
1 | import { checkCollision, preventOverlap } from "../utils.js";
2 | import { debugMode } from "./debugMode.js";
3 |
4 | export function makeCollidable(p, x, y, width, height) {
5 | return {
6 | x,
7 | y,
8 | screenX: x,
9 | screenY: y,
10 | width,
11 | height,
12 | preventPassthroughFrom(entity) {
13 | const collision = checkCollision(this, entity);
14 |
15 | if (collision) preventOverlap(this, entity);
16 | },
17 |
18 | update(camera) {
19 | this.screenX = this.x + camera.x;
20 | this.screenY = this.y + camera.y;
21 | },
22 |
23 | draw() {
24 | debugMode.drawHitbox(p, this);
25 | },
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/scenes/menu.js:
--------------------------------------------------------------------------------
1 | export function makeMenu(p) {
2 | return {
3 | startScreenImgRef: null,
4 | startTextImgRef: null,
5 | easing: 0.5,
6 | alpha: 255,
7 | blinkBack: false,
8 | load() {
9 | this.startScreenImgRef = p.loadImage("./assets/title.png");
10 | this.startTextImgRef = p.loadImage("./assets/start.png");
11 | },
12 | update() {
13 | if (this.alpha <= 0) this.blinkBack = true;
14 | if (this.alpha >= 255) this.blinkBack = false;
15 |
16 | if (this.blinkBack) {
17 | this.alpha += 0.7 * this.easing * p.deltaTime;
18 | } else {
19 | this.alpha -= 0.7 * this.easing * p.deltaTime;
20 | }
21 | },
22 | draw() {
23 | p.clear();
24 | p.image(this.startScreenImgRef, 0, 0);
25 | p.tint(255, this.alpha);
26 | p.image(this.startTextImgRef, 0, 320);
27 | p.noTint();
28 | },
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 JSLegend
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
23 |
24 |
25 |
26 |
27 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/entities/character.js:
--------------------------------------------------------------------------------
1 | export function makeCharacter(p) {
2 | return {
3 | spriteRef: null,
4 | anims: {},
5 | currentAnim: null,
6 | currentFrame: 0,
7 | currentFrameData: null,
8 | animationTimer: 0,
9 | previousTime: 0,
10 | tileWidth: 32,
11 | tileHeight: 48,
12 | width: 32,
13 | height: 32,
14 |
15 | setAnim(name) {
16 | if (this.currentAnim !== name) {
17 | this.currentAnim = name;
18 | this.currentFrame = 0;
19 | this.animationTimer = 0;
20 | this.previousTime = 0;
21 | }
22 | },
23 |
24 | setDirection(direction) {
25 | if (this.direction !== direction) this.direction = direction;
26 | },
27 |
28 | setAnimFrame(animData) {
29 | if (typeof animData === "number") {
30 | this.currentFrame = animData;
31 | return this.frames[this.currentFrame];
32 | }
33 |
34 | if (this.currentFrame === 0) {
35 | this.currentFrame = animData.from;
36 | }
37 |
38 | if (this.currentFrame > animData.to && animData.loop) {
39 | this.currentFrame = animData.from;
40 | }
41 |
42 | const currentFrameData = this.frames[this.currentFrame];
43 |
44 | const durationPerFrame = 1000 / animData.speed;
45 | if (this.animationTimer >= durationPerFrame) {
46 | this.currentFrame++;
47 | this.animationTimer -= durationPerFrame;
48 | }
49 |
50 | return currentFrameData;
51 | },
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/entities/dialogBox.js:
--------------------------------------------------------------------------------
1 | export function makeDialogBox(p, x, y) {
2 | return {
3 | x,
4 | y,
5 | spriteRef: null,
6 | currentTime: 0,
7 | previousTime: 0,
8 | lineChars: null,
9 | line: "",
10 | isVisible: false,
11 | onCompleteCallback: null,
12 | isComplete: false,
13 | load() {
14 | this.spriteRef = p.loadImage("assets/overlay_message.png");
15 | },
16 |
17 | setVisibility(isVisible) {
18 | this.isVisible = isVisible;
19 | },
20 |
21 | displayTextImmediately(content) {
22 | this.line = content;
23 | this.isComplete = true;
24 | },
25 |
26 | displayText(content, onComplete) {
27 | this.lineChars = content.split("");
28 | this.isComplete = false;
29 | if (onComplete) {
30 | this.onCompleteCallback = onComplete;
31 | return;
32 | }
33 |
34 | this.onCompleteCallback = null;
35 | },
36 |
37 | clearText() {
38 | this.line = "";
39 | this.lineChars = [];
40 | },
41 |
42 | update() {
43 | if (!this.isVisible) return;
44 | this.currentTime += p.deltaTime;
45 | const durationPerFrame = 1000 / 60;
46 | if (this.currentTime >= durationPerFrame) {
47 | this.currentTime -= durationPerFrame;
48 |
49 | const nextChar = this.lineChars.shift();
50 |
51 | if (this.isComplete) return;
52 |
53 | if (!nextChar && !this.isComplete) {
54 | this.isComplete = true;
55 | if (this.onCompleteCallback) this.onCompleteCallback();
56 | return;
57 | }
58 |
59 | this.line += nextChar;
60 | }
61 | },
62 | draw() {
63 | if (!this.isVisible) return;
64 | p.image(this.spriteRef, this.x, this.y);
65 | p.fill("black");
66 | p.textSize(24);
67 | p.text(this.line, this.x + 30, this.y + 42);
68 | },
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | import { makeMenu } from "./scenes/menu.js";
2 | import { debugMode } from "./entities/debugMode.js";
3 | import { makeWorld } from "./scenes/world.js";
4 | import { makeBattle } from "./scenes/battle.js";
5 |
6 | new p5((p) => {
7 | let font;
8 | const scenes = ["menu", "world", "battle"];
9 | let currentScene = "menu";
10 | function setScene(name) {
11 | if (scenes.includes(name)) {
12 | currentScene = name;
13 | }
14 | }
15 |
16 | const menu = makeMenu(p);
17 | const world = makeWorld(p, setScene);
18 | const battle = makeBattle(p);
19 |
20 | p.preload = () => {
21 | font = p.loadFont("./assets/power-clear.ttf");
22 | world.load();
23 | menu.load();
24 | battle.load();
25 | };
26 |
27 | p.setup = () => {
28 | const canvasEl = p.createCanvas(512, 384, document.getElementById("game"));
29 | // make canvas sharper temporarly
30 | p.pixelDensity(3);
31 | canvasEl.canvas.style = "";
32 |
33 | p.textFont(font);
34 | p.noSmooth(); // for pixels to not become blurry
35 |
36 | world.setup();
37 | battle.setup();
38 | };
39 |
40 | p.draw = () => {
41 | switch (currentScene) {
42 | case "menu":
43 | menu.update();
44 | menu.draw();
45 | break;
46 | case "world":
47 | world.update();
48 | world.draw();
49 | break;
50 | case "battle":
51 | battle.update();
52 | battle.draw();
53 | break;
54 | default:
55 | }
56 |
57 | debugMode.drawFpsCounter(p);
58 | };
59 |
60 | p.keyPressed = (keyEvent) => {
61 | if (keyEvent.key === "Shift") {
62 | debugMode.toggle();
63 | }
64 |
65 | if (keyEvent.keyCode === p.ENTER && currentScene === "menu")
66 | setScene("world");
67 |
68 | if (currentScene === "battle") battle.onKeyPressed(keyEvent);
69 | };
70 |
71 | p.keyReleased = () => {
72 | if (currentScene === "world") {
73 | world.keyReleased();
74 | }
75 | };
76 | });
77 |
--------------------------------------------------------------------------------
/entities/npc.js:
--------------------------------------------------------------------------------
1 | import { makeCharacter } from "./character.js";
2 | import { debugMode } from "./debugMode.js";
3 | import {
4 | drawTile,
5 | getFramesPos,
6 | checkCollision,
7 | preventOverlap,
8 | } from "../utils.js";
9 |
10 | export function makeNPC(p, x, y) {
11 | return {
12 | ...makeCharacter(p),
13 | x,
14 | y,
15 | screenX: x,
16 | screenY: y,
17 | spriteX: 0,
18 | spriteY: -15,
19 | load() {
20 | this.spriteRef = p.loadImage("assets/trainer_GENTLEMAN.png");
21 | },
22 |
23 | prepareAnims() {
24 | this.frames = getFramesPos(4, 4, this.tileWidth, this.tileHeight);
25 |
26 | this.anims = {
27 | "idle-down": 0,
28 | };
29 | },
30 |
31 | setup() {
32 | this.prepareAnims();
33 | this.setAnim("idle-down");
34 | },
35 |
36 | update() {
37 | this.previousTime = this.animationTimer;
38 | this.animationTimer += p.deltaTime;
39 | const animData = this.anims[this.currentAnim];
40 | this.currentFrameData = this.setAnimFrame(animData);
41 | },
42 |
43 | draw(camera) {
44 | this.screenX = this.x + camera.x;
45 | this.screenY = this.y + camera.y;
46 |
47 | debugMode.drawHitbox(p, this);
48 | drawTile(
49 | p,
50 | this.spriteRef,
51 | this.screenX + this.spriteX,
52 | this.screenY + this.spriteY,
53 | this.currentFrameData.x,
54 | this.currentFrameData.y,
55 | this.tileWidth,
56 | this.tileHeight
57 | );
58 | },
59 |
60 | handleCollisionsWith(entity, collisionEvent) {
61 | // If the player has already collided and is frozen due to dialog
62 | // no need to recompute collision
63 | if (entity.freeze) return;
64 |
65 | const collision = checkCollision(this, entity);
66 |
67 | if (collision) {
68 | preventOverlap(this, entity);
69 | entity.freeze = true;
70 | collisionEvent();
71 | }
72 | },
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | export function checkCollision(objA, objB) {
2 | return !(
3 | objA.x + objA.width < objB.x ||
4 | objA.x > objB.x + objB.width ||
5 | objA.y + objA.height < objB.y ||
6 | objA.y > objB.y + objB.height
7 | );
8 | }
9 |
10 | export function preventOverlap(objA, objB) {
11 | const overlapX =
12 | Math.min(objA.x + objA.width, objB.x + objB.width) -
13 | Math.max(objA.x, objB.x);
14 | const overlapY =
15 | Math.min(objA.y + objA.height, objB.y + objB.height) -
16 | Math.max(objA.y, objB.y);
17 |
18 | if (overlapX < overlapY) {
19 | if (objA.x < objB.x) {
20 | // right
21 | objB.x = objA.x + objA.width;
22 | return;
23 | }
24 | // left
25 | objB.x = objA.x - objA.width;
26 | return;
27 | }
28 |
29 | if (objA.y < objB.y) {
30 | // bottom
31 | objB.y = objA.y + objA.height;
32 | return;
33 | }
34 | // top
35 | objB.y = objA.y - objB.height;
36 | }
37 |
38 | export function isMaxOneKeyDown(p) {
39 | let isOnlyOneKeyDown = false;
40 | for (const key of [p.RIGHT_ARROW, p.LEFT_ARROW, p.UP_ARROW, p.DOWN_ARROW]) {
41 | if (!isOnlyOneKeyDown && p.keyIsDown(key)) {
42 | isOnlyOneKeyDown = true;
43 | continue;
44 | }
45 |
46 | if (isOnlyOneKeyDown && p.keyIsDown(key)) {
47 | return false;
48 | }
49 | }
50 |
51 | return true;
52 | }
53 |
54 | export function getFramesPos(nbCols, nbRows, tileWidth, tileHeight) {
55 | const framesPos = [];
56 | let currentTileX = 0;
57 | let currentTileY = 0;
58 | for (let i = 0; i < nbRows; i++) {
59 | for (let j = 0; j < nbCols; j++) {
60 | framesPos.push({ x: currentTileX, y: currentTileY });
61 | currentTileX += tileWidth;
62 | }
63 | currentTileX = 0;
64 | currentTileY += tileHeight;
65 | }
66 |
67 | return framesPos;
68 | }
69 |
70 | export function drawTile(
71 | p,
72 | src,
73 | destinationX,
74 | destinationY,
75 | srcX,
76 | srcY,
77 | tileWidth,
78 | tileHeight
79 | ) {
80 | p.image(
81 | src,
82 | destinationX,
83 | destinationY,
84 | tileWidth,
85 | tileHeight,
86 | srcX,
87 | srcY,
88 | tileWidth,
89 | tileHeight
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/entities/map.js:
--------------------------------------------------------------------------------
1 | import { makeCollidable } from "./collidable.js";
2 | import { drawTile, getFramesPos } from "../utils.js";
3 |
4 | export function makeTiledMap(p, x, y) {
5 | return {
6 | tileWidth: 32,
7 | tileHeight: 32,
8 | x,
9 | y,
10 | async load(tilesetURL, tiledMapURL) {
11 | this.mapImage = p.loadImage(tilesetURL);
12 | const response = await fetch(tiledMapURL);
13 | this.tiledData = await response.json();
14 | },
15 | prepareTiles() {
16 | this.tilesPos = getFramesPos(8, 55, this.tileWidth, this.tileHeight);
17 | },
18 | getSpawnPoints() {
19 | for (const layer of this.tiledData.layers) {
20 | if (layer.name === "SpawnPoints") {
21 | return layer.objects;
22 | }
23 | }
24 | },
25 | draw(camera, player) {
26 | for (const layer of this.tiledData.layers) {
27 | if (layer.type === "tilelayer") {
28 | const currentTilePos = {
29 | x: this.x,
30 | y: this.y,
31 | };
32 | let nbOfTilesDrawn = 0;
33 | for (const tileNumber of layer.data) {
34 | if (nbOfTilesDrawn % layer.width === 0) {
35 | currentTilePos.x = this.x;
36 | currentTilePos.y += this.tileHeight;
37 | } else {
38 | currentTilePos.x += this.tileWidth;
39 | }
40 | nbOfTilesDrawn++;
41 |
42 | if (tileNumber === 0) continue;
43 |
44 | drawTile(
45 | p,
46 | this.mapImage,
47 | Math.round(currentTilePos.x + camera.x),
48 | Math.round(currentTilePos.y + camera.y),
49 | this.tilesPos[tileNumber - 1].x,
50 | this.tilesPos[tileNumber - 1].y,
51 | this.tileWidth,
52 | this.tileHeight
53 | );
54 | }
55 | }
56 |
57 | if (layer.type === "objectgroup" && layer.name === "Boundaries") {
58 | for (const boundary of layer.objects) {
59 | const collidable = makeCollidable(
60 | p,
61 | this.x + boundary.x,
62 | this.y + boundary.y + this.tileHeight,
63 | boundary.width,
64 | boundary.height
65 | );
66 | collidable.preventPassthroughFrom(player);
67 | collidable.update(camera);
68 | collidable.draw();
69 | }
70 | }
71 | }
72 | },
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/entities/player.js:
--------------------------------------------------------------------------------
1 | import { makeCharacter } from "./character.js";
2 | import { debugMode } from "./debugMode.js";
3 | import { drawTile, getFramesPos, isMaxOneKeyDown } from "../utils.js";
4 |
5 | export function makePlayer(p, x, y) {
6 | return {
7 | ...makeCharacter(p),
8 | speed: 200,
9 | x,
10 | y,
11 | screenX: x,
12 | screenY: y,
13 | spriteX: 0,
14 | spriteY: -15,
15 | freeze: false,
16 | load() {
17 | this.spriteRef = p.loadImage("assets/boy_run.png");
18 | },
19 |
20 | prepareAnims() {
21 | this.frames = getFramesPos(4, 4, this.tileWidth, this.tileHeight);
22 |
23 | this.anims = {
24 | "idle-down": 0,
25 | "idle-side": 6,
26 | "idle-up": 12,
27 | "run-down": { from: 0, to: 3, loop: true, speed: 8 },
28 | "run-side": { from: 4, to: 7, loop: true, speed: 8 },
29 | "run-up": { from: 12, to: 15, loop: true, speed: 8 },
30 | };
31 | },
32 |
33 | movePlayer(moveBy) {
34 | if (!isMaxOneKeyDown(p) || this.freeze) return;
35 |
36 | if (p.keyIsDown(p.RIGHT_ARROW)) {
37 | this.setDirection("right");
38 | this.setAnim("run-side");
39 | this.x += moveBy;
40 | }
41 |
42 | if (p.keyIsDown(p.LEFT_ARROW)) {
43 | this.setDirection("left");
44 | this.setAnim("run-side");
45 | this.x -= moveBy;
46 | }
47 |
48 | if (p.keyIsDown(p.UP_ARROW)) {
49 | this.setDirection("up");
50 | this.setAnim("run-up");
51 | this.y -= moveBy;
52 | }
53 |
54 | if (p.keyIsDown(p.DOWN_ARROW)) {
55 | this.setDirection("down");
56 | this.setAnim("run-down");
57 | this.y += moveBy;
58 | }
59 | },
60 |
61 | setup() {
62 | this.prepareAnims();
63 | this.direction = "down";
64 | this.setAnim("idle-down");
65 | },
66 |
67 | update() {
68 | this.previousTime = this.animationTimer;
69 | this.animationTimer += p.deltaTime;
70 |
71 | const moveBy = (this.speed / 1000) * p.deltaTime;
72 | this.movePlayer(moveBy);
73 |
74 | const animData = this.anims[this.currentAnim];
75 | this.currentFrameData = this.setAnimFrame(animData);
76 | },
77 |
78 | draw(camera) {
79 | this.screenX = this.x + camera.x;
80 | this.screenY = this.y + camera.y;
81 |
82 | p.push();
83 | if (this.direction === "right") {
84 | p.scale(-1, 1);
85 | p.translate(-2 * this.screenX - this.tileWidth, 0);
86 | }
87 | debugMode.drawHitbox(p, this);
88 | drawTile(
89 | p,
90 | this.spriteRef,
91 | this.screenX + this.spriteX,
92 | this.screenY + this.spriteY,
93 | this.currentFrameData.x,
94 | this.currentFrameData.y,
95 | this.tileWidth,
96 | this.tileHeight
97 | );
98 | p.pop();
99 | },
100 | };
101 | }
102 |
--------------------------------------------------------------------------------
/scenes/world.js:
--------------------------------------------------------------------------------
1 | import { makeNPC } from "../entities/npc.js";
2 | import { makePlayer } from "../entities/player.js";
3 | import { makeTiledMap } from "../entities/map.js";
4 | import { makeDialogBox } from "../entities/dialogBox.js";
5 | import { makeCamera } from "../entities/camera.js";
6 |
7 | export function makeWorld(p, setScene) {
8 | return {
9 | camera: makeCamera(p, 100, 0),
10 | player: makePlayer(p, 0, 0),
11 | npc: makeNPC(p, 0, 0),
12 | map: makeTiledMap(p, 100, -150),
13 | dialogBox: makeDialogBox(p, 0, 280),
14 | makeScreenFlash: false,
15 | alpha: 0,
16 | blinkBack: false,
17 | easing: 3,
18 | load() {
19 | this.dialogBox.load();
20 | this.map.load("./assets/Trainer Tower interior.png", "./maps/world.json");
21 | this.player.load();
22 | this.npc.load();
23 | },
24 | setup() {
25 | this.map.prepareTiles();
26 | const spawnPoints = this.map.getSpawnPoints();
27 | for (const spawnPoint of spawnPoints) {
28 | switch (spawnPoint.name) {
29 | case "player":
30 | this.player.x = this.map.x + spawnPoint.x;
31 | this.player.y = this.map.y + spawnPoint.y + 32;
32 | break;
33 | case "npc":
34 | this.npc.x = this.map.x + spawnPoint.x;
35 | this.npc.y = this.map.y + spawnPoint.y + 32;
36 | break;
37 | default:
38 | }
39 | }
40 | this.player.setup();
41 | this.camera.attachTo(this.player);
42 |
43 | this.npc.setup();
44 | },
45 |
46 | update() {
47 | this.camera.update();
48 | this.player.update(); // this being before the map draw call is important
49 | this.npc.update();
50 | this.dialogBox.update();
51 | if (this.alpha <= 0) this.blinkBack = true;
52 | if (this.alpha >= 255) this.blinkBack = false;
53 |
54 | if (this.blinkBack) {
55 | this.alpha += 0.7 * this.easing * p.deltaTime;
56 | } else {
57 | this.alpha -= 0.7 * this.easing * p.deltaTime;
58 | }
59 | },
60 | draw() {
61 | p.clear();
62 | p.background(0);
63 | this.npc.handleCollisionsWith(this.player, () => {
64 | this.dialogBox.displayText(
65 | "I see that you need training.\nLet's battle !",
66 | async () => {
67 | await new Promise((resolve) => setTimeout(resolve, 1000));
68 | this.dialogBox.setVisibility(false);
69 | this.makeScreenFlash = true;
70 | await new Promise((resolve) => setTimeout(resolve, 1000));
71 | this.makeScreenFlash = false;
72 | setScene("battle");
73 | }
74 | );
75 | this.dialogBox.setVisibility(true);
76 | });
77 | this.map.draw(this.camera, this.player);
78 | this.npc.draw(this.camera);
79 | this.player.draw(this.camera);
80 | this.dialogBox.draw();
81 |
82 | if (this.makeScreenFlash) {
83 | p.fill(0, 0, 0, this.alpha);
84 | p.rect(0, 0, 512, 384);
85 | }
86 | },
87 |
88 | keyReleased() {
89 | for (const key of [
90 | p.RIGHT_ARROW,
91 | p.LEFT_ARROW,
92 | p.UP_ARROW,
93 | p.DOWN_ARROW,
94 | ]) {
95 | if (p.keyIsDown(key)) {
96 | return;
97 | }
98 | }
99 |
100 | switch (this.player.direction) {
101 | case "up":
102 | this.player.setAnim("idle-up");
103 | break;
104 | case "down":
105 | this.player.setAnim("idle-down");
106 | break;
107 | case "left":
108 | case "right":
109 | this.player.setAnim("idle-side");
110 | break;
111 | default:
112 | }
113 | },
114 | };
115 | }
116 |
--------------------------------------------------------------------------------
/maps/world.json:
--------------------------------------------------------------------------------
1 | { "compressionlevel":-1,
2 | "height":18,
3 | "infinite":false,
4 | "layers":[
5 | {
6 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11 | 0, 0, 0, 0, 58, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
12 | 0, 0, 0, 0, 66, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
13 | 0, 0, 0, 0, 66, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
14 | 0, 0, 0, 0, 66, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
15 | 0, 0, 0, 0, 66, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
16 | 0, 0, 0, 0, 66, 57, 57, 57, 57, 46, 47, 48, 57, 0, 0, 0, 0,
17 | 0, 0, 0, 0, 66, 57, 57, 57, 57, 54, 55, 56, 57, 0, 0, 0, 0,
18 | 0, 0, 0, 0, 66, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0,
19 | 0, 0, 0, 66, 66, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0,
20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
24 | "height":18,
25 | "id":2,
26 | "locked":true,
27 | "name":"Ground",
28 | "opacity":1,
29 | "type":"tilelayer",
30 | "visible":true,
31 | "width":17,
32 | "x":0,
33 | "y":0
34 | },
35 | {
36 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
38 | 0, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 0, 0, 0, 0,
39 | 0, 0, 0, 6, 18, 18, 18, 1, 11, 11, 11, 11, 14, 0, 0, 0, 0,
40 | 0, 0, 0, 14, 25, 25, 25, 9, 11, 11, 11, 11, 14, 0, 0, 0, 0,
41 | 0, 0, 0, 14, 0, 0, 0, 9, 11, 11, 11, 11, 14, 0, 0, 0, 0,
42 | 0, 0, 0, 14, 0, 0, 0, 9, 11, 11, 11, 11, 14, 0, 0, 0, 0,
43 | 0, 0, 0, 14, 0, 0, 0, 9, 11, 11, 11, 11, 14, 0, 0, 0, 0,
44 | 0, 0, 0, 14, 0, 0, 0, 17, 18, 19, 20, 21, 22, 0, 0, 0, 0,
45 | 0, 0, 0, 14, 0, 0, 0, 25, 26, 27, 28, 29, 30, 0, 0, 0, 0,
46 | 0, 0, 0, 14, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0,
47 | 0, 0, 0, 22, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0,
48 | 0, 0, 0, 30, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0,
49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
51 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
52 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
53 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
54 | "height":18,
55 | "id":1,
56 | "name":"Walls",
57 | "opacity":1,
58 | "type":"tilelayer",
59 | "visible":true,
60 | "width":17,
61 | "x":0,
62 | "y":0
63 | },
64 | {
65 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
66 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
67 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
68 | 0, 0, 0, 0, 35, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
69 | 0, 0, 0, 0, 43, 44, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
70 | 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
71 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
72 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
73 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
74 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0,
75 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0,
76 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
77 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
78 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
79 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
80 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
82 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
83 | "height":18,
84 | "id":3,
85 | "name":"Decorations",
86 | "opacity":1,
87 | "type":"tilelayer",
88 | "visible":true,
89 | "width":17,
90 | "x":0,
91 | "y":0
92 | },
93 | {
94 | "draworder":"topdown",
95 | "id":4,
96 | "name":"Boundaries",
97 | "objects":[
98 | {
99 | "height":256,
100 | "id":1,
101 | "name":"",
102 | "rotation":0,
103 | "type":"",
104 | "visible":true,
105 | "width":32,
106 | "x":96,
107 | "y":160
108 | },
109 | {
110 | "height":32,
111 | "id":2,
112 | "name":"",
113 | "rotation":0,
114 | "type":"",
115 | "visible":true,
116 | "width":96,
117 | "x":96,
118 | "y":128
119 | },
120 | {
121 | "height":160,
122 | "id":4,
123 | "name":"",
124 | "rotation":0,
125 | "type":"",
126 | "visible":true,
127 | "width":32,
128 | "x":224,
129 | "y":160
130 | },
131 | {
132 | "height":64,
133 | "id":5,
134 | "name":"",
135 | "rotation":0,
136 | "type":"",
137 | "visible":true,
138 | "width":96,
139 | "x":288,
140 | "y":256
141 | },
142 | {
143 | "height":128,
144 | "id":8,
145 | "name":"",
146 | "rotation":0,
147 | "type":"",
148 | "visible":true,
149 | "width":32,
150 | "x":256,
151 | "y":256
152 | },
153 | {
154 | "height":32,
155 | "id":10,
156 | "name":"",
157 | "rotation":0,
158 | "type":"",
159 | "visible":true,
160 | "width":32,
161 | "x":64,
162 | "y":416
163 | },
164 | {
165 | "height":32,
166 | "id":11,
167 | "name":"",
168 | "rotation":0,
169 | "type":"",
170 | "visible":true,
171 | "width":320,
172 | "x":96,
173 | "y":448
174 | },
175 | {
176 | "height":128,
177 | "id":12,
178 | "name":"",
179 | "rotation":0,
180 | "type":"",
181 | "visible":true,
182 | "width":32,
183 | "x":416,
184 | "y":320
185 | },
186 | {
187 | "height":64,
188 | "id":13,
189 | "name":"",
190 | "rotation":0,
191 | "type":"",
192 | "visible":true,
193 | "width":32,
194 | "x":192,
195 | "y":128
196 | },
197 | {
198 | "height":96,
199 | "id":15,
200 | "name":"",
201 | "rotation":0,
202 | "type":"",
203 | "visible":true,
204 | "width":32,
205 | "x":384,
206 | "y":256
207 | }],
208 | "opacity":1,
209 | "type":"objectgroup",
210 | "visible":true,
211 | "x":0,
212 | "y":0
213 | },
214 | {
215 | "draworder":"topdown",
216 | "id":5,
217 | "name":"SpawnPoints",
218 | "objects":[
219 | {
220 | "height":0,
221 | "id":17,
222 | "name":"player",
223 | "point":true,
224 | "rotation":0,
225 | "type":"",
226 | "visible":true,
227 | "width":0,
228 | "x":320,
229 | "y":320
230 | },
231 | {
232 | "height":0,
233 | "id":18,
234 | "name":"npc",
235 | "point":true,
236 | "rotation":0,
237 | "type":"",
238 | "visible":true,
239 | "width":0,
240 | "x":160,
241 | "y":160
242 | }],
243 | "opacity":1,
244 | "type":"objectgroup",
245 | "visible":true,
246 | "x":0,
247 | "y":0
248 | }],
249 | "nextlayerid":6,
250 | "nextobjectid":19,
251 | "orientation":"orthogonal",
252 | "renderorder":"right-down",
253 | "tiledversion":"1.10.2",
254 | "tileheight":32,
255 | "tilesets":[
256 | {
257 | "columns":8,
258 | "firstgid":1,
259 | "image":"..\/assets\/Trainer Tower interior.png",
260 | "imageheight":1760,
261 | "imagewidth":256,
262 | "margin":0,
263 | "name":"Trainer Tower interior",
264 | "spacing":0,
265 | "tilecount":440,
266 | "tileheight":32,
267 | "tilewidth":32
268 | }],
269 | "tilewidth":32,
270 | "type":"map",
271 | "version":"1.10",
272 | "width":17
273 | }
--------------------------------------------------------------------------------
/scenes/battle.js:
--------------------------------------------------------------------------------
1 | import { makeDialogBox } from "../entities/dialogBox.js";
2 |
3 | const states = {
4 | default: "default",
5 | introNpc: "intro-npc",
6 | introNpcPokemon: "intro-npc-pokemon",
7 | introPlayerPokemon: "intro-player-pokemon",
8 | playerTurn: "player-turn",
9 | playerAttack: "player-attack",
10 | npcTurn: "npc-turn",
11 | battleEnd: "battle-end",
12 | winnerDeclared: "winner-declared",
13 | };
14 |
15 | function makePokemon(name, x, finalX, y, maxHp, attacks, dataBox) {
16 | return {
17 | name,
18 | finalX,
19 | x,
20 | y,
21 | spriteRef: null,
22 | maxHp,
23 | hp: maxHp,
24 | attacks,
25 | selectedAttack: null,
26 | isFainted: false,
27 | dataBox,
28 | };
29 | }
30 |
31 | function makeDataBox(x, y, nameX, nameY, healthBarX, healthBarY) {
32 | return {
33 | x,
34 | y,
35 | nameOffset: {
36 | x: nameX,
37 | y: nameY,
38 | },
39 | healthBarOffset: {
40 | x: healthBarX,
41 | y: healthBarY,
42 | },
43 | spriteRef: null,
44 | maxHealthBarLength: 96,
45 | healthBarLength: 96,
46 | };
47 | }
48 |
49 | export function makeBattle(p) {
50 | return {
51 | dialogBox: makeDialogBox(p, 0, 288),
52 | currentState: "default",
53 | npc: {
54 | x: 350,
55 | y: 20,
56 | spriteRef: null,
57 | },
58 | npcPokemon: makePokemon(
59 | "VENUSAUR",
60 | 600,
61 | 310,
62 | 20,
63 | 100,
64 | [
65 | { name: "TACKLE", power: 10 },
66 | { name: "RAZOR LEAF", power: 55 },
67 | { name: "TAKE DOWN", power: 45 },
68 | { name: "POWER WHIP", power: 50 },
69 | ],
70 | makeDataBox(-300, 40, 15, 30, 118, 40)
71 | ),
72 | playerPokemon: makePokemon(
73 | "BLASTOISE",
74 | -170,
75 | 20,
76 | 128,
77 | 100,
78 | [
79 | { name: "TACKLE", power: 10 },
80 | { name: "HYDRO PUMP", power: 50 },
81 | { name: "HYDRO CANNON", power: 45 },
82 | { name: "WATER GUN", power: 50 },
83 | ],
84 | makeDataBox(510, 220, 38, 30, 136, 40)
85 | ),
86 | drawDataBox(pokemon) {
87 | p.image(pokemon.dataBox.spriteRef, pokemon.dataBox.x, pokemon.dataBox.y);
88 | p.text(
89 | pokemon.name,
90 | pokemon.dataBox.x + pokemon.dataBox.nameOffset.x,
91 | pokemon.dataBox.y + pokemon.dataBox.nameOffset.y
92 | );
93 |
94 | p.push();
95 | p.angleMode(p.DEGREES);
96 | p.rotate(360);
97 | p.noStroke();
98 | if (pokemon.dataBox.healthBarLength > 50) {
99 | p.fill(0, 200, 0);
100 | }
101 | if (pokemon.dataBox.healthBarLength < 50) {
102 | p.fill(255, 165, 0);
103 | }
104 | if (pokemon.dataBox.healthBarLength < 20) {
105 | p.fill(200, 0, 0);
106 | }
107 | p.rect(
108 | pokemon.dataBox.x + pokemon.dataBox.healthBarOffset.x,
109 | pokemon.dataBox.y + pokemon.dataBox.healthBarOffset.y,
110 | pokemon.dataBox.healthBarLength,
111 | 6
112 | );
113 | p.pop();
114 | },
115 | async dealDamage(targetPokemon, attackingPokemon) {
116 | targetPokemon.hp -= attackingPokemon.selectedAttack.power;
117 | if (targetPokemon.hp > 0) {
118 | targetPokemon.dataBox.healthBarLength =
119 | (targetPokemon.hp * targetPokemon.dataBox.maxHealthBarLength) /
120 | targetPokemon.maxHp;
121 | return;
122 | }
123 | targetPokemon.dataBox.healthBarLength = 0;
124 | targetPokemon.isFainted = true;
125 | await new Promise((resolve) => setTimeout(resolve, 1000));
126 | this.currentState = states.battleEnd;
127 | },
128 | load() {
129 | this.battleBackgroundImage = p.loadImage("assets/battle-background.png");
130 | this.npc.spriteRef = p.loadImage("assets/GENTLEMAN.png");
131 | this.npcPokemon.spriteRef = p.loadImage("assets/VENUSAUR.png");
132 | this.playerPokemon.spriteRef = p.loadImage("assets/BLASTOISE.png");
133 | this.playerPokemon.dataBox.spriteRef = p.loadImage(
134 | "assets/databox_thin.png"
135 | );
136 | this.npcPokemon.dataBox.spriteRef = p.loadImage(
137 | "assets/databox_thin_foe.png"
138 | );
139 | this.dialogBox.load();
140 | },
141 | setup() {
142 | this.dialogBox.displayText(
143 | "Mark the gentleman wants to battle !",
144 | async () => {
145 | await new Promise((resolve) => setTimeout(resolve, 2000));
146 | this.currentState = states.introNpc;
147 | this.dialogBox.clearText();
148 | this.dialogBox.displayText(
149 | `He sends out a ${this.npcPokemon.name} !`,
150 | async () => {
151 | this.currentState = states.introNpcPokemon;
152 | await new Promise((resolve) => setTimeout(resolve, 1000));
153 | this.dialogBox.clearText();
154 | this.dialogBox.displayText(
155 | `Go! ${this.playerPokemon.name} !`,
156 | async () => {
157 | this.currentState = states.introPlayerPokemon;
158 | await new Promise((resolve) => setTimeout(resolve, 1000));
159 | this.dialogBox.clearText();
160 | this.dialogBox.displayText(
161 | `What will ${this.playerPokemon.name} do ?`,
162 | async () => {
163 | await new Promise((resolve) => setTimeout(resolve, 1000));
164 | this.currentState = states.playerTurn;
165 | }
166 | );
167 | }
168 | );
169 | }
170 | );
171 | }
172 | );
173 | this.dialogBox.setVisibility(true);
174 | },
175 | update() {
176 | if (this.currentState === states.introNpc) {
177 | this.npc.x += 0.5 * p.deltaTime;
178 | }
179 |
180 | if (
181 | this.currentState === states.introNpcPokemon &&
182 | this.npcPokemon.x >= this.npcPokemon.finalX
183 | ) {
184 | this.npcPokemon.x -= 0.5 * p.deltaTime;
185 | if (this.npcPokemon.dataBox.x <= 0)
186 | this.npcPokemon.dataBox.x += 0.5 * p.deltaTime;
187 | }
188 |
189 | if (
190 | this.currentState === states.introPlayerPokemon &&
191 | this.playerPokemon.x <= this.playerPokemon.finalX
192 | ) {
193 | this.playerPokemon.x += 0.5 * p.deltaTime;
194 | this.playerPokemon.dataBox.x -= 0.65 * p.deltaTime;
195 | }
196 |
197 | if (this.playerPokemon.isFainted) {
198 | this.playerPokemon.y += 0.8 * p.deltaTime;
199 | }
200 |
201 | if (this.npcPokemon.isFainted) {
202 | this.npcPokemon.y += 0.8 * p.deltaTime;
203 | }
204 |
205 | this.dialogBox.update();
206 | },
207 | draw() {
208 | p.clear();
209 | p.background(0);
210 | p.image(this.battleBackgroundImage, 0, 0);
211 |
212 | p.image(this.npcPokemon.spriteRef, this.npcPokemon.x, this.npcPokemon.y);
213 |
214 | this.drawDataBox(this.npcPokemon);
215 |
216 | p.image(
217 | this.playerPokemon.spriteRef,
218 | this.playerPokemon.x,
219 | this.playerPokemon.y
220 | );
221 |
222 | this.drawDataBox(this.playerPokemon);
223 |
224 | if (
225 | this.currentState === states.default ||
226 | this.currentState === states.introNpc
227 | )
228 | p.image(this.npc.spriteRef, this.npc.x, this.npc.y);
229 |
230 | if (
231 | this.currentState === states.playerTurn &&
232 | !this.playerPokemon.selectedAttack
233 | ) {
234 | this.dialogBox.displayTextImmediately(
235 | `1) ${this.playerPokemon.attacks[0].name} 3) ${this.playerPokemon.attacks[2].name}\n2) ${this.playerPokemon.attacks[1].name} 4) ${this.playerPokemon.attacks[3].name}`
236 | );
237 | }
238 |
239 | if (
240 | this.currentState === states.playerTurn &&
241 | this.playerPokemon.selectedAttack &&
242 | !this.playerPokemon.isAttacking &&
243 | !this.playerPokemon.isFainted
244 | ) {
245 | this.dialogBox.clearText();
246 | this.dialogBox.displayText(
247 | `${this.playerPokemon.name} used ${this.playerPokemon.selectedAttack.name} !`,
248 | async () => {
249 | await this.dealDamage(this.npcPokemon, this.playerPokemon);
250 | if (this.currentState !== states.battleEnd) {
251 | await new Promise((resolve) => setTimeout(resolve, 1000));
252 | this.dialogBox.clearText();
253 | this.currentState = states.npcTurn;
254 | }
255 | }
256 | );
257 | this.playerPokemon.isAttacking = true;
258 | }
259 |
260 | if (this.currentState === states.npcTurn && !this.npcPokemon.isFainted) {
261 | this.npcPokemon.selectedAttack =
262 | this.npcPokemon.attacks[
263 | Math.floor(Math.random() * this.npcPokemon.attacks.length)
264 | ];
265 | this.dialogBox.clearText();
266 | this.dialogBox.displayText(
267 | `The foe's ${this.npcPokemon.name} used ${this.npcPokemon.selectedAttack.name} !`,
268 | async () => {
269 | await this.dealDamage(this.playerPokemon, this.npcPokemon);
270 | if (this.currentState !== states.battleEnd) {
271 | await new Promise((resolve) => setTimeout(resolve, 1000));
272 | this.playerPokemon.selectedAttack = null;
273 | this.playerPokemon.isAttacking = false;
274 | }
275 | }
276 | );
277 | this.currentState = states.playerTurn;
278 | }
279 |
280 | if (this.currentState === states.battleEnd) {
281 | if (this.npcPokemon.isFainted) {
282 | this.dialogBox.clearText();
283 | this.dialogBox.displayText(
284 | `${this.npcPokemon.name} fainted ! You won !`
285 | );
286 | this.currentState = states.winnerDeclared;
287 | return;
288 | }
289 |
290 | if (this.playerPokemon.isFainted) {
291 | this.dialogBox.clearText();
292 | this.dialogBox.displayText(
293 | `${this.playerPokemon.name} fainted ! You lost !`
294 | );
295 | this.currentState = states.winnerDeclared;
296 | }
297 | }
298 |
299 | p.rect(0, 288, 512, 200);
300 | this.dialogBox.draw();
301 | },
302 | onKeyPressed(keyEvent) {
303 | if (this.currentState === states.playerTurn) {
304 | switch (keyEvent.key) {
305 | case "1":
306 | this.playerPokemon.selectedAttack = this.playerPokemon.attacks[0];
307 | break;
308 | case "2":
309 | this.playerPokemon.selectedAttack = this.playerPokemon.attacks[1];
310 | break;
311 | case "3":
312 | this.playerPokemon.selectedAttack = this.playerPokemon.attacks[2];
313 | break;
314 | case "4":
315 | this.playerPokemon.selectedAttack = this.playerPokemon.attacks[3];
316 | break;
317 | default:
318 | }
319 | }
320 | },
321 | };
322 | }
323 |
--------------------------------------------------------------------------------