├── 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 | ![Screenshot of the game](./screenshot.png) 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 | --------------------------------------------------------------------------------