Last active
May 31, 2025 19:05
-
-
Save easierbycode/202f5eaea5423329593fb22eab641391 to your computer and use it in GitHub Desktop.
hero.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { isset } from './utils.js'; | |
const HERO_DEPTH = 50; | |
const IS_DEV = new URL(window.location.href).searchParams.get("debug") == "1"; | |
// Running | |
const RUNNING_RIGHT = 'RUNNING_RIGHT'; | |
const RUNNING_LEFT = 'RUNNING_LEFT'; | |
// Walking | |
const WALKING_RIGHT = 'WALKING_RIGHT'; | |
const WALKING_LEFT = 'WALKING_LEFT'; | |
// Jumping | |
const JUMPING_START = 'JUMPING_START'; | |
const JUMPING = 'JUMPING'; | |
const JUMPING_RIGHT = 'JUMPING_RIGHT'; | |
const JUMPING_LEFT = 'JUMPING_LEFT'; | |
const BOOSTING_JUMP = 'BOOSTING_JUMP'; | |
const BOOSTING_JUMP_RIGHT = 'BOOSTING_JUMP_RIGHT'; | |
const BOOSTING_JUMP_LEFT = 'BOOSTING_JUMP_LEFT'; | |
const JUMPING_START_RIGHT = 'JUMPING_START_RIGHT'; | |
const JUMPING_START_LEFT = 'JUMPING_START_LEFT'; | |
// Running && Jumping | |
const RUN_JUMPING_START = 'RUN_JUMPING_START'; | |
const RUN_JUMPING = 'RUN_JUMPING'; | |
const RUN_JUMPING_RIGHT = 'RUN_JUMPING_RIGHT'; | |
const RUN_JUMPING_LEFT = 'RUN_JUMPING_LEFT'; | |
const RUN_BOOSTING_JUMP = 'RUN_BOOSTING_JUMP'; | |
const RUN_BOOSTING_JUMP_RIGHT = 'RUN_BOOSTING_JUMP_RIGHT'; | |
const RUN_BOOSTING_JUMP_LEFT = 'RUN_BOOSTING_JUMP_LEFT'; | |
const RUN_JUMPING_START_RIGHT = 'RUN_JUMPING_START_RIGHT'; | |
const RUN_JUMPING_START_LEFT = 'RUN_JUMPING_START_LEFT'; | |
// Falling | |
const FALLING = 'FALLING'; | |
const FALLING_RIGHT = 'FALLING_RIGHT'; | |
const FALLING_LEFT = 'FALLING_LEFT'; | |
// Running && Falling | |
const RUN_FALLING = 'RUN_FALLING'; | |
const RUN_FALLING_RIGHT = 'RUN_FALLING_RIGHT'; | |
const RUN_FALLING_LEFT = 'RUN_FALLING_LEFT'; | |
// Attacking | |
const ATTACKING_START = 'ATTACKING_START'; | |
// Idle | |
const IDLE = 'IDLE'; | |
class Hero extends Phaser.GameObjects.Sprite { | |
constructor({ | |
scene, | |
x = 0, | |
y = 0, | |
enablePhysics = true, | |
addToScene = true, | |
asset = 'player', | |
frame, | |
gamepad | |
}) { | |
super(scene, x, y, asset, frame); | |
this.setDepth(HERO_DEPTH); | |
this.setOrigin(0, 1); | |
// Properties | |
this.gamepad = gamepad; | |
this.enablePhysics = enablePhysics; | |
this.attackDuration = 300; | |
this.jumpTimer = 0; | |
this.runTimer = 0; | |
this.stopRunTimer = 0; | |
this.delayStopRunning = false; | |
this.pressedRunRight = false; | |
this.pressedRunLeft = false; | |
this.heroState = IDLE; | |
if (addToScene) { | |
scene.add.existing(this); | |
} | |
if (enablePhysics) { | |
scene.physics.add.existing(this); | |
this.body.setDrag(1000, 0); | |
this.body.setMaxVelocity(150, 400); | |
// this.body.setSize(18, 26); | |
// this.body.setOffset(12, 0); | |
this.body.setBounce(0, 0); | |
} | |
this.createAnimations(); | |
this.setAnimation('idle'); | |
// Keep track of previous button state for 'just pressed' logic | |
this.prevGamepadButtons = Array(20).fill(false); | |
if (IS_DEV) { | |
this.debugText = this.scene.add.text(0, 0, ''); | |
this.debugText.setDepth(900); | |
this.debugText.setFontSize(12); | |
} | |
const { | |
LEFT: left, | |
RIGHT: right, | |
UP: up, | |
DOWN: down, | |
W: w, | |
A: a, | |
S: s, | |
D: d, | |
SPACE: space, | |
F: f, | |
SHIFT: shift, | |
ENTER: enter, | |
} = Phaser.Input.Keyboard.KeyCodes; | |
this.controlKeys = scene.input.keyboard.addKeys({ | |
left, | |
right, | |
up, | |
down, | |
w, | |
a, | |
d, | |
space, | |
f, | |
shift, | |
enter, | |
}); | |
} | |
createAnimations = () => { | |
const assetKey = this.texture.key; | |
// if (!this.scene.anims.exists(`${assetKey}_idle`)) { | |
// this.scene.anims.create({ | |
// key: `${assetKey}_idle`, | |
// frames: this.scene.anims.generateFrameNames(assetKey, { | |
// frames: [ | |
// 'hero_idle_01', | |
// 'hero_idle_02', | |
// 'hero_idle_03', | |
// ], | |
// }), | |
// frameRate: 4, | |
// // yoyo: true, | |
// repeat: -1, | |
// }); | |
// } | |
// if (!this.scene.anims.exists(`${assetKey}_walk`)) { | |
// this.scene.anims.create({ | |
// key: `${assetKey}_walk`, | |
// frames: this.scene.anims.generateFrameNames(assetKey, { | |
// frames: [ | |
// 'hero_run_01', | |
// 'hero_run_02', | |
// 'hero_run_03', | |
// 'hero_run_04', | |
// 'hero_run_05', | |
// 'hero_run_06', | |
// 'hero_run_07', | |
// 'hero_run_08', | |
// ], | |
// }), | |
// frameRate: 10, | |
// // yoyo: true, | |
// repeat: -1, | |
// }); | |
// } | |
// if (!this.scene.anims.exists(`${assetKey}_jump`)) { | |
// this.scene.anims.create({ | |
// key: `${assetKey}_jump`, | |
// frames: this.scene.anims.generateFrameNames(assetKey, { | |
// frames: [ | |
// 'hero_jump_01', | |
// ], | |
// }), | |
// frameRate: 10, | |
// // yoyo: true, | |
// repeat: 0, | |
// }); | |
// } | |
// if (!this.scene.anims.exists(`${assetKey}_attack`)) { | |
// this.scene.anims.create({ | |
// key: `${assetKey}_attack`, | |
// frames: this.scene.anims.generateFrameNames(assetKey, { | |
// frames: [ | |
// 'hero_attack_01', | |
// ], | |
// }), | |
// frameRate: (5 * 1000) / this.attackDuration, | |
// // yoyo: true, | |
// repeat: 0, | |
// }); | |
// } | |
// if (!this.scene.anims.exists(`${assetKey}_run`)) { | |
// this.scene.anims.create({ | |
// key: `${assetKey}_run`, | |
// frames: this.scene.anims.generateFrameNames(assetKey, { | |
// frames: [ | |
// 'hero_run_01', | |
// 'hero_run_02', | |
// 'hero_run_03', | |
// 'hero_run_04', | |
// 'hero_run_05', | |
// 'hero_run_06', | |
// 'hero_run_07', | |
// 'hero_run_08', | |
// ], | |
// }), | |
// frameRate: 20, | |
// repeat: -1, | |
// }); | |
// } | |
// Load animations from Aseprite | |
const createdAnimations = this.anims.createFromAseprite(assetKey); | |
createdAnimations.forEach(animData => { | |
const anim = this.anims.get(animData.key); | |
if (anim) { | |
anim.repeat = -1; | |
} | |
}); | |
}; | |
setAnimation = (animationName, ignoreIfPlaying = true) => { | |
if (!isset(this.anims) || this.currentAnimationName === animationName) { | |
return; | |
} | |
const assetKey = this.texture.key; | |
const animationKey = `${assetKey}_${animationName}`; | |
this.currentAnimationName = animationName; | |
this.currentAnimationKey = animationKey; | |
this.anims.play(animationKey, ignoreIfPlaying); | |
}; | |
setHeroState(heroState) { | |
if (IS_DEV && this.heroState !== heroState) { | |
console.log(heroState); | |
this.debugText.setText(heroState); | |
} | |
this.heroState = heroState; | |
} | |
// Handle button controllers | |
isRightDown() { | |
let gamepadPressed = false; | |
if (this.gamepad && this.gamepad.buttons[15]) { | |
gamepadPressed = this.gamepad.buttons[15].pressed; | |
} | |
return this.controlKeys.right.isDown | |
|| this.controlKeys.d.isDown | |
|| gamepadPressed; | |
} | |
isLeftDown() { | |
let gamepadPressed = false; | |
if (this.gamepad && this.gamepad.buttons[14]) { | |
gamepadPressed = this.gamepad.buttons[14].pressed; | |
} | |
return this.controlKeys.left.isDown | |
|| this.controlKeys.a.isDown | |
|| gamepadPressed; | |
} | |
isUpDown() { | |
let gamepadPressed = false; | |
if (this.gamepad && this.gamepad.buttons[12]) { | |
gamepadPressed = this.gamepad.buttons[12].pressed; | |
} | |
return this.controlKeys.up.isDown | |
|| this.controlKeys.w.isDown | |
|| gamepadPressed; | |
} | |
isDownDown() { | |
let gamepadPressed = false; | |
if (this.gamepad && this.gamepad.buttons[13]) { | |
gamepadPressed = this.gamepad.buttons[13].pressed; | |
} | |
return this.controlKeys.down.isDown | |
|| this.controlKeys.s.isDown | |
|| gamepadPressed; | |
} | |
isAButtonDown() { | |
let gamepadPressed = false; | |
if (this.gamepad && this.gamepad.buttons[0]) { | |
gamepadPressed = this.gamepad.buttons[0].pressed; | |
} | |
return this.controlKeys.space.isDown | |
|| gamepadPressed; | |
} | |
isBButtonDown() { | |
let gamepadPressed = false; | |
if (this.gamepad && this.gamepad.buttons[1]) { | |
gamepadPressed = this.gamepad.buttons[1].pressed; | |
} | |
return this.controlKeys.f.isDown | |
|| this.controlKeys.enter.isDown | |
|| gamepadPressed; | |
} | |
isRightJustDown() { | |
const gamepadJustDown = this._justGamepadDown(15); | |
return Phaser.Input.Keyboard.JustDown(this.controlKeys.f) | |
|| Phaser.Input.Keyboard.JustDown(this.controlKeys.enter) | |
|| gamepadJustDown; | |
} | |
_justGamepadDown(buttonIndex) { | |
// Returns true if gamepad button is pressed this frame, but not in the previous frame | |
if (!this.gamepad || !this.gamepad.buttons[buttonIndex]) { | |
return false; | |
} | |
const pressedNow = this.gamepad.buttons[buttonIndex].pressed; | |
const wasPressed = this.prevGamepadButtons[buttonIndex]; | |
return pressedNow && !wasPressed; | |
} | |
isLeftJustDown() { | |
const gamepadJustDown = this._justGamepadDown(14); | |
return Phaser.Input.Keyboard.JustDown(this.controlKeys.left) | |
|| Phaser.Input.Keyboard.JustDown(this.controlKeys.a) | |
|| gamepadJustDown; | |
} | |
isUpJustDown() { | |
const gamepadJustDown = this._justGamepadDown(12); | |
return Phaser.Input.Keyboard.JustDown(this.controlKeys.up) | |
|| Phaser.Input.Keyboard.JustDown(this.controlKeys.w) | |
|| gamepadJustDown; | |
} | |
isAButtonJustDown() { | |
const gamepadJustDown = this._justGamepadDown(0); | |
return Phaser.Input.Keyboard.JustDown(this.controlKeys.space) | |
|| gamepadJustDown; | |
} | |
isBButtonJustDown() { | |
const gamepadJustDown = this._justGamepadDown(1); | |
return Phaser.Input.Keyboard.JustDown(this.controlKeys.f) | |
|| Phaser.Input.Keyboard.JustDown(this.controlKeys.enter) | |
|| gamepadJustDown; | |
} | |
calculateHeroAccelerationX() { | |
if (this.isHeroRunning()) { | |
this.body.setMaxVelocity(250, 400); | |
return 600; | |
} | |
this.body.setMaxVelocity(150, 400); | |
if (this.isHeroJumping() || this.isHeroFalling()) { | |
return 200; | |
} | |
return 600; | |
} | |
calculateHeroAccelerationY() { | |
this.body.setMaxVelocity(150, 400); | |
return 200; | |
} | |
// Handle is hero jumping | |
isHeroJumping() { | |
// Is hero jumping? Doesn't the other states | |
return this.isHeroJumpingStraight() | |
|| this.isHeroJumpingLeft() | |
|| this.isHeroJumpingRight(); | |
} | |
isHeroJumpingWhileRunning() { | |
return this.isHeroJumpingStraightWhileRunning() | |
|| this.isHeroJumpingLeftWhileRunning() | |
|| this.isHeroJumpingRightWhileRunning(); | |
} | |
isHeroJumpingWhileWalking() { | |
return this.isHeroJumpingStraightWhileWalking() | |
|| this.isHeroJumpingLeftWhileWalking() | |
|| this.isHeroJumpingRightWhileWalking(); | |
} | |
isHeroJumpingStraight() { | |
return this.isHeroJumpingStraightWhileRunning() | |
|| this.isHeroJumpingStraightWhileWalking(); | |
} | |
isHeroJumpingStraightWhileRunning() { | |
return [ | |
RUN_JUMPING_START, | |
RUN_BOOSTING_JUMP, | |
RUN_JUMPING, | |
].includes(this.heroState); | |
} | |
isHeroJumpingStraightWhileWalking() { | |
return [ | |
JUMPING_START, | |
BOOSTING_JUMP, | |
JUMPING, | |
].includes(this.heroState); | |
} | |
isHeroJumpingLeft() { | |
return this.isHeroJumpingLeftWhileRunning() | |
|| this.isHeroJumpingLeftWhileWalking(); | |
} | |
isHeroJumpingLeftWhileRunning() { | |
return [ | |
RUN_JUMPING_START_LEFT, | |
RUN_BOOSTING_JUMP_LEFT, | |
RUN_JUMPING_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroJumpingLeftWhileWalking() { | |
return [ | |
JUMPING_START_LEFT, | |
BOOSTING_JUMP_LEFT, | |
JUMPING_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroJumpingRight() { | |
return this.isHeroJumpingRightWhileRunning() | |
|| this.isHeroJumpingRightWhileWalking(); | |
} | |
isHeroJumpingRightWhileRunning() { | |
return [ | |
RUN_JUMPING_START_RIGHT, | |
RUN_BOOSTING_JUMP_RIGHT, | |
RUN_JUMPING_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroJumpingRightWhileWalking() { | |
return [ | |
JUMPING_START_RIGHT, | |
BOOSTING_JUMP_RIGHT, | |
JUMPING_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroFalling() { | |
return this.isHeroFallingStraight() | |
|| this.isHeroFallingLeft() | |
|| this.isHeroFallingRight(); | |
} | |
isHeroFallingWhileWalking() { | |
return this.isHeroFallingStraightWhileWalking() | |
|| this.isHeroFallingLeftWhileWalking() | |
|| this.isHeroFallingRightWhileWalking(); | |
} | |
isHeroFallingWhileRunning() { | |
return this.isHeroFallingStraightWhileRunning() | |
|| this.isHeroFallingLeftWhileRunning() | |
|| this.isHeroFallingRightWhileRunning(); | |
} | |
isHeroFallingStraight() { | |
return this.isHeroFallingStraightWhileWalking() | |
|| this.isHeroFallingStraightWhileRunning(); | |
} | |
isHeroFallingStraightWhileWalking() { | |
return [ | |
FALLING, | |
].includes(this.heroState); | |
} | |
isHeroFallingStraightWhileRunning() { | |
return [ | |
RUN_FALLING, | |
].includes(this.heroState); | |
} | |
isHeroFallingLeft() { | |
return this.isHeroFallingLeftWhileWalking() | |
|| this.isHeroFallingLeftWhileRunning(); | |
} | |
isHeroFallingLeftWhileWalking() { | |
return [ | |
FALLING_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroFallingLeftWhileRunning() { | |
return [ | |
RUN_FALLING_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroFallingRight() { | |
return this.isHeroFallingRightWhileWalking() | |
|| this.isHeroFallingRightWhileRunning(); | |
} | |
isHeroFallingRightWhileWalking() { | |
return [ | |
FALLING_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroFallingRightWhileRunning() { | |
return [ | |
RUN_FALLING_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroOnGround() { | |
return this.body.blocked.down || this.body.touching.down; | |
} | |
isHeroRunning() { | |
// Is hero running? Doesn't the other states | |
return this.isHeroRunningRight() | |
|| this.isHeroRunningLeft() | |
|| this.isHeroRunningWhileFallingStraight() | |
|| this.delayStopRunning; | |
} | |
isHeroRunningRight() { | |
return this.isHeroRunningRightWhileJumping() | |
|| this.isHeroRunningRightOnGround() | |
|| this.isHeroRunningRightWhileFalling(); | |
} | |
isHeroRunningRightWhileJumping() { | |
return [ | |
RUN_JUMPING_RIGHT, | |
RUN_BOOSTING_JUMP_RIGHT, | |
RUN_JUMPING_START_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroRunningRightOnGround() { | |
return [ | |
RUNNING_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroRunningRightWhileFalling() { | |
return [ | |
RUN_FALLING_RIGHT, | |
].includes(this.heroState); | |
} | |
isHeroRunningLeft() { | |
return this.isHeroRunningLeftWhileJumping() | |
|| this.isHeroRunningLeftWhileOnGround() | |
|| this.isHeroRunningLeftWhileFalling(); | |
} | |
isHeroRunningLeftWhileJumping() { | |
return [ | |
RUN_JUMPING_LEFT, | |
RUN_BOOSTING_JUMP_LEFT, | |
RUN_JUMPING_START_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroRunningLeftWhileOnGround() { | |
return [ | |
RUNNING_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroRunningLeftWhileFalling() { | |
return [ | |
RUN_FALLING_LEFT, | |
].includes(this.heroState); | |
} | |
isHeroRunningWhileFallingStraight() { | |
return this.heroState === RUN_FALLING; | |
} | |
update(time, delta) { | |
if (IS_DEV) { | |
this.debugText.setX(this.x); | |
this.debugText.setY(this.y - 50); | |
} | |
this.handleHeroState(); | |
this.handleHeroMovement(); | |
this.handleHeroAnimation(); | |
this.gamepad?.buttons.forEach((button, index) => { | |
this.prevGamepadButtons[index] = button.pressed; | |
}); | |
} | |
handleHeroState() { | |
const isRightDown = this.isRightDown(); | |
const isLeftDown = this.isLeftDown(); | |
const isUpDown = this.isUpDown(); | |
const isAButtonDown = this.isAButtonDown(); | |
const pressedRight = this.isRightJustDown(); | |
const pressedLeft = this.isLeftJustDown(); | |
const isJumping = this.isHeroJumping(); | |
// Handle hero idle | |
if (this.isHeroOnGround() && !this.delayStopRunning) { | |
if (!isRightDown && !isLeftDown) { | |
if (([ | |
RUNNING_RIGHT, | |
RUNNING_LEFT, | |
WALKING_RIGHT, | |
WALKING_LEFT, | |
].includes(this.heroState) | |
|| this.isHeroFalling() | |
)) { | |
this.setHeroState(IDLE); | |
return; | |
} | |
} | |
// This fixes the bug when jumping | |
// up a wall or something with collidesUp only true | |
if (([ | |
JUMPING, | |
JUMPING_RIGHT, | |
JUMPING_LEFT, | |
].includes(this.heroState) | |
)) { | |
this.setHeroState(IDLE); | |
return; | |
} | |
} | |
// Handle hero running | |
if (!isJumping) { | |
if (this.runTimer <= 0) { | |
if (pressedRight || pressedLeft) { | |
this.pressedRunRight = pressedRight; | |
this.pressedRunLeft = pressedLeft; | |
this.runTimer = 1; | |
} else if ( | |
this.isHeroOnGround() | |
&& this.isHeroRunning() | |
&& !this.delayStopRunning | |
&& (isRightDown || isLeftDown) | |
) { | |
// This will handle when hero just jumped while running | |
this.delayStopRunning = true; | |
if (isRightDown) { | |
this.setHeroState(RUNNING_RIGHT); | |
} else if (isLeftDown) { | |
this.setHeroState(RUNNING_LEFT); | |
} | |
} | |
} else if (this.runTimer <= 10) { | |
if (this.pressedRunRight && isRightDown && pressedRight) { | |
this.setHeroState(RUNNING_RIGHT); | |
this.delayStopRunning = true; | |
} else if (this.pressedRunLeft && isLeftDown && pressedLeft) { | |
this.setHeroState(RUNNING_LEFT); | |
this.delayStopRunning = true; | |
} | |
} else { | |
this.runTimer = 0; | |
this.pressedRunRight = false; | |
this.pressedRunLeft = false; | |
} | |
if (this.runTimer > 0) { | |
this.runTimer += 1; | |
} | |
if (this.delayStopRunning) { | |
if (!isRightDown && !isLeftDown) { | |
this.stopRunTimer += 1; | |
if (this.stopRunTimer > 6) { | |
this.delayStopRunning = false; | |
this.stopRunTimer = 0; | |
} | |
} else if (!isLeftDown && isRightDown && this.heroState === RUNNING_LEFT) { | |
this.setHeroState(RUNNING_RIGHT); | |
} else if (!isRightDown && isLeftDown && this.heroState === RUNNING_RIGHT) { | |
this.setHeroState(RUNNING_LEFT); | |
} | |
} | |
} else { | |
this.delayStopRunning = false; | |
} | |
// Handle hero walking | |
if (this.isHeroOnGround() && !isJumping && !this.isHeroRunning()) { | |
if (isRightDown) { | |
this.setHeroState(WALKING_RIGHT); | |
} else if (isLeftDown) { | |
this.setHeroState(WALKING_LEFT); | |
} | |
} | |
// Handle hero jumping | |
if (this.isHeroOnGround() && (this.isUpJustDown() || this.isAButtonJustDown())) { | |
this.jumpTimer = 1; | |
let newHeroState = JUMPING_START; | |
if (this.isHeroRunning()) { | |
newHeroState = RUN_JUMPING_START; | |
} | |
this.setHeroState(newHeroState); | |
} else if (this.jumpTimer !== 0) { | |
if (isUpDown || isAButtonDown) { | |
this.jumpTimer += 1; | |
if (this.jumpTimer > 8) { | |
// hero has been holding jump for over 100 milliseconds | |
// it's time to stop the hero | |
this.jumpTimer = 0; | |
let newHeroState = JUMPING; | |
if (this.isHeroRunning()) { | |
newHeroState = RUN_JUMPING; | |
} | |
this.setHeroState(newHeroState); | |
} else if (this.jumpTimer > 7) { | |
let newHeroState = BOOSTING_JUMP; | |
if (this.isHeroRunning()) { | |
newHeroState = RUN_BOOSTING_JUMP; | |
} | |
this.setHeroState(newHeroState); | |
} | |
} else { | |
this.jumpTimer = 0; | |
let newHeroState = JUMPING; | |
if (this.isHeroRunning()) { | |
newHeroState = RUN_JUMPING; | |
} | |
this.setHeroState(newHeroState); | |
} | |
} | |
// Check if it is jumping in a certain direction | |
if (this.isHeroJumping()) { | |
let newHeroState = this.heroState; | |
let runningPrefix = ''; | |
if ( | |
this.isHeroRunning() | |
&& ![ | |
RUN_JUMPING_START, | |
RUN_BOOSTING_JUMP, | |
RUN_JUMPING, | |
RUN_JUMPING_START_LEFT, | |
RUN_BOOSTING_JUMP_LEFT, | |
RUN_JUMPING_LEFT, | |
RUN_JUMPING_START_RIGHT, | |
RUN_BOOSTING_JUMP_RIGHT, | |
RUN_JUMPING_RIGHT, | |
].includes(this.heroState)) { | |
runningPrefix = '_RUN'; | |
} | |
if (isRightDown) { | |
if ([JUMPING_START, BOOSTING_JUMP, JUMPING, RUN_JUMPING_START, RUN_BOOSTING_JUMP, RUN_JUMPING].includes(this.heroState)) { | |
newHeroState = `${runningPrefix}${this.heroState}_RIGHT`; | |
} else if ([JUMPING_START_LEFT, BOOSTING_JUMP_LEFT, JUMPING_LEFT, RUN_JUMPING_START_LEFT, RUN_BOOSTING_JUMP_LEFT, RUN_JUMPING_LEFT].includes(this.heroState)) { | |
const parts = this.heroState.split('_LEFT'); | |
newHeroState = `${runningPrefix}${parts[0]}_RIGHT`; | |
} | |
} else if (isLeftDown) { | |
if ([JUMPING_START, BOOSTING_JUMP, JUMPING, RUN_JUMPING_START, RUN_BOOSTING_JUMP, RUN_JUMPING].includes(this.heroState)) { | |
newHeroState = `${runningPrefix}${this.heroState}_LEFT`; | |
} else if ([JUMPING_START_RIGHT, BOOSTING_JUMP_RIGHT, JUMPING_RIGHT, RUN_JUMPING_START_RIGHT, RUN_BOOSTING_JUMP_RIGHT, RUN_JUMPING_RIGHT].includes(this.heroState)) { | |
const parts = this.heroState.split('_RIGHT'); | |
newHeroState = `${runningPrefix}${parts[0]}_LEFT`; | |
} | |
} | |
this.setHeroState(newHeroState); | |
} | |
// If it's already jumping and pressed the jump button | |
// bail | |
if (isJumping) { | |
if (this.isUpJustDown() || this.isAButtonJustDown()) { | |
return; | |
} | |
} | |
// Handle hero falling | |
if (!this.isHeroFalling() && this.body.velocity.y > 0 && !this.isHeroOnGround()) { | |
if (this.isHeroRunning()) { | |
if (![RUNNING_RIGHT, RUNNING_LEFT].includes(this.heroState)) { | |
this.setHeroState(RUN_FALLING); | |
} | |
} else { | |
this.setHeroState(FALLING); | |
} | |
} | |
// Check if it is falling in a certain direction | |
if (this.isHeroFalling() && !this.isHeroOnGround()) { | |
if (isRightDown) { | |
let newHeroState = this.heroState; | |
if ([FALLING, FALLING_LEFT, RUN_FALLING, RUN_FALLING_LEFT].includes(this.heroState)) { | |
newHeroState = FALLING_RIGHT; | |
if (this.isHeroRunning()) { | |
newHeroState = RUN_FALLING_RIGHT; | |
} | |
} | |
this.setHeroState(newHeroState); | |
} else if (isLeftDown) { | |
let newHeroState = this.heroState; | |
if ([FALLING, FALLING_RIGHT, RUN_FALLING, RUN_FALLING_RIGHT].includes(this.heroState)) { | |
newHeroState = FALLING_LEFT; | |
if (this.isHeroRunning()) { | |
newHeroState = RUN_FALLING_LEFT; | |
} | |
} | |
this.setHeroState(newHeroState); | |
} | |
} | |
// handle hero attacking | |
if (this.isHeroOnGround() && this.isBButtonJustDown()) { | |
this.setHeroState(ATTACKING_START); | |
} | |
} | |
handleHeroMovement() { | |
const { heroState } = this; | |
const accelerationY = this.calculateHeroAccelerationY(); | |
const accelerationX = this.calculateHeroAccelerationX(); | |
if (this.isHeroOnGround()) { | |
if (!this.isHeroRunning()) { | |
this.body.setVelocityY(0); | |
} | |
} | |
// Handle walking movement | |
if (heroState === WALKING_RIGHT) { | |
this.body.setAccelerationX(accelerationX); | |
} else if (heroState === WALKING_LEFT) { | |
this.body.setAccelerationX(-accelerationX); | |
} | |
if (heroState === RUNNING_RIGHT) { | |
this.body.setAccelerationX(accelerationX); | |
} else if (heroState === RUNNING_LEFT) { | |
this.body.setAccelerationX(-accelerationX); | |
} | |
// Handle jumping movement | |
if ([ | |
JUMPING_START, | |
BOOSTING_JUMP, | |
JUMPING_START_RIGHT, | |
BOOSTING_JUMP_RIGHT, | |
JUMPING_START_LEFT, | |
BOOSTING_JUMP_LEFT, | |
RUN_JUMPING_START, | |
RUN_BOOSTING_JUMP, | |
RUN_JUMPING_START_RIGHT, | |
RUN_BOOSTING_JUMP_RIGHT, | |
RUN_JUMPING_START_LEFT, | |
RUN_BOOSTING_JUMP_LEFT, | |
].includes(heroState)) { | |
// does not includes the JUMPING status | |
this.body.setVelocityY(-accelerationY); | |
} | |
if ([ | |
JUMPING_RIGHT, | |
JUMPING_START_RIGHT, | |
BOOSTING_JUMP_RIGHT, | |
RUN_JUMPING_RIGHT, | |
RUN_JUMPING_START_RIGHT, | |
RUN_BOOSTING_JUMP_RIGHT, | |
].includes(heroState)) { | |
this.body.setAccelerationX(accelerationX); | |
} | |
if ([ | |
JUMPING_LEFT, | |
JUMPING_START_LEFT, | |
BOOSTING_JUMP_LEFT, | |
RUN_JUMPING_LEFT, | |
RUN_JUMPING_START_LEFT, | |
RUN_BOOSTING_JUMP_LEFT, | |
].includes(heroState)) { | |
this.body.setAccelerationX(-accelerationX); | |
} | |
// Handle idle movement | |
if (heroState === IDLE) { | |
this.body.setAccelerationX(0); | |
} | |
} | |
handleHeroAnimation() { | |
const { heroState } = this; | |
// Handle walking animation | |
if (heroState === WALKING_RIGHT) { | |
this.setAnimation('walk'); | |
this.setFlipX(false); | |
// this.body.setOffset(10, 4); // TODO | |
} else if (heroState === WALKING_LEFT) { | |
this.setAnimation('walk'); | |
this.setFlipX(true); | |
// this.body.setOffset(4, 4); // TODO | |
} | |
if (heroState === RUNNING_RIGHT) { | |
this.setAnimation('run'); | |
this.setFlipX(false); | |
// this.body.setOffset(8, -2); // TODO | |
} else if (heroState === RUNNING_LEFT) { | |
this.setAnimation('run'); | |
this.setFlipX(true); | |
// this.body.setOffset(6, 4); // TODO | |
} | |
// Handle jumping animation | |
if (this.isHeroJumping()) { | |
// this.body.setOffset(8, -2); | |
if (this.isHeroRunning()) { | |
this.setAnimation('run_jump'); | |
} else { | |
this.setAnimation('jump'); | |
} | |
} | |
// Handle falling animation | |
if (this.isHeroFalling()) { | |
// this.body.setOffset(8, -2); | |
if (this.isHeroRunning()) { | |
// this.setFrame('hero_fall_01'); | |
this.setFrame('1'); | |
} else { | |
// this.setFrame('hero_fall_01'); | |
this.setFrame('1'); | |
} | |
} | |
// Handle idle animation | |
if (heroState === IDLE) { | |
this.setAnimation('idle'); | |
// this.body.setOffset(8, -2); // TODO | |
} | |
} | |
} | |
export default Hero; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { isset } from './utils.js'; | |
const HERO_DEPTH = 50; | |
const IS_DEV = new URL(window.location.href).searchParams.get('debug') === '1'; | |
/** | |
* A lean Hero class focused on belt‑scroller 360° ground movement. | |
* – Digital D‑Pad + keyboard arrows/A‑W‑S‑D + left‑stick fallback | |
* – Velocity normalisation so diagonals aren’t faster | |
* – Automatic depth‑sorting (lower sprites draw over higher) | |
* | |
* Jump / run / combo logic from the previous version has been stripped. | |
* Re‑add it later in separate systems if needed. | |
*/ | |
export default class Hero extends Phaser.Physics.Arcade.Sprite { | |
constructor({ | |
scene, | |
x = 0, | |
y = 0, | |
asset = 'player', | |
frame, | |
}) { | |
super(scene, x, y, asset, frame); | |
// --- display & physics ------------------------------------------------- | |
scene.add.existing(this); | |
scene.physics.add.existing(this); | |
this.setOrigin(0, 1) // anchor at the feet | |
.setDepth(HERO_DEPTH) | |
.setSize(16, 12) // hit‑box just around the feet | |
.setOffset(8, 20) | |
.setCollideWorldBounds(true); | |
// Feel free to tweak | |
this.baseSpeed = 130; | |
// --- input ------------------------------------------------------------- | |
this.cursors = scene.input.keyboard.createCursorKeys(); | |
scene.input.keyboard.addKeys({ W: 'W', A: 'A', S: 'S', D: 'D' }); | |
scene.input.gamepad.once('connected', (pad) => { | |
this.pad = pad; | |
}); | |
// Debug helper ---------------------------------------------------------- | |
if (IS_DEV) { | |
this.debugText = scene.add.text(0, 0, '', { | |
fontSize: 12, | |
color: '#fff', | |
}).setDepth(999); | |
} | |
// Load Aseprite animations in one go | |
this.anims.createFromAseprite(asset).forEach((a) => (a.repeat = -1)); | |
this.play(`${asset}_idle`); | |
} | |
/* ----------------------------------------------------------------------- */ | |
preUpdate(time, delta) { | |
super.preUpdate(time, delta); | |
this.handleMovement(); | |
} | |
/* ----------------------------------------------------------------------- */ | |
/** | |
* Keyboard ↔ Game‑Pad unification + normalised velocity. | |
*/ | |
handleMovement() { | |
let vx = 0; | |
let vy = 0; | |
// Keyboard ------------------------------------------------------------- | |
if (this.cursors.left.isDown || this.scene.input.keyboard.keys[65]?.isDown) vx = -1; // A | |
else if (this.cursors.right.isDown || this.scene.input.keyboard.keys[68]?.isDown) vx = 1; // D | |
if (this.cursors.up.isDown || this.scene.input.keyboard.keys[87]?.isDown) vy = -1; // W | |
else if (this.cursors.down.isDown || this.scene.input.keyboard.keys[83]?.isDown) vy = 1; // S | |
// Game‑pad ------------------------------------------------------------- | |
if (this.pad) { | |
// Digital d‑pad buttons | |
if (this.pad.buttons[14]?.pressed) vx = -1; // left | |
else if (this.pad.buttons[15]?.pressed) vx = 1; // right | |
if (this.pad.buttons[12]?.pressed) vy = -1; // up | |
else if (this.pad.buttons[13]?.pressed) vy = 1; // down | |
// Analog stick fallback | |
const ax = this.pad.axes[0]?.getValue(); | |
const ay = this.pad.axes[1]?.getValue(); | |
if (Math.abs(ax) > 0.1) vx = ax; | |
if (Math.abs(ay) > 0.1) vy = ay; | |
} | |
// Normalise so diagonals aren’t faster --------------------------------- | |
if (vx !== 0 || vy !== 0) { | |
const len = Math.hypot(vx, vy); | |
vx = (vx / len) * this.baseSpeed; | |
vy = (vy / len) * this.baseSpeed; | |
} | |
this.setVelocity(vx, vy); | |
// Depth‑sorting: draw lower sprites over higher ones -------------------- | |
this.depth = this.y; | |
// ---------------------------------------------------------------------- | |
if (IS_DEV) { | |
this.debugText.setPosition(this.x, this.y - 48) | |
.setText(`vx:${vx.toFixed(0)} vy:${vy.toFixed(0)}`); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment