incanto 0.74.0 → 0.75.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/bin/incanto-multiplay.mjs +52 -4
- package/dist/2d.d.ts +83 -7
- package/dist/2d.js +4 -3
- package/dist/3d.d.ts +743 -167
- package/dist/3d.js +6 -6
- package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
- package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
- package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
- package/dist/{create-game-BAiA-FjP.js → create-game-DVUhHs2B.js} +79 -10
- package/dist/{create-game-ssvGvP7W.js → create-game-DnwhJ5nW.js} +83 -7
- package/dist/debug.d.ts +1 -1
- package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-O8mxpgRX.d.ts} +1 -1
- package/dist/editor.js +1600 -1529
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-BkqlWewf.js → environment-presets-BP77Dl0K.js} +2005 -60
- package/dist/{gameplay-Bxe1sMVT.js → gameplay-D-JuGura.js} +9309 -6595
- package/dist/gameplay.d.ts +573 -31
- package/dist/gameplay.js +3 -2
- package/dist/index.d.ts +150 -6
- package/dist/index.js +6 -6
- package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-C-SfvjzH.js → physics-2d-x3bCrQf0.js} +250 -31
- package/dist/{physics-3d-rOzeg890.js → physics-3d-Bz2VPQ6D.js} +673 -51
- package/dist/{picking-BMV34Pjl.js → picking-Buita5T9.js} +2 -2
- package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
- package/dist/{register-DRQyZKGU.js → register-DxAbTO2T.js} +191 -430
- package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
- package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
- package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
- package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
- package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
- package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
- package/dist/{src-CqtvYtSN.js → src-DLKVc6Hj.js} +1 -1
- package/dist/{test-CEl0y2Sw.js → test-HlVUeCSn.js} +487 -82
- package/dist/test.d.ts +90 -12
- package/dist/test.js +2 -2
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-BlzturGi.js → agent8-BXlYM9rv.js} +1 -1
- package/editor/assets/{debug-BMUi8usj.js → debug-S42XGP4Y.js} +1 -1
- package/editor/assets/index-DgrgUKno.js +11046 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +1630 -299
- package/skills/incanto-3d-character.md +358 -20
- package/skills/incanto-audio.md +24 -2
- package/skills/incanto-building-2d-games.md +21 -2
- package/skills/incanto-building-3d-games.md +38 -3
- package/skills/incanto-editor.md +17 -6
- package/skills/incanto-environment.md +227 -2
- package/skills/incanto-game-feel.md +60 -1
- package/skills/incanto-gameplay-behaviors.md +668 -20
- package/skills/incanto-hud.md +116 -0
- package/skills/incanto-multiplayer.md +141 -2
- package/skills/incanto-node-reference.md +393 -16
- package/skills/incanto-performance.md +32 -0
- package/skills/incanto-physics-and-input.md +241 -13
- package/skills/incanto-playtesting.md +2 -2
- package/skills/incanto-save-slots.md +61 -2
- package/skills/incanto-scene-json-authoring.md +13 -0
- package/skills/incanto-verifying-your-game.md +86 -3
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +151 -2
- package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/molehill-2d/src/game.scene.json +129 -2
- package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/behaviors.ts +40 -26
- package/templates-app/platformer-2d/src/game.scene.json +144 -2
- package/templates-app/platformer-2d/verify.ts +18 -1
- package/templates-app/star-survivor/PROJECT/Status.md +7 -0
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/behaviors.ts +9 -8
- package/templates-app/star-survivor/src/game.scene.json +142 -4
- package/templates-app/star-survivor/verify.ts +13 -0
- package/templates-app/tps-3d/PROJECT/Status.md +6 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +71 -10
- package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
- package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
- package/templates-app/village-quest-3d/src/village.scene.json +126 -2
- package/templates-app/village-quest-3d/verify.ts +52 -2
- package/editor/assets/index-CrUCQoaB.js +0 -11046
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"jump": { "type": "button", "keys": ["Space"], "touch": "button" },
|
|
35
35
|
"sprint": { "type": "button", "keys": ["ShiftLeft", "ShiftRight"] },
|
|
36
36
|
"interact": { "type": "button", "keys": ["KeyE"], "touch": "button" },
|
|
37
|
-
"attack": { "type": "button", "keys": ["Mouse0", "KeyF"], "touch": "button" }
|
|
37
|
+
"attack": { "type": "button", "keys": ["Mouse0", "KeyF"], "touch": "button" },
|
|
38
|
+
"pause": { "type": "button", "keys": ["Escape"] },
|
|
39
|
+
"restart": { "type": "button", "keys": ["KeyR"] }
|
|
38
40
|
},
|
|
39
41
|
"assets": {
|
|
40
42
|
"characters/base": {
|
|
@@ -2756,6 +2758,110 @@
|
|
|
2756
2758
|
"anchor": "bottomRight",
|
|
2757
2759
|
"shadow": true
|
|
2758
2760
|
}
|
|
2761
|
+
},
|
|
2762
|
+
{
|
|
2763
|
+
"name": "PauseMenu",
|
|
2764
|
+
"type": "UiPanel",
|
|
2765
|
+
"uid": "n_36ox1wee9cdz2cgj",
|
|
2766
|
+
"props": {
|
|
2767
|
+
"anchor": "center",
|
|
2768
|
+
"layout": "column",
|
|
2769
|
+
"gap": 12,
|
|
2770
|
+
"padding": 22,
|
|
2771
|
+
"visible": false,
|
|
2772
|
+
"background": "rgba(10,14,22,0.92)",
|
|
2773
|
+
"radius": 12
|
|
2774
|
+
},
|
|
2775
|
+
"children": [
|
|
2776
|
+
{
|
|
2777
|
+
"name": "PausedTitle",
|
|
2778
|
+
"type": "UiText",
|
|
2779
|
+
"uid": "n_kdecsevdetjk0q1i",
|
|
2780
|
+
"props": { "text": "PAUSED", "size": 26 }
|
|
2781
|
+
},
|
|
2782
|
+
{
|
|
2783
|
+
"name": "Resume",
|
|
2784
|
+
"type": "UiButton",
|
|
2785
|
+
"uid": "n_vaxmkxrqot30vd7u",
|
|
2786
|
+
"props": { "text": "RESUME", "size": 18 }
|
|
2787
|
+
},
|
|
2788
|
+
{
|
|
2789
|
+
"name": "PauseOptions",
|
|
2790
|
+
"type": "UiButton",
|
|
2791
|
+
"uid": "n_3nt1ztixpsvjtb86",
|
|
2792
|
+
"props": { "text": "OPTIONS", "size": 16 }
|
|
2793
|
+
},
|
|
2794
|
+
{
|
|
2795
|
+
"name": "Save",
|
|
2796
|
+
"type": "UiButton",
|
|
2797
|
+
"uid": "n_enx1clunsomchjx2",
|
|
2798
|
+
"props": { "text": "SAVE", "size": 16 }
|
|
2799
|
+
},
|
|
2800
|
+
{
|
|
2801
|
+
"name": "Continue",
|
|
2802
|
+
"type": "UiButton",
|
|
2803
|
+
"uid": "n_3hpo1n3ho12l7po2",
|
|
2804
|
+
"props": { "text": "CONTINUE", "size": 16 }
|
|
2805
|
+
},
|
|
2806
|
+
{
|
|
2807
|
+
"name": "Restart",
|
|
2808
|
+
"type": "UiButton",
|
|
2809
|
+
"uid": "n_0ytaknljuqmthwpl",
|
|
2810
|
+
"props": { "text": "START OVER", "size": 16 }
|
|
2811
|
+
}
|
|
2812
|
+
]
|
|
2813
|
+
},
|
|
2814
|
+
{
|
|
2815
|
+
"name": "Options",
|
|
2816
|
+
"type": "UiPanel",
|
|
2817
|
+
"uid": "n_drf0bze03zae5h4l",
|
|
2818
|
+
"props": {
|
|
2819
|
+
"anchor": "center",
|
|
2820
|
+
"layout": "column",
|
|
2821
|
+
"gap": 10,
|
|
2822
|
+
"padding": 22,
|
|
2823
|
+
"visible": false,
|
|
2824
|
+
"background": "rgba(10,14,22,0.92)",
|
|
2825
|
+
"radius": 12
|
|
2826
|
+
},
|
|
2827
|
+
"children": [
|
|
2828
|
+
{
|
|
2829
|
+
"name": "OptionsTitle",
|
|
2830
|
+
"type": "UiText",
|
|
2831
|
+
"uid": "n_wn6vz16vfsnz4guj",
|
|
2832
|
+
"props": { "text": "OPTIONS", "size": 24 }
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
"name": "Master",
|
|
2836
|
+
"type": "UiVolumeSlider",
|
|
2837
|
+
"uid": "n_aguq7s3aeapln07y",
|
|
2838
|
+
"props": { "label": "master", "bus": "master", "width": 260 }
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
"name": "Sfx",
|
|
2842
|
+
"type": "UiVolumeSlider",
|
|
2843
|
+
"uid": "n_yyaguhkmv9z4u9a8",
|
|
2844
|
+
"props": { "label": "effects", "bus": "sfx", "width": 260 }
|
|
2845
|
+
},
|
|
2846
|
+
{
|
|
2847
|
+
"name": "Mute",
|
|
2848
|
+
"type": "UiMuteToggle",
|
|
2849
|
+
"uid": "n_3yj77bemd2sjm4yi",
|
|
2850
|
+
"props": { "label": "mute everything" }
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
"name": "Quality",
|
|
2854
|
+
"type": "UiQualitySelect",
|
|
2855
|
+
"uid": "n_e3ehj0ln9p28ib59",
|
|
2856
|
+
"props": { "label": "quality" }
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
"name": "Back",
|
|
2860
|
+
"type": "UiButton",
|
|
2861
|
+
"uid": "n_y95ob0mpwrvvhr78",
|
|
2862
|
+
"props": { "text": "BACK", "size": 16 }
|
|
2863
|
+
}
|
|
2864
|
+
]
|
|
2759
2865
|
}
|
|
2760
2866
|
],
|
|
2761
2867
|
"uid": "n_0q1r3a6rijr3hc1w"
|
|
@@ -2895,12 +3001,55 @@
|
|
|
2895
3001
|
}
|
|
2896
3002
|
}
|
|
2897
3003
|
]
|
|
3004
|
+
},
|
|
3005
|
+
{
|
|
3006
|
+
"name": "Flow",
|
|
3007
|
+
"type": "Node",
|
|
3008
|
+
"uid": "n_ibf6agaxzcxjhcmg",
|
|
3009
|
+
"script": { "name": "GameFlow", "props": { "saveSlots": "beacon-isle" } }
|
|
2898
3010
|
}
|
|
2899
3011
|
],
|
|
2900
3012
|
"uid": "n_nxftk1ep8wbi1bn9"
|
|
2901
3013
|
},
|
|
2902
3014
|
"connections": [
|
|
2903
3015
|
{ "signal": "interacted", "from": "Keeper", "to": "/root", "handler": "onTalk" },
|
|
2904
|
-
{ "signal": "died", "from": "Player", "to": "/root", "handler": "onPlayerDown" }
|
|
3016
|
+
{ "signal": "died", "from": "Player", "to": "/root", "handler": "onPlayerDown" },
|
|
3017
|
+
{
|
|
3018
|
+
"from": "/root/HUD/PauseMenu/Resume",
|
|
3019
|
+
"signal": "pressed",
|
|
3020
|
+
"to": "/root/Flow",
|
|
3021
|
+
"handler": "resume"
|
|
3022
|
+
},
|
|
3023
|
+
{
|
|
3024
|
+
"from": "/root/HUD/PauseMenu/PauseOptions",
|
|
3025
|
+
"signal": "pressed",
|
|
3026
|
+
"to": "/root/Flow",
|
|
3027
|
+
"handler": "screen",
|
|
3028
|
+
"args": ["/root/HUD/Options"]
|
|
3029
|
+
},
|
|
3030
|
+
{
|
|
3031
|
+
"from": "/root/HUD/PauseMenu/Restart",
|
|
3032
|
+
"signal": "pressed",
|
|
3033
|
+
"to": "/root/Flow",
|
|
3034
|
+
"handler": "restart"
|
|
3035
|
+
},
|
|
3036
|
+
{
|
|
3037
|
+
"from": "/root/HUD/Options/Back",
|
|
3038
|
+
"signal": "pressed",
|
|
3039
|
+
"to": "/root/Flow",
|
|
3040
|
+
"handler": "back"
|
|
3041
|
+
},
|
|
3042
|
+
{
|
|
3043
|
+
"from": "/root/HUD/PauseMenu/Save",
|
|
3044
|
+
"signal": "pressed",
|
|
3045
|
+
"to": "/root/Flow",
|
|
3046
|
+
"handler": "save"
|
|
3047
|
+
},
|
|
3048
|
+
{
|
|
3049
|
+
"from": "/root/HUD/PauseMenu/Continue",
|
|
3050
|
+
"signal": "pressed",
|
|
3051
|
+
"to": "/root/Flow",
|
|
3052
|
+
"handler": "continueFrom"
|
|
3053
|
+
}
|
|
2905
3054
|
]
|
|
2906
3055
|
}
|
|
@@ -25,3 +25,10 @@
|
|
|
25
25
|
`tags` entry on the node and a branch in `onMoleClicked`.
|
|
26
26
|
- Turn the field into a GRID of clickable cells and you have a match-3, a
|
|
27
27
|
minesweeper or a tower-defense build menu; the input path is identical.
|
|
28
|
+
|
|
29
|
+
## Pausing
|
|
30
|
+
|
|
31
|
+
ESC pauses and shows the pause menu; OPTIONS opens the settings over it and
|
|
32
|
+
BACK returns to the menu that asked. All of it is `GameFlow` (`pausePanelPath`,
|
|
33
|
+
`screen(path)`, `back()`) wired with `connections` — there is no TypeScript for
|
|
34
|
+
any screen in this game.
|
|
@@ -1175,8 +1175,106 @@
|
|
|
1175
1175
|
"type": "UiButton",
|
|
1176
1176
|
"uid": "n_u20dbq90sumk4806",
|
|
1177
1177
|
"props": { "anchor": "bottom", "text": "PLAY AGAIN", "visible": false }
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
"name": "PauseMenu",
|
|
1181
|
+
"type": "UiPanel",
|
|
1182
|
+
"uid": "n_z9nyxykbc17ausrw",
|
|
1183
|
+
"props": {
|
|
1184
|
+
"anchor": "center",
|
|
1185
|
+
"layout": "column",
|
|
1186
|
+
"gap": 12,
|
|
1187
|
+
"padding": 22,
|
|
1188
|
+
"visible": false,
|
|
1189
|
+
"background": "rgba(10,14,22,0.92)",
|
|
1190
|
+
"radius": 12
|
|
1191
|
+
},
|
|
1192
|
+
"children": [
|
|
1193
|
+
{
|
|
1194
|
+
"name": "PausedTitle",
|
|
1195
|
+
"type": "UiText",
|
|
1196
|
+
"uid": "n_f7eckdwb24bwqpo8",
|
|
1197
|
+
"props": { "text": "PAUSED", "size": 26 }
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
"name": "Resume",
|
|
1201
|
+
"type": "UiButton",
|
|
1202
|
+
"uid": "n_oz53g65a66krulb0",
|
|
1203
|
+
"props": { "text": "RESUME", "size": 18 }
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
"name": "PauseOptions",
|
|
1207
|
+
"type": "UiButton",
|
|
1208
|
+
"uid": "n_cn8ix0y56khi3i90",
|
|
1209
|
+
"props": { "text": "OPTIONS", "size": 16 }
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
"name": "Restart",
|
|
1213
|
+
"type": "UiButton",
|
|
1214
|
+
"uid": "n_cy1svsikf8hhtz3m",
|
|
1215
|
+
"props": { "text": "START OVER", "size": 16 }
|
|
1216
|
+
}
|
|
1217
|
+
]
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
"name": "Options",
|
|
1221
|
+
"type": "UiPanel",
|
|
1222
|
+
"uid": "n_2t2waf1lsux76rw9",
|
|
1223
|
+
"props": {
|
|
1224
|
+
"anchor": "center",
|
|
1225
|
+
"layout": "column",
|
|
1226
|
+
"gap": 10,
|
|
1227
|
+
"padding": 22,
|
|
1228
|
+
"visible": false,
|
|
1229
|
+
"background": "rgba(10,14,22,0.92)",
|
|
1230
|
+
"radius": 12
|
|
1231
|
+
},
|
|
1232
|
+
"children": [
|
|
1233
|
+
{
|
|
1234
|
+
"name": "OptionsTitle",
|
|
1235
|
+
"type": "UiText",
|
|
1236
|
+
"uid": "n_rp0oo781nder7a6m",
|
|
1237
|
+
"props": { "text": "OPTIONS", "size": 24 }
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
"name": "Master",
|
|
1241
|
+
"type": "UiVolumeSlider",
|
|
1242
|
+
"uid": "n_8sufldfevj26zdb2",
|
|
1243
|
+
"props": { "label": "master", "bus": "master", "width": 260 }
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
"name": "Sfx",
|
|
1247
|
+
"type": "UiVolumeSlider",
|
|
1248
|
+
"uid": "n_ahyngscv3eatp9mi",
|
|
1249
|
+
"props": { "label": "effects", "bus": "sfx", "width": 260 }
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
"name": "Mute",
|
|
1253
|
+
"type": "UiMuteToggle",
|
|
1254
|
+
"uid": "n_478neek47ba9eaa4",
|
|
1255
|
+
"props": { "label": "mute everything" }
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
"name": "Quality",
|
|
1259
|
+
"type": "UiQualitySelect",
|
|
1260
|
+
"uid": "n_f35z57tq3jsxrdz1",
|
|
1261
|
+
"props": { "label": "quality" }
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
"name": "Back",
|
|
1265
|
+
"type": "UiButton",
|
|
1266
|
+
"uid": "n_m5d8dwpml28xj9jz",
|
|
1267
|
+
"props": { "text": "BACK", "size": 16 }
|
|
1268
|
+
}
|
|
1269
|
+
]
|
|
1178
1270
|
}
|
|
1179
1271
|
]
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
"name": "Flow",
|
|
1275
|
+
"type": "Node",
|
|
1276
|
+
"uid": "n_08m0apva5yx370w6",
|
|
1277
|
+
"script": { "name": "GameFlow" }
|
|
1180
1278
|
}
|
|
1181
1279
|
]
|
|
1182
1280
|
},
|
|
@@ -1200,6 +1298,35 @@
|
|
|
1200
1298
|
{ "signal": "lost", "from": ".", "to": "Hud", "handler": "onLost" },
|
|
1201
1299
|
{ "signal": "won", "from": ".", "to": "Field", "handler": "stop" },
|
|
1202
1300
|
{ "signal": "lost", "from": ".", "to": "Field", "handler": "stop" },
|
|
1203
|
-
{ "signal": "pressed", "from": "Hud/Again", "to": "Hud", "handler": "onAgain" }
|
|
1204
|
-
|
|
1301
|
+
{ "signal": "pressed", "from": "Hud/Again", "to": "Hud", "handler": "onAgain" },
|
|
1302
|
+
{
|
|
1303
|
+
"from": "/root/Hud/PauseMenu/Resume",
|
|
1304
|
+
"signal": "pressed",
|
|
1305
|
+
"to": "/root/Flow",
|
|
1306
|
+
"handler": "resume"
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
"from": "/root/Hud/PauseMenu/PauseOptions",
|
|
1310
|
+
"signal": "pressed",
|
|
1311
|
+
"to": "/root/Flow",
|
|
1312
|
+
"handler": "screen",
|
|
1313
|
+
"args": ["/root/Hud/Options"]
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
"from": "/root/Hud/PauseMenu/Restart",
|
|
1317
|
+
"signal": "pressed",
|
|
1318
|
+
"to": "/root/Flow",
|
|
1319
|
+
"handler": "restart"
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
"from": "/root/Hud/Options/Back",
|
|
1323
|
+
"signal": "pressed",
|
|
1324
|
+
"to": "/root/Flow",
|
|
1325
|
+
"handler": "back"
|
|
1326
|
+
}
|
|
1327
|
+
],
|
|
1328
|
+
"input": {
|
|
1329
|
+
"pause": { "type": "button", "keys": ["Escape"] },
|
|
1330
|
+
"restart": { "type": "button", "keys": ["KeyR"] }
|
|
1331
|
+
}
|
|
1205
1332
|
}
|
|
@@ -65,3 +65,10 @@ A course past GroundD (x 3000+): `Trampoline` (`restitution 1`) with a dead `Bou
|
|
|
65
65
|
(`friction 0`) with a `Sled` at 260 px/s, and a `Hound` (`CharacterBody2D` + `Chase`
|
|
66
66
|
`ground: true`) under a `Perch`. `verify.ts` measures the rebound (287 of 300 px), the slide
|
|
67
67
|
(400 px of ice) and that the hound stays on the floor under a perched player.
|
|
68
|
+
|
|
69
|
+
## Pausing
|
|
70
|
+
|
|
71
|
+
ESC pauses and shows the pause menu; OPTIONS opens the settings over it and
|
|
72
|
+
BACK returns to the menu that asked. All of it is `GameFlow` (`pausePanelPath`,
|
|
73
|
+
`screen(path)`, `back()`) wired with `connections` — there is no TypeScript for
|
|
74
|
+
any screen in this game.
|
|
@@ -17,8 +17,9 @@ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template
|
|
|
17
17
|
`createGame2D({ behaviors })` and attached with `"script": { "name": "Name" }`.
|
|
18
18
|
This game's custom behaviors are the platformer GAME FEEL the library leaves
|
|
19
19
|
open: `PlayerController` (coyote-time, jump-buffer, double-jump, variable jump
|
|
20
|
-
height, stomp,
|
|
21
|
-
|
|
20
|
+
height, stomp, checkpoint respawn, moving-platform carry — hearts, i-frames,
|
|
21
|
+
the knockback and the control lock are the Player's `Vitals` **Health**, which
|
|
22
|
+
the controller reads: `staggered`, `kickVelocity`, `takeLift()`), `GoblinSkin`, `FollowCam` (follow + screen-shake), `ParallaxLayer`,
|
|
22
23
|
`HudUpdater`.
|
|
23
24
|
4. 🔑 UIDS ARE GENERATED: omit `uid` and the loader assigns one (via the engine's
|
|
24
25
|
`newUid`). NEVER hand-invent readable uid strings.
|
|
@@ -23,6 +23,7 @@ import type { Node, UiBanner, UiText } from 'incanto';
|
|
|
23
23
|
import { Behavior } from 'incanto';
|
|
24
24
|
import type { AnimatedSprite2D, Camera2D, CharacterBody2D, Node2D } from 'incanto/2d';
|
|
25
25
|
import { Particles2D } from 'incanto/2d';
|
|
26
|
+
import type { Health } from 'incanto/gameplay';
|
|
26
27
|
|
|
27
28
|
// ---- tuning (px, y-down; scene gravity is [0, 1800]) --------------------------
|
|
28
29
|
const RUN_SPEED = 250;
|
|
@@ -35,12 +36,9 @@ const BUFFER = 0.12; // press jump slightly early and it still fires on landing
|
|
|
35
36
|
const JUMP_CUT = 0.45; // release jump while rising → cut the climb (variable height)
|
|
36
37
|
const MAX_JUMPS = 2; // ground jump + one air (double) jump
|
|
37
38
|
const STOMP_BOUNCE = 560; // upward pop after squashing a goblin
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const CONTROL_LOCK = 0.22; // seconds the player can't steer after a knockback
|
|
41
|
-
const INVULN = 1.1; // i-frames after a hit
|
|
39
|
+
// hearts, i-frames, the knockback and the control lock after a hit are the
|
|
40
|
+
// Player's `Vitals` Health (game.scene.json) — this controller reads them
|
|
42
41
|
const RESPAWN_INVULN = 1.4;
|
|
43
|
-
const MAX_HEARTS = 3;
|
|
44
42
|
const PLAYER_HW = 18;
|
|
45
43
|
const PLAYER_HH = 33;
|
|
46
44
|
const ENEMY_HW = 26;
|
|
@@ -71,14 +69,15 @@ interface Audio {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
export class PlayerController extends Behavior {
|
|
74
|
-
|
|
72
|
+
/** Hearts = the Vitals' hit points; the HUD reads this. */
|
|
73
|
+
get hearts(): number {
|
|
74
|
+
return this.health.current;
|
|
75
|
+
}
|
|
75
76
|
|
|
76
77
|
private gravity = 1800;
|
|
77
78
|
private coyote = 0;
|
|
78
79
|
private buffer = 0;
|
|
79
80
|
private jumps = 0;
|
|
80
|
-
private invuln = 0;
|
|
81
|
-
private lock = 0;
|
|
82
81
|
private wasGrounded = true;
|
|
83
82
|
private respawn: [number, number] = [0, 0];
|
|
84
83
|
private platform: Node2D | null = null;
|
|
@@ -89,6 +88,9 @@ export class PlayerController extends Behavior {
|
|
|
89
88
|
private get body(): CharacterBody2D {
|
|
90
89
|
return this.node as unknown as CharacterBody2D;
|
|
91
90
|
}
|
|
91
|
+
private get health(): Health {
|
|
92
|
+
return this.getNode('Vitals').behavior as unknown as Health;
|
|
93
|
+
}
|
|
92
94
|
private get skin(): AnimatedSprite2D {
|
|
93
95
|
return this.getNode('Skin') as unknown as AnimatedSprite2D;
|
|
94
96
|
}
|
|
@@ -108,6 +110,9 @@ export class PlayerController extends Behavior {
|
|
|
108
110
|
this.getNode('/root').on('lost', () => {
|
|
109
111
|
this.over = true;
|
|
110
112
|
});
|
|
113
|
+
// a death is the Vitals' signal, whoever dealt it — a hit, a pit, or a
|
|
114
|
+
// probe calling `kill()` — so every route spends a life the same way
|
|
115
|
+
this.getNode('Vitals').on('died', () => this.die());
|
|
111
116
|
// moving-platform CARRY: a Feet sensor remembers the platform we stand on so we
|
|
112
117
|
// ride its motion (the kinematic controller doesn't inherit platform velocity).
|
|
113
118
|
const feet = this.getNode('Feet');
|
|
@@ -137,15 +142,19 @@ export class PlayerController extends Behavior {
|
|
|
137
142
|
|
|
138
143
|
this.coyote = Math.max(0, this.coyote - dt);
|
|
139
144
|
this.buffer = Math.max(0, this.buffer - dt);
|
|
140
|
-
this.invuln = Math.max(0, this.invuln - dt);
|
|
141
|
-
this.lock = Math.max(0, this.lock - dt);
|
|
142
145
|
|
|
143
146
|
const grounded = body.isOnFloor();
|
|
144
147
|
const dir = this.input.getVector('move');
|
|
145
148
|
let vx = body.velocity[0] ?? 0;
|
|
146
149
|
let vy = body.velocity[1] ?? 0;
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
// a hit's stagger takes the stick, and its kick IS the velocity while it
|
|
152
|
+
// lasts: this controller drives the body, so it reads the Health's kick
|
|
153
|
+
// (`applyKnockback: false` in the scene) instead of Health moving the body
|
|
154
|
+
const health = this.health;
|
|
155
|
+
if (!health.staggered) vx = dir.x * RUN_SPEED;
|
|
156
|
+
const kick = health.kickVelocity;
|
|
157
|
+
if (kick.length > 0) vx = kick[0] ?? vx;
|
|
149
158
|
|
|
150
159
|
if (grounded) {
|
|
151
160
|
this.jumps = 0;
|
|
@@ -177,6 +186,13 @@ export class PlayerController extends Behavior {
|
|
|
177
186
|
}
|
|
178
187
|
if (this.input.justReleased('jump') && vy < 0) vy *= JUMP_CUT;
|
|
179
188
|
|
|
189
|
+
// the hit's hop: the kick's vertical part comes out ONCE, then gravity owns it
|
|
190
|
+
const lift = health.takeLift();
|
|
191
|
+
if (lift !== 0) {
|
|
192
|
+
vy = lift;
|
|
193
|
+
this.platform = null;
|
|
194
|
+
}
|
|
195
|
+
|
|
180
196
|
body.velocity = [vx, vy];
|
|
181
197
|
|
|
182
198
|
// ride a moving platform: add its per-frame delta before resolving collisions
|
|
@@ -224,12 +240,12 @@ export class PlayerController extends Behavior {
|
|
|
224
240
|
body.velocity = [body.velocity[0] ?? 0, vy];
|
|
225
241
|
this.jumps = 1; // a stomp refreshes the air jump — chain bounces
|
|
226
242
|
this.shake(8);
|
|
227
|
-
} else if (this.
|
|
228
|
-
this.takeHit(
|
|
243
|
+
} else if (this.health.invulnerableRemaining <= 0) {
|
|
244
|
+
this.takeHit([ex, ey]);
|
|
229
245
|
}
|
|
230
246
|
}
|
|
231
247
|
|
|
232
|
-
if (this.
|
|
248
|
+
if (this.health.invulnerableRemaining <= 0) {
|
|
233
249
|
for (const h of tree.getNodesInGroup('hazard')) {
|
|
234
250
|
const hn = h as unknown as Node2D;
|
|
235
251
|
const size = ((hn as unknown as { collider?: { size?: number[] } }).collider?.size ?? [
|
|
@@ -247,7 +263,7 @@ export class PlayerController extends Behavior {
|
|
|
247
263
|
(size[1] ?? 40) / 2,
|
|
248
264
|
)
|
|
249
265
|
) {
|
|
250
|
-
this.takeHit(this.facing *
|
|
266
|
+
this.takeHit([px + this.facing * 10, py]); // a hazard hits from the front
|
|
251
267
|
break;
|
|
252
268
|
}
|
|
253
269
|
}
|
|
@@ -270,7 +286,7 @@ export class PlayerController extends Behavior {
|
|
|
270
286
|
(size[1] ?? 80) / 2,
|
|
271
287
|
)
|
|
272
288
|
) {
|
|
273
|
-
this.die()
|
|
289
|
+
this.health.kill(); // → `died` → `die()`
|
|
274
290
|
return;
|
|
275
291
|
}
|
|
276
292
|
}
|
|
@@ -300,23 +316,20 @@ export class PlayerController extends Behavior {
|
|
|
300
316
|
}
|
|
301
317
|
}
|
|
302
318
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.body.velocity = [dirX * KNOCK_X, -KNOCK_UP];
|
|
319
|
+
/** Hurt from a world point: the Vitals take the heart, the i-frames, the kick and the stagger. */
|
|
320
|
+
private takeHit(from: [number, number]): void {
|
|
321
|
+
const health = this.health;
|
|
322
|
+
health.damage(1, from); // the killing blow reaches `die()` through `died`
|
|
308
323
|
this.sfx('SfxHurt');
|
|
309
324
|
this.shake(10);
|
|
310
|
-
if (this.hearts <= 0) this.die();
|
|
311
325
|
}
|
|
312
326
|
|
|
313
327
|
private die(): void {
|
|
314
328
|
const sk = this.scoreKeeper;
|
|
315
329
|
sk.loseLife();
|
|
316
330
|
if (sk.lives > 0) {
|
|
317
|
-
this.
|
|
318
|
-
this.
|
|
319
|
-
this.lock = 0;
|
|
331
|
+
this.health.revive();
|
|
332
|
+
this.health.protect(RESPAWN_INVULN);
|
|
320
333
|
this.platform = null;
|
|
321
334
|
this.body.velocity = [0, 0];
|
|
322
335
|
this.body.position = [this.respawn[0], this.respawn[1]];
|
|
@@ -381,7 +394,8 @@ export class PlayerController extends Behavior {
|
|
|
381
394
|
this.playOnce(skin, clip);
|
|
382
395
|
|
|
383
396
|
// blink while invulnerable
|
|
384
|
-
|
|
397
|
+
const inv = this.health.invulnerableRemaining;
|
|
398
|
+
skin.opacity = inv > 0 && Math.floor(inv * 14) % 2 === 0 ? 0.35 : 1;
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
private lastClip = '';
|