@origingame/create-game 0.2.0 → 0.4.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.
Files changed (163) hide show
  1. package/README.md +2 -2
  2. package/archetypes/README.md +2 -1
  3. package/archetypes/arena/.origin/setup +14 -0
  4. package/archetypes/arena/LICENSES.md +8 -0
  5. package/archetypes/arena/README.md +67 -0
  6. package/archetypes/arena/assets/audio/coin.ogt +9 -0
  7. package/archetypes/arena/assets/audio/dash.ogt +9 -0
  8. package/archetypes/arena/assets/audio/win.ogt +9 -0
  9. package/archetypes/arena/assets/fx/coin.ogt +20 -0
  10. package/archetypes/arena/assets/looks/arena.ogt +10 -0
  11. package/archetypes/arena/gitattributes +22 -0
  12. package/archetypes/arena/gitignore +8 -0
  13. package/archetypes/arena/host.d.ts +46 -0
  14. package/archetypes/arena/package.json +18 -0
  15. package/archetypes/arena/project.ogt +14 -0
  16. package/archetypes/arena/src/game.ts +314 -0
  17. package/archetypes/arena/src/main.ts +100 -0
  18. package/archetypes/arena/tests/duel.og.ts +32 -0
  19. package/archetypes/arena/tests/solo.og.ts +22 -0
  20. package/archetypes/arena/tsconfig.json +17 -0
  21. package/archetypes/idle/LICENSES.md +1 -1
  22. package/archetypes/idle/README.md +5 -5
  23. package/archetypes/idle/assets/looks/idle.ogt +2 -2
  24. package/archetypes/idle/assets/{skins → styles}/sparkorb/LICENSES.md +6 -6
  25. package/archetypes/idle/assets/{skins/sparkorb/skin.ogt → styles/sparkorb/style.ogt} +47 -47
  26. package/archetypes/idle/package.json +1 -1
  27. package/archetypes/idle/project.ogt +1 -1
  28. package/archetypes/idle/references/metal/card.png +0 -0
  29. package/archetypes/idle/references/metal/orbit.png +0 -0
  30. package/archetypes/idle/references/metal/title.png +0 -0
  31. package/archetypes/idle/src/components.ts +1 -1
  32. package/archetypes/idle/src/main.ts +5 -5
  33. package/archetypes/idle/src/ui.ts +13 -13
  34. package/archetypes/idle/tools/make-assets.py +5 -5
  35. package/archetypes/match3/package.json +1 -1
  36. package/archetypes/match3/project.ogt +1 -1
  37. package/archetypes/rhythm/LICENSES.md +1 -1
  38. package/archetypes/rhythm/README.md +5 -5
  39. package/archetypes/rhythm/assets/{skins → styles}/lanebeat/LICENSES.md +6 -6
  40. package/archetypes/rhythm/assets/{skins/lanebeat/skin.ogt → styles/lanebeat/style.ogt} +45 -45
  41. package/archetypes/rhythm/package.json +1 -1
  42. package/archetypes/rhythm/project.ogt +1 -1
  43. package/archetypes/rhythm/references/metal/results.png +0 -0
  44. package/archetypes/rhythm/references/metal/title.png +0 -0
  45. package/archetypes/rhythm/src/components.ts +1 -1
  46. package/archetypes/rhythm/src/main.ts +3 -3
  47. package/archetypes/rhythm/src/ui.ts +12 -12
  48. package/archetypes/roguelike/LICENSES.md +1 -1
  49. package/archetypes/roguelike/README.md +5 -5
  50. package/archetypes/roguelike/assets/{skins → styles}/lampdive/LICENSES.md +6 -6
  51. package/archetypes/roguelike/assets/{skins/lampdive/skin.ogt → styles/lampdive/style.ogt} +41 -41
  52. package/archetypes/roguelike/package.json +1 -1
  53. package/archetypes/roguelike/project.ogt +1 -1
  54. package/archetypes/roguelike/references/metal/over.png +0 -0
  55. package/archetypes/roguelike/references/metal/title.png +0 -0
  56. package/archetypes/roguelike/src/components.ts +1 -1
  57. package/archetypes/roguelike/src/main.ts +2 -2
  58. package/archetypes/roguelike/src/ui.ts +8 -8
  59. package/archetypes/runner/package.json +1 -1
  60. package/archetypes/runner/project.ogt +1 -1
  61. package/archetypes/third-person/LICENSES.md +1 -1
  62. package/archetypes/third-person/README.md +4 -4
  63. package/archetypes/third-person/assets/{skins → styles}/ember/LICENSES.md +4 -4
  64. package/archetypes/third-person/assets/{skins/ember/skin.ogt → styles/ember/style.ogt} +40 -41
  65. package/archetypes/third-person/package.json +1 -1
  66. package/archetypes/third-person/project.ogt +1 -1
  67. package/archetypes/third-person/references/metal/beacon.png +0 -0
  68. package/archetypes/third-person/references/metal/dawn.png +0 -0
  69. package/archetypes/third-person/references/metal/title.png +0 -0
  70. package/archetypes/third-person/references/webgl2/beacon.png +0 -0
  71. package/archetypes/third-person/src/components.ts +1 -1
  72. package/archetypes/third-person/src/main.ts +3 -3
  73. package/archetypes/third-person/src/sound.ts +1 -1
  74. package/archetypes/third-person/src/ui.ts +11 -11
  75. package/archetypes/topdown/package.json +1 -1
  76. package/archetypes/topdown/project.ogt +1 -1
  77. package/archetypes/tower-defense/LICENSES.md +1 -1
  78. package/archetypes/tower-defense/README.md +5 -5
  79. package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LICENSES.md +4 -4
  80. package/archetypes/tower-defense/assets/styles/pathguard/atlas.png +0 -0
  81. package/archetypes/tower-defense/assets/styles/pathguard/style.ogt +111 -0
  82. package/archetypes/tower-defense/package.json +1 -1
  83. package/archetypes/tower-defense/project.ogt +1 -1
  84. package/archetypes/tower-defense/references/metal/end.png +0 -0
  85. package/archetypes/tower-defense/references/metal/title.png +0 -0
  86. package/archetypes/tower-defense/references/metal/wave.png +0 -0
  87. package/archetypes/tower-defense/src/components.ts +1 -1
  88. package/archetypes/tower-defense/src/main.ts +2 -2
  89. package/archetypes/tower-defense/src/ui.ts +20 -18
  90. package/bin/create.ts +11 -8
  91. package/looks/catalogue.json +40 -40
  92. package/package.json +2 -2
  93. package/template/README.md +2 -1
  94. package/template/assets/audio/sting.ogt +10 -0
  95. package/template/assets/timelines/intro.ogt +101 -0
  96. package/template/assets/timelines/narration.srt +11 -0
  97. package/template/host.d.ts +46 -0
  98. package/template/package.json +1 -1
  99. package/template/project.ogt +1 -1
  100. package/template/references/metal/intro-4.png +0 -0
  101. package/template/references/metal/intro-8.png +0 -0
  102. package/template/src/main.ts +1 -1
  103. package/template/tests/frames.ts +134 -0
  104. package/template/tests/intro.og.ts +65 -0
  105. package/template/tests/smoke.og.ts +30 -36
  106. package/template/tsconfig.json +1 -1
  107. package/template-2d/LICENSES.md +1 -0
  108. package/template-2d/README.md +6 -2
  109. package/template-2d/assets/rigs/keeper.ogt +292 -0
  110. package/template-2d/assets/rigs/keeper.png +0 -0
  111. package/template-2d/assets/rigs/keeper.png.ogt +7 -0
  112. package/template-2d/package.json +1 -1
  113. package/template-2d/project.ogt +1 -1
  114. package/template-2d/src/main.ts +240 -4
  115. package/template-2d/tests/keeper.og.ts +178 -0
  116. package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
  117. package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +0 -111
  118. /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
  119. /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +0 -0
  120. /package/archetypes/idle/assets/{skins → styles}/sparkorb/OFL.txt +0 -0
  121. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf +0 -0
  122. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf.ogt +0 -0
  123. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf +0 -0
  124. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf.ogt +0 -0
  125. /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png +0 -0
  126. /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png.ogt +0 -0
  127. /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png +0 -0
  128. /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png.ogt +0 -0
  129. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf +0 -0
  130. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf.ogt +0 -0
  131. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf +0 -0
  132. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf.ogt +0 -0
  133. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/OFL.txt +0 -0
  134. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf +0 -0
  135. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf.ogt +0 -0
  136. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png +0 -0
  137. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png.ogt +0 -0
  138. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png +0 -0
  139. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png.ogt +0 -0
  140. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf +0 -0
  141. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf.ogt +0 -0
  142. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/OFL.txt +0 -0
  143. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png +0 -0
  144. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png.ogt +0 -0
  145. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png +0 -0
  146. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png.ogt +0 -0
  147. /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf +0 -0
  148. /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf.ogt +0 -0
  149. /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf +0 -0
  150. /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf.ogt +0 -0
  151. /package/archetypes/third-person/assets/{skins → styles}/ember/OFL.txt +0 -0
  152. /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png +0 -0
  153. /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png.ogt +0 -0
  154. /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png +0 -0
  155. /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png.ogt +0 -0
  156. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf +0 -0
  157. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf.ogt +0 -0
  158. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf +0 -0
  159. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf.ogt +0 -0
  160. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/OFL.txt +0 -0
  161. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/atlas.png.ogt +0 -0
  162. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png +0 -0
  163. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png.ogt +0 -0
