@lagless/create 0.0.38 → 0.0.39

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.
Files changed (46) hide show
  1. package/LICENSE +26 -0
  2. package/dist/index.js +96 -16
  3. package/dist/index.js.map +1 -1
  4. package/package.json +5 -4
  5. package/templates/pixi-react/AGENTS.md +57 -27
  6. package/templates/pixi-react/CLAUDE.md +225 -49
  7. package/templates/pixi-react/README.md +16 -6
  8. package/templates/pixi-react/__packageName__-backend/package.json +1 -0
  9. package/templates/pixi-react/__packageName__-backend/src/main.ts +2 -0
  10. package/templates/pixi-react/__packageName__-frontend/package.json +8 -0
  11. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/grid-background.tsx +4 -0
  12. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/player-view.tsx +68 -0
  13. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/runner-provider.tsx +57 -0
  14. package/templates/pixi-react/__packageName__-frontend/src/app/hooks/use-start-multiplayer-match.ts +5 -5
  15. package/templates/pixi-react/__packageName__-frontend/src/app/screens/title.screen.tsx +18 -1
  16. package/templates/pixi-react/__packageName__-simulation/package.json +7 -0
  17. package/templates/pixi-react/__packageName__-simulation/src/lib/arena.ts +12 -0
  18. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/ecs.yaml +90 -6
  19. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/apply-move-input.system.ts +73 -0
  20. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/boundary.system.ts +2 -0
  21. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/damping.system.ts +2 -0
  22. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/index.ts +8 -0
  23. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/integrate.system.ts +2 -0
  24. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/physics-step.system.ts +65 -0
  25. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-connection.system.ts +158 -0
  26. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-leave.system.ts +70 -0
  27. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/save-prev-transform.system.ts +46 -0
  28. package/templates/pixi-react/docs/01-schema-and-codegen.md +244 -0
  29. package/templates/pixi-react/docs/02-ecs-systems.md +293 -0
  30. package/templates/pixi-react/docs/03-determinism.md +204 -0
  31. package/templates/pixi-react/docs/04-input-system.md +255 -0
  32. package/templates/pixi-react/docs/05-signals.md +175 -0
  33. package/templates/pixi-react/docs/06-rendering.md +256 -0
  34. package/templates/pixi-react/docs/07-multiplayer.md +277 -0
  35. package/templates/pixi-react/docs/08-physics2d.md +266 -0
  36. package/templates/pixi-react/docs/08-physics3d.md +312 -0
  37. package/templates/pixi-react/docs/09-recipes.md +362 -0
  38. package/templates/pixi-react/docs/10-common-mistakes.md +224 -0
  39. package/templates/pixi-react/docs/api-quick-reference.md +254 -0
  40. package/templates/pixi-react/package.json +6 -0
  41. /package/templates/pixi-react/__packageName__-backend/{tsconfig.json → tsconfig.json.ejs} +0 -0
  42. /package/templates/pixi-react/__packageName__-frontend/{tsconfig.json → tsconfig.json.ejs} +0 -0
  43. /package/templates/pixi-react/__packageName__-frontend/{vite.config.ts → vite.config.ts.ejs} +0 -0
  44. /package/templates/pixi-react/__packageName__-simulation/{.swcrc → .swcrc.ejs} +0 -0
  45. /package/templates/pixi-react/__packageName__-simulation/{tsconfig.json → tsconfig.json.ejs} +0 -0
  46. /package/templates/pixi-react/{tsconfig.base.json → tsconfig.base.json.ejs} +0 -0
