Skip to content

Commit 0eec0b8

Browse files
committed
- state management for hero's actions
1 parent 5ce708f commit 0eec0b8

File tree

8 files changed

+198
-66
lines changed

8 files changed

+198
-66
lines changed

package-lock.json

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"license": "MIT",
2222
"dependencies": {
2323
"phaser": "^3.87.0",
24-
"phaser-animated-tiles": "^2.0.2"
24+
"phaser-animated-tiles": "^2.0.2",
25+
"rxjs": "^7.8.1"
2526
},
2627
"devDependencies": {
2728
"@jgoz/esbuild-plugin-livereload": "^2.0.5",
Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Scene } from "phaser";
22
import { depthsConfig } from "../configs";
3+
import { HeroState } from "../helpers";
34
import { GameHelper } from "../helpers/game.helper";
45
import { ArcadeBody, ArcadeSprite, Sprite } from "../phaser-aliases";
56
import { AnimationTag, ImageTag, SfxTag, SpritesheetTag } from "../tags";
67

78
export class Hero extends Sprite {
89
public declare body: ArcadeBody;
910
public projectiles: Phaser.Physics.Arcade.Group;
11+
1012
public get isMovingLeft(): boolean {
1113
return this._cursors.left.isDown;
1214
}
@@ -19,6 +21,9 @@ export class Hero extends Sprite {
1921
public get damage(): number {
2022
return this._damage;
2123
}
24+
public get offset(): { x: number; y: number } {
25+
return this._offset;
26+
}
2227

2328
private _speed = 160;
2429
private _jumpSpeed = 360;
@@ -35,6 +40,7 @@ export class Hero extends Sprite {
3540
x: 8,
3641
y: 14,
3742
};
43+
private _state: HeroState;
3844

3945
constructor(scene: Scene) {
4046
super(scene, 0, 0, SpritesheetTag.HERO);
@@ -45,6 +51,8 @@ export class Hero extends Sprite {
4551
throw Error("Keyboard plugin is not available");
4652
}
4753

54+
this._state = new HeroState(this);
55+
4856
this._keyboard = this.scene.input.keyboard;
4957
this._cursors = this._keyboard.createCursorKeys();
5058
this._shootKey = this._keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
@@ -66,32 +74,46 @@ export class Hero extends Sprite {
6674
const hitboxHeight = this.height * 0.8;
6775
this.body.setSize(hitboxWidth, hitboxHeight);
6876
this.body.setOffset(this._offset.x, this._offset.y);
77+
78+
this._state.set({ action: "IDLE" });
6979
}
7080

7181
public update(time: number, _: number): void {
7282
this.handleMovements(time);
7383
this.handleProjectiles();
7484
}
7585

76-
private handleMovements(time: number): void {
77-
const isGrounded = this.body.touching.down;
78-
79-
if (isGrounded) {
80-
if (this.isMovingLeft || this.isMovingRight) {
81-
GameHelper.animate(this, AnimationTag.WALK, {
82-
exceptIf: [AnimationTag.JUMP, AnimationTag.SHOOT],
83-
});
84-
} //
85-
else {
86-
GameHelper.animate(this, AnimationTag.IDLE, {
87-
exceptIf: [AnimationTag.JUMP, AnimationTag.SHOOT],
88-
});
89-
90-
this.body.setVelocityX(0);
91-
}
86+
public shoot(): void {
87+
this.scene.sound.play(SfxTag.ARROW_SHOOT);
88+
89+
GameHelper.animate(this, AnimationTag.HERO_SHOOT, {
90+
ignoreIfPlaying: false,
91+
});
92+
const velocity = this.flipX ? -600 : 600;
93+
94+
const projectile: ArcadeSprite = this.projectiles.create(this.x, this.y, ImageTag.PROJECTILE_ARROW);
95+
projectile.setFlipX(this.flipX);
96+
projectile.setVelocityX(velocity);
97+
}
9298

93-
// for some reason, this "isGrounded" is sometimes true, right after jumping
99+
public jump(): void {
100+
if (this._noOfJump <= 0) {
101+
return;
102+
}
103+
104+
this._noOfJump--;
105+
this._isJumping = true;
106+
107+
this.body.setVelocityY(-this._jumpSpeed);
108+
this.scene.sound.play(SfxTag.JUMP);
109+
GameHelper.animate(this, AnimationTag.HERO_JUMP);
110+
}
111+
112+
private handleMovements(time: number): void {
113+
if (this.body.touching.down) {
114+
// for some reason, touching.down is sometimes true, right after jumping
94115
const stupidHack = time - this._lastJumpTime > 100;
116+
95117
//~ Is landing
96118
if (this._isJumping && stupidHack) {
97119
this._isJumping = false;
@@ -101,24 +123,24 @@ export class Hero extends Sprite {
101123
}
102124

103125
if (this.isMovingRight) {
104-
this.body.setVelocityX(this._speed);
105-
this.setFlipX(false);
106-
this.body.setOffset(this._offset.x, this._offset.y);
107-
}
108-
109-
if (this.isMovingLeft) {
110-
this.body.setVelocityX(-this._speed);
111-
this.setFlipX(true);
112-
this.body.setOffset(this._offset.x + 6, this._offset.y);
126+
this._state.set({ action: "MOVING-RIGHT" });
127+
} else if (this.isMovingLeft) {
128+
this._state.set({ action: "MOVING-LEFT" });
129+
} else {
130+
this.body.setVelocityX(0);
113131
}
114132

115133
if (Phaser.Input.Keyboard.JustDown(this._cursors.up)) {
116134
this._lastJumpTime = time;
117-
this.jump();
135+
this._state.set({ action: "JUMPING" });
118136
}
119137

120138
if (Phaser.Input.Keyboard.JustDown(this._shootKey)) {
121-
this.shoot();
139+
this._state.set({ action: "SHOOTING" });
140+
}
141+
142+
if (this.body.velocity.x === 0 && this.body.velocity.y === 0) {
143+
this._state.set({ action: "IDLE" });
122144
}
123145
}
124146

@@ -138,22 +160,9 @@ export class Hero extends Sprite {
138160
}, this);
139161
}
140162

141-
private jump(): void {
142-
if (this._noOfJump <= 0) {
143-
return;
144-
}
145-
146-
this._noOfJump--;
147-
this._isJumping = true;
148-
149-
this.body.setVelocityY(-this._jumpSpeed);
150-
this.scene.sound.play(SfxTag.JUMP);
151-
GameHelper.animate(this, AnimationTag.JUMP);
152-
}
153-
154163
private createAnimations(): void {
155164
this.anims.create({
156-
key: AnimationTag.IDLE,
165+
key: AnimationTag.HERO_IDLE,
157166
frames: this.anims.generateFrameNumbers(SpritesheetTag.HERO, {
158167
start: 0,
159168
end: 3,
@@ -163,7 +172,7 @@ export class Hero extends Sprite {
163172
});
164173

165174
this.anims.create({
166-
key: AnimationTag.WALK,
175+
key: AnimationTag.HERO_WALK,
167176
frames: this.anims.generateFrameNumbers(SpritesheetTag.HERO, {
168177
start: 5,
169178
end: 8,
@@ -173,7 +182,7 @@ export class Hero extends Sprite {
173182
});
174183

175184
this.anims.create({
176-
key: AnimationTag.JUMP,
185+
key: AnimationTag.HERO_JUMP,
177186
frames: this.anims.generateFrameNumbers(SpritesheetTag.HERO, {
178187
start: 20,
179188
end: 22,
@@ -182,25 +191,30 @@ export class Hero extends Sprite {
182191
});
183192

184193
this.anims.create({
185-
key: AnimationTag.SHOOT,
194+
key: AnimationTag.HERO_SHOOT,
186195
frames: this.anims.generateFrameNumbers(SpritesheetTag.HERO, {
187196
start: 15,
188197
end: 19,
189198
}),
190199
frameRate: 15,
191200
});
192-
}
193-
194-
private shoot(): void {
195-
this.scene.sound.play(SfxTag.ARROW_SHOOT);
196201

197-
GameHelper.animate(this, AnimationTag.SHOOT, {
198-
ignoreIfPlaying: false,
202+
this.anims.create({
203+
key: AnimationTag.HERO_HURT,
204+
frames: this.anims.generateFrameNumbers(SpritesheetTag.HERO, {
205+
start: 9,
206+
end: 10,
207+
}),
208+
frameRate: 10,
199209
});
200-
const velocity = this.flipX ? -600 : 600;
201210

202-
const projectile: ArcadeSprite = this.projectiles.create(this.x, this.y, ImageTag.PROJECTILE_ARROW);
203-
projectile.setFlipX(this.flipX);
204-
projectile.setVelocityX(velocity);
211+
this.anims.create({
212+
key: AnimationTag.HERO_DIE,
213+
frames: this.anims.generateFrameNumbers(SpritesheetTag.HERO, {
214+
start: 11,
215+
end: 12,
216+
}),
217+
frameRate: 10,
218+
});
205219
}
206220
}

src/helpers/hero.state.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Observable, Subject } from "rxjs";
2+
import { Hero } from "../game-objects";
3+
import { AnimationTag } from "../tags";
4+
import { HeroStateParams } from "../types";
5+
import { GameHelper } from "./game.helper";
6+
7+
export class HeroState {
8+
public readonly state$: Observable<HeroStateParams>;
9+
10+
private _state$$ = new Subject<HeroStateParams>();
11+
12+
constructor(private _hero: Hero) {
13+
this.state$ = this._state$$.asObservable();
14+
15+
const subscription = this.state$
16+
// .pipe(distinctUntilChanged((prev, next) => prev.action === next.action))
17+
.subscribe((state) => this.handle(state));
18+
19+
this._hero.on("destroy", () => subscription.unsubscribe());
20+
}
21+
22+
public set(state: HeroStateParams): void {
23+
this._state$$.next(state);
24+
}
25+
26+
private handle(state: HeroStateParams): void {
27+
const isGrounded = this._hero.body.touching.down;
28+
29+
switch (state.action) {
30+
case "IDLE":
31+
GameHelper.animate(this._hero, AnimationTag.HERO_IDLE, {
32+
exceptIf: [AnimationTag.HERO_JUMP, AnimationTag.HERO_SHOOT],
33+
});
34+
35+
this._hero.body.setVelocityX(0);
36+
break;
37+
case "MOVING-LEFT":
38+
case "MOVING-RIGHT":
39+
const direction = state.action === "MOVING-LEFT" ? -1 : 1;
40+
const extraXOffset = state.action === "MOVING-LEFT" ? 6 : 0;
41+
42+
this._hero.body.setVelocityX(direction * this._hero.speed);
43+
this._hero.setFlipX(state.action === "MOVING-LEFT");
44+
this._hero.body.setOffset(this._hero.offset.x + extraXOffset, this._hero.offset.y);
45+
46+
if (isGrounded) {
47+
GameHelper.animate(this._hero, AnimationTag.HERO_WALK, {
48+
exceptIf: [AnimationTag.HERO_SHOOT],
49+
});
50+
}
51+
break;
52+
case "JUMPING":
53+
this._hero.jump();
54+
break;
55+
case "SHOOTING":
56+
this._hero.shoot();
57+
break;
58+
case "HURT":
59+
this._hero.play(AnimationTag.HERO_HURT);
60+
break;
61+
case "DYING":
62+
this._hero.play(AnimationTag.HERO_DIE);
63+
}
64+
}
65+
}

src/helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './function';
22
export * from './game.helper';
3+
export * from './hero.state';
34
export * from './systems.helper';

src/tags/animation.tag.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
export enum AnimationTag {
2-
WALK = "walk_animation",
3-
IDLE = "idle_animation",
4-
JUMP = "jump_animation",
5-
SHOOT = "shoot_animation",
6-
ENNEMY_IDLE = "ennemy_idle_animation",
7-
ENNEMY_MOVING = "ennemy_moving_animation",
8-
ENNEMY_HURT = "ennemy_hurt_animation",
9-
ENNEMY_DEATH = "ennemy_death_animation",
2+
HERO_WALK = "hero-walk_animation",
3+
HERO_IDLE = "hero-idle_animation",
4+
HERO_JUMP = "hero-jump_animation",
5+
HERO_SHOOT = "hero-shoot_animation",
6+
HERO_HURT = "hero-hurt_animation",
7+
HERO_DIE = "hero-die_animation",
8+
ENNEMY_IDLE = "ennemy-idle_animation",
9+
ENNEMY_MOVING = "ennemy-moving_animation",
10+
ENNEMY_HURT = "ennemy-hurt_animation",
11+
ENNEMY_DEATH = "ennemy-death_animation",
1012
}

src/types/hero-state.type.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export type HeroAction =
2+
| "IDLE"
3+
| "MOVING-LEFT"
4+
| "MOVING-RIGHT"
5+
| "JUMPING"
6+
| "FALLING"
7+
| "SHOOTING"
8+
| "HURT"
9+
| "DYING";
10+
11+
type BaseStateParams<TAction extends HeroAction, TData = undefined> = {
12+
action: TAction;
13+
} & (TData extends undefined ? {} : { data: TData });
14+
15+
export type IdleStateParams = BaseStateParams<"IDLE">;
16+
export type MovingLeftStateParams = BaseStateParams<"MOVING-LEFT">;
17+
export type MovingRightStateParams = BaseStateParams<"MOVING-RIGHT">;
18+
export type JumpingStateParams = BaseStateParams<"JUMPING">;
19+
export type FallingStateParams = BaseStateParams<"FALLING">;
20+
export type ShootingStateParams = BaseStateParams<"SHOOTING">;
21+
export type HurtStateParams = BaseStateParams<"HURT">;
22+
export type DyingStateParams = BaseStateParams<"DYING">;
23+
24+
export type HeroStateParams =
25+
| IdleStateParams
26+
| MovingLeftStateParams
27+
| MovingRightStateParams
28+
| JumpingStateParams
29+
| FallingStateParams
30+
| ShootingStateParams
31+
| HurtStateParams
32+
| DyingStateParams;

0 commit comments

Comments
 (0)