@@ -0,0 +1,292 @@
1
+ type rig2d
2
+ version 1
3
+ ---
4
+ # The lamp keeper (templates/game-2d): a 2D rig drawn as textured triangles.
5
+ # Bones are pixels with +y up, unit 20 pixels to a world unit; the legs are
6
+ # two-bone IK chains planted on the ramp by og.IkTarget2D (src/main.ts, Plant),
7
+ # the cape a weighted mesh over the hip and two cape bones, fluttered by the
8
+ # idle clip's deform keys. The walk clip bobs the hip on Bézier keys, swings
9
+ # the arm and trails the cape; its "step" events mark each foot's contact,
10
+ # and src/main.ts's Plant moves the feet's IK targets in step with it (a
11
+ # 0.8 s cycle of a 0.4 m stride: the keeper walks 0.5 m/s). Written by hand
12
+ # (og-game §8, the rig2d grammar).
13
+ texture "keeper.png"
14
+ filter nearest
15
+ unit 20
16
+ bone root {
17
+ }
18
+ bone hip {
19
+ parent root
20
+ at 0 22
21
+ rotation 90
22
+ length 16
23
+ }
24
+ bone head {
25
+ parent hip
26
+ at 16 0
27
+ rotation -90
28
+ }
29
+ bone arm {
30
+ parent hip
31
+ at 14 -5
32
+ rotation 180
33
+ length 13
34
+ }
35
+ bone thigh-b {
36
+ parent hip
37
+ at 0 2
38
+ rotation 180
39
+ length 11
40
+ }
41
+ bone shin-b {
42
+ parent thigh-b
43
+ at 11 0
44
+ length 11
45
+ }
46
+ bone thigh-f {
47
+ parent hip
48
+ at 0 -2
49
+ rotation 180
50
+ length 11
51
+ }
52
+ bone shin-f {
53
+ parent thigh-f
54
+ at 11 0
55
+ length 11
56
+ }
57
+ bone cape-1 {
58
+ parent hip
59
+ at 15 4
60
+ rotation 180
61
+ length 12
62
+ }
63
+ bone cape-2 {
64
+ parent cape-1
65
+ at 12 0
66
+ length 12
67
+ }
68
+ bone foot-b {
69
+ parent root
70
+ at -3 0
71
+ }
72
+ bone foot-f {
73
+ parent root
74
+ at 3 0
75
+ }
76
+ slot cape {
77
+ bone hip
78
+ attachment cape
79
+ }
80
+ slot thigh-b {
81
+ bone thigh-b
82
+ attachment thigh-b
83
+ color 0.72 0.72 0.8 1
84
+ }
85
+ slot shin-b {
86
+ bone shin-b
87
+ attachment shin-b
88
+ color 0.72 0.72 0.8 1
89
+ }
90
+ slot boot-b {
91
+ bone shin-b
92
+ attachment boot-b
93
+ color 0.72 0.72 0.8 1
94
+ }
95
+ slot torso {
96
+ bone hip
97
+ attachment torso
98
+ }
99
+ slot thigh-f {
100
+ bone thigh-f
101
+ attachment thigh-f
102
+ }
103
+ slot shin-f {
104
+ bone shin-f
105
+ attachment shin-f
106
+ }
107
+ slot boot-f {
108
+ bone shin-f
109
+ attachment boot-f
110
+ }
111
+ slot head {
112
+ bone head
113
+ attachment head
114
+ }
115
+ slot arm {
116
+ bone arm
117
+ attachment arm
118
+ }
119
+ slot lantern {
120
+ bone arm
121
+ attachment lantern
122
+ }
123
+ region torso {
124
+ slot torso
125
+ rect 0 0 12 20
126
+ at 8 0
127
+ rotation -90
128
+ size 12 20
129
+ }
130
+ region head {
131
+ slot head
132
+ rect 15 0 14 12
133
+ at 0 5
134
+ rotation 0
135
+ size 14 12
136
+ }
137
+ region arm {
138
+ slot arm
139
+ rect 32 16 4 14
140
+ at 6.5 0
141
+ rotation 90
142
+ size 4 14
143
+ }
144
+ region lantern {
145
+ slot lantern
146
+ rect 52 16 8 10
147
+ at 15 0
148
+ rotation 90
149
+ size 8 10
150
+ }
151
+ region thigh-b {
152
+ slot thigh-b
153
+ rect 32 0 5 12
154
+ at 5.5 0
155
+ rotation 90
156
+ size 5 12
157
+ }
158
+ region shin-b {
159
+ slot shin-b
160
+ rect 40 0 5 12
161
+ at 5.5 0
162
+ rotation 90
163
+ size 5 12
164
+ }
165
+ region boot-b {
166
+ slot boot-b
167
+ rect 48 0 10 5
168
+ at 10.5 2
169
+ rotation 90
170
+ size 10 5
171
+ }
172
+ region thigh-f {
173
+ slot thigh-f
174
+ rect 32 0 5 12
175
+ at 5.5 0
176
+ rotation 90
177
+ size 5 12
178
+ }
179
+ region shin-f {
180
+ slot shin-f
181
+ rect 40 0 5 12
182
+ at 5.5 0
183
+ rotation 90
184
+ size 5 12
185
+ }
186
+ region boot-f {
187
+ slot boot-f
188
+ rect 48 0 10 5
189
+ at 10.5 2
190
+ rotation 90
191
+ size 10 5
192
+ }
193
+ mesh cape {
194
+ slot cape
195
+ vertex 0 32 hip 16.000 16.000 1
196
+ vertex 6 32 hip 16.000 9.000 1
197
+ vertex 12 32 hip 16.000 2.000 1
198
+ vertex 0 40 hip 7.000 16.000 0.4 cape-1 8.000 -12.000 0.6
199
+ vertex 6 40 hip 7.000 9.000 0.4 cape-1 8.000 -5.000 0.6
200
+ vertex 12 40 hip 7.000 2.000 0.4 cape-1 8.000 2.000 0.6
201
+ vertex 0 48 cape-1 17.000 -12.000 0.5 cape-2 5.000 -12.000 0.5
202
+ vertex 6 48 cape-1 17.000 -5.000 0.5 cape-2 5.000 -5.000 0.5
203
+ vertex 12 48 cape-1 17.000 2.000 0.5 cape-2 5.000 2.000 0.5
204
+ vertex 0 56 cape-2 14.000 -12.000 1
205
+ vertex 6 56 cape-2 14.000 -5.000 1
206
+ vertex 12 56 cape-2 14.000 2.000 1
207
+ triangles 0 3 1 1 3 4 1 4 2 2 4 5 3 6 4 4 6 7 4 7 5 5 7 8 6 9 7 7 9 10 7 10 8 8 10 11
208
+ }
209
+ ik leg-b {
210
+ bones thigh-b shin-b
211
+ target foot-b
212
+ bend clockwise
213
+ }
214
+ ik leg-f {
215
+ bones thigh-f shin-f
216
+ target foot-f
217
+ bend clockwise
218
+ }
219
+ clip idle {
220
+ duration 2
221
+ loop true
222
+ bone hip {
223
+ translate 0 0 0
224
+ translate 1 0 -1.5
225
+ translate 2 0 0
226
+ }
227
+ bone head {
228
+ rotate 0 0
229
+ rotate 1 4
230
+ rotate 2 0
231
+ }
232
+ bone arm {
233
+ rotate 0 4
234
+ rotate 1 -8
235
+ rotate 2 4
236
+ }
237
+ bone cape-1 {
238
+ rotate 0 -4
239
+ rotate 1 8
240
+ rotate 2 -4
241
+ }
242
+ bone cape-2 {
243
+ rotate 0 -6
244
+ rotate 1 12
245
+ rotate 2 -6
246
+ }
247
+ deform cape {
248
+ key 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
249
+ key 0.5 0 0 0 0 0 0 0 -1 0 -1 0 -1 0 -1 0 -1 0 -1 0 -3 0 -3 0 -3 0 -3 0 -3 0 -3 0 -5 0 -5 0 -5
250
+ key 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 2 0 2 0 2 0 2 0 2 0 2 0 3 0 3 0 3
251
+ key 1.5 0 0 0 0 0 0 0 -1 0 -1 0 -1 0 -1 0 -1 0 -1 0 -3.5 0 -3.5 0 -3.5 0 -3.5 0 -3.5 0 -3.5 0 -6 0 -6 0 -6
252
+ key 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
253
+ }
254
+ event 0 "inhale"
255
+ }
256
+ clip walk {
257
+ duration 0.8
258
+ loop true
259
+ bone hip {
260
+ translate 0 0 -1 curve 0.3 0 0.6 1
261
+ translate 0.2 0 0.6 curve 0.4 0 0.7 1
262
+ translate 0.4 0 -1 curve 0.3 0 0.6 1
263
+ translate 0.6 0 0.6 curve 0.4 0 0.7 1
264
+ translate 0.8 0 -1
265
+ }
266
+ bone head {
267
+ rotate 0 -2
268
+ rotate 0.4 2
269
+ rotate 0.8 -2
270
+ }
271
+ bone arm {
272
+ rotate 0 16 curve 0.42 0 0.58 1
273
+ rotate 0.4 -12 curve 0.42 0 0.58 1
274
+ rotate 0.8 16
275
+ }
276
+ bone cape-1 {
277
+ rotate 0 -10
278
+ rotate 0.2 -14
279
+ rotate 0.4 -10
280
+ rotate 0.6 -14
281
+ rotate 0.8 -10
282
+ }
283
+ bone cape-2 {
284
+ rotate 0 -12
285
+ rotate 0.3 -20
286
+ rotate 0.5 -12
287
+ rotate 0.7 -20
288
+ rotate 0.8 -12
289
+ }
290
+ event 0 "step"
291
+ event 0.4 "step"
292
+ }
@@ -0,0 +1,7 @@
1
+ type import
2
+ version 1
3
+ ---
4
+ # Pixel art: lossless, sampled nearest.
5
+ texture {
6
+ kind pixel
7
+ }
@@ -13,6 +13,6 @@
13
13
  "test": "og test ."