@@ -1,8 +1,20 @@
1
+ <% if (simulationType === 'physics3d') { -%>
2
+ export const <%= projectName %>Arena = {
3
+ width: 40,
4
+ depth: 30,
5
+ playerRadius: 0.5,
6
+ moveSpeed: 5.0,
7
+ hashReportInterval: 120,
8
+ } as const;
9
+ <% } else { -%>
1
10
  export const <%= projectName %>Arena = {
2
11
  width: 800,
3
12
  height: 600,
4
13
  playerRadius: 20,
5
14
  moveSpeed: 3.0,
15
+ <% if (simulationType === 'raw') { -%>
6
16
  damping: 0.85,
17
+ <% } -%>
7
18
  hashReportInterval: 120,
8
19
  } as const;
20
+ <% } -%>
@@ -1,3 +1,4 @@
1
+ <% if (simulationType === 'raw') { -%>
1
2
  # Run codegen: npx @lagless/codegen -c <%= packageName %>-simulation/src/lib/schema/ecs.yaml
2
3
  projectName: <%= projectName %>
3
4
 
@@ -43,10 +44,93 @@ inputs:
43
44
 
44
45
  filters:
45
46
  PlayerFilter:
46
- include:
47
- - Transform2d
48
- - PlayerBody
47
+ include: [Transform2d, PlayerBody]
49
48
  MovingFilter:
50
- include:
51
- - Transform2d
52
- - Velocity2d
49
+ include: [Transform2d, Velocity2d]
50
+ <% } else if (simulationType === 'physics2d') { -%>
51
+ # Run codegen: npx @lagless/codegen -c <%= packageName %>-simulation/src/lib/schema/ecs.yaml
52
+ projectName: <%= projectName %>
53
+ simulationType: physics2d
54
+
55
+ # Transform2d (positionX/Y, rotation, prevPositionX/Y, prevRotation) and PhysicsRefs
56
+ # are auto-prepended by codegen when simulationType is physics2d.
57
+
58
+ components:
59
+ PlayerBody:
60
+ playerSlot: uint8
61
+ radius: float32
62
+
63
+ singletons:
64
+ GameState:
65
+ gamePhase: uint8
66
+
67
+ playerResources:
68
+ PlayerResource:
69
+ id: uint8[16]
70
+ entity: uint32
71
+ connected: uint8
72
+ lastReportedHash: uint32
73
+ lastReportedHashTick: uint32
74
+ hashMismatchCount: uint16
75
+
76
+ inputs:
77
+ PlayerJoined:
78
+ slot: uint8
79
+ playerId: uint8[16]
80
+ PlayerLeft:
81
+ slot: uint8
82
+ reason: uint8
83
+ MoveInput:
84
+ directionX: float32
85
+ directionY: float32
86
+ ReportHash:
87
+ hash: uint32
88
+ atTick: uint32
89
+
90
+ filters:
91
+ PlayerFilter:
92
+ include: [Transform2d, PhysicsRefs, PlayerBody]
93
+ <% } else { -%>
94
+ # Run codegen: npx @lagless/codegen -c <%= packageName %>-simulation/src/lib/schema/ecs.yaml
95
+ projectName: <%= projectName %>
96
+ simulationType: physics3d
97
+
98
+ # Transform3d (positionX/Y/Z, rotationX/Y/Z/W, prev*) and PhysicsRefs
99
+ # are auto-prepended by codegen when simulationType is physics3d.
100
+
101
+ components:
102
+ PlayerBody:
103
+ playerSlot: uint8
104
+ radius: float32
105
+
106
+ singletons:
107
+ GameState:
108
+ gamePhase: uint8
109
+
110
+ playerResources:
111
+ PlayerResource:
112
+ id: uint8[16]
113
+ entity: uint32
114
+ connected: uint8
115
+ lastReportedHash: uint32
116
+ lastReportedHashTick: uint32
117
+ hashMismatchCount: uint16
118
+
119
+ inputs:
120
+ PlayerJoined:
121
+ slot: uint8
122
+ playerId: uint8[16]
123
+ PlayerLeft:
124
+ slot: uint8
125
+ reason: uint8
126
+ MoveInput:
127
+ directionX: float32
128
+ directionY: float32
129
+ ReportHash:
130
+ hash: uint32
131
+ atTick: uint32
132
+
133
+ filters:
134
+ PlayerFilter:
135
+ include: [Transform3d, PhysicsRefs, PlayerBody]
136
+ <% } -%>
@@ -1,3 +1,75 @@
1
+ <% if (simulationType === 'physics3d') { -%>
2
+ import { ECSSystem, IECSSystem, InputProvider, PlayerResources } from '@lagless/core';
3
+ import { MoveInput, PlayerResource } from '../schema/code-gen/index.js';
4
+ import { PhysicsWorldManager3d } from '@lagless/physics3d';
5
+ import { PhysicsRefs } from '../schema/code-gen/index.js';
6
+ import { <%= projectName %>Arena } from '../arena.js';
7
+
8
+ const finite = (v: number): number => Number.isFinite(v) ? v : 0;
9
+
10
+ @ECSSystem()
11
+ export class ApplyMoveInputSystem implements IECSSystem {
12
+ constructor(
13
+ private readonly _InputProvider: InputProvider,
14
+ private readonly _PlayerResources: PlayerResources,
15
+ private readonly _PhysicsRefs: PhysicsRefs,
16
+ private readonly _WorldManager: PhysicsWorldManager3d,
17
+ ) {}
18
+
19
+ public update(tick: number): void {
20
+ const rpcs = this._InputProvider.collectTickRPCs(tick, MoveInput);
21
+
22
+ for (const rpc of rpcs) {
23
+ const playerResource = this._PlayerResources.get(PlayerResource, rpc.meta.playerSlot);
24
+ const entity = playerResource.safe.entity;
25
+
26
+ // Sanitize input
27
+ const dirX = finite(rpc.data.directionX);
28
+ const dirZ = finite(rpc.data.directionY); // directionY maps to Z axis in 3D
29
+
30
+ const body = this._WorldManager.getBody(this._PhysicsRefs.unsafe.bodyHandle[entity]);
31
+ body.setLinvel(
32
+ { x: dirX * <%= projectName %>Arena.moveSpeed, y: body.linvel().y, z: dirZ * <%= projectName %>Arena.moveSpeed },
33
+ true,
34
+ );
35
+ }
36
+ }
37
+ }
38
+ <% } else if (simulationType === 'physics2d') { -%>
39
+ import { ECSSystem, IECSSystem, InputProvider, PlayerResources } from '@lagless/core';
40
+ import { MoveInput, PlayerResource } from '../schema/code-gen/index.js';
41
+ import { PhysicsWorldManager2d } from '@lagless/physics2d';
42
+ import { PhysicsRefs } from '../schema/code-gen/index.js';
43
+ import { <%= projectName %>Arena } from '../arena.js';
44
+
45
+ const finite = (v: number): number => Number.isFinite(v) ? v : 0;
46
+
47
+ @ECSSystem()
48
+ export class ApplyMoveInputSystem implements IECSSystem {
49
+ constructor(
50
+ private readonly _InputProvider: InputProvider,
51
+ private readonly _PlayerResources: PlayerResources,
52
+ private readonly _PhysicsRefs: PhysicsRefs,
53
+ private readonly _WorldManager: PhysicsWorldManager2d,
54
+ ) {}
55
+
56
+ public update(tick: number): void {
57
+ const rpcs = this._InputProvider.collectTickRPCs(tick, MoveInput);
58
+
59
+ for (const rpc of rpcs) {
60
+ const playerResource = this._PlayerResources.get(PlayerResource, rpc.meta.playerSlot);
61
+ const entity = playerResource.safe.entity;
62
+
63
+ // Sanitize input
64
+ const dirX = finite(rpc.data.directionX);
65
+ const dirY = finite(rpc.data.directionY);
66
+
67
+ const body = this._WorldManager.getBody(this._PhysicsRefs.unsafe.bodyHandle[entity]);
68
+ body.setLinvel({ x: dirX * <%= projectName %>Arena.moveSpeed, y: dirY * <%= projectName %>Arena.moveSpeed }, true);
69
+ }
70
+ }
71
+ }
72
+ <% } else { -%>
1
73
  import { ECSSystem, IECSSystem, InputProvider, PlayerResources } from '@lagless/core';
2
74
  import { MoveInput, PlayerResource, Velocity2d } from '../schema/code-gen/index.js';
3
75
  import { <%= projectName %>Arena } from '../arena.js';
@@ -21,3 +93,4 @@ export class ApplyMoveInputSystem implements IECSSystem {
21
93
  }
22
94
  }
