From 0cc87b4ba0deb8f9a0b35d61141590fdd33809a2 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 4 Jun 2023 21:47:43 -0400 Subject: [PATCH] Add sprite shadows and update engine --- package-lock.json | 47 +++----------- package.json | 2 +- src/battle-scene.ts | 4 +- src/data/battle-anims.ts | 28 ++++---- src/data/battler-tag.ts | 3 +- src/data/move.ts | 5 +- src/evolution-phase.ts | 24 +++---- src/pipelines/sprite.ts | 121 ++++++++++++++++++++++++++++++++--- src/pokemon.ts | 7 +- src/ui/message-ui-handler.ts | 4 +- src/utils.ts | 4 ++ 11 files changed, 157 insertions(+), 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index 847d6301b..9395668b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "pokemon-rogue-battle", "version": "0.0.1", "dependencies": { - "phaser": "^3.55.2", + "phaser": "^3.60.0", "phaser3-rex-plugins": "^1.1.84" }, "devDependencies": { @@ -1059,9 +1059,9 @@ } }, "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -1889,15 +1889,6 @@ "node": ">= 0.8" } }, - "node_modules/path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", - "dependencies": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1938,12 +1929,11 @@ "dev": true }, "node_modules/phaser": { - "version": "3.55.2", - "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.55.2.tgz", - "integrity": "sha512-amKXsbb2Ht29dGPKvt1edq3yGGYKtq8373GpJYGKPNPnneYY6MtVTOgjHDuZwtmUyK4v86FugkT3hzW/N4tjxQ==", + "version": "3.60.0", + "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.60.0.tgz", + "integrity": "sha512-IKUy35EnoEVcl2EmJ8WOyK4X8OoxHYdlhZLgRGpNrvD1fEagYffhVmwHcapE/tGiLgyrnezmXIo5RrH2NcrTHw==", "dependencies": { - "eventemitter3": "^4.0.7", - "path": "^0.12.7" + "eventemitter3": "^5.0.0" } }, "node_modules/phaser3-rex-plugins": { @@ -2003,14 +1993,6 @@ "node": ">= 0.8.0" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2419,19 +2401,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 8ab38d717..4efb944b3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "vite-plugin-fs": "^1.0.0-beta.6" }, "dependencies": { - "phaser": "^3.55.2", + "phaser": "^3.60.0", "phaser3-rex-plugins": "^1.1.84" } } diff --git a/src/battle-scene.ts b/src/battle-scene.ts index eabcdd262..133360859 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -239,7 +239,7 @@ export default class BattleScene extends Phaser.Scene { this.loadAtlas('battle_stats', 'effects'); this.loadAtlas('shiny', 'effects'); this.loadImage('evo_sparkle', 'effects'); - this.load.video('evo_bg', 'images/effects/evo_bg.mp4', null, false, true); + this.load.video('evo_bg', 'images/effects/evo_bg.mp4', true); this.loadAtlas('pb', ''); this.loadAtlas('items', ''); @@ -283,8 +283,6 @@ export default class BattleScene extends Phaser.Scene { this.loadBgm('level_up_fanfare'); this.loadBgm('evolution'); this.loadBgm('evolution_fanfare'); - - //this.load.glsl('sprite', 'shaders/sprite.frag'); populateAnims(); } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index ac6f3613d..c6c6a0c8d 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -304,8 +304,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent { if (Object.keys(tweenProps).length) { scene.tweens.add(Object.assign({ targets: moveAnim.bgSprite, - duration: this.duration * 3, - useFrames: true + duration: Utils.getFrameMs(this.duration * 3) }, tweenProps)) } return this.duration * 2; @@ -331,12 +330,11 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { moveAnim.bgSprite.setScale(1.25); moveAnim.bgSprite.setAlpha(this.opacity / 255); scene.field.add(moveAnim.bgSprite); - scene.field.moveBelow(moveAnim.bgSprite, scene.getEnemyPokemon() || scene.getPlayerPokemon()); + scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon()); scene.tweens.add({ targets: moveAnim.bgSprite, - duration: this.duration * 3, - useFrames: true + duration: Utils.getFrameMs(this.duration * 3) }); return this.duration * 2; @@ -642,8 +640,7 @@ export abstract class BattleAnim { const spritePriorities: integer[] = []; scene.tweens.addCounter({ - useFrames: true, - duration: 3, + duration: Utils.getFrameMs(3), repeat: anim.frames.length, onRepeat: () => { if (!f) { @@ -666,7 +663,7 @@ export abstract class BattleAnim { const spriteSource = isUser ? userSprite : targetSprite; let sprite: Phaser.GameObjects.Sprite; sprite = scene.add.sprite(0, 0, spriteSource.texture, spriteSource.frame.name); - sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ] }); + sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: true }); spriteSource.on('animationupdate', (_anim, frame) => sprite.setFrame(frame.textureFrame)); scene.field.add(sprite); sprites.push(sprite); @@ -702,7 +699,7 @@ export abstract class BattleAnim { const setSpritePriority = (priority: integer) => { switch (priority) { case 0: - scene.field.moveBelow(moveSprite, scene.getEnemyPokemon() || scene.getPlayerPokemon()); + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon()); break; case 1: scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); @@ -711,12 +708,12 @@ export abstract class BattleAnim { switch (frame.focus) { case AnimFocus.USER: if (this.bgSprite) - scene.field.moveAbove(moveSprite, this.bgSprite); + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); else - scene.field.moveBelow(moveSprite, this.user); + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user); break; case AnimFocus.TARGET: - scene.field.moveBelow(moveSprite, this.target); + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target); break; default: setSpritePriority(1); @@ -726,10 +723,10 @@ export abstract class BattleAnim { case 3: switch (frame.focus) { case AnimFocus.USER: - scene.field.moveAbove(moveSprite, this.user); + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user); break; case AnimFocus.TARGET: - scene.field.moveAbove(moveSprite, this.target); + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target); break; default: setSpritePriority(1); @@ -804,8 +801,7 @@ export abstract class BattleAnim { } if (r) { scene.tweens.addCounter({ - duration: r, - useFrames: true, + duration: Utils.getFrameMs(r), onComplete: () => cleanUpAndComplete() }); } else diff --git a/src/data/battler-tag.ts b/src/data/battler-tag.ts index 27572f710..37d46199b 100644 --- a/src/data/battler-tag.ts +++ b/src/data/battler-tag.ts @@ -565,8 +565,7 @@ export class HideSpriteTag extends BattlerTag { onRemove(pokemon: Pokemon): void { // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible pokemon.scene.tweens.addCounter({ - duration: 2, - useFrames: true, + duration: Utils.getFrameMs(2), onComplete: () => pokemon.setVisible(true) }); } diff --git a/src/data/move.ts b/src/data/move.ts index ab502749b..db5dbc751 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2922,9 +2922,10 @@ export function initMoves() { new StatusMove(Moves.BLOCK, "Block", Type.NORMAL, -1, 5, -1, "Opponent cannot flee or switch.", -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true), new SelfStatusMove(Moves.HOWL, "Howl", Type.NORMAL, -1, 40, -1, "Raises Attack of allies.", -1, 0, 3) - .attr(StatChangeAttr, BattleStat.ATK, 1, true), // TODO + .attr(StatChangeAttr, BattleStat.ATK, 1, true) + .target(MoveTarget.USER_AND_ALLIES), // TODO new AttackMove(Moves.DRAGON_CLAW, "Dragon Claw", Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 15, 78, "", -1, 0, 3) - .target(MoveTarget.USER_AND_ALLIES), + .target(MoveTarget.NEAR_OTHER), new AttackMove(Moves.FRENZY_PLANT, "Frenzy Plant", Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, 155, "User must recharge next turn.", -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true), new SelfStatusMove(Moves.BULK_UP, "Bulk Up", Type.FIGHTING, -1, 20, 64, "Raises user's Attack and Defense.", -1, 0, 3) diff --git a/src/evolution-phase.ts b/src/evolution-phase.ts index 3868640e9..ed049caae 100644 --- a/src/evolution-phase.ts +++ b/src/evolution-phase.ts @@ -204,8 +204,7 @@ export class EvolutionPhase extends BattlePhase { this.scene.tweens.addCounter({ repeat: 64, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { if (f < 64) { if (!(f & 7)) { @@ -223,8 +222,7 @@ export class EvolutionPhase extends BattlePhase { this.scene.tweens.addCounter({ repeat: 96, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { if (f < 96) { if (f < 6) { @@ -270,8 +268,7 @@ export class EvolutionPhase extends BattlePhase { this.scene.tweens.addCounter({ repeat: 48, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { if (!f) { for (let i = 0; i < 16; i++) @@ -290,8 +287,7 @@ export class EvolutionPhase extends BattlePhase { this.scene.tweens.addCounter({ repeat: 48, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { if (!f) { for (let i = 0; i < 8; i++) @@ -313,8 +309,7 @@ export class EvolutionPhase extends BattlePhase { const particleTimer = this.scene.tweens.addCounter({ repeat: -1, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { updateParticle(); } @@ -350,8 +345,7 @@ export class EvolutionPhase extends BattlePhase { const particleTimer = this.scene.tweens.addCounter({ repeat: -1, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { updateParticle(); } @@ -383,8 +377,7 @@ export class EvolutionPhase extends BattlePhase { const particleTimer = this.scene.tweens.addCounter({ repeat: -1, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { updateParticle(); } @@ -419,8 +412,7 @@ export class EvolutionPhase extends BattlePhase { const particleTimer = this.scene.tweens.addCounter({ repeat: -1, - duration: 1, - useFrames: true, + duration: Utils.getFrameMs(1), onRepeat: () => { updateParticle(); } diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index b8c98f448..3a02ece1d 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -1,6 +1,7 @@ -const spriteFragShader = ` -#define SHADER_NAME PHASER_MULTI_FS +import BattleScene from "../battle-scene"; +import Pokemon from "../pokemon"; +const spriteFragShader = ` #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else @@ -11,14 +12,21 @@ uniform sampler2D uMainSampler[%count%]; varying vec2 outTexCoord; varying float outTexId; +varying vec2 outPosition; varying float outTintEffect; varying vec4 outTint; +uniform bool hasShadow; +uniform bool yCenter; +uniform float vCutoff; +uniform vec2 relPosition; +uniform vec2 size; +uniform float yOffset; uniform vec4 tone; const vec3 lumaF = vec3(.299, .587, .114); -void main () +void main() { vec4 texture; @@ -47,41 +55,138 @@ void main () /* Apply tone */ color.rgb += tone.rgb * (color.a / 255.0); + if (hasShadow) { + float width = size.x - (yOffset / 2.0); + + float spriteX = ((floor(outPosition.x / 6.0) - relPosition.x) / width) + 0.5; + float spriteY = ((floor(outPosition.y / 6.0) - relPosition.y) / size.y); + + if (yCenter) { + spriteY += 0.5; + } else { + spriteY += 1.0; + } + + bool yOverflow = outTexCoord.y >= vCutoff; + + if ((spriteY >= 0.9 && (color.a == 0.0 || yOverflow))) { + float shadowSpriteY = (spriteY - 0.9) * (1.0 / 0.15); + if (distance(vec2(spriteX, shadowSpriteY), vec2(0.5, 0.5)) < 0.5) { + color = vec4(vec3(0.0, 0.0, 0.0), 0.5); + } else if (yOverflow) { + discard; + } + } else if (yOverflow) { + discard; + } + } + gl_FragColor = color; } `; -export default class SpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline +const spriteVertShader = ` +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +uniform mat4 uProjectionMatrix; + +attribute vec2 inPosition; +attribute vec2 inTexCoord; +attribute float inTexId; +attribute float inTintEffect; +attribute vec4 inTint; + +varying vec2 outTexCoord; +varying float outTexId; +varying vec2 outPosition; +varying float outTintEffect; +varying vec4 outTint; + +void main() { + gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); + + outTexCoord = inTexCoord; + outTexId = inTexId; + outPosition = inPosition; + outTint = inTint; + outTintEffect = inTintEffect; +} +`; + +export default class SpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline { private _tone: number[]; constructor(game: Phaser.Game) { super({ game: game, name: 'sprite', - fragShader: spriteFragShader + fragShader: spriteFragShader, + vertShader: spriteVertShader }); this._tone = [ 0, 0, 0, 0 ]; } onPreRender(): void { + this.setBoolean('hasShadow', false); + this.setBoolean('yCenter', false); + this.set2f('relPosition', 0, 0); + this.set2f('size', 0, 0); + this.set1f('yOffset', 0); this.set4f('tone', this._tone[0], this._tone[1], this._tone[2], this._tone[3]); } onBind(gameObject: Phaser.GameObjects.GameObject): void { super.onBind(); - const data = (gameObject as Phaser.GameObjects.Sprite).pipelineData; - const tone = data['tone'] as number[]; + const sprite = (gameObject as Phaser.GameObjects.Sprite); + const data = sprite.pipelineData; + const tone = data['tone'] as number[]; + const hasShadow = data['hasShadow'] as boolean; + + const position = sprite.parentContainer instanceof Pokemon + ? [ sprite.parentContainer.x, sprite.parentContainer.y ] + : [ sprite.x, sprite.y ]; + position[0] += -(sprite.width - sprite.frame.width) / 2 + sprite.frame.x; + this.setBoolean('hasShadow', hasShadow); + this.setBoolean('yCenter', sprite.originY === 0.5); + this.set2f('relPosition', position[0], position[1]); + this.set2f('size', sprite.frame.width, sprite.height); + this.set1f('yOffset', sprite.height - sprite.frame.height); this.set4f('tone', tone[0], tone[1], tone[2], tone[3]); } onBatch(gameObject: Phaser.GameObjects.GameObject): void { - if (gameObject) { + if (gameObject) this.flush(); + } + + batchQuad(gameObject: Phaser.GameObjects.GameObject, x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, + u0: number, v0: number, u1: number, v1: number, tintTL: number, tintTR: number, tintBL: number, tintBR: number, tintEffect: number | boolean, + texture?: WebGLTexture, unit?: number): boolean { + const sprite = gameObject as Phaser.GameObjects.Sprite; + + this.set1f('vCutoff', v1); + + const hasShadow = sprite.pipelineData['hasShadow'] as boolean; + if (hasShadow) { + const baseY = (sprite.parentContainer instanceof Pokemon + ? sprite.parentContainer.y + : sprite.y + sprite.height / 2) * 6; + const bottomPadding = Math.ceil(sprite.height * 6 * 0.05); + const yDelta = (baseY - y1) / 6; + y2 = y1 = baseY + bottomPadding; + const pixelHeight = (v1 - v0) / sprite.frame.height; + v1 += (yDelta + bottomPadding / 6) * pixelHeight; } + + return super.batchQuad(gameObject, x0, y0, x1, y1, x2, y2, x3, y3, u0, v0, u1, v1, tintTL, tintTR, tintBL, tintBR, tintEffect, texture, unit); } get tone(): number[] { diff --git a/src/pokemon.ts b/src/pokemon.ts index 90b920925..d8d07ff8c 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -153,14 +153,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.battleInfo.initInfo(this); - const getSprite = () => { + const getSprite = (hasShadow?: boolean) => { const ret = this.scene.add.sprite(0, 0, `pkmn__${this.isPlayer() ? 'back__' : ''}sub`); ret.setOrigin(0.5, 1); - ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ] }); + ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow }); + ret.preFX.add return ret; }; - const sprite = getSprite(); + const sprite = getSprite(true); const tintSprite = getSprite(); tintSprite.setVisible(false); diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index 2d5c9cef8..7086a793e 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -1,6 +1,7 @@ import BattleScene from "../battle-scene"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { Mode } from "./ui"; +import * as Utils from "../utils"; export default abstract class MessageUiHandler extends AwaitableUiHandler { protected textTimer: Phaser.Time.TimerEvent; @@ -80,8 +81,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { if (charDelay) { this.textTimer.paused = true; this.scene.tweens.addCounter({ - duration: charDelay, - useFrames: true, + duration: Utils.getFrameMs(charDelay), onComplete: () => { this.textTimer.paused = false; advance(); diff --git a/src/utils.ts b/src/utils.ts index 36b5fc023..555159f20 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,6 +30,10 @@ export function randInt(range: integer, min?: integer): integer { return Math.floor(Math.random() * range) + min; } +export function getFrameMs(frameCount: integer): integer { + return Math.floor((1 / 60) * 1000 * frameCount); +} + export function binToDec(input: string): integer { let place:integer[] = []; let binary:string[] = [];