14
14
  },
15
15
  "dependencies": {
16
- "@origingame/engine": "0.2.0"
16
+ "@origingame/engine": "0.4.0"
17
17
  }
18
18
  }
@@ -7,7 +7,7 @@ version 1
7
7
  name "Origin Game 2D"
8
8
  id "origin-game-2d"
9
9
  # The engine this game is written against (design §9: upgrades are explicit).
10
- engine "0.2.0"
10
+ engine "0.4.0"
11
11
  entry "src/main.ts"
12
12
  # 2d: an orthographic camera, sprites, flipbooks, tilemaps and 2D physics.
13
13
  profile "2d"
@@ -11,11 +11,22 @@
11
11
  // Everything the starter shows is in og-game §8 (sprites, flipbooks,
12
12
  // tilemaps), §13 (the controller) and og-feel §12 (its tunables).
13
13
  //
14
+ // Beside the spawn a lamp keeper stands on a ramp: a 2D skeletal rig
15
+ // (assets/rigs/keeper.ogt, og.Skeleton2D) whose cape is a weighted mesh the
16
+ // idle clip deforms, whose feet two-bone IK planted on the slope
17
+ // (og.IkTarget2D, the Plant system), and whose lantern lights the sprite
18
+ // layers through og.Lighting2D (og-game §8; og-anim's 2D skeletons). Sent
19
+ // along the ramp (`keeper:<x>`), it turns to face the way (a paper turn
20
+ // over a few ticks), plays its walk clip there, each foot planted while it
21
+ // carries the keeper and lifted while it swings, lets both feet land when
22
+ // it arrives, turns back and breathes again (the Walk and Plant systems).
23
+ // No foot ever moves faster than the gait's own swing.
24
+ //
14
25
  // The frame's look is data: GameDesc.look names a file under assets/looks/
15
26
  // that extends a preset of the style catalogue, applied before start runs.
16
27
 
17
28
  import { og, defineComponent, defineResource, defineSystem, bool, f32, u32, SHAPE_BOX } from "@origingame/engine";
18
- import type { FlipbookInfo, Result, TileObject } from "@origingame/engine";
29
+ import type { FlipbookInfo, Result, Rig2DInfo, TileObject, World } from "@origingame/engine";
19
30
 
20
31
  /** The courier's clips, in the sheet's file order (og-game §8): `start` checks them. */
21
32
  const CLIP = { idle: 0, run: 1, jump: 2, fall: 3 } as const;
@@ -28,6 +39,26 @@ const BOX = { w: 0.9, h: 1.9 } as const;
28
39
  /** Below this the courier fell into the pit. */
29
40
  const FALL_Y = -3;
30
41
  const LIVES = 3;
42
+ /** The keeper's ramp: a plank from (x0, y0) on the roof rising at `slope` (tan 12°) to x1. */
43
+ const RAMP = { x0: 4.5, y0: 2, x1: 7.6, slope: 0.21256, sinHalf: 0.104528, cosHalf: 0.994522 } as const;
44
+ /** The ramp's top at x. */
45
+ const rampY = (x: number): number => RAMP.y0 + RAMP.slope * (x - RAMP.x0);
46
+ /** The keeper's ankles and its root above the ramp (its legs reach 1.1 from a hip 1.1 up: a slight bend), the lantern from its feet, in world units. */
47
+ const ANKLE = 0.12;
48
+ const LIFT = 0.08;
49
+ const LANTERN = { x: 0.25, y: 1.0 } as const;
50
+ /** The keeper's gait: the walk clip's 0.8 s cycle carries it a stride of 0.4 m (a step per foot), each foot lifted at most `lift` mid-swing. */
51
+ const WALK = { speed: 0.5, stride: 0.4, lift: 0.08 } as const;
52
+ /**
53
+ * The fastest a foot moves (m/s): the gait's swing at its start, a stride
54
+ * forward in half a cycle (twice the walking speed) while it rises up the
55
+ * slope and off it (the lift's parabola, 4 lift over the swing's length,
56
+ * at its steepest). A foot sent elsewhere (setting off, stopping, a turn)
57
+ * steps there no faster.
58
+ */
59
+ const FOOT_SPEED = Math.hypot(2 * WALK.speed, 2 * WALK.speed * RAMP.slope + (8 * WALK.lift * WALK.speed) / WALK.stride);
60
+ /** A turn's ticks: the rig narrows to its edge and opens the other way (og.Transform's scale x, never 0). */
61
+ const TURN_TICKS = 12;
31
62
 
32
63
  /** The courier. */
33
64
  export const Hero = defineComponent("Hero", { spawnX: f32(0, { unit: "m" }), spawnY: f32(0, { unit: "m" }) });
@@ -35,6 +66,18 @@ export const Hero = defineComponent("Hero", { spawnX: f32(0, { unit: "m" }), spa
35
66
  /** A coin of the map's `coin` objects; taken once. */
36
67
  export const Coin = defineComponent("Coin", { taken: bool() });
37
68
 
69
+ /**
70
+ * One of the keeper's feet: its IK target stands on the ramp `dx` ahead of
71
+ * the keeper (behind when negative), `lag` of a gait cycle after the front
72
+ * foot; `x`, `y` is where the foot is.
73
+ */
74
+ export const Plant = defineComponent("Plant", {
75
+ dx: f32(0, { unit: "m" }),
76
+ lag: f32(0, { min: 0, max: 1 }),
77
+ x: f32(0, { unit: "m" }),
78
+ y: f32(0, { unit: "m" }),
79
+ });
80
+
38
81
  /** A voice: the sound bit (Sound) it plays when Game.sfx asks for it. */
39
82
  export const Voice = defineComponent("Voice", { bit: u32() });
40
83
 
@@ -58,6 +101,28 @@ export const Game = defineResource("Game", {
58
101
  /** The HUD's touch controls this tick (a phone): the run and the held jump. */
59
102
  touchX: f32(0, { min: -1, max: 1 }),
60
103
  touchJump: bool(),
104
+ /** The keeper's breaths: og.Skeleton2DEvent "inhale" (the idle clip's event at 0). */
105
+ inhales: u32(),
106
+ /** The keeper's footfalls: og.Skeleton2DEvent "step" (the walk clip's, one per foot). */
107
+ steps: u32(),
108
+ });
109
+
110
+ /** Where the keeper walks (`keeper:<x>`), how far it has walked since it set off (its gait's phase), its turn and stop, and its clips. */
111
+ export const KeeperWalk = defineResource("KeeperWalk", {
112
+ goal: f32(6, { unit: "m" }),
113
+ walked: f32(0, { unit: "m" }),
114
+ walking: bool(),
115
+ /** -1 facing left (towards the spawn), 1 right. */
116
+ facing: f32(-1),
117
+ /** Ticks of a turn done (0: not turning). */
118
+ turning: u32(),
119
+ /** og.Transform's scale x: 1, or through a turn from 1 towards -1. */
120
+ squash: f32(1),
121
+ /** Arrived, the feet stepping to stand (Plant sets `landed` when both are down). */
122
+ settling: bool(),
123
+ landed: bool(true),
124
+ idle: u32(),
125
+ walk: u32(),
61
126
  });
62
127
 
63
128
  function must<T>(r: Result<T>): T {
@@ -161,6 +226,133 @@ export const Animate = defineSystem("Animate", { reads: [Hero, og.PlatformerCont
161
226
  }
162
227
  });
163
228
 
229
+ /**
230
+ * Walks the keeper towards KeeperWalk.goal along the ramp at the gait's
231
+ * speed (by scaled time, as the clip plays: paused, both hold), facing the
232
+ * way it goes, with the walk clip. Facing the other way first, it turns
233
+ * over TURN_TICKS (og.Transform's scale x eased from 1 to -1, never 0, then
234
+ * flipX), its feet stepping across with it. On arrival it holds the walk
235
+ * pose until Plant has both feet down, turns to face the spawn, and once
236
+ * they are down again breathes with the idle clip (which starts over,
237
+ * breathing in).
238
+ */
239
+ export const Walk = defineSystem("Walk", { writes: [KeeperWalk, og.Skeleton2D, og.Transform], after: ["Control"] }, (_q, time, world) => {
240
+ const keeper = world.find("keeper");
241
+ if (keeper === null) return;
242
+ const w = must(world.resource(KeeperWalk));
243
+ const x = must(world.get(keeper, og.Transform)).position.x;
244
+ const s = must(world.get(keeper, og.Skeleton2D));
245
+ const place = (nx: number) => must(world.set(keeper, og.Transform, { position: { x: nx, y: rampY(nx) + LIFT, z: 0 }, scale: { x: w.squash, y: 1, z: 1 } }));
246
+ if (w.turning > 0 || (time.scaledDt > 0 && w.turning === 0 && wantsTurn(w, x))) {
247
+ // A turn: the rig narrows to its edge and opens facing the other way.
248
+ if (time.scaledDt === 0) return;
249
+ w.turning += 1;
250
+ if (w.turning < TURN_TICKS) {
251
+ w.squash = Math.cos((Math.PI * (w.turning - 0.5)) / (TURN_TICKS - 0.5));
252
+ } else {
253
+ w.turning = 0;
254
+ w.squash = 1;
255
+ w.facing = -w.facing;
256
+ must(world.set(keeper, og.Skeleton2D, { flipX: w.facing < 0 }));
257
+ }
258
+ place(x);
259
+ return;
260
+ }
261
+ const left = w.goal - x;
262
+ if (left !== 0) {
263
+ const step = WALK.speed * time.scaledDt;
264
+ const arrived = Math.abs(left) <= step;
265
+ const nx = arrived ? w.goal : x + Math.sign(left) * step;
266
+ w.walked = (w.walking ? w.walked : 0) + Math.abs(nx - x);
267
+ w.walking = !arrived;
268
+ w.settling = arrived;
269
+ w.landed = false;
270
+ place(nx);
271
+ // Walking plays the walk clip from its first step; arrived, it holds its pose while the feet land.
272
+ if (s.clip !== w.walk) must(world.set(keeper, og.Skeleton2D, { clip: w.walk, speed: 1 }));
273
+ else if (arrived) must(world.set(keeper, og.Skeleton2D, { speed: 0 }));
274
+ return;
275
+ }
276
+ if (w.settling && w.landed) w.settling = false;
277
+ // Standing, both feet down and facing the spawn: breathe.
278
+ if (!w.settling && w.landed && w.facing < 0 && s.clip !== w.idle) must(world.set(keeper, og.Skeleton2D, { clip: w.idle, speed: 1 }));
279
+ });
280
+
281
+ /** Whether the keeper must turn before it goes on: towards its goal, or back to the spawn once it stands with both feet down. */
282
+ function wantsTurn(w: { goal: number; facing: number; settling: boolean; walking: boolean }, x: number): boolean {
283
+ const left = w.goal - x;
284
+ if (left !== 0) return Math.sign(left) !== w.facing && !w.walking;
285
+ return !w.settling && w.facing > 0;
286
+ }
287
+
288
+ /**
289
+ * Puts the keeper's feet on the ramp (og.IkTarget2D: the legs' two-bone IK
290
+ * reaches each target, the knees bend as the hip bobs). Standing, each foot
291
+ * is `dx` from the keeper; walking, the front foot's gait phase is the
292
+ * distance walked over the stride and the back foot's half a cycle later:
293
+ * for half a cycle a foot stays where it is on the plank while the keeper
294
+ * passes over it, for the other half it swings a stride forward, lifted on
295
+ * a parabola. A foot steps to where it should be at most FOOT_SPEED fast,
296
+ * lifted while it is still on its way, so turning, setting off and
297
+ * stopping are steps, not jumps (each leg hangs from its own hip: the
298
+ * front foot crosses to the other side in a turn). Keeps the lantern's light at its hand, and
299
+ * counts the clips' breaths and footfalls.
300
+ */
301
+ export const Plant2D = defineSystem("Plant", { reads: [og.Skeleton2DEvent], writes: [Game, KeeperWalk, Plant, og.IkTarget2D, og.Transform], after: ["Walk"] }, (q, time, world) => {
302
+ const keeper = world.find("keeper");
303
+ if (keeper === null) return;
304
+ const at = must(world.get(keeper, og.Transform)).position;
305
+ const w = must(world.resource(KeeperWalk));
306
+ const reach = FOOT_SPEED * time.scaledDt;
307
+ let down = true;
308
+ for (const a of q.archetypes()) {
309
+ const plant = a.column(Plant);
310
+ const target = a.column(og.IkTarget2D).target;
311
+ for (let i = 0; i < a.count; i++) {
312
+ let ahead = plant.dx[i]!;
313
+ let lift = 0;
314
+ if (w.walking) {
315
+ const phase = w.walked / WALK.stride + plant.lag[i]!;
316
+ const p = phase - Math.floor(phase);
317
+ if (p < 0.5) {
318
+ // Planted: it slides back under the keeper as fast as the keeper walks.
319
+ ahead += WALK.stride * (0.25 - p);
320
+ } else {
321
+ const s = (p - 0.5) * 2;
322
+ ahead += WALK.stride * (s - 0.5) * 0.5;
323
+ lift = WALK.lift * 4 * s * (1 - s);
324
+ }
325
+ }
326
+ // Turning, each foot follows its hip as the rig narrows and opens the other way.
327
+ const wx = at.x + w.facing * w.squash * ahead;
328
+ const [x, y] = toward(plant.x[i]!, plant.y[i]!, wx, rampY(wx) + ANKLE + lift, reach);
329
+ // Still on its way: lifted with how far it has left (a step's height at half a stride), as fast as it may rise.
330
+ const rest = Math.abs(wx - x);
331
+ const [fx, fy] = rest > 1e-6 ? toward(plant.x[i]!, plant.y[i]!, x, Math.max(y, rampY(x) + ANKLE + WALK.lift * Math.min(1, rest / (WALK.stride / 2))), reach) : [x, y];
332
+ plant.x[i] = fx;
333
+ plant.y[i] = fy;
334
+ target[i * 2] = fx;
335
+ target[i * 2 + 1] = fy;
336
+ if (Math.hypot(wx - fx, rampY(wx) + ANKLE + lift - fy) > 1e-5 || lift > 0) down = false;
337
+ }
338
+ }
339
+ w.landed = down;
340
+ const lantern = world.find("lantern");
341
+ if (lantern !== null) must(world.set(lantern, og.Transform, { position: { x: at.x - w.facing * w.squash * LANTERN.x, y: at.y + LANTERN.y, z: 0 } }));
342
+ const g = must(world.resource(Game));
343
+ for (const e of world.events(og.Skeleton2DEvent)) {
344
+ if (e.name === "inhale") g.inhales += 1;
345
+ else if (e.name === "step") g.steps += 1;
346
+ }
347
+ });
348
+
349
+ /** (x, y) moved towards (tx, ty) by at most `reach`. */
350
+ function toward(x: number, y: number, tx: number, ty: number, reach: number): [number, number] {
351
+ const d = Math.hypot(tx - x, ty - y);
352
+ if (d <= reach) return [tx, ty];
353
+ return [x + ((tx - x) * reach) / d, y + ((ty - y) * reach) / d];
354
+ }
355
+
164
356
  /** Plays each sound asked for once: a voice still playing is stopped this tick and played again the next. */
165
357
  export const Voices = defineSystem("Voices", { reads: [Voice], writes: [Game, og.AudioSource], after: ["Pickups"] }, (q, _time, world) => {
166
358
  const g = must(world.resource(Game));
@@ -212,6 +404,42 @@ export const Hud = defineSystem("Hud", { writes: [Game] }, (_q, _time, world) =>
212
404
  }
213
405
  });
214
406
 
407
+ /**
408
+ * The lamp keeper: a plank ramp, the rig on it facing the spawn, a foot
409
+ * target per leg, and its lantern's light; og.Lighting2D lights layers 1
410
+ * and 2 (the keeper, the courier, the coins), the city stays as drawn.
411
+ */
412
+ function spawnKeeper(world: World, rig: Rig2DInfo): void {
413
+ const idle = og.anim.clip(rig, "idle");
414
+ const walk = og.anim.clip(rig, "walk");
415
+ if (idle < 0 || walk < 0) throw new Error("rigs/keeper.ogt needs clips idle and walk");
416
+ const mid = (RAMP.x0 + RAMP.x1) / 2;
417
+ const plank = must(world.spawn({ name: "ramp", position: { x: mid, y: rampY(mid) - 0.2, z: 0 }, quaternion: { x: 0, y: 0, z: RAMP.sinHalf, w: RAMP.cosHalf } }));
418
+ // The plank is a region of the keeper's texture (pixels 24..64 x 32..40 of 64 x 64).
419
+ must(world.insert(plank, og.Sprite, { texture: rig.texture, width: RAMP.x1 - RAMP.x0, height: 0.4, uvMin: { x: 0.375, y: 0.5 }, uvMax: { x: 1, y: 0.625 }, layer: 1 }));
420
+ const x = 6;
421
+ const keeper = must(world.spawn({ name: "keeper", position: { x, y: rampY(x) + LIFT, z: 0 } }));
422
+ must(world.insert(keeper, og.Skeleton2D, { rig: rig.rig, clip: idle, flipX: true, layer: 1 }));
423
+ const w = must(world.resource(KeeperWalk));
424
+ Object.assign(w, { goal: x, walked: 0, walking: false, facing: -1, turning: 0, squash: 1, settling: false, landed: true, idle, walk });
425
+ // Facing left, the front foot is the left one; the back foot steps half a cycle later.
426
+ for (const [name, ahead, lag] of [["leg-f", 0.15, 0], ["leg-b", -0.15, 0.5]] as const) {
427
+ const constraint = rig.constraints.indexOf(name);
428
+ if (constraint < 0) throw new Error(`rigs/keeper.ogt has no ik ${name}`);
429
+ const fx = x - ahead;
430
+ const foot = must(world.spawn({ name: `keeper-${name}` }));
431
+ must(world.insert(foot, og.IkTarget2D, { skeleton: keeper, constraint, target: { x: fx, y: rampY(fx) + ANKLE }, tolerance: 0.02 }));
432
+ must(world.insert(foot, Plant, { dx: ahead, lag, x: fx, y: rampY(fx) + ANKLE }));
433
+ }
434
+ const light = must(world.spawn({ name: "lantern", position: { x: x + LANTERN.x, y: rampY(x) + LIFT + LANTERN.y, z: 0 } }));
435
+ must(world.insert(light, og.Light2D, { color: { r: 1, g: 0.72, b: 0.35 }, intensity: 1.6, radius: 4, castShadows: false }));
436
+ const lighting = must(world.resource(og.Lighting2D));
437
+ lighting.enabled = true;
438
+ lighting.ambient = { r: 0.62, g: 0.64, b: 0.78 };
439
+ lighting.minLayer = 1;
440
+ lighting.maxLayer = 2;
441
+ }
442
+
215
443
  function checkClips(info: FlipbookInfo): void {
216
444
  for (const [name, index] of Object.entries(CLIP)) {
217
445
  const at = og.anim.clip(info, name);
@@ -229,13 +457,14 @@ export default og.game({
229
457
  look: "looks/origin-game-2d.ogt",
230
458
  tick: 60,
231
459
  seed: 1,
232
- resources: [Game],
233
- systems: [Hud, Control, Pickups, Animate, Voices],
460
+ resources: [Game, KeeperWalk],
461
+ systems: [Hud, Control, Pickups, Animate, Walk, Plant2D, Voices],
234
462
  start(world) {
235
463
  const map = must(og.load.tilemap(world, "maps/level.ogt"));
236
464
  const courier = must(og.load.flipbook(world, "sprites/courier.ogt"));
237
465
  checkClips(courier);
238
466
  const coin = must(og.load.flipbook(world, "sprites/coin.ogt"));
467
+ spawnKeeper(world, must(og.load.rig2d(world, "rigs/keeper.ogt")));
239
468
  const height = map.height * map.tileHeight;
240
469
 
241
470
  const camera = must(world.resource(og.Camera));
@@ -293,13 +522,20 @@ export default og.game({
293
522
  if (hero === null || !Number.isFinite(x) || !Number.isFinite(y)) throw new Error("write warp:<x>:<y>");
294
523
  must(world.set(hero, og.Transform, { position: { x, y, z: 0 } }));
295
524
  },
525
+ /** `keeper:<x>`: send the keeper walking along the ramp to x (the Walk system moves it; its feet step on the plank). */
526
+ keeper: (world, args) => {
527
+ const x = Math.max(RAMP.x0 + 0.4, Math.min(RAMP.x1 - 0.4, Number(args[0])));
528
+ if (world.find("keeper") === null || !Number.isFinite(x)) throw new Error("write keeper:<x>");
529
+ must(world.resource(KeeperWalk)).goal = x;
530
+ },
296
531
  },
297
532
  commandHelp: {
298
533
  warp: { usage: "<x>:<y>", doc: "Put the courier's feet at a world point." },
534
+ keeper: { usage: "<x>", doc: "Send the lamp keeper walking along its ramp to x." },
299
535
  },
300
536
  facts: (world) => {
301
537
  const g = world.resource(Game);
302
538
  if (!g.ok) return {};
303
- return { coins: g.value.coins, total: g.value.total, lives: g.value.lives, won: g.value.won, x: g.value.heroX, y: g.value.heroY };
539
+ return { coins: g.value.coins, total: g.value.total, lives: g.value.lives, won: g.value.won, inhales: g.value.inhales, steps: g.value.steps, x: g.value.heroX, y: g.value.heroY };
304
540
  },
305
541
  });