23
95
  }
96
+ <% } -%>
@@ -1,3 +1,4 @@
1
+ <% if (simulationType === 'raw') { -%>
1
2
  import { ECSSystem, IECSSystem } from '@lagless/core';
2
3
  import { Transform2d, PlayerBody, PlayerFilter } from '../schema/code-gen/index.js';
3
4
  import { <%= projectName %>Arena } from '../arena.js';
@@ -32,3 +33,4 @@ export class BoundarySystem implements IECSSystem {
32
33
  }
33
34
  }
34
35
  }
36
+ <% } -%>
@@ -1,3 +1,4 @@
1
+ <% if (simulationType === 'raw') { -%>
1
2
  import { ECSSystem, IECSSystem } from '@lagless/core';
2
3
  import { Velocity2d, MovingFilter } from '../schema/code-gen/index.js';
3
4
  import { <%= projectName %>Arena } from '../arena.js';
@@ -16,3 +17,4 @@ export class DampingSystem implements IECSSystem {
16
17
  }
17
18
  }
18
19
  }
20
+ <% } -%>
@@ -3,9 +3,13 @@ import { SavePrevTransformSystem } from './save-prev-transform.system.js';
3
3
  import { PlayerConnectionSystem } from './player-connection.system.js';
4
4
  import { PlayerLeaveSystem } from './player-leave.system.js';
