• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hiddentao / clockwork-engine / 17638111425

11 Sep 2025 08:03AM UTC coverage: 99.925%. Remained the same
17638111425

push

github

hiddentao
fix(ci): separate main and demo linting to resolve TypeScript resolution issues

- Remove demo TypeScript checking from root lint scripts
- Update CI workflow to build main package before demo linting
- Ensure dist/ exists when demo tries to import from @hiddentao/clockwork-engine
- Fix typo in README (Buil-tin -> Built-in)

1330 of 1331 relevant lines covered (99.92%)

49.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.4
/src/GameObject.ts
1
import { EventEmitter } from "./EventEmitter"
2
import type { ICollisionSource } from "./geometry/CollisionUtils"
3
import type { IPositionable } from "./geometry/IPositionable"
4
import { Vector2D } from "./geometry/Vector2D"
5

6
// Forward declaration to avoid circular dependency
7
export interface GameEngineInterface {
8
  registerGameObject(gameObject: GameObject): void
9
  getTotalFrames(): number
10
}
11

12
export enum GameObjectEventType {
114✔
13
  POSITION_CHANGED = "positionChanged",
124✔
14
  HEALTH_CHANGED = "healthChanged",
116✔
15
  MAX_HEALTH_CHANGED = "maxHealthChanged",
130✔
16
  DESTROYED = "destroyed",
98✔
17
  SIZE_CHANGED = "sizeChanged",
108✔
18
  VELOCITY_CHANGED = "velocityChanged",
124✔
19
  ROTATION_CHANGED = "rotationChanged",
126✔
20
}
21

22
export interface SerializedGameObject {
23
  position: {
24
    x: number
25
    y: number
26
  }
27
  size: {
28
    x: number
29
    y: number
30
  }
31
  velocity: {
32
    x: number
33
    y: number
34
  }
35
  rotation: number
36
  health: number
37
  maxHealth: number
38
  isDestroyed: boolean
39
}
40

41
/**
42
 * Base events that all GameObjects can emit
43
 * All events include the GameObject instance as the first parameter
44
 */
45
export interface GameObjectEvents
46
  extends Record<string, (...args: any[]) => void> {
47
  [GameObjectEventType.POSITION_CHANGED]: (
48
    gameObject: GameObject,
49
    oldPosition: Vector2D,
50
    newPosition: Vector2D,
51
  ) => void
52
  [GameObjectEventType.HEALTH_CHANGED]: (
53
    gameObject: GameObject,
54
    health: number,
55
    maxHealth: number,
56
  ) => void
57
  [GameObjectEventType.MAX_HEALTH_CHANGED]: (
58
    gameObject: GameObject,
59
    oldMaxHealth: number,
60
    newMaxHealth: number,
61
  ) => void
62
  [GameObjectEventType.DESTROYED]: (gameObject: GameObject) => void
63
}
64

65
export abstract class GameObject<T extends GameObjectEvents = GameObjectEvents>
62✔
66
  extends EventEmitter<T>
26✔
67
  implements IPositionable, ICollisionSource
