@antha/entity-2d 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,13 +15,13 @@ npm i @antha/entity-2d
15
15
  ```TypeScript
16
16
  import {AnthaEngine, defineAnthaMod} from '@antha/engine';
17
17
  import {createAnthaGraphics2dMod} from '@antha/graphics-2d';
18
- import {type AnthaEntity2dModState, createAnthaEntityMod2d} from '@antha/entity-2d';
18
+ import {type AnthaEntity2dModState, createAnthaEntity2dSuite} from '@antha/entity-2d';
19
19
 
20
20
  type GameState = AnthaEntity2dModState<{
21
21
  hasCreatedScoreEntity: boolean;
22
22
  }>;
23
23
 
24
- const {defineLogicEntity, mod: entityMod} = createAnthaEntityMod2d<{
24
+ const {defineLogicEntity, updateEntitiesMod} = createAnthaEntity2dSuite<{
25
25
  hasCreatedScoreEntity: boolean;
26
26
  }>();
27
27
 
@@ -38,7 +38,7 @@ const engine = new AnthaEngine<GameState>({
38
38
  },
39
39
  mods: [
40
40
  createAnthaGraphics2dMod(),
41
- entityMod,
41
+ updateEntitiesMod,
42
42
  defineAnthaMod<GameState>({
43
43
  modName: 'game-logic',
44
44
  async execute({state}) {
@@ -1,36 +1,206 @@
1
1
  import { type AnthaAssetModState } from '@antha/asset';
2
- import { type AnthaMod } from '@antha/engine';
2
+ import { type ModTrigger } from '@antha/engine';
3
3
  import { type AnthaGraphics2dModState } from '@antha/graphics-2d';
4
- import { type AnyObject, type PartialWithUndefined } from '@augment-vir/common';
5
- import { type EntityStore2d, type EntityStore2dConstructorParams } from './entity.js';
4
+ import { type AnyObject, type Constructor, type PartialWithUndefined } from '@augment-vir/common';
5
+ import { type Shape } from 'object-shape-tester';
6
+ import { BaseEntity2d, EntityStore2d, ViewEntity2d, type BaseEntityAssetDefinitions, type EntityCollisionDefinition, type EntityStore2dConstructorParams, type ParamsMap, type StaticEntity2dParts } from './entity.js';
6
7
  /**
7
- * State for {@link createAnthaEntityMod2d}.
8
+ * Params for both `defineEntity` and `defineLogicEntity`.
9
+ *
10
+ * @category Internal
11
+ */
12
+ export type DefineEntity2dArgs<ParamsShape extends Shape | undefined, EntityAssets extends BaseEntityAssetDefinitions | undefined> = {
13
+ /** Entity classes this entity observes collisions with. Omit to observe none. */
14
+ collidesWith?: EntityCollisionDefinition | undefined;
15
+ /**
16
+ * This key is used for deserialization of entities to track which class needs to be
17
+ * constructed. Do not use duplicate key strings across multiple entity classes.
18
+ */
19
+ key: string;
20
+ /**
21
+ * This should contain all parameters necessary to reconstruct this entity from scratch so it
22
+ * can be serialized, sent across the network in JSON format, then reconstructed on another
23
+ * device (for multiplayer support).
24
+ *
25
+ * Make sure to include {@link entityPositionParamsShape} as part of the shape if you want to
26
+ * include entity position parameters.
27
+ */
28
+ paramsShape?: ParamsShape;
29
+ /**
30
+ * A mapping of the entity's params object (defined by {@link DefineEntity2dArgs.paramsShape})
31
+ * keys to hitbox and/or view properties.
32
+ *
33
+ * Use `standardParamsMap` to automatically map the params `x` and `y` in `paramsShape` to both
34
+ * the entity's hitbox x/y and the entity's view x/y.
35
+ *
36
+ * @example
37
+ *
38
+ * ```ts
39
+ * const customMapping = {
40
+ * paramsShape: defineShape({
41
+ * left: -1,
42
+ * top: -1,
43
+ * }),
44
+ * paramsMap: {
45
+ * hitbox: {
46
+ * x: 'left', // maps `left` from `paramsShape` to the entity's hitbox.x
47
+ * y: 'top', // maps `top` from `paramsShape` to the entity's hitbox.y
48
+ * },
49
+ * view: {
50
+ * x: 'left', // maps `left` from `paramsShape` to the entity's view.x
51
+ * y: 'top', // maps `top` from `paramsShape` to the entity's view.y
52
+ * },
53
+ * },
54
+ * };
55
+ * ```
56
+ *
57
+ * @example
58
+ *
59
+ * ```ts
60
+ * const standardMapping = {
61
+ * paramsShape: defineShape({
62
+ * left: -1,
63
+ * top: -1,
64
+ * }),
65
+ * paramsMap: standardParamsMap, // use the standard x/y mapping
66
+ * };
67
+ * ```
68
+ *
69
+ * @example
70
+ *
71
+ * ```ts
72
+ * const undefinedMapping = {
73
+ * paramsShape: defineShape({
74
+ * left: -1,
75
+ * top: -1,
76
+ * }),
77
+ * paramsMap: undefined, // no mapping at all
78
+ * };
79
+ * ```
80
+ *
81
+ * @example
82
+ *
83
+ * ```ts
84
+ * const omittedMapping = {
85
+ * paramsShape: defineShape({
86
+ * left: -1,
87
+ * top: -1,
88
+ * }),
89
+ * // no mapping at all
90
+ * };
91
+ * ```
92
+ *
93
+ * @default undefined // no mapping
94
+ */
95
+ paramsMap?: ParamsMap<NoInfer<ParamsShape> extends Shape ? NoInfer<ParamsShape>['runtimeType'] : undefined> | undefined;
96
+ assets?: EntityAssets;
97
+ };
98
+ /**
99
+ * ========================
100
+ *
101
+ * # View Entity
102
+ *
103
+ * Types for entity definitions that have a view.
104
+ *
105
+ * ========================
106
+ */
107
+ /**
108
+ * The constructor output of {@link DefinedViewEntity2dConstructor}.
109
+ *
110
+ * @category Internal
111
+ */
112
+ export type DefinedViewEntity2dInstance<State extends AnyObject, ParamsShape extends Shape<Record<string, any>> | undefined, EntityAssets extends BaseEntityAssetDefinitions | undefined> = ViewEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined, EntityAssets>;
113
+ /**
114
+ * Output of {@link DefineViewEntity2d}.
115
+ *
116
+ * @category Internal
117
+ */
118
+ export type DefinedViewEntity2dConstructor<State extends AnyObject, ParamsShape extends Shape | undefined, EntityAssets extends BaseEntityAssetDefinitions | undefined> = Constructor<DefinedViewEntity2dInstance<State, ParamsShape, EntityAssets>, ConstructorParameters<typeof ViewEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>>> & StaticEntity2dParts<State, ParamsShape>;
119
+ /**
120
+ * Type for `defineEntity`.
121
+ *
122
+ * @category Internal
123
+ */
124
+ export type DefineViewEntity2d<State extends AnyObject> = <const ParamsShape extends Shape | undefined, const EntityAssets extends BaseEntityAssetDefinitions | undefined>(params: DefineEntity2dArgs<ParamsShape, EntityAssets>) => DefinedViewEntity2dConstructor<State, NoInfer<ParamsShape>, NoInfer<EntityAssets>> & StaticEntity2dParts<NoInfer<State>, NoInfer<ParamsShape>>;
125
+ /**
126
+ * ========================
127
+ *
128
+ * # Logic Entity
129
+ *
130
+ * Types for entity definitions that don't have a view. The only difference between these types and
131
+ * the view types are that this uses `BaseEntity2d` instead of `ViewEntity2d`.
132
+ *
133
+ * ========================
134
+ */
135
+ /**
136
+ * The constructor output of {@link DefinedLogicEntity2dConstructor}.
137
+ *
138
+ * @category Internal
139
+ */
140
+ export type DefinedLogicEntity2dInstance<State extends AnyObject, ParamsShape extends Shape | undefined> = BaseEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>;
141
+ /**
142
+ * Output of {@link DefineLogicEntity2d}.
143
+ *
144
+ * @category Internal
145
+ */
146
+ export type DefinedLogicEntity2dConstructor<State extends AnyObject, ParamsShape extends Shape | undefined> = Constructor<DefinedLogicEntity2dInstance<State, ParamsShape>, ConstructorParameters<typeof BaseEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>>> & StaticEntity2dParts<State, ParamsShape>;
147
+ /**
148
+ * Type for `defineLogicEntity`.
149
+ *
150
+ * @category Internal
151
+ */
152
+ export type DefineLogicEntity2d<State extends AnyObject> = <const ParamsShape extends Shape | undefined, const EntityAssets extends BaseEntityAssetDefinitions | undefined>(params: DefineEntity2dArgs<ParamsShape, EntityAssets>) => DefinedLogicEntity2dConstructor<NoInfer<State>, NoInfer<ParamsShape>>;
153
+ /**
154
+ * State for {@link createAnthaEntity2dSuite}.
8
155
  *
9
156
  * @category Internal
10
157
  */
11
158
  export type AnthaEntity2dModState<State extends AnyObject = AnyObject> = {
12
159
  entityStore: EntityStore2d<Partial<AnthaEntity2dModState<State>>>;
13
160
  /** If true, entity updates and collision checks are skipped. */
14
- disableEntityUpdates: boolean;
161
+ disableEntityUpdate: boolean;
162
+ /** If true, entity renders are skipped. */
163
+ disableEntityRender: boolean;
15
164
  /** If `true`, hit boxes are visually rendered for debugging purposes. */
16
- debugHitboxes: boolean;
165
+ showHitboxDebug: boolean;
17
166
  } & State & AnthaGraphics2dModState & AnthaAssetModState;
18
167
  /**
19
- * Options for {@link createAnthaEntityMod2d}.
168
+ * Options for {@link createAnthaEntity2dSuite}.
20
169
  *
21
170
  * @category Internal
22
171
  */
23
172
  export type AnthaEntity2dModOptions = PartialWithUndefined<EntityStore2dConstructorParams & {
24
173
  debug: boolean;
174
+ /**
175
+ * The entity update mod's trigger. If left undefined, enabled entity updates will run on
176
+ * every engine tick.
177
+ */
178
+ updateTrigger: ModTrigger;
179
+ /**
180
+ * If `true`, the `updateEntitiesMod` does not update entity data and collision state.
181
+ *
182
+ * @default false
183
+ */
184
+ disableEntityUpdate: boolean;
185
+ /**
186
+ * If `true`, the `updateEntitiesMod` does not update transient entity render state.
187
+ *
188
+ * @default false
189
+ */
190
+ disableEntityRender: boolean;
25
191
  }>;
26
192
  /**
27
- * A mod for rendering entities and handling collisions between them.
193
+ * Creates an entity update mod and entity factories.
28
194
  *
29
195
  * @category Pre-built Mods
30
196
  */
31
- export declare function createAnthaEntityMod2d<ExtraState extends AnyObject>(options?: Readonly<AnthaEntity2dModOptions>): {
32
- defineEntity: import("./entity-suite.js").DefineViewEntity2d<AnthaEntity2dModState<ExtraState>>;
33
- defineLogicEntity: import("./entity-suite.js").DefineLogicEntity2d<AnthaEntity2dModState<ExtraState>>;
197
+ export declare function createAnthaEntity2dSuite<ExtraState extends AnyObject>(options?: Readonly<AnthaEntity2dModOptions>): {
198
+ /**
199
+ * Updates entity data an/or transient render state, depending on the options you provided
200
+ * to `createAnthaEntity2dSuite`.
201
+ */
202
+ updateEntitiesMod: import("@antha/engine").AnthaMod<NoInfer<AnthaEntity2dModState<ExtraState>>>;
203
+ defineEntity: DefineViewEntity2d<AnthaEntity2dModState<ExtraState>>;
204
+ defineLogicEntity: DefineLogicEntity2d<AnthaEntity2dModState<ExtraState>>;
34
205
  entityKeys: Set<string>;
35
- mod: AnthaMod<AnthaEntity2dModState<ExtraState>>;
36
206
  };
@@ -1,60 +1,101 @@
1
- import { anthaAssetModName, AssetLoader } from '@antha/asset';
2
1
  import { defineAnthaMod, SkipExecution } from '@antha/engine';
3
- import { mergeDefinedProperties, } from '@augment-vir/common';
2
+ import { assertWrap } from '@augment-vir/assert';
3
+ import { getObjectTypedEntries, mergeDefinedProperties, } from '@augment-vir/common';
4
4
  import { html } from 'element-vir';
5
- import { defineEntitySuite2d } from './entity-suite.js';
5
+ import { BaseEntity2d, EntityStore2d, reverseParamsMap, ViewEntity2d, } from './entity.js';
6
+ function ensureEntityStore({ state, options, }) {
7
+ if (!state.pixi?.pixiApplication || !state.assetLoader) {
8
+ return false;
9
+ }
10
+ if (!state.entityStore) {
11
+ state.entityStore = new EntityStore2d(mergeDefinedProperties({
12
+ pixi: state.pixi.pixiApplication,
13
+ state,
14
+ assetLoader: state.assetLoader,
15
+ }, options));
16
+ }
17
+ return true;
18
+ }
6
19
  /**
7
- * A mod for rendering entities and handling collisions between them.
20
+ * Creates an entity update mod and entity factories.
8
21
  *
9
22
  * @category Pre-built Mods
10
23
  */
11
- export function createAnthaEntityMod2d(options = {}) {
12
- const { EntityStore, ...entitySuite } = defineEntitySuite2d();
13
- const mod = defineAnthaMod({
14
- modName: 'antha-entity-2d',
24
+ export function createAnthaEntity2dSuite(options = {}) {
25
+ const entityKeys = new Set();
26
+ function createDefiner(entityParent) {
27
+ return (params) => {
28
+ if (params.assets) {
29
+ getObjectTypedEntries(params.assets).forEach(([key, rawAsset,]) => {
30
+ rawAsset.assetName = [
31
+ params.key,
32
+ key,
33
+ ].join(':');
34
+ });
35
+ }
36
+ return defineEntity(entityParent, params);
37
+ };
38
+ }
39
+ function defineEntity(entityParent, { collidesWith, key, paramsShape, paramsMap, assets }) {
40
+ if (entityKeys.has(key)) {
41
+ throw new Error(`Entity key '${key}' has already been attached to an entity class.`);
42
+ }
43
+ entityKeys.add(key);
44
+ const classWrapper = {
45
+ // @ts-expect-error: abstract methods are intentionally not implemented here
46
+ [key]: class extends entityParent {
47
+ static collidesWith = collidesWith;
48
+ static collidesWithSet = new Set(collidesWith?.collidesWithOtherEntities);
49
+ static entityKey = key;
50
+ static paramsShape = paramsShape;
51
+ static assets = assets || {};
52
+ static paramsMap = paramsMap;
53
+ static reverseParamsMap = reverseParamsMap(paramsMap);
54
+ },
55
+ };
56
+ return assertWrap.isDefined(classWrapper[key]);
57
+ }
58
+ const updateEntitiesMod = defineAnthaMod({
59
+ modName: 'antha-entity-2d-update',
15
60
  initState: {
16
- debugHitboxes: !!options.debug,
61
+ showHitboxDebug: !!options.debug,
62
+ disableEntityRender: !!options.disableEntityRender,
63
+ disableEntityUpdate: !!options.disableEntityUpdate,
17
64
  },
18
65
  cleanup({ state }) {
19
66
  state.entityStore?.destroy();
20
67
  },
68
+ trigger: options.updateTrigger,
21
69
  async execute(executeParams) {
22
- /**
23
- * If we don't have a mod that is expected to create the asset loader, then we create
24
- * one ourself.
25
- */
26
- if (!executeParams.state.assetLoader &&
27
- !executeParams.engine.currentMods.some((mod) => mod.modName === anthaAssetModName)) {
28
- executeParams.state.assetLoader = new AssetLoader();
29
- }
30
- const pixiApplication = executeParams.state.pixi?.pixiApplication;
31
- if (!pixiApplication) {
70
+ if (!ensureEntityStore({
71
+ options,
72
+ state: executeParams.state,
73
+ })) {
32
74
  return SkipExecution;
33
75
  }
34
- if (executeParams.state.entityStore) {
35
- if (!executeParams.state.disableEntityUpdates) {
76
+ else if (executeParams.state.entityStore) {
77
+ if (!executeParams.state.disableEntityUpdate) {
36
78
  await executeParams.state.entityStore.updateAllEntities(executeParams);
37
79
  }
80
+ if (!executeParams.state.disableEntityRender) {
81
+ await executeParams.state.entityStore.renderAllEntities(executeParams);
82
+ }
38
83
  }
39
- else if (executeParams.state.assetLoader) {
40
- executeParams.state.entityStore = new EntityStore(mergeDefinedProperties({
41
- pixi: pixiApplication,
42
- state: executeParams.state,
43
- assetLoader: executeParams.state.assetLoader,
44
- }, options));
45
- }
46
- if (executeParams.state.debugHitboxes) {
47
- return html `
48
- <canvas class="hitbox-debug-canvas"></canvas>
49
- `;
50
- }
51
- else {
52
- return undefined;
53
- }
84
+ return executeParams.state.showHitboxDebug
85
+ ? html `
86
+ <canvas class="hitbox-debug-canvas"></canvas>
87
+ `
88
+ : undefined;
54
89
  },
55
90
  });
56
91
  return {
57
- mod,
58
- ...entitySuite,
92
+ /**
93
+ * Updates entity data an/or transient render state, depending on the options you provided
94
+ * to `createAnthaEntity2dSuite`.
95
+ */
96
+ updateEntitiesMod,
97
+ defineEntity: createDefiner(ViewEntity2d),
98
+ defineLogicEntity: createDefiner(BaseEntity2d),
99
+ entityKeys,
59
100
  };
60
101
  }
package/dist/entity.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { type Asset, type AssetLoader, type AssetValue } from '@antha/asset';
2
2
  import { type ModExecuteParams } from '@antha/engine';
3
3
  import { type PixiApplication } from '@antha/graphics-2d';
4
- import { ConstructorInstanceMap, type AbstractConstructor, type AnyObject, type Constructor, type EmptyObject, type ExtractKeysWithMatchingValues, type IsEqual, type IsNever, type MaybePromise, type PartialWithUndefined, type WritableKeysOf } from '@augment-vir/common';
4
+ import { ConstructorInstanceMap, type AbstractConstructor, type AnyObject, type Constructor, type EmptyObject, type ExtractKeysWithMatchingValues, type IsAny, type IsEqual, type IsNever, type MaybePromise, type PartialWithUndefined, type WritableKeysOf } from '@augment-vir/common';
5
5
  import { System as HitboxSystem, type Response as Collision, type Body as Hitbox } from 'detect-collisions';
6
6
  import { type Shape } from 'object-shape-tester';
7
7
  import { type Container, type ViewContainer } from 'pixi.js';
8
8
  import { GenericListenTarget } from 'typed-event-target';
9
- import { type StaticEntity2dParts } from './entity-suite.js';
10
9
  export { System as HitboxSystem, type Response as Collision } from 'detect-collisions';
11
10
  /**
12
11
  * Definition of entity assets used when defining an entity.
@@ -74,6 +73,37 @@ export type EntityStore2dConstructorParams<State extends AnyObject = any> = {
74
73
  * @category Internal
75
74
  */
76
75
  export type Entity2dConstructor = Constructor<BaseEntity2d> & StaticEntity2dParts<any, any, BaseEntityAssetDefinitions>;
76
+ /**
77
+ * Static members of both view and logic entity constructors.
78
+ *
79
+ * @category Internal
80
+ */
81
+ export type StaticEntity2dParts<State extends AnyObject = any, ParamsShape extends Shape | undefined = any, EntityAssets extends BaseEntityAssetDefinitions | undefined = any> = {
82
+ /** Entity classes this entity observes collisions with. Omit to observe none. */
83
+ collidesWith: EntityCollisionDefinition | undefined;
84
+ /** Cached entity classes this entity observes collisions with. */
85
+ collidesWithSet: ReadonlySet<Entity2dConstructor>;
86
+ /**
87
+ * This key is used for deserialization of entities to track which class needs to be
88
+ * constructed. You cannot have duplicate keys loaded at the same time.
89
+ *
90
+ * This is used instead of inferring the entity key from the class name so that you can still
91
+ * minify your class names without making debugging nigh impossible (you'll still know which
92
+ * entities are being serialized and deserialized even if your class names are minified).
93
+ */
94
+ entityKey: string;
95
+ /** Shape definition of this entity's parameters. */
96
+ paramsShape: ParamsShape;
97
+ /**
98
+ * Defines which properties from {@link BaseEntity2d.params} will be mapped to hitbox and/or view
99
+ * properties.
100
+ */
101
+ paramsMap: IsAny<ParamsShape> extends true ? any : ParamsMap<ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>;
102
+ /** Parses the serialized params generated by {@link BaseEntity2d.serialize}. */
103
+ deserialize(serialized: string | undefined): AnyObject | undefined;
104
+ assets: MappedEntityAssets<EntityAssets>;
105
+ ConstructorArgsType: Entity2dConstructorParams<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>;
106
+ };
77
107
  /**
78
108
  * Defines which entity classes this entity observes collisions with.
79
109
  *
@@ -139,10 +169,10 @@ export declare class EntityStore2d<State extends AnyObject = any> {
139
169
  * Runs `.update()` on all current entities and runs collision detection for all hitboxes. If
140
170
  * any entities get marked as destroyed during their update, then they will be removed from the
141
171
  * set of entities.
142
- *
143
- * @returns All detected hitbox collisions (if any).
144
172
  */
145
173
  updateAllEntities(updateParams: Readonly<ModExecuteParams<NoInfer<State>>>): Promise<void>;
174
+ /** Runs presentation-only updates for every entity without simulating collisions. */
175
+ renderAllEntities(renderParams: Readonly<ModExecuteParams<NoInfer<State>>>): Promise<void>;
146
176
  /** Get all current instances of the given entity class constructor. */
147
177
  getEntities<T>(entityClassConstructor: AbstractConstructor<T> | Constructor<T>): Set<T>;
148
178
  /** Remove an entity from the store. */
@@ -299,6 +329,12 @@ export declare const position2dParamsMap: {
299
329
  * @category Internal
300
330
  */
301
331
  export type ReverseParamsMap = Record<string, Partial<Record<'hitbox' | 'view', string[]>>>;
332
+ /**
333
+ * Converts {@link ParamsMap} to {@link ReverseParamsMap}.
334
+ *
335
+ * @category Internal
336
+ */
337
+ export declare function reverseParamsMap(paramsMap: ParamsMap | undefined): ReverseParamsMap | undefined;
302
338
  /**
303
339
  * Base entity class, types, and functionality.
304
340
  *
@@ -357,6 +393,12 @@ export declare abstract class BaseEntity2d<State extends AnyObject = any, Params
357
393
  * entity definition classes.
358
394
  */
359
395
  abstract update(updateParams: Readonly<ModExecuteParams<NoInfer<State>>>): MaybePromise<void>;
396
+ /**
397
+ * This is meant to be used to update transient render state without changing the authoritative
398
+ * entity state or logic. This is optional. `updateEntitiesMod` calls it (if it exists) after
399
+ * each entity data update unless `disableEntityRender` is `true`.
400
+ */
401
+ render(_renderParams: Readonly<ModExecuteParams<NoInfer<State>>>): MaybePromise<void>;
360
402
  /** Called after construction to perform async initialization (e.g. creating views). */
361
403
  initInstance(): MaybePromise<void>;
362
404
  /** The game's current state. */
package/dist/entity.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { assert, check } from '@augment-vir/assert';
2
- import { ConstructorInstanceMap, getObjectTypedEntries, makeWritable, mapObjectValues, } from '@augment-vir/common';
2
+ import { ConstructorInstanceMap, getObjectTypedEntries, getOrSet, makeWritable, mapObjectValues, } from '@augment-vir/common';
3
3
  import { System as HitboxSystem, Response, } from 'detect-collisions';
4
4
  import { assertValidShape, defineShape } from 'object-shape-tester';
5
5
  import { ParticleContainer } from 'pixi.js';
@@ -174,8 +174,6 @@ export class EntityStore2d {
174
174
  * Runs `.update()` on all current entities and runs collision detection for all hitboxes. If
175
175
  * any entities get marked as destroyed during their update, then they will be removed from the
176
176
  * set of entities.
177
- *
178
- * @returns All detected hitbox collisions (if any).
179
177
  */
180
178
  async updateAllEntities(updateParams) {
181
179
  if (this.isDestroyed) {
@@ -229,6 +227,18 @@ export class EntityStore2d {
229
227
  });
230
228
  await Promise.all(collisionPromises);
231
229
  }
230
+ /** Runs presentation-only updates for every entity without simulating collisions. */
231
+ async renderAllEntities(renderParams) {
232
+ if (this.isDestroyed) {
233
+ throw new Error('Cannot operate on a destroyed entity store.');
234
+ }
235
+ for (const entity of this.currentEntityInstances) {
236
+ if (entity.isDestroyed) {
237
+ return;
238
+ }
239
+ await entity.render(renderParams);
240
+ }
241
+ }
232
242
  /** Get all current instances of the given entity class constructor. */
233
243
  getEntities(entityClassConstructor) {
234
244
  return this.entityInstanceMap.getInstances(entityClassConstructor);
@@ -361,6 +371,34 @@ export const position2dParamsMap = {
361
371
  y: true,
362
372
  },
363
373
  };
374
+ /**
375
+ * Converts {@link ParamsMap} to {@link ReverseParamsMap}.
376
+ *
377
+ * @category Internal
378
+ */
379
+ export function reverseParamsMap(paramsMap) {
380
+ if (!paramsMap) {
381
+ return undefined;
382
+ }
383
+ const reverseParamsMap = {};
384
+ getObjectTypedEntries(paramsMap).forEach(([topKey, mappings,]) => {
385
+ getObjectTypedEntries(mappings).forEach(([mapToKey, mapFromKey,]) => {
386
+ if (!mapFromKey) {
387
+ return;
388
+ }
389
+ const paramMapping = getOrSet(reverseParamsMap, check.isString(mapFromKey) ? mapFromKey : mapToKey, () => {
390
+ return {};
391
+ });
392
+ const mapToArray = getOrSet(paramMapping, topKey, () => {
393
+ return [];
394
+ });
395
+ if (!mapToArray.includes(mapToKey)) {
396
+ mapToArray.push(mapToKey);
397
+ }
398
+ });
399
+ });
400
+ return reverseParamsMap;
401
+ }
364
402
  /**
365
403
  * Base entity class, types, and functionality.
366
404
  *
@@ -438,6 +476,13 @@ export class BaseEntity2d {
438
476
  entityAssets: assets,
439
477
  });
440
478
  }
479
+ /**
480
+ * This is meant to be used to update transient render state without changing the authoritative
481
+ * entity state or logic. This is optional. `updateEntitiesMod` calls it (if it exists) after
482
+ * each entity data update unless `disableEntityRender` is `true`.
483
+ */
484
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
485
+ render(_renderParams) { }
441
486
  /** Called after construction to perform async initialization (e.g. creating views). */
442
487
  initInstance() {
443
488
  return;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from './antha-entity-2d.mod.js';
2
- export * from './entity-suite.js';
3
2
  export * from './entity.js';
4
3
  export * from './load-antha-assets.js';
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from './antha-entity-2d.mod.js';
2
- export * from './entity-suite.js';
3
2
  export * from './entity.js';
4
3
  export * from './load-antha-assets.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antha/entity-2d",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "An Antha mod for handling 2D entities.",
5
5
  "keywords": [
6
6
  "vir",
@@ -39,8 +39,11 @@
39
39
  "@augment-vir/common": "^32.3.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@antha/engine": "^0.17.0",
43
- "@antha/web-test-runner-plugin-pixi": "^0.17.0",
42
+ "@antha/asset": "^0.18.0",
43
+ "@antha/audio": "^0.18.0",
44
+ "@antha/engine": "^0.18.0",
45
+ "@antha/graphics-2d": "^0.18.0",
46
+ "@antha/web-test-runner-plugin-pixi": "^0.18.0",
44
47
  "@augment-vir/test": "^32.3.0",
45
48
  "@web/dev-server-esbuild": "^2.0.0",
46
49
  "@web/test-runner": "^1.0.0",
@@ -52,10 +55,10 @@
52
55
  "typed-event-target": "^4.3.3"
53
56
  },
54
57
  "peerDependencies": {
55
- "@antha/asset": "^0.17.0",
56
- "@antha/audio": "^0.17.0",
57
- "@antha/engine": "^0.17.0",
58
- "@antha/graphics-2d": "^0.17.0",
58
+ "@antha/asset": "^0.18.0",
59
+ "@antha/audio": "^0.18.0",
60
+ "@antha/engine": "^0.18.0",
61
+ "@antha/graphics-2d": "^0.18.0",
59
62
  "detect-collisions": "^10",
60
63
  "element-vir": "^27",
61
64
  "object-shape-tester": "^6",
@@ -1,228 +0,0 @@
1
- import { type AnyObject, type Constructor, type IsAny } from '@augment-vir/common';
2
- import { type Shape } from 'object-shape-tester';
3
- import { BaseEntity2d, type BaseEntityAssetDefinitions, type Entity2dConstructor, type Entity2dConstructorParams, type EntityCollisionDefinition, EntityStore2d, type EntityStore2dConstructorParams, type MappedEntityAssets, type ParamsMap, type ReverseParamsMap, ViewEntity2d } from './entity.js';
4
- /**
5
- * Params for both {@link EntitySuite2d.defineEntity} and {@link EntitySuite2d.defineLogicEntity}.
6
- *
7
- * @category Internal
8
- */
9
- export type DefineEntity2dArgs<ParamsShape extends Shape | undefined, EntityAssets extends BaseEntityAssetDefinitions | undefined> = {
10
- /** Entity classes this entity observes collisions with. Omit to observe none. */
11
- collidesWith?: EntityCollisionDefinition | undefined;
12
- /**
13
- * This key is used for deserialization of entities to track which class needs to be
14
- * constructed. Do not use duplicate key strings across multiple entity classes.
15
- */
16
- key: string;
17
- /**
18
- * This should contain all parameters necessary to reconstruct this entity from scratch so it
19
- * can be serialized, sent across the network in JSON format, then reconstructed on another
20
- * device (for multiplayer support).
21
- *
22
- * Make sure to include {@link entityPositionParamsShape} as part of the shape if you want to
23
- * include entity position parameters.
24
- */
25
- paramsShape?: ParamsShape;
26
- /**
27
- * A mapping of the entity's params object (defined by {@link DefineEntity2dArgs.paramsShape})
28
- * keys to hitbox and/or view properties.
29
- *
30
- * Use `standardParamsMap` to automatically map the params `x` and `y` in `paramsShape` to both
31
- * the entity's hitbox x/y and the entity's view x/y.
32
- *
33
- * @example
34
- *
35
- * ```ts
36
- * const customMapping = {
37
- * paramsShape: defineShape({
38
- * left: -1,
39
- * top: -1,
40
- * }),
41
- * paramsMap: {
42
- * hitbox: {
43
- * x: 'left', // maps `left` from `paramsShape` to the entity's hitbox.x
44
- * y: 'top', // maps `top` from `paramsShape` to the entity's hitbox.y
45
- * },
46
- * view: {
47
- * x: 'left', // maps `left` from `paramsShape` to the entity's view.x
48
- * y: 'top', // maps `top` from `paramsShape` to the entity's view.y
49
- * },
50
- * },
51
- * };
52
- * ```
53
- *
54
- * @example
55
- *
56
- * ```ts
57
- * const standardMapping = {
58
- * paramsShape: defineShape({
59
- * left: -1,
60
- * top: -1,
61
- * }),
62
- * paramsMap: standardParamsMap, // use the standard x/y mapping
63
- * };
64
- * ```
65
- *
66
- * @example
67
- *
68
- * ```ts
69
- * const undefinedMapping = {
70
- * paramsShape: defineShape({
71
- * left: -1,
72
- * top: -1,
73
- * }),
74
- * paramsMap: undefined, // no mapping at all
75
- * };
76
- * ```
77
- *
78
- * @example
79
- *
80
- * ```ts
81
- * const omittedMapping = {
82
- * paramsShape: defineShape({
83
- * left: -1,
84
- * top: -1,
85
- * }),
86
- * // no mapping at all
87
- * };
88
- * ```
89
- *
90
- * @default undefined // no mapping
91
- */
92
- paramsMap?: ParamsMap<NoInfer<ParamsShape> extends Shape ? NoInfer<ParamsShape>['runtimeType'] : undefined> | undefined;
93
- assets?: EntityAssets;
94
- };
95
- /**
96
- * Static members of both view and logic entity constructors.
97
- *
98
- * @category Internal
99
- */
100
- export type StaticEntity2dParts<State extends AnyObject = any, ParamsShape extends Shape | undefined = any, EntityAssets extends BaseEntityAssetDefinitions | undefined = any> = {
101
- /** Entity classes this entity observes collisions with. Omit to observe none. */
102
- collidesWith: EntityCollisionDefinition | undefined;
103
- /** Cached entity classes this entity observes collisions with. */
104
- collidesWithSet: ReadonlySet<Entity2dConstructor>;
105
- /**
106
- * This key is used for deserialization of entities to track which class needs to be
107
- * constructed. You cannot have duplicate keys loaded at the same time.
108
- *
109
- * This is used instead of inferring the entity key from the class name so that you can still
110
- * minify your class names without making debugging nigh impossible (you'll still know which
111
- * entities are being serialized and deserialized even if your class names are minified).
112
- */
113
- entityKey: string;
114
- /** Shape definition of this entity's parameters. */
115
- paramsShape: ParamsShape;
116
- /**
117
- * Defines which properties from {@link BaseEntity2d.params} will be mapped to hitbox and/or view
118
- * properties.
119
- */
120
- paramsMap: IsAny<ParamsShape> extends true ? any : ParamsMap<ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>;
121
- /** Parses the serialized params generated by {@link BaseEntity2d.serialize}. */
122
- deserialize(serialized: string | undefined): AnyObject | undefined;
123
- assets: MappedEntityAssets<EntityAssets>;
124
- ConstructorArgsType: Entity2dConstructorParams<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>;
125
- };
126
- /**
127
- * ========================
128
- *
129
- * # View Entity
130
- *
131
- * Types for entity definitions that have a view.
132
- *
133
- * ========================
134
- */
135
- /**
136
- * The constructor output of {@link DefinedViewEntity2dConstructor}.
137
- *
138
- * @category Internal
139
- */
140
- export type DefinedViewEntity2dInstance<State extends AnyObject, ParamsShape extends Shape<Record<string, any>> | undefined, EntityAssets extends BaseEntityAssetDefinitions | undefined> = ViewEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined, EntityAssets>;
141
- /**
142
- * Output of {@link DefineViewEntity2d}.
143
- *
144
- * @category Internal
145
- */
146
- export type DefinedViewEntity2dConstructor<State extends AnyObject, ParamsShape extends Shape | undefined, EntityAssets extends BaseEntityAssetDefinitions | undefined> = Constructor<DefinedViewEntity2dInstance<State, ParamsShape, EntityAssets>, ConstructorParameters<typeof ViewEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>>> & StaticEntity2dParts<State, ParamsShape>;
147
- /**
148
- * Type for {@link EntitySuite2d.defineEntity}.
149
- *
150
- * @category Internal
151
- */
152
- export type DefineViewEntity2d<State extends AnyObject> = <const ParamsShape extends Shape | undefined, const EntityAssets extends BaseEntityAssetDefinitions | undefined>(params: DefineEntity2dArgs<ParamsShape, EntityAssets>) => DefinedViewEntity2dConstructor<State, NoInfer<ParamsShape>, NoInfer<EntityAssets>> & StaticEntity2dParts<NoInfer<State>, NoInfer<ParamsShape>>;
153
- /**
154
- * ========================
155
- *
156
- * # Logic Entity
157
- *
158
- * Types for entity definitions that don't have a view. The only difference between these types and
159
- * the view types are that this uses `BaseEntity2d` instead of `ViewEntity2d`.
160
- *
161
- * ========================
162
- */
163
- /**
164
- * The constructor output of {@link DefinedLogicEntity2dConstructor}.
165
- *
166
- * @category Internal
167
- */
168
- export type DefinedLogicEntity2dInstance<State extends AnyObject, ParamsShape extends Shape | undefined> = BaseEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>;
169
- /**
170
- * Output of {@link DefineLogicEntity2d}.
171
- *
172
- * @category Internal
173
- */
174
- export type DefinedLogicEntity2dConstructor<State extends AnyObject, ParamsShape extends Shape | undefined> = Constructor<DefinedLogicEntity2dInstance<State, ParamsShape>, ConstructorParameters<typeof BaseEntity2d<State, ParamsShape extends Shape ? ParamsShape['runtimeType'] : undefined>>> & StaticEntity2dParts<State, ParamsShape>;
175
- /**
176
- * Type for `EntitySuite.defineEntity`.
177
- *
178
- * @category Internal
179
- */
180
- export type DefineLogicEntity2d<State extends AnyObject> = <const ParamsShape extends Shape | undefined, const EntityAssets extends BaseEntityAssetDefinitions | undefined>(params: DefineEntity2dArgs<ParamsShape, EntityAssets>) => DefinedLogicEntity2dConstructor<NoInfer<State>, NoInfer<ParamsShape>>;
181
- /**
182
- * ========================
183
- *
184
- * # Entity Suite
185
- *
186
- * ========================
187
- */
188
- /**
189
- * Output of {@link defineEntitySuite2d}, used to defining and creating entities.
190
- *
191
- * @category Internal
192
- */
193
- export type EntitySuite2d<State extends AnyObject> = {
194
- /**
195
- * The suite's entity store constructor. Instantiate this and to add your first entities.
196
- *
197
- * All defined entities will also have a reference to this store so they can add additional
198
- * entities by themselves.
199
- */
200
- EntityStore: new (params: Readonly<EntityStore2dConstructorParams>) => EntityStore2d<State>;
201
- /**
202
- * Define a standard entity (with a view). This is intended to be extended from your entity
203
- * class.
204
- */
205
- defineEntity: DefineViewEntity2d<State>;
206
- /** Define an entity that doesn't have an attached view. These are likely to be rare. */
207
- defineLogicEntity: DefineLogicEntity2d<State>;
208
- /**
209
- * A set of entity keys used within this entity suite. This will only be populated by all
210
- * classes that are defined with `defineEntity` or `defineLogicEntity` (so this will miss any
211
- * not-yet-resolved dynamic imports). This will be populated even before the classes are ever
212
- * instantiated.
213
- */
214
- entityKeys: Set<string>;
215
- };
216
- /**
217
- * This is the starting point of the @game-vir/entity package. Call this to produce the function
218
- * needed to define new entities and the store needed to add entity instances.
219
- *
220
- * @category Main
221
- */
222
- export declare function defineEntitySuite2d<State extends AnyObject>(): EntitySuite2d<State>;
223
- /**
224
- * Converts {@link ParamsMap} to {@link ReverseParamsMap}.
225
- *
226
- * @category Internal
227
- */
228
- export declare function reverseParamsMap(paramsMap: ParamsMap | undefined): ReverseParamsMap | undefined;
@@ -1,79 +0,0 @@
1
- import { check } from '@augment-vir/assert';
2
- import { getObjectTypedEntries, getOrSet, } from '@augment-vir/common';
3
- import { BaseEntity2d, EntityStore2d, ViewEntity2d, } from './entity.js';
4
- /**
5
- * This is the starting point of the @game-vir/entity package. Call this to produce the function
6
- * needed to define new entities and the store needed to add entity instances.
7
- *
8
- * @category Main
9
- */
10
- export function defineEntitySuite2d() {
11
- const entityKeys = new Set();
12
- function createDefiner(entityParent) {
13
- return (params) => {
14
- if (params.assets) {
15
- getObjectTypedEntries(params.assets).forEach(([key, rawAsset,]) => {
16
- rawAsset.assetName = [
17
- params.key,
18
- key,
19
- ].join(':');
20
- });
21
- }
22
- return defineEntity(entityParent, params);
23
- };
24
- }
25
- function defineEntity(entityParent, { collidesWith, key, paramsShape, paramsMap, assets }) {
26
- if (entityKeys.has(key)) {
27
- throw new Error(`Entity key '${key}' has already been attached to an entity class.`);
28
- }
29
- entityKeys.add(key);
30
- const classWrapper = {
31
- // @ts-expect-error: abstract methods are intentionally not implemented here
32
- [key]: class extends entityParent {
33
- static collidesWith = collidesWith;
34
- static collidesWithSet = new Set(collidesWith?.collidesWithOtherEntities);
35
- static entityKey = key;
36
- static paramsShape = paramsShape;
37
- static assets = assets || {};
38
- static paramsMap = paramsMap;
39
- static reverseParamsMap = reverseParamsMap(paramsMap);
40
- },
41
- };
42
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
43
- return classWrapper[key];
44
- }
45
- return {
46
- EntityStore: EntityStore2d,
47
- defineEntity: createDefiner(ViewEntity2d),
48
- defineLogicEntity: createDefiner(BaseEntity2d),
49
- entityKeys,
50
- };
51
- }
52
- /**
53
- * Converts {@link ParamsMap} to {@link ReverseParamsMap}.
54
- *
55
- * @category Internal
56
- */
57
- export function reverseParamsMap(paramsMap) {
58
- if (!paramsMap) {
59
- return undefined;
60
- }
61
- const reverseParamsMap = {};
62
- getObjectTypedEntries(paramsMap).forEach(([topKey, mappings,]) => {
63
- getObjectTypedEntries(mappings).forEach(([mapToKey, mapFromKey,]) => {
64
- if (!mapFromKey) {
65
- return;
66
- }
67
- const paramMapping = getOrSet(reverseParamsMap, check.isString(mapFromKey) ? mapFromKey : mapToKey, () => {
68
- return {};
69
- });
70
- const mapToArray = getOrSet(paramMapping, topKey, () => {
71
- return [];
72
- });
73
- if (!mapToArray.includes(mapToKey)) {
74
- mapToArray.push(mapToKey);
75
- }
76
- });
77
- });
78
- return reverseParamsMap;
79
- }