5
5
  import { ApplyMoveInputSystem } from './apply-move-input.system.js';
6
+ <% if (simulationType === 'raw') { -%>
6
7
  import { IntegrateSystem } from './integrate.system.js';
7
8
  import { DampingSystem } from './damping.system.js';
8
9
  import { BoundarySystem } from './boundary.system.js';
10
+ <% } else { -%>
11
+ import { PhysicsStepSystem } from './physics-step.system.js';
12
+ <% } -%>
9
13
  import { HashVerificationSystem } from './hash-verification.system.js';
10
14
 
11
15
  export const <%= projectName %>Systems: IECSSystemConstructor[] = [
@@ -13,8 +17,12 @@ export const <%= projectName %>Systems: IECSSystemConstructor[] = [
13
17
  PlayerConnectionSystem,
14
18
  PlayerLeaveSystem,
15
19
  ApplyMoveInputSystem,
20
+ <% if (simulationType === 'raw') { -%>
16
21
  IntegrateSystem,
17
22
  DampingSystem,
18
23
  BoundarySystem,
24
+ <% } else { -%>
25
+ PhysicsStepSystem,
26
+ <% } -%>
19
27
  HashVerificationSystem,
20
28
  ];
@@ -1,3 +1,4 @@
1
+ <% if (simulationType === 'raw') { -%>
1
2
  import { ECSSystem, IECSSystem } from '@lagless/core';
2
3
  import { Transform2d, Velocity2d, MovingFilter } from '../schema/code-gen/index.js';
3
4
 
@@ -16,3 +17,4 @@ export class IntegrateSystem implements IECSSystem {
16
17
  }
17
18
  }
18
19
  }
20
+ <% } -%>
@@ -0,0 +1,65 @@
1
+ <% if (simulationType === 'physics3d') { -%>
2
+ import { ECSSystem, IECSSystem } from '@lagless/core';
3
+ import { PhysicsWorldManager3d } from '@lagless/physics3d';
4
+ import { Transform3d, PhysicsRefs, PlayerFilter } from '../schema/code-gen/index.js';
5
+
6
+ @ECSSystem()
7
+ export class PhysicsStepSystem implements IECSSystem {
8
+ constructor(
9
+ private readonly _WorldManager: PhysicsWorldManager3d,
10
+ private readonly _PlayerFilter: PlayerFilter,
11
+ private readonly _Transform3d: Transform3d,
12
+ private readonly _PhysicsRefs: PhysicsRefs,
13
+ ) {}
14
+
15
+ public update(): void {
16
+ this._WorldManager.step();
17
+
18
+ // Sync dynamic bodies: Rapier → ECS Transform
19
+ const t = this._Transform3d.unsafe;
20
+ const pr = this._PhysicsRefs.unsafe;
21
+ for (const entity of this._PlayerFilter) {
22
+ const body = this._WorldManager.getBody(pr.bodyHandle[entity]);
23
+ const pos = body.translation();
24
+ t.positionX[entity] = pos.x;
25
+ t.positionY[entity] = pos.y;
26
+ t.positionZ[entity] = pos.z;
27
+
28
+ const rot = body.rotation();
29
+ t.rotationX[entity] = rot.x;
30
+ t.rotationY[entity] = rot.y;
31
+ t.rotationZ[entity] = rot.z;
32
+ t.rotationW[entity] = rot.w;
33
+ }
34
+ }
35
+ }
36
+ <% } else if (simulationType === 'physics2d') { -%>
37
+ import { ECSSystem, IECSSystem } from '@lagless/core';
38
+ import { PhysicsWorldManager2d } from '@lagless/physics2d';
39
+ import { Transform2d, PhysicsRefs, PlayerFilter } from '../schema/code-gen/index.js';
40
+
41
+ @ECSSystem()
42
+ export class PhysicsStepSystem implements IECSSystem {
43
+ constructor(
44
+ private readonly _WorldManager: PhysicsWorldManager2d,
45
+ private readonly _PlayerFilter: PlayerFilter,
46
+ private readonly _Transform2d: Transform2d,
47
+ private readonly _PhysicsRefs: PhysicsRefs,
48
+ ) {}
49
+
50
+ public update(): void {
51
+ this._WorldManager.step();
52
+
53
+ // Sync dynamic bodies: Rapier → ECS Transform
54
+ const t = this._Transform2d.unsafe;
55
+ const pr = this._PhysicsRefs.unsafe;
56
+ for (const entity of this._PlayerFilter) {
57
+ const body = this._WorldManager.getBody(pr.bodyHandle[entity]);
58
+ const pos = body.translation();
59
+ t.positionX[entity] = pos.x;
60
+ t.positionY[entity] = pos.y;
61
+ t.rotation[entity] = body.rotation();
62
+ }
63
+ }
64
+ }
65
+ <% } -%>
@@ -1,3 +1,160 @@
1
+ <% if (simulationType === 'physics3d') { -%>
2
+ import { ECSConfig, ECSSystem, EntitiesManager, IECSSystem, InputProvider, PlayerResources, Prefab } from '@lagless/core';
3
+ import { PlayerBody, PlayerJoined, PlayerResource, Transform3d, PhysicsRefs } from '../schema/code-gen/index.js';
4
+ import { PhysicsWorldManager3d } from '@lagless/physics3d';
5
+ import { BodyType } from '@lagless/physics-shared';
6
+ import { <%= projectName %>Arena } from '../arena.js';
7
+
8
+ @ECSSystem()
9
+ export class PlayerConnectionSystem implements IECSSystem {
10
+ private readonly _playerPrefab = Prefab.create()
11
+ .with(Transform3d)
12
+ .with(PhysicsRefs)
13
+ .with(PlayerBody, { radius: <%= projectName %>Arena.playerRadius });
14
+
15
+ constructor(
16
+ private readonly _ECSConfig: ECSConfig,
17
+ private readonly _InputProvider: InputProvider,
18
+ private readonly _Transform3d: Transform3d,
19
+ private readonly _PhysicsRefs: PhysicsRefs,
20
+ private readonly _PlayerBody: PlayerBody,
21
+ private readonly _EntitiesManager: EntitiesManager,
22
+ private readonly _PlayerResources: PlayerResources,
23
+ private readonly _WorldManager: PhysicsWorldManager3d,
24
+ ) {}
25
+
26
+ public update(tick: number): void {
27
+ const rpcs = this._InputProvider.collectTickRPCs(tick, PlayerJoined);
28
+ const maxPlayers = this._ECSConfig.maxPlayers;
29
+ const spacing = <%= projectName %>Arena.width * 0.5 / Math.max(maxPlayers, 1);
30
+
31
+ for (const rpc of rpcs) {
32
+ const slot = rpc.data.slot;
33
+ const entity = this._EntitiesManager.createEntity(this._playerPrefab);
34
+
35
+ this._PlayerBody.unsafe.playerSlot[entity] = slot;
36
+
37
+ const spawnX = -<%= projectName %>Arena.width * 0.25 + slot * spacing;
38
+ const spawnY = 1.0;
39
+ const spawnZ = 0;
40
+
41
+ const t = this._Transform3d.unsafe;
42
+ t.positionX[entity] = spawnX;
43
+ t.positionY[entity] = spawnY;
44
+ t.positionZ[entity] = spawnZ;
45
+ t.prevPositionX[entity] = spawnX;
46
+ t.prevPositionY[entity] = spawnY;
47
+ t.prevPositionZ[entity] = spawnZ;
48
+ // Identity quaternion
49
+ t.rotationX[entity] = 0;
50
+ t.rotationY[entity] = 0;
51
+ t.rotationZ[entity] = 0;
52
+ t.rotationW[entity] = 1;
53
+ t.prevRotationX[entity] = 0;
54
+ t.prevRotationY[entity] = 0;
55
+ t.prevRotationZ[entity] = 0;
56
+ t.prevRotationW[entity] = 1;
57
+
58
+ // Create physics body
59
+ const body = this._WorldManager.createDynamicBody();
60
+ body.setTranslation({ x: spawnX, y: spawnY, z: spawnZ }, true);
61
+ body.setLinearDamping(5.0);
62
+
63
+ // Create ball collider
64
+ const collider = this._WorldManager.createBallCollider(<%= projectName %>Arena.playerRadius, body);
65
+
66
+ // Store handles in PhysicsRefs
67
+ const pr = this._PhysicsRefs.unsafe;
68
+ pr.bodyHandle[entity] = body.handle;
69
+ pr.colliderHandle[entity] = collider.handle;
70
+ pr.bodyType[entity] = BodyType.DYNAMIC;
71
+
72
+ // Register in collider-entity map
73
+ this._WorldManager.registerCollider(collider.handle, entity);
74
+
75
+ const playerResource = this._PlayerResources.get(PlayerResource, slot);
76
+ playerResource.safe.entity = entity;
77
+ playerResource.safe.connected = 1;
78
+ for (let i = 0; i < rpc.data.playerId.length; i++) {
79
+ playerResource.unsafe.id[i] = rpc.data.playerId[i];
80
+ }
81
+ }
82
+ }
83
+ }
84
+ <% } else if (simulationType === 'physics2d') { -%>
85
+ import { ECSConfig, ECSSystem, EntitiesManager, IECSSystem, InputProvider, PlayerResources, Prefab } from '@lagless/core';
86
+ import { PlayerBody, PlayerJoined, PlayerResource, Transform2d, PhysicsRefs } from '../schema/code-gen/index.js';
87
+ import { PhysicsWorldManager2d } from '@lagless/physics2d';
88
+ import { BodyType } from '@lagless/physics-shared';
89
+ import { <%= projectName %>Arena } from '../arena.js';
90
+
91
+ @ECSSystem()
92
+ export class PlayerConnectionSystem implements IECSSystem {
93
+ private readonly _playerPrefab = Prefab.create()
94
+ .with(Transform2d)
95
+ .with(PhysicsRefs)
96
+ .with(PlayerBody, { radius: <%= projectName %>Arena.playerRadius });
97
+
98
+ constructor(
99
+ private readonly _ECSConfig: ECSConfig,
100
+ private readonly _InputProvider: InputProvider,
101
+ private readonly _Transform2d: Transform2d,
102
+ private readonly _PhysicsRefs: PhysicsRefs,
103
+ private readonly _PlayerBody: PlayerBody,
104
+ private readonly _EntitiesManager: EntitiesManager,
105
+ private readonly _PlayerResources: PlayerResources,
106
+ private readonly _WorldManager: PhysicsWorldManager2d,
107
+ ) {}
108
+
109
+ public update(tick: number): void {
110
+ const rpcs = this._InputProvider.collectTickRPCs(tick, PlayerJoined);
111
+ const maxPlayers = this._ECSConfig.maxPlayers;
112
+ const spacing = <%= projectName %>Arena.width * 0.5 / Math.max(maxPlayers, 1);
113
+
114
+ for (const rpc of rpcs) {
115
+ const slot = rpc.data.slot;
116
+ const entity = this._EntitiesManager.createEntity(this._playerPrefab);
117
+
118
+ this._PlayerBody.unsafe.playerSlot[entity] = slot;
119
+
120
+ const spawnX = <%= projectName %>Arena.width * 0.25 + slot * spacing;
121
+ const spawnY = <%= projectName %>Arena.height * 0.5;
122
+
123
+ const t = this._Transform2d.unsafe;
124
+ t.positionX[entity] = spawnX;
125
+ t.positionY[entity] = spawnY;
126
+ t.rotation[entity] = 0;
127
+ t.prevPositionX[entity] = spawnX;
128
+ t.prevPositionY[entity] = spawnY;
129
+ t.prevRotation[entity] = 0;
130
+
131
+ // Create physics body
132
+ const body = this._WorldManager.createDynamicBody();
133
+ body.setTranslation({ x: spawnX, y: spawnY }, true);
134
+ body.setLinearDamping(5.0);
135
+
136
+ // Create ball collider
137
+ const collider = this._WorldManager.createBallCollider(<%= projectName %>Arena.playerRadius, body);
138
+
139
+ // Store handles in PhysicsRefs
140
+ const pr = this._PhysicsRefs.unsafe;
141
+ pr.bodyHandle[entity] = body.handle;
142
+ pr.colliderHandle[entity] = collider.handle;
143
+ pr.bodyType[entity] = BodyType.DYNAMIC;
144
+
145
+ // Register in collider-entity map
146
+ this._WorldManager.registerCollider(collider.handle, entity);
147
+
148
+ const playerResource = this._PlayerResources.get(PlayerResource, slot);
149
+ playerResource.safe.entity = entity;
150
+ playerResource.safe.connected = 1;
151
+ for (let i = 0; i < rpc.data.playerId.length; i++) {
152
+ playerResource.unsafe.id[i] = rpc.data.playerId[i];
153
+ }
154
+ }
155
+ }
156
+ }
157
+ <% } else { -%>
1
158
  import { ECSConfig, ECSSystem, EntitiesManager, IECSSystem, InputProvider, PlayerResources, Prefab } from '@lagless/core';
2
159
  import { PlayerBody, PlayerJoined, PlayerResource, Transform2d, Velocity2d } from '../schema/code-gen/index.js';
3
160
  import { <%= projectName %>Arena } from '../arena.js';
@@ -45,3 +202,4 @@ export class PlayerConnectionSystem implements IECSSystem {
45
202
  }
46
203
  }
47
204
  }
205
+ <% } -%>
@@ -1,3 +1,72 @@
1
+ <% if (simulationType === 'physics3d') { -%>
2
+ import { ECSSystem, EntitiesManager, IECSSystem, InputProvider, PlayerResources } from '@lagless/core';
3
+ import { PlayerLeft, PlayerResource, PhysicsRefs } from '../schema/code-gen/index.js';
4
+ import { PhysicsWorldManager3d } from '@lagless/physics3d';
5
+
6
+ @ECSSystem()
7
+ export class PlayerLeaveSystem implements IECSSystem {
8
+ constructor(
9
+ private readonly _InputProvider: InputProvider,
10
+ private readonly _EntitiesManager: EntitiesManager,
11
+ private readonly _PlayerResources: PlayerResources,
12
+ private readonly _PhysicsRefs: PhysicsRefs,
13
+ private readonly _WorldManager: PhysicsWorldManager3d,
14
+ ) {}
15
+
16
+ public update(tick: number): void {
17
+ const rpcs = this._InputProvider.collectTickRPCs(tick, PlayerLeft);
18
+
19
+ for (const rpc of rpcs) {
20
+ const player = this._PlayerResources.get(PlayerResource, rpc.data.slot);
21
+ const entity = player.safe.entity;
22
+
23
+ // Remove physics objects
24
+ const colliderHandle = this._PhysicsRefs.unsafe.colliderHandle[entity];
25
+ const bodyHandle = this._PhysicsRefs.unsafe.bodyHandle[entity];
26
+ this._WorldManager.unregisterCollider(colliderHandle);
27
+ this._WorldManager.removeCollider(colliderHandle);
28
+ this._WorldManager.removeBody(bodyHandle);
29
+
30
+ player.safe.connected = 0;
31
+ this._EntitiesManager.removeEntity(entity);
32
+ }
33
+ }
34
+ }
35
+ <% } else if (simulationType === 'physics2d') { -%>
36
+ import { ECSSystem, EntitiesManager, IECSSystem, InputProvider, PlayerResources } from '@lagless/core';
37
+ import { PlayerLeft, PlayerResource, PhysicsRefs } from '../schema/code-gen/index.js';
38
+ import { PhysicsWorldManager2d } from '@lagless/physics2d';
39
+
40
+ @ECSSystem()
41
+ export class PlayerLeaveSystem implements IECSSystem {
42
+ constructor(
43
+ private readonly _InputProvider: InputProvider,
44
+ private readonly _EntitiesManager: EntitiesManager,
45
+ private readonly _PlayerResources: PlayerResources,
46
+ private readonly _PhysicsRefs: PhysicsRefs,
47
+ private readonly _WorldManager: PhysicsWorldManager2d,
48
+ ) {}
49
+
50
+ public update(tick: number): void {
51
+ const rpcs = this._InputProvider.collectTickRPCs(tick, PlayerLeft);
52
+
53
+ for (const rpc of rpcs) {
54
+ const player = this._PlayerResources.get(PlayerResource, rpc.data.slot);
55
+ const entity = player.safe.entity;
56
+
57
+ // Remove physics objects
58
+ const colliderHandle = this._PhysicsRefs.unsafe.colliderHandle[entity];
59
+ const bodyHandle = this._PhysicsRefs.unsafe.bodyHandle[entity];
60
+ this._WorldManager.unregisterCollider(colliderHandle);
61
+ this._WorldManager.removeCollider(colliderHandle);
62
+ this._WorldManager.removeBody(bodyHandle);
63
+
64
+ player.safe.connected = 0;
65
+ this._EntitiesManager.removeEntity(entity);
66
+ }
67
+ }
68
+ }
69
+ <% } else { -%>
1
70
  import { ECSSystem, EntitiesManager, IECSSystem, InputProvider, PlayerResources } from '@lagless/core';
2
71
  import { PlayerLeft, PlayerResource } from '../schema/code-gen/index.js';
3
72
 
@@ -19,3 +88,4 @@ export class PlayerLeaveSystem implements IECSSystem {
19
88
  }
20
89
  }