68
{
20✔
69
  public static debug = false
32✔
70

71
  protected id: string
10✔
72
  protected position: Vector2D
22✔
73
  protected size: Vector2D
14✔
74
  protected velocity: Vector2D
22✔
75
  protected rotation: number
22✔
76
  protected health: number
18✔
77
  protected maxHealth: number
24✔
78
  protected destroyed: boolean
24✔
79
  protected engine?: GameEngineInterface
17✔
80

81
  constructor(
13✔
82
    id: string,
8✔
83
    position: Vector2D,
20✔
84
    size: Vector2D,
12✔
85
    health = 0,
24✔
86
    engine?: GameEngineInterface,
16✔
87
  ) {
10✔
88
    super()
24✔
89
    this.id = id
34✔
90
    this.position = position
58✔
91
    this.size = size
42✔
92
    this.velocity = new Vector2D(0, 0)
78✔
93
    this.rotation = 0
44✔
94
    this.health = health
50✔
95
    this.maxHealth = health
56✔
96
    this.destroyed = false
54✔
97
    this.engine = engine
50✔
98

99
    // Auto-register with engine if provided
100
    if (engine) {
37✔
101
      engine.registerGameObject(this)
72✔
102
    }
9✔
103

104
    if (GameObject.debug) {
44✔
105
      // debug: creation log
106
    }
9✔
107
  }
108

109
  public update(deltaFrames: number, _totalFrames: number): void {
72✔
110
    if (this.destroyed) {
53✔
111
      return
17✔
112
    }
9✔
113

114
    // Move object based on velocity
115
    const movement = this.velocity.scale(deltaFrames)
108✔
116
    this.position = this.position.add(movement)
96✔
117
  }
118

119
  public getPosition(): Vector2D {
27✔
120
    return this.position
47✔
121
  }
122

123
  public setPosition(position: Vector2D): void {
43✔
124
    const oldPosition = this.position
76✔
125
    this.position = position
58✔
126
    ;(this.emit as any)(
20✔
127
      GameObjectEventType.POSITION_CHANGED,
84✔
128
      this,
12✔
129
      oldPosition,
26✔
130
      position,
16✔
131
    )
12✔
132
  }
133

134
  public getSize(): Vector2D {
23✔
135
    return this.size
39✔
136
  }
137

138
  public setSize(size: Vector2D): void {
31✔
139
    this.size = size
42✔
140
  }
141

142
  public getVelocity(): Vector2D {
27✔
143
    return this.velocity
47✔
144
  }
145

146
  public setVelocity(velocity: Vector2D): void {
43✔
147
    this.velocity = velocity
58✔
148
  }
149

150
  public getRotation(): number {
27✔
151
    return this.rotation
47✔
152
  }
153

154
  public setRotation(rotation: number): void {
43✔
155
    this.rotation = rotation
58✔
156
  }
157

158
  public getHealth(): number {
25✔
159
    return this.health
43✔
160
  }
161

162
  public getMaxHealth(): number {
28✔
163
    return this.maxHealth
49✔
164
  }
165

166
  public setMaxHealth(maxHealth: number): void {
46✔
167
    const oldMaxHealth = this.maxHealth
80✔
168
    this.maxHealth = maxHealth
62✔
169
    if (this.maxHealth !== oldMaxHealth) {
87✔
170
      ;(this.emit as any)(
20✔
171
        GameObjectEventType.MAX_HEALTH_CHANGED,
90✔
172
        this,
12✔
173
        oldMaxHealth,
28✔
174
        maxHealth,
18✔
175
      )
12✔
176
    }
9✔
177
  }
178

179
  public setHealth(health: number): void {
37✔
180
    const oldHealth = this.health
68✔
181
    this.health = Math.max(0, Math.min(this.maxHealth, health))
128✔
182
    if (this.health !== oldHealth) {
75✔
183
      ;(this.emit as any)(
20✔
184
        GameObjectEventType.HEALTH_CHANGED,
76✔
185
        this,
12✔
186
        this.health,
26✔
187
        this.maxHealth,
28✔
188
      )
12✔
189
    }
9✔
190
    if (this.health === 0) {
59✔
191
      this.destroyed = true
56✔
192
      ;(this.emit as any)(GameObjectEventType.DESTROYED, this)
98✔
193
    }
9✔
194
  }
195

196
  public takeDamage(amount: number): void {
38✔
197
    const oldHealth = this.health
68✔
198
    this.health = Math.max(0, this.health - amount)
104✔
199
    if (this.health !== oldHealth) {
75✔
200
      ;(this.emit as any)(
20✔
201
        GameObjectEventType.HEALTH_CHANGED,
76✔
202
        this,
12✔
203
        this.health,
26✔
204
        this.maxHealth,
28✔
205
      )
12✔
206
    }
9✔
207
    if (this.health === 0) {
59✔
208
      this.destroyed = true
56✔
209
      ;(this.emit as any)(GameObjectEventType.DESTROYED, this)
98✔
210
    }
9✔
211
  }
212

213
  public heal(amount: number): void {
32✔
214
    const oldHealth = this.health
68✔
215
    this.health = Math.min(this.maxHealth, this.health + amount)
130✔
216
    if (this.health !== oldHealth) {
75✔
217
      ;(this.emit as any)(
20✔
218
        GameObjectEventType.HEALTH_CHANGED,
76✔
219
        this,
12✔
220
        this.health,
26✔
221
        this.maxHealth,
28✔
222
      )
12✔
223
    }
9✔
224
  }
225

226
  public isDestroyed(): boolean {
27✔
227
    return this.destroyed
49✔
228
  }
229

230
  public destroy(): void {
23✔
231
    this.destroyed = true
52✔
232
    if (GameObject.debug) {
44✔
233
      // debug: destroy log
234
    }
9✔
235
    ;(this.emit as any)(GameObjectEventType.DESTROYED, this)
98✔
236
  }
237

238
  /**
239
   * Get the unique identifier for this game object
240
   * @returns The unique ID of this game object
241
   */
242
  public getId(): string {
21✔
243
    return this.id
35✔
244
  }
245

246
  /**
247
   * Get the type of this game object
248
   * @returns The type identifier of this game object
249
   */
250
  public abstract getType(): string
251

252
  /**
253
   * Get the engine this GameObject is registered with
254
   * @returns The game engine instance, or undefined if not registered
255
   */
256
  public getEngine(): GameEngineInterface | undefined {
25✔
257
    return this.engine
43✔
258
  }
259

260
  /**
261
   * Register this GameObject with an engine
262
   * @param engine The engine to register with
263
   */
264
  public registerWithEngine(engine: GameEngineInterface): void {
46✔
265
    this.engine = engine
50✔
266
    engine.registerGameObject(this)
72✔
267
  }
268

269
  /**
270
   * Get the collision source identifier for this game object
271
   * @returns The collision source ID of this game object
272
   */
273
  public getCollisionSourceId(): string {
×
274
    return this.getId()
26✔
275
  }
276

277
  public serialize(): SerializedGameObject {
25✔
278
    return {
28✔
279
      position: {
38✔
280
        x: this.position.x,
54✔
281
        y: this.position.y,
48✔
282
      },
16✔
283
      size: {
30✔
284
        x: this.size.x,
46✔
285
        y: this.size.y,
40✔
286
      },
16✔
287
      velocity: {
38✔
288
        x: this.velocity.x,
54✔
289
        y: this.velocity.y,
48✔
290
      },
16✔
291
      rotation: this.rotation,
60✔
292
      health: this.health,
52✔
293
      maxHealth: this.maxHealth,
64✔
294
      isDestroyed: this.destroyed,
62✔
295
    }
23✔
296
  }
297
  public static deserialize(_data: any): GameObject {
37✔
298
    throw new Error("GameObject.deserialize must be implemented by subclasses")
156✔
299
  }
300
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc