Skip to content

Commit 64cbddb

Browse files
obiotclaude
andcommitted
[#1008] Deprecate Entity in favor of Sprite/Renderable + Body
Entity is now deprecated with a console warning and detailed migration guide documenting why (anchor point confusion, indirect API, custom rendering pipeline) and how to migrate with three examples. New TextureAtlas.getAnimationSettings() method returns Sprite constructor settings from atlas frame names, stripping per-frame anchorPoints and bottom-aligning smaller frames via trim offsets. This enables extending Sprite directly with texture atlas animations. Platformer example fully migrated from Entity to Sprite + Body: - PlayerEntity extends Sprite with centered/bottom-aligned body - SlimeEnemyEntity uses gravity + world collision instead of gravityScale=0 - FlyEnemyEntity uses vertical forces for proper flight physics - createAnimationFromName() unchanged for backward compatibility Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c089b9e commit 64cbddb

5 files changed

Lines changed: 659 additions & 121 deletions

File tree

Lines changed: 97 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,51 @@
1-
import { audio, collision, Entity, game, ParticleEmitter, Rect } from "melonjs";
1+
import {
2+
audio,
3+
Body,
4+
collision,
5+
game,
6+
ParticleEmitter,
7+
Rect,
8+
Sprite,
9+
} from "melonjs";
210
import { gameState } from "../gameState";
311

412
/**
5-
* An enemy entity
13+
* A base enemy entity using Sprite + Body
614
* follow a horizontal path defined by the box size in Tiled
715
*/
8-
class PathEnemyEntity extends Entity {
16+
class PathEnemyEntity extends Sprite {
17+
alive: boolean;
18+
startX: number;
19+
endX: number;
20+
walkLeft: boolean;
21+
isMovingEnemy: boolean;
22+
particleTint: string;
23+
924
/**
1025
* constructor
1126
*/
12-
constructor(x, y, settings) {
27+
constructor(x, y, settings, frameNames: string[]) {
1328
// save the area size defined in Tiled
1429
const width = settings.width || settings.framewidth;
1530

16-
// adjust the setting size to the sprite one
17-
settings.width = settings.framewidth;
18-
settings.height = settings.frameheight;
31+
// create the sprite from texture atlas animation frames
32+
super(x, y, {
33+
...gameState.texture.getAnimationSettings(frameNames),
34+
anchorPoint: { x: 0, y: 0 },
35+
});
1936

20-
// redefine the default shape (used to define path) with a shape matching the renderable
21-
settings.shapes[0] = new Rect(
22-
0,
23-
0,
24-
settings.framewidth,
25-
settings.frameheight,
26-
);
27-
28-
// call the super constructor
29-
super(x, y, settings);
37+
// add a physic body matching the sprite max frame dimensions
38+
this.body = new Body(this, new Rect(0, 0, this.width, this.height));
3039

3140
// set start/end position based on the initial area size
32-
this.startX = this.pos.x;
33-
this.endX = this.pos.x + width - settings.framewidth;
34-
this.pos.x = this.pos.x + width - settings.framewidth;
35-
36-
// enemies are not impacted by gravity
37-
this.body.gravityScale = 0;
41+
this.startX = x;
42+
this.endX = x + width - settings.framewidth;
43+
this.pos.x = x + width - settings.framewidth;
3844

3945
this.walkLeft = false;
4046

4147
// body walking & flying speed
42-
this.body.setMaxVelocity(settings.velX || 1, settings.velY || 0);
48+
this.body.setMaxVelocity(settings.velX || 1, settings.velY || 15);
4349

4450
// set a "enemyObject" type
4551
this.body.collisionType = collision.types.ENEMY_OBJECT;
@@ -55,6 +61,9 @@ class PathEnemyEntity extends Entity {
5561
// a specific flag to recognize these enemies
5662
this.isMovingEnemy = true;
5763

64+
// living state
65+
this.alive = true;
66+
5867
// default tint for particles
5968
this.particleTint = "#FFF";
6069
}
@@ -68,7 +77,7 @@ class PathEnemyEntity extends Entity {
6877
if (this.pos.x <= this.startX) {
6978
// if reach start position
7079
this.walkLeft = false;
71-
this.renderable.flipX(true);
80+
this.flipX(true);
7281
} else {
7382
this.body.force.x = -this.body.maxVel.x;
7483
}
@@ -78,7 +87,7 @@ class PathEnemyEntity extends Entity {
7887
if (this.pos.x >= this.endX) {
7988
// if reach the end position
8089
this.walkLeft = true;
81-
this.renderable.flipX(false);
90+
this.flipX(false);
8291
} else {
8392
this.body.force.x = this.body.maxVel.x;
8493
}
@@ -91,7 +100,12 @@ class PathEnemyEntity extends Entity {
91100
/**
92101
* collision handle
93102
*/
94-
onCollision(response) {
103+
onCollision(response, other) {
104+
if (other.body.collisionType === collision.types.WORLD_SHAPE) {
105+
// solid against world shapes (platforms, ground)
106+
return true;
107+
}
108+
95109
// res.y >0 means touched by something on the bottom
96110
// which mean at top position for this one
97111
if (this.alive && response.overlapV.y > 0 && response.a.body.falling) {
@@ -102,7 +116,7 @@ class PathEnemyEntity extends Entity {
102116
// make the body static
103117
this.body.setStatic(true);
104118
// set dead animation
105-
this.renderable.setCurrentAnimation("dead");
119+
this.setCurrentAnimation("dead");
106120

107121
const emitter = new ParticleEmitter(this.centerX, this.centerY, {
108122
width: this.width / 4,
@@ -129,84 +143,102 @@ class PathEnemyEntity extends Entity {
129143
}
130144

131145
/**
132-
* An Slime enemy entity
146+
* A Slime enemy entity
133147
* follow a horizontal path defined by the box size in Tiled
134148
*/
135149
export class SlimeEnemyEntity extends PathEnemyEntity {
136150
/**
137151
* constructor
138152
*/
139153
constructor(x, y, settings) {
140-
// super constructor
141-
super(x, y, settings);
142-
143-
// set a renderable
144-
this.renderable = gameState.texture.createAnimationFromName([
154+
// super constructor with slime frame names
155+
super(x, y, settings, [
145156
"slime_normal.png",
146157
"slime_walk.png",
147158
"slime_dead.png",
148159
]);
149160

150161
// custom animation speed ?
151162
if (settings.animationspeed) {
152-
this.renderable.animationspeed = settings.animationspeed;
163+
this.animationspeed = settings.animationspeed;
153164
}
154165

155-
// walking animatin
156-
this.renderable.addAnimation("walk", [
157-
"slime_normal.png",
158-
"slime_walk.png",
159-
]);
160-
// dead animatin
161-
this.renderable.addAnimation("dead", ["slime_dead.png"]);
166+
// walking animation
167+
this.addAnimation("walk", ["slime_normal.png", "slime_walk.png"]);
168+
// dead animation
169+
this.addAnimation("dead", ["slime_dead.png"]);
162170

163171
// set default one
164-
this.renderable.setCurrentAnimation("walk");
165-
166-
// set the renderable position to bottom center
167-
this.anchorPoint.set(0.5, 1.0);
172+
this.setCurrentAnimation("walk");
168173

169174
// particle tint matching the sprite color
170175
this.particleTint = "#FF35B8";
171176
}
172177
}
173178

174179
/**
175-
* An Fly enemy entity
180+
* A Fly enemy entity
176181
* follow a horizontal path defined by the box size in Tiled
177182
*/
178183
export class FlyEnemyEntity extends PathEnemyEntity {
184+
startY: number;
185+
endY: number;
186+
flyUp: boolean;
187+
179188
/**
180189
* constructor
181190
*/
182191
constructor(x, y, settings) {
183-
// super constructor
184-
super(x, y, settings);
185-
186-
// set a renderable
187-
this.renderable = gameState.texture.createAnimationFromName([
188-
"fly_normal.png",
189-
"fly_fly.png",
190-
"fly_dead.png",
191-
]);
192+
// super constructor with fly frame names
193+
super(x, y, settings, ["fly_normal.png", "fly_fly.png", "fly_dead.png"]);
194+
195+
// set vertical patrol range (bob up and down by half height)
196+
const bobRange = settings.height || this.height;
197+
this.startY = y;
198+
this.endY = y + bobRange;
199+
this.flyUp = true;
200+
201+
// allow vertical movement
202+
this.body.setMaxVelocity(settings.velX || 1, settings.velY || 1);
192203

193204
// custom animation speed ?
194205
if (settings.animationspeed) {
195-
this.renderable.animationspeed = settings.animationspeed;
206+
this.animationspeed = settings.animationspeed;
196207
}
197208

198-
// walking animatin
199-
this.renderable.addAnimation("walk", ["fly_normal.png", "fly_fly.png"]);
200-
// dead animatin
201-
this.renderable.addAnimation("dead", ["fly_dead.png"]);
209+
// walking animation
210+
this.addAnimation("walk", ["fly_normal.png", "fly_fly.png"]);
211+
// dead animation
212+
this.addAnimation("dead", ["fly_dead.png"]);
202213

203214
// set default one
204-
this.renderable.setCurrentAnimation("walk");
205-
206-
// set the renderable position to bottom center
207-
this.anchorPoint.set(0.5, 1.0);
215+
this.setCurrentAnimation("walk");
208216

209217
// particle tint matching the sprite color
210218
this.particleTint = "#000000";
211219
}
220+
221+
/**
222+
* manage the fly movement (horizontal + vertical bobbing)
223+
*/
224+
update(dt) {
225+
if (this.alive) {
226+
// vertical bobbing — apply force against gravity to fly
227+
if (this.flyUp) {
228+
if (this.pos.y <= this.startY) {
229+
this.flyUp = false;
230+
} else {
231+
this.body.force.y = -this.body.maxVel.y;
232+
}
233+
} else {
234+
if (this.pos.y >= this.endY) {
235+
this.flyUp = true;
236+
} else {
237+
this.body.force.y = this.body.maxVel.y;
238+
}
239+
}
240+
}
241+
242+
return super.update(dt);
243+
}
212244
}

0 commit comments

Comments
 (0)