21
90
  }
91
+ <% } -%>
@@ -1,3 +1,48 @@
1
+ <% if (simulationType === 'physics3d') { -%>
2
+ import { ECSSystem, IECSSystem } from '@lagless/core';
3
+ import { Transform3d, PlayerFilter } from '../schema/code-gen/index.js';
4
+
5
+ @ECSSystem()
6
+ export class SavePrevTransformSystem implements IECSSystem {
7
+ constructor(
8
+ private readonly _PlayerFilter: PlayerFilter,
9
+ private readonly _Transform3d: Transform3d,
10
+ ) {}
11
+
12
+ public update(): void {
13
+ const t = this._Transform3d.unsafe;
14
+ for (const entity of this._PlayerFilter) {
15
+ t.prevPositionX[entity] = t.positionX[entity];
16
+ t.prevPositionY[entity] = t.positionY[entity];
17
+ t.prevPositionZ[entity] = t.positionZ[entity];
18
+ t.prevRotationX[entity] = t.rotationX[entity];
19
+ t.prevRotationY[entity] = t.rotationY[entity];
20
+ t.prevRotationZ[entity] = t.rotationZ[entity];
21
+ t.prevRotationW[entity] = t.rotationW[entity];
22
+ }
23
+ }
24
+ }
25
+ <% } else if (simulationType === 'physics2d') { -%>
26
+ import { ECSSystem, IECSSystem } from '@lagless/core';
27
+ import { Transform2d, PlayerFilter } from '../schema/code-gen/index.js';
28
+
29
+ @ECSSystem()
30
+ export class SavePrevTransformSystem implements IECSSystem {
31
+ constructor(
32
+ private readonly _PlayerFilter: PlayerFilter,
33
+ private readonly _Transform2d: Transform2d,
34
+ ) {}
35
+
36
+ public update(): void {
37
+ const t = this._Transform2d.unsafe;
38
+ for (const entity of this._PlayerFilter) {
39
+ t.prevPositionX[entity] = t.positionX[entity];
40
+ t.prevPositionY[entity] = t.positionY[entity];
41
+ t.prevRotation[entity] = t.rotation[entity];
42
+ }
43
+ }
44
+ }
45
+ <% } else { -%>
1
46
  import { ECSSystem, IECSSystem } from '@lagless/core';
2
47
  import { Transform2d, PlayerFilter } from '../schema/code-gen/index.js';
3
48
 
@@ -15,3 +60,4 @@ export class SavePrevTransformSystem implements IECSSystem {
15
60
  }
16
61
  }
17
62
  }
63
+ <% } -%>