-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbox2dxt-platformer.livecodescript
More file actions
9576 lines (9168 loc) · 410 KB
/
Copy pathbox2dxt-platformer.livecodescript
File metadata and controls
9576 lines (9168 loc) · 410 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- =====================================================================
-- box2dxt-platformer.livecodescript · Game Kit Phases 1+2+3+4 showcase
--
-- A FOUR-LEVEL collect-them-all platformer (each level its own
-- scrolling world in a 1024px viewport) that exercises everything
-- built so far: the Kit's PLAYER CONTROLLER (b2kPlayerAttach drives
-- movement, jumping, grounding, state and animations - this file holds
-- no movement code of its own), atlas spritesheets with named
-- animations and scaling, the camera (follow + bounds + shake),
-- synthesized sound, the full Wave 1 toy box, the Wave 3 bestiary, and
-- now the kit's JOINT + dynamics systems (a sagging rope bridge, a
-- rolling boulder, an exploding barrel) plus four more enemy species.
-- Collect EVERY coin on a level (the goal
-- flag turns GOLD), touch the flag, and the next level builds; level
-- 4's flag is the win. Anything without sheet art falls back to plain
-- graphics, so all four levels run even with no asset folder (the coin
-- totals count themselves as each level builds).
--
-- HOW TO RUN
-- 1. In OXT, make a new stack. Put this whole file into the STACK script.
-- 2. Make sure the box2dxt extension is loaded. Close and reopen.
-- 3. First run asks for the repo's Spritesheets folder (remembered in a
-- stack property; Shift+Reset re-asks; Cancel = placeholder shapes).
--
-- CONTROLS arrows or A/D run · SPACE / UP / W jumps (tap = hop, hold =
-- full) · jump AGAIN in mid-air = DOUBLE JUMP · jump off a wall
-- you are sliding = WALL JUMP · SHIFT or X = DASH (a flat zip) ·
-- DOWN/S ducks (brakes to a stop in place) - and DOWN+JUMP
-- on a one-way deck (bridge, clouds) DROPS THROUGH it · UP/DOWN
-- at the L2 ladder CLIMBS (JUMP lets go) · ride the moving LIFTS
-- (they carry you) · the MOUSE DRAGS the crate (chained weights
-- are NOT draggable) · R restarts the CURRENT level · ESC pauses
-- · M mutes the synthesized sound
--
-- THE FOUR LEVELS (every beat holds a coin; the flag advances):
-- LEVEL 1 GREEN HILLS (8640px) - movement + the toys: the
-- SPRINGBOARD mid-meadow (sky coin above; a 42px hop for
-- non-bouncers), the BONK ROW (headbutt ?-boxes, SMASH bricks
-- into debris), the one-way BRIDGE over a spike slime, the
-- slope MOUND, one-way CLOUDS with the fly, the bee, the
-- SPIKE PIT, a fast MOUSE, a coin on the open meadow run, a
-- flying ladybug - then the rope-bridge finale over a chasm,
-- the crusher alley, and the hilltop SWIM POOL to the flag.
-- LEVEL 2 THE WORKS (5952px) - the machines: drag the
-- CRATE onto the yellow BUTTON to open the gate (coin in
-- the gateway), the Wave 2 LADDER up to its bonus ledge,
-- the red CHECKPOINT, the saw LEVER (STAND at it to power
-- the sweeping saw down - running past never flips it),
-- both SAWS, a crawling WORM, THWOMP ALLEY (chained weights:
-- ride the second one's head to the top coin; grab the
-- YELLOW KEY between them), a breather cloud with its bee,
-- the Wave 5 LIFT BAY (its signature: RIDE the moving deck
-- across the grinder for a mid-bay coin - platform-carry),
-- then the crusher alley and the WALLED DOOR - a stone
-- curtain to the ceiling, so the stone steps, the coins and
-- the FLAG behind it are reachable ONLY through the door.
-- LEVEL 3 FROZEN CITADEL (6592px) - everything at once,
-- on ICE (~15% acceleration: momentum rules): the Wave 5
-- WALL-JUMP SHAFT (its signature: climb two ice pillars to a
-- top coin - or double-jump the slot), a ladybug, spring
-- over the first spiked pit, a bonk row, the first sweeping
-- saw, a second spiked pit, a checkpoint, a thwomp guarding
-- the RED key, the glacier run (a SECOND always-on saw under
-- a snow cloud) with a ROLLING BOULDER that slides the ice
-- head-on (leap it), the blue crusher alley, the red walled
-- door, snow steps, the final flag.
-- LEVEL 4 HAUNTED HOLLOW (6656px) - WAVE 3's bestiary in
-- the purple biome: a MIMIC field (grass blocks that do
-- not belong - they wake and lunge), the SNAIL (stomp it
-- into a SHELL, kick the shell, bowl the slime down the
-- path), two BATS roosting under a stone bar (they drop
-- and swoop), a pit, a long FOUR-burrow PIRANHA row (they
-- will not rise under your feet), the shy GHOST that drifts
-- closer while you face away, two faced CRUSHERS around a
-- LAVA strip crossed by the Wave 5 LAVA LIFT (ride the deck
-- over the lava, or double-jump it), a smouldering FIRE
-- SLIME, the POWDER KEG bay (an explosive barrel + woodpile,
-- scattered by b2kExplode), the red crusher alley (its
-- signature gauntlet), purple steps, the last flag.
--
-- WHAT TO VERIFY (the Phase 3 + level-rebuild OXT pass)
-- 1. Feel: a TAPPED jump is clearly shorter than a HELD one; jumping
-- still fires ~90ms after running off a ledge (coyote); pressing
-- jump just before touchdown fires on landing (buffer).
-- 2. SOLID GROUND EVERYWHERE (the chain ghost-rule fix): the mound
-- walks up the left ramp, across, down the right with no
-- fall-through at any edge; the bridge deck and BOTH clouds are
-- solid out to their visible ends; the LEVEL EDGES are hard stops
-- (thick slabs - no creeping past the first/last screen).
-- 3. NO EVENT EVER MISSES (per-step harvest fix): every coin touch
-- collects, every stomp registers - even on heavy frames. The
-- PRESSURE PLATE is now POLLED, not event-counted: pressed exactly
-- while something physically sits on it (a crate that settles and
-- falls ASLEEP still holds it; ~0.2s release grace - no flapping,
-- no chime spam). Things shoved into the PIT are DESTROYED below
-- the kill floor, never fall forever; losing the crate closes the
-- gate.
-- 4. STOMPS SQUASH (judged by the controller's land/fall state, not
-- post-impact velocity): land on a normal slime = squash + bounce;
-- its sides hurt; the SPIKE slime hurts from every side.
-- 5. Thwomps: underside kills while falling/rising; the resting head
-- is a platform (it rests STATIC - a chained weight can NOT be
-- dragged or shoved by the player); it rises and re-arms in place.
-- 6. HUD "lands" rises by exactly 1 per touchdown; stand still 2+ min
-- (never ASLEEP); hurt/win cut control then respawn restores it.
-- 7. Camera follows smoothly over each level's full width; build time
-- per level stays tolerable; ms/frame stays ~16-18.
-- 8. SOUND (Phase 5 audio, all b2kToneMake - zero asset files): cues
-- for jump, land, coin, stomp, hurt, checkpoint, gate, win. M
-- mutes/unmutes; an engine with no audio stays SILENT, no errors
-- (the HUD would show the b2kSoundStatus reason).
-- 9. Reset and stack-reopen leave no ghosts (R resets, ESC pauses).
-- 10. WAVE 1 toys (all verdicts POLLED geometry + windows, no contact
-- events on statics): the springboard relaunches whenever your
-- feet rest in its band (b2kPlayerJump, so no ground-snap swallow)
-- and reaches the sky coin; each ?-box pays exactly ONE coin then
-- shows its empty face; bricks shatter into chunks that the kill
-- floor sweeps (no debris ghosts after the bounce); the gate
-- button shows pressed/idle switch art in step with the polled
-- plate; STANDING at the lever (not running past) powers the
-- sweeping saw down (ghosted, spin stopped, harmless) and a second
-- stand powers it back up; the key rides the hero's shoulder, the
-- WALL-DOOR opens on approach (no pressing-in needed; the passage
-- body is parked off-world then disabled, the lock dissolves into
-- the wall) and STAYS open through respawns; the hero walks
-- through IN FRONT of the open door art (scenery builds before
-- him) and the passage coin pays on the way; the wall runs to the
-- ceiling, so NO route - thwomp-ride arcs included - reaches the
-- steps or the flag without the door; the crate wears the
-- empty-box face but still drags; spike tips show in the pit; the
-- steps past the door are STONE biome.
-- 11. WIN-STATE CLARITY: collecting a level's LAST coin turns its goal
-- flag GOLD with a banner + chime; touching the flag short of the
-- total BUZZES and banners how many coins remain (throttled; the
-- HUD repeats the hint). On the walled-door levels the last coins
-- live behind the door, so the GOLD moment lands in sight of the
-- flag. The red checkpoint flag only marks respawn - never a win.
-- 12. THE LEVEL FLOW: flag on L1/L2 banners "LEVEL N CLEAR!" and the
-- next level builds ~1.2s later (outside the physics frame); R
-- restarts only the CURRENT level; the final dialog shows TOTAL
-- time and falls across all four; Play again is a fresh L1 run.
-- The boundaries audit rides pfBounds: every level gets thick
-- side slabs PLUS edge wall segments PLUS the matching camera
-- clamp - check you cannot leave the world on any level.
-- 13. WAVE 2 player actions (the Kit's controller, harness v10):
-- DUCK: DOWN anywhere on the ground brakes to a crouch (the duck
-- pose; hitbox unchanged this wave - you cannot duck under saws).
-- DROP-THROUGH: DOWN+JUMP on the L1 bridge drops to the low road
-- (mind the spike slime) and off both cloud rests; same on L2's
-- breather cloud; solid ground just ducks. The deck is SOLID
-- again immediately after (land back on it).
-- CLIMB: the L2 ladder (right of the gate) climbs to a bonus
-- ledge + coin; UP grabs it, JUMP lets go with a normal jump,
-- climbing down to the floor steps off.
-- KNOCKBACK SPLIT: contact damage (slime sides, spike slime, saw
-- brushes, spike tips, thwomp undersides, the fly) now POPS the
-- hero away with a hit pose + ~1s of mercy (HUD counts "hits") -
-- he NO LONGER teleports to the checkpoint; only lethal FALLS
-- (pits, leaving the world) respawn ("falls" counts those).
-- 14. WAVE 3 bestiary (LEVEL 4; all polled, zero Kit changes):
-- SNAIL: stomp = shell (not a kill); ANY touch of the parked
-- shell kicks it sliding at 520px/s AWAY from you; it bowls over
-- the slime (and any walker it reaches), REVERSES off walls, and
-- its sides hurt - stomp it again to park it. The kick sound is
-- new. MIMICS: the two grass blocks sit dead still until you are
-- ~90px away, then wake (jump face) and lunge in hops; one stomp
-- each. BATS: roost under the stone bar, drop when you walk
-- near, swoop to head height and bob; one stomp each. PIRANHAS:
-- rise from the two burrow holes on a cycle, bite, sink - and
-- refuse to rise while you stand over a mouth; unkillable (skip
-- or time them). GHOST: drifts toward you ONLY while you face
-- away; turn around and it freezes in its shy pose; untouchable
-- - it is the level's pressure, not a kill. CRUSHERS: the L4
-- thwomps wear the block FACES (idle/fall/rest swap with state).
-- If a spook's left/right facing looks mirrored, report it (the
-- sheet's native facing is unverifiable statically).
-- No enemies.png in the folder? The four spook makers skip
-- SILENTLY and L4 still completes (snail/crushers/lava remain).
-- 15. THE SHOWCASE MECHANISMS (the kit's JOINT + dynamics systems, all
-- example-side, zero Kit change; verdicts are POLLED, so a hit can
-- never be outrun). L1 ROPE BRIDGE: the planks SAG under you and
-- spring back; cross by walking (a moving body needs no carry); a
-- slip into the chasm respawns you at the new CHECKPOINT on the
-- brink (a quick retry, never a whole-level replay). L3 BOULDER (a
-- sprite-faced stone): it slides the icy flat head-on, JUMPING lifts
-- you clear (it passes through you and is judged by a poll); it
-- recycles forever, never reaching the kill floor. L4 POWDER KEG:
-- coming near lights the fuse, then
-- b2kExplode scatters the woodpile and knocks you if you lingered;
-- retreat on the telegraph. NEW SPECIES (slime FAMILY, native foes
-- art): a fast MOUSE (L1), a slow WORM (L2), a LADYBUG (L1/L3), a
-- FIRE SLIME by the lava (L4, spike-type - hurts every side). The
-- HUD now shows "awake N/M" bodies (watch it fall as things sleep).
-- 16. WAVE 5 MOVES (the Kit controller's new abilities, on everywhere;
-- each level leans on one new beat - all example-side, zero Kit
-- change). DOUBLE JUMP: a second jump fires in mid-air (clears the
-- taller beats). WALL JUMP: hug a wall while airborne to slow the
-- fall, then jump to launch up-and-away. DASH (SHIFT or X): a flat
-- horizontal burst on a cooldown. DUCK: DOWN brakes to a stop in place.
-- PLATFORM CARRY: a grounded hero on a moving lift inherits its
-- velocity. The signature beats: L1 has a coin on the open meadow run;
-- L2 LIFT BAY (jump the
-- pedestal, RIDE the deck across the grinder for the mid-bay coin,
-- step off the far pedestal - a fall onto the grinder is a recoverable
-- knockback; the ~200px gap is also double-jumpable); L3 WALL-JUMP
-- SHAFT (climb the two floating ice pillars to the top coin - a
-- straight double-jump up the slot also reaches it, so it is never
-- trick-only; walk under the pillars unobstructed); L4 LAVA LIFT (ride
-- the deck over the lava between the crushers for the mid-lava coin -
-- or double-jump the 128px strip; a fall onto the lava is a knockback,
-- recoverable). EVERY new-move coin is also reachable WITHOUT the
-- trick (the double-jump is universal), so no level can dead-end.
-- FEEL is unverifiable statically - tune the Wave 5 knobs in OXT and
-- confirm: the lifts carry you (stand still and drift with the deck),
-- a crouched hero clears the L1 bar a standing one cannot, and the L3
-- slot is climbable by wall-jumps AND by a plain double-jump.
-- =====================================================================
local gStarted, gHero, gHeroSpr, gHudLast, gHurtLock
local gCoins, gCoinsTotal, gAssetsOK, gLoadNote, gWon
local gGate, gGateSprA, gGateSprB, gPlateHoldMS, gPlateLook, gCamOK
local gIntroPan, gRunStart, gFalls, gWinLock, gFlagHint, gWinSecs
local gOuches -- Wave 2: contact hits (knockbacks) - falls stay falls
local gBuilding, gLands, gPrevState, gHudNextMS, gGateVel, gAirMS
-- the four-level game: which level, its width/name, and the banked
-- totals carried across level clears for the final win screen
local gLevel, gLevelW, gLevelName, gTotalFalls, gSecsBank
local gPlateX, gGateUpY, gGateDownY, gDoorX, gDoorWord, gCheckX
-- enemy/trap tables, indexed 1..N (see pfMakeSlime/pfMakeThwomp/pfAddMover)
local gSlimeN, gSlimeB, gSlimeSpr, gSlimeKind, gSlimeDir
local gSlimeMin, gSlimeMax, gSlimeGoneAt
local gSlimeT -- Wave 3: per-row timer (mimic hop cooldowns)
local gBlockN, gBlockB, gBlockSpr, gBlockState, gBlockT, gBlockX
local gMovN, gMovSpr, gMovX, gMovY, gMovAX, gMovPX, gMovAY, gMovPY
local gMovHurtW, gMovHurtH, gMovFlip
-- Wave 3 (bestiary I): the piranha burrows, the ghost, the spooks sheet
local gPlantN, gPlantSpr, gPlantX, gPlantState, gPlantT
local gGhostSpr, gGhostX, gGhostY, gGhostShy
local gSpooksOK -- enemies.png loaded (ghost/bat/piranha/mimic art)
local gCheckSpr, gCheckpointOn, gRespawnX, gRespawnY
-- Wave 1 (the iconic-feel base): springboard, bonk row, lever, key+door
local gToysOK, gSpringSpr, gSpringX, gSpringMS, gSpringBackMS
local gBonkN, gBonkHost, gBonkSpr, gBonkKind, gBonkUsed, gBonkX
local gBonkMS, gPlateSpr
-- the per-frame hero snapshot (read ONCE in b2kFrame; every tick
-- shares it - each b2kPosition/b2kPlayerState is an FFI round-trip)
local gHeroPX, gHeroPY, gHeroState, gJumpMS
local gHeroHalfH -- the hero capsule's half-height (head reach for bonks)
local gDebrisNext, gDebrisUntil
local gLeverSpr, gLeverX, gLeverArmed, gSawOn, gSawMov, gSawSpr
local gHasKey, gKeySpr, gDoorOpen, gLockSpr, gDoorSprT, gDoorSprB
local gDebrisB, gDebrisSpr, gCrateB, gCrateSpr
local gBlockChainA, gBlockChainB, gBlockFace
local gGoalSpr, gNoticeUntil, gFlagNoteMS
-- Polish pass (the showcase round): three classic mechanisms riding the
-- Kit's JOINT + dynamics subsystems (all example-side, zero Kit change).
-- The ROPE BRIDGE is a row of plank bodies hinged end-to-end and pinned
-- to the world; the ROLLING BOULDER is a dynamic ball (a sprite-faced
-- stone) re-parked on a cycle so it never nears the kill floor; the
-- EXPLOSIVE BARREL fires b2kExplode to scatter a woodpile of crates.
-- Per-critter speed + squash frame let the slime FAMILY carry the new
-- walker species (mouse, worm, ladybug, fire slime) with no new tables.
-- gSlimeFlip is the per-row facing polarity (gotcha 26): the snail's
-- sheet art faces the OPPOSITE way to the slimes', so it flips inverted.
local gSlimeSpeed, gSlimeFlat, gSlimeFlip
local gBoulderN, gBoulderB, gBoulderSpr, gBoulderHomeX, gBoulderHomeY
local gBoulderResetX, gBoulderT, gBoulderHurtR, gBoulderGrace, gBoulderVX
local gBarrelN, gBarrelB, gBarrelSpr, gBarrelX, gBarrelY, gBarrelState, gBarrelT
-- Wave 5 showcase: the moving-platform LIFT (platform-carry). A KINEMATIC
-- box driven by VELOCITY (carry reads b2BodyVX, so a position-driven body
-- would report zero velocity and never carry its rider). pfTickLift flips
-- each lift's vx at its min/max endpoints (write-on-change only - a resting
-- lift must keep moving, so it ASSERTS velocity, but only at the turns).
-- gLiftVX caches the last-written vx so the tick never re-sets an unchanged
-- velocity (gotcha 17). Indexed 1..gLiftN.
local gLiftN, gLift, gLiftMin, gLiftMax, gLiftSpeed, gLiftVX
constant kMoveSpeed = 280
constant kJumpSpeed = 430
constant kPfUIVersion = "7"
constant kHeroSheetB64 = "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"
-- =====================================================================
-- Lifecycle
-- =====================================================================
on openCard
-- Load the five Kenney atlases ONCE, not every level: persisted sheets
-- survive b2kTeardown (like sounds), so a level rebuild reuses them
-- instead of re-decoding/re-parsing/re-slicing. They also ride along in
-- a SAVED stack, so a reopened stack skips the disk import entirely
-- (Shift+Reset purges the cache to re-pick the art folder).
b2kSheetPersist true
if the uPfUIVersionTag of this stack is not kPfUIVersion then buildPfUI
if gStarted is true then
b2kStart
else
put 1 into gLevel
put 0 into gTotalFalls
put 0 into gSecsBank
pfStartGame
end if
pass openCard
end openCard
on closeCard
b2kStop
pass closeCard
end closeCard
-- =====================================================================
-- Chrome (version-tagged; rebuilds when a newer example is pasted in)
-- =====================================================================
command buildPfUI
if there is a field "pfTitle" then delete field "pfTitle"
if there is a field "pfHelp" then delete field "pfHelp"
if there is a field "pfHud" then delete field "pfHud"
if there is a button "pfbtn_pause" then delete button "pfbtn_pause"
if there is a button "pfbtn_reset" then delete button "pfbtn_reset"
set the width of this stack to 1024
set the height of this stack to 640
set the loc of this stack to the screenLoc
try
set the title of this stack to "Box2Dxt - Platformer Demo"
catch tErr
end try
create field "pfTitle"
set the rect of it to 20, 8, 1004, 30
set the lockText of it to true
set the traversalOn of it to false
set the textSize of it to 14
set the text of it to "Box2Dxt platformer - a scrolling Game Kit showcase (input + sprites + player + camera + joints)"
create field "pfHelp"
set the rect of it to 20, 34, 1004, 92
set the lockText of it to true
set the traversalOn of it to false
set the textSize of it to 11
create field "pfHud"
set the rect of it to 20, 584, 760, 612
set the lockText of it to true
set the traversalOn of it to false
set the textSize of it to 11
create button "pfbtn_pause"
set the rect of it to 800, 582, 884, 610
set the label of it to "Pause"
set the traversalOn of it to false
create button "pfbtn_reset"
set the rect of it to 900, 582, 984, 610
set the label of it to "Reset"
set the traversalOn of it to false
-- intro splash (shown during the opening pan)
create field "pfSplash"
set the rect of it to 112, 220, 912, 330
set the lockText of it to true
set the traversalOn of it to false
set the textAlign of it to "center"
set the textSize of it to 26
set the textStyle of it to "bold"
set the opaque of it to false
set the showBorder of it to false
set the text of it to "B O X 2 D X T" & cr & "a physics platformer demo - arrows/WASD run, SPACE jumps"
set the visible of it to false
-- the win dialogue (hidden until all coins + the flag)
create graphic "pfWinShade"
set the style of it to "roundrect"
set the rect of it to 262, 170, 762, 430
set the filled of it to true
set the backgroundColor of it to "26,30,44"
set the blendLevel of it to 18
set the visible of it to false
create field "pfWinText"
set the rect of it to 282, 190, 742, 360
set the lockText of it to true
set the traversalOn of it to false
set the textAlign of it to "center"
set the textSize of it to 16
set the textColor of it to "255,250,235"
set the opaque of it to false
set the showBorder of it to false
set the visible of it to false
create button "pfbtn_again"
set the rect of it to 452, 374, 572, 406
set the label of it to "Play again"
set the traversalOn of it to false
set the visible of it to false
set the uPfUIVersionTag of this stack to kPfUIVersion
end buildPfUI
-- =====================================================================
-- Assets: the Kenney atlases, or the embedded placeholder when missing
-- =====================================================================
function pfLoadSheets
local tFolder, tN
put empty into gLoadNote
put the uSpriteSheetFolderPath of this stack into tFolder
if tFolder is empty or there is no file (tFolder & "/spritesheet-characters-default.png") then
answer folder "Locate the repo's Spritesheets folder (Cancel = built-in placeholder art):"
put it into tFolder
if tFolder is empty or the result is "cancel" then
put "no folder chosen" into gLoadNote
return false
end if
if there is no file (tFolder & "/spritesheet-characters-default.png") then
put "spritesheet-characters-default.png is not inside: " & tFolder into gLoadNote
return false
end if
set the uSpriteSheetFolderPath of this stack to tFolder
end if
b2kSheetLoadAtlas "chars", tFolder & "/spritesheet-characters-default.png"
put the result into tN
if tN < 1 then
put "characters atlas gave 0 frames (png or xml unreadable) in: " & tFolder into gLoadNote
return false
end if
b2kSheetLoadAtlas "foes", tFolder & "/spritesheet-enemies-default.png"
put the result into tN
if tN < 1 then put "enemies atlas gave 0 frames. " after gLoadNote
b2kSheetLoadAtlas "tiles", tFolder & "/spritesheet-tiles-default.png"
put the result into tN
if tN < 1 then put "tiles atlas gave 0 frames. " after gLoadNote
b2kSheetLoadAtlas "bg", tFolder & "/spritesheet-backgrounds-default.png"
put the result into tN
if tN < 1 then put "backgrounds atlas gave 0 frames. " after gLoadNote
b2kSheetScale "bg", 2.5 -- 256px panels -> 640px (full card height)
-- Wave 3: the SPOOKS (ghost/bat/piranha/grassBlock) live on the
-- Family C enemies.png - a ~70px-grid sheet in a 64px-grid level,
-- normalised per the mixed-grids law (frame names keep their .png
-- suffix on this sheet). Optional: missing = those makers skip.
b2kSheetLoadAtlas "spooks", tFolder & "/enemies.png"
put the result into tN
if tN >= 1 then
b2kSheetScale "spooks", 0.9
b2kAnimDef "spooks", "batfly", "bat_fly.png,bat.png", 8, true
end if
-- the hero (beige; swap the colour word to re-skin)
b2kAnimDef "chars", "idle", "character_beige_idle", 2, true
b2kAnimDef "chars", "walk", "character_beige_walk_a,character_beige_walk_b", 6, true
b2kAnimDef "chars", "jump", "character_beige_jump", 1, true
b2kAnimDef "chars", "hit", "character_beige_hit", 2, false
-- Wave 2 poses: the default characters sheet HAS duck and climb
-- frames for the beige hero (the design doc guessed it did not).
-- "hurtpose" LOOPS on purpose: the Kit's knockback drives it, and a
-- non-looping anim would fire b2kSpriteOnFinish -> pfHurtDone (the
-- RESPAWN) mid-knockback. The respawn path keeps the one-shot "hit".
b2kAnimDef "chars", "duck", "character_beige_duck", 1, true
b2kAnimDef "chars", "climb", "character_beige_climb_a,character_beige_climb_b", 6, true
b2kAnimDef "chars", "hurtpose", "character_beige_hit", 2, true
b2kAnimDef "foes", "buzz", "bee_a,bee_b", 8, true
b2kAnimDef "foes", "sawspin", "saw_a,saw_b", 10, true
b2kAnimDef "foes", "slimewalk", "slime_normal_walk_a,slime_normal_walk_b", 4, true
b2kAnimDef "foes", "spikewalk", "slime_spike_walk_a,slime_spike_walk_b", 4, true
b2kAnimDef "foes", "snailwalk", "snail_walk_a,snail_walk_b", 4, true
-- the showcase round's new walker species (all native 64px foes art,
-- so no sheet/scale juggling; they ride the slime FAMILY as new kinds)
b2kAnimDef "foes", "mousewalk", "mouse_walk_a,mouse_walk_b", 9, true
b2kAnimDef "foes", "wormwalk", "worm_normal_move_a,worm_normal_move_b", 4, true
b2kAnimDef "foes", "ladywalk", "ladybug_walk_a,ladybug_walk_b", 7, true
b2kAnimDef "foes", "firewalk", "slime_fire_walk_a,slime_fire_walk_b", 5, true
b2kAnimDef "foes", "ladyflit", "ladybug_fly,ladybug_walk_a", 10, true
b2kAnimDef "tiles", "spin", "coin_gold,coin_gold_side", 4, true
b2kAnimDef "tiles", "wave", "flag_blue_a,flag_blue_b", 4, true
b2kAnimDef "tiles", "wavegold", "flag_yellow_a,flag_yellow_b", 4, true
b2kAnimDef "tiles", "coinpop", "coin_gold_side,coin_gold,coin_gold_side", 14, false
b2kAnimDef "tiles", "checkwave", "flag_red_a,flag_red_b", 4, true
b2kAnimDef "foes", "flit", "fly_a,fly_b", 10, true
b2kAnimDef "chars", "win", "character_beige_duck,character_beige_idle", 3, true
return true
end pfLoadSheets
-- Every sound is SYNTHESIZED (b2kToneMake): zero asset files. Sounds
-- survive b2kTeardown (Kit policy), so after the first build a reset
-- skips the synthesis entirely. Short and plucky on purpose - the
-- engine plays one clip at a time, so a new cue cuts the previous.
command pfMakeSounds
-- guard on the NEWEST cue, so a stack upgraded in place re-synthesizes
if b2kSoundIsLoaded("boom") then exit pfMakeSounds -- already built
b2kToneMake "jump", "392,587", 40, 55
b2kToneMake "coin", "1319,1760", 36, 45
b2kToneMake "stomp", "165,110", 40, 70
b2kToneMake "hurt", "220,156,110", 55, 65
b2kToneMake "land", "98", 26, 40, "sine"
b2kToneMake "check", "659,880", 50, 50, "sine"
b2kToneMake "gate", "294,392", 40, 35, "sine"
b2kToneMake "win", "523,659,784,1047", 110, 65
-- Wave 1 cues
b2kToneMake "spring", "262,392,587", 36, 55
b2kToneMake "smash", "147,98,73", 40, 65
b2kToneMake "key", "988,1319", 45, 45, "sine"
b2kToneMake "unlock", "392,523,659", 55, 50, "sine"
b2kToneMake "lever", "294,220", 30, 45
b2kToneMake "deny", "196,147", 60, 55
-- Wave 3 cue: the shell kick
b2kToneMake "kick", "330,494", 32, 60
-- the showcase round's mechanism cues
b2kToneMake "creak", "175,147", 34, 35, "sine" -- a foot on the bridge
b2kToneMake "rumble", "73,87,65", 60, 55, "sine" -- the boulder lets go
b2kToneMake "boom", "131,98,73,55", 80, 70 -- the barrel blast
end pfMakeSounds
command pfEmbedPlaceholder
if there is no image "pf_sheetsrc" then
create image "pf_sheetsrc"
set the visible of it to false
set the text of image "pf_sheetsrc" to base64Decode(kHeroSheetB64)
set the lockLoc of it to true -- AFTER the content, so it auto-sized
end if
b2kSheetFromImage "chars", the long id of image "pf_sheetsrc", 48, 64
b2kAnimDef "chars", "idle", "1-4", 4, true
b2kAnimDef "chars", "walk", "9-16", 10, true
b2kAnimDef "chars", "jump", "17", 1, true
b2kAnimDef "chars", "hit", "19", 2, false
b2kAnimDef "chars", "hurtpose", "19", 2, true -- looping twin (knockback)
b2kAnimDef "chars", "win", "1-4", 6, true
end pfEmbedPlaceholder
-- =====================================================================
-- World build / reset
-- =====================================================================
command pfStartGame
local tRef, tW, tH, tDY, tX, tS
if gBuilding is true then exit pfStartGame -- ignore R-mash mid-rebuild
put true into gBuilding
if the shiftKey is down then
set the uSpriteSheetFolderPath of this stack to empty
b2kSheetsWipe -- drop the cached sheets so a re-picked folder loads fresh
end if
try
b2kClear -- spawned bodies (confetti, the crate) go
catch tErr -- while the world still exists
end try
b2kTeardown -- clears the world + camera; cached sheets survive (persist)
pfWipeStage
put false into gHurtLock
put false into gWon
put false into gWinLock
put false into gFlagHint
put false into gCheckpointOn
put 120 into gRespawnX
put 480 into gRespawnY
put empty into gWinSecs
put 0 into gFalls
put 0 into gOuches
put 0 into gLands
put 0 into gHudNextMS
put empty into gGateVel
put 0 into gAirMS
put the milliseconds into gRunStart
put 0 into gCoins
if there is a graphic "pfWinShade" then set the visible of graphic "pfWinShade" to false
if there is a field "pfWinText" then set the visible of field "pfWinText" to false
if there is a button "pfbtn_again" then set the visible of button "pfbtn_again" to false
if there is a field "pfSplash" then set the visible of field "pfSplash" to true
put 0 into gPlateHoldMS
put empty into gPlateLook
-- per-level machine refs: a rebuilt level must never tick a deleted
-- control's stale long id from the previous one
put empty into gGate
put empty into gGateSprA
put empty into gGateSprB
put empty into gPlateX
put empty into gDoorX
put empty into gDoorWord
put empty into gCheckX
put empty into gLevelName
put 2880 into gLevelW
-- the indexed enemy/trap tables start the run empty
put 0 into gSlimeN
put empty into gSlimeB
put empty into gSlimeSpr
put empty into gSlimeKind
put empty into gSlimeDir
put empty into gSlimeMin
put empty into gSlimeMax
put empty into gSlimeGoneAt
put empty into gSlimeT
-- Wave 3 tables start the run empty
put 0 into gPlantN
put empty into gPlantSpr
put empty into gPlantX
put empty into gPlantState
put empty into gPlantT
put empty into gGhostSpr
put 0 into gGhostX
put 0 into gGhostY
put true into gGhostShy
put 0 into gBlockN
put empty into gBlockB
put empty into gBlockSpr
put empty into gBlockState
put empty into gBlockT
put empty into gBlockX
put 0 into gMovN
put empty into gMovSpr
put empty into gMovX
put empty into gMovY
put empty into gMovAX
put empty into gMovPX
put empty into gMovAY
put empty into gMovPY
put empty into gMovHurtW
put empty into gMovHurtH
put empty into gMovFlip
put empty into gPrevState
-- Wave 1 state starts the run empty/idle
put false into gToysOK
put empty into gSpringSpr
put 0 into gSpringMS
put 0 into gSpringBackMS
put 0 into gBonkN
put empty into gBonkHost
put empty into gBonkSpr
put empty into gBonkKind
put empty into gBonkUsed
put empty into gBonkX
put 0 into gBonkMS
put empty into gPlateSpr
put empty into gLeverSpr
put true into gLeverArmed
put true into gSawOn
put empty into gSawMov
put empty into gSawSpr
put false into gHasKey
put empty into gKeySpr
put false into gDoorOpen
put empty into gLockSpr
put empty into gDoorSprT
put empty into gDoorSprB
put 0 into gDebrisNext
put empty into gDebrisB
put empty into gDebrisSpr
put empty into gDebrisUntil
put 0 into gHeroPX
put 0 into gHeroPY
put empty into gHeroState
put 0 into gJumpMS
put empty into gCrateB
put empty into gCrateSpr
put empty into gBlockChainA
put empty into gBlockChainB
put empty into gBlockFace
put empty into gGoalSpr
put 0 into gNoticeUntil
put 0 into gFlagNoteMS
-- the showcase round's mechanism tables start the run empty/idle
put 0 into gBoulderN
put empty into gBoulderB
put empty into gBoulderSpr
put empty into gBoulderHomeX
put empty into gBoulderHomeY
put empty into gBoulderResetX
put empty into gBoulderT
put empty into gBoulderHurtR
put empty into gBoulderGrace
put empty into gBoulderVX
put 0 into gBarrelN
put empty into gBarrelB
put empty into gBarrelSpr
put empty into gBarrelX
put empty into gBarrelY
put empty into gBarrelState
put empty into gBarrelT
-- the Wave 5 moving-platform lifts start the run empty/idle
put 0 into gLiftN
put empty into gLift
put empty into gLiftMin
put empty into gLiftMax
put empty into gLiftSpeed
put empty into gLiftVX
-- per-critter speed + squash frame + facing polarity (slime columns)
put empty into gSlimeSpeed
put empty into gSlimeFlat
put empty into gSlimeFlip
b2kSetup
b2kSetScale 60
pfMakeSounds
put pfLoadSheets() into gAssetsOK
if gAssetsOK is not true then pfEmbedPlaceholder
-- Wave 1 toys need the tiles atlas (spring/box/brick/lever/key/door
-- art); without it each level builds its frozen plain-shape fallback
put (gAssetsOK is true and b2kSheetHasFrame("tiles", "spring_out")) into gToysOK
-- Wave 3 spooks need enemies.png (ghost/bat/piranha/mimic); without
-- it those four makers skip silently - L4 stays completable
put (gAssetsOK is true and b2kSheetHasFrame("spooks", "ghost.png")) into gSpooksOK
-- the coin total COUNTS ITSELF: pfMakeCoin and every "?-box" add one
-- as they are built, so totals can never drift from the layout
put 0 into gCoinsTotal
lock screen
-- ===== far backdrop: static panels on the CARD (they do not scroll) ====
if gAssetsOK is true and b2kSheetHasFrame("bg", "background_color_hills") then
b2kSpriteNew "bg", "background_color_hills", 320, 320
put the result into tRef
if tRef is not empty then set the layer of tRef to 1
if b2kSheetHasFrame("bg", "background_color_trees") then
b2kSpriteNew "bg", "background_color_trees", 960, 320
else
b2kSpriteNew "bg", "background_color_hills", 960, 320
end if
put the result into tRef
if tRef is not empty then set the layer of tRef to 2
end if
-- ===== the camera: everything Kit-made from here lives in the view ====
b2kCamOn
put (b2kCamIsOn() and b2kCamStatus() is empty) into gCamOK
-- regular-platformer camera: the hero is LOCKED to screen centre and
-- the world scrolls 1:1; the bounds are set per level by pfBounds.
-- ORDER LAW (gotcha #12, learned the hard way): NEVER b2kCamGoto
-- before the bounds exist - an unbounded goto scrolls the empty
-- viewport (120 centred = scroll -392), and every control built
-- after that lands SCROLL-SHIFTED: art ~400px away from its
-- physics. Build first at scroll 0, bound (pfBounds), THEN goto.
b2kCamDeadzone 0, 0
-- ===== LEVEL SCENERY (terrain, machines, Wave 1 toys) =====
-- Built BEFORE the hero so every piece of it layers BEHIND him (he
-- must walk in front of doors and levers, never vanish). Each level
-- is a builder pair: ...Scene here, ...Cast after the hero exists.
switch gLevel
case 2
pfL2Scene
break
case 3
pfL3Scene
break
case 4
pfL4Scene
break
default
put 1 into gLevel
pfL1Scene
end switch
b2kCamGoto 120, 480 -- bounds exist now: clamps to scroll 0, no slide
-- ===== the hero ====
if gAssetsOK is true then
b2kSheetScale "chars", 0.75
put 48 into tW
-- the 128px character frame is bottom-aligned with headroom, so an
-- 88px hitbox topped out ABOVE the visible head -- heads "missed"
-- bricks by that headroom. 76 matches the art; tDY keeps the FEET on
-- the ground (sprite half = 128*0.75/2 = 48 vs hitbox half tH/2).
-- If the head still gaps under bricks, lower tH a touch; if it sinks
-- INTO them, raise it. tDY + gHeroHalfH follow automatically.
put 76 into tH
put (tH div 2) - 48 into tDY
else
put 36 into tW
put 60 into tH
put -2 into tDY
end if
put tH / 2 into gHeroHalfH -- bonks reach the brick at the real head
create graphic "pf_heroBody"
set the style of it to "rectangle"
set the rect of it to 120 - tW div 2, 480 - tH div 2, 120 + tW div 2, 480 + tH div 2
set the visible of it to false
put the long id of graphic "pf_heroBody" into gHero
if gAssetsOK is true then
b2kSpriteNew "chars", "character_beige_idle", 120, 480
else
b2kSpriteNew "chars", 1, 120, 480
end if
put the result into gHeroSpr
b2kSpriteBind gHeroSpr, gHero, 0, tDY
b2kSpriteOnFinish gHeroSpr, "pfHurtDone"
-- Phase 3: the Kit's controller owns movement, grounding, jump feel
-- (coyote/buffer/jump-cut), state and anims. Attaching adds the
-- capsule + fixed rotation + sleep-off + low friction in one call;
-- the bound sprite above is auto-discovered as the art to animate.
b2kPlayerAttach gHero
-- duck/climb map when the atlas loaded them (placeholder art has
-- neither: empty slots fall back to idle/jump inside the Kit). The
-- knockback pose is the LOOPING "hurtpose" -- never the one-shot
-- "hit", whose finish message means RESPAWN in this game
if gAssetsOK is true and b2kSheetHasFrame("chars", "character_beige_duck") then
b2kPlayerAnims "idle", "walk", "jump", "jump", "", "duck", "climb", "hurtpose"
else
b2kPlayerAnims "idle", "walk", "jump", "jump", "", "", "", "hurtpose"
end if
b2kPlayerSet "moveSpeed", kMoveSpeed
b2kPlayerSet "jumpSpeed", kJumpSpeed
-- Wave 4 swim feel: heavier than the Kit defaults. swimGravity (the
-- sink between strokes) is well up so the water feels weighty; swimJump
-- (the stroke) is the ONLY thing that sets the escape height, so it is
-- trimmed back -- you must swim to the bank and stroke to climb out, not
-- bob straight up. All four are live; tune to taste in OXT.
b2kPlayerSet "swimSpeed", 165
b2kPlayerSet "swimJump", 300
b2kPlayerSet "swimGravity", 0.6
b2kPlayerSet "swimMaxFall", 200
-- Wave 5 moves, on for the whole showcase (each beat below leans on one;
-- all stay live everywhere). The Kit falls the wall-slide pose back to
-- the fall/jump frame and the dash pose to the run frame -- the beige
-- hero has no dedicated wall/dash art -- so they read correctly. Tune in
-- OXT to taste: these are first-pass feel numbers.
b2kPlayerSet "airJumps", 1 -- DOUBLE JUMP (a second jump in mid-air)
b2kPlayerSet "wallJumpX", 240 -- WALL JUMP: launch away from a wall...
b2kPlayerSet "wallJumpY", 430 -- ...and up (matches the ground jump)
b2kPlayerSet "wallSlideMax", 120 -- ...after a slowed slide down it
b2kPlayerSet "dashSpeed", 560 -- DASH (SHIFT or X): a flat GROUND zip
b2kPlayerSet "dashMs", 150
b2kPlayerSet "dashCooldownMs", 420
b2kPlayerSet "airDash", 0 -- no mid-air dash: DASH only fires while grounded
b2kPlayerSet "duckScale", 1 -- DOWN brakes to a stop in place (no hitbox reshape)
b2kPlayerSet "platformCarry", 1 -- ride the moving platforms (L2 lift bay, L4 lava lift)
put 75 into gIntroPan -- a splash beat (~1.2 s), then control
-- ===== LEVEL CAST (coins, enemies, checkpoint, the goal flag) =====
switch gLevel
case 2
pfL2Cast
break
case 3
pfL3Cast
break
case 4
pfL4Cast
break
default
pfL1Cast
end switch
-- help + the level banner on the splash (visible through the intro
-- beat); gCoinsTotal counted itself during the build above
if gAssetsOK is true and gLoadNote is empty then
set the text of field "pfHelp" to "Arrows/A-D run, SPACE jumps - press it again in mid-air to DOUBLE JUMP, or off a wall to WALL-JUMP. SHIFT or X = DASH." & cr & "DOWN ducks to a stop (DOWN+JUMP drops through bridges/clouds); UP/DOWN climbs ladders. R restarts, ESC pauses, M mutes, MOUSE drags the crate." & cr & "LEVEL " & gLevel & " of 4: " & gLevelName & ". Collect ALL " & gCoinsTotal & " coins - the flag turns GOLD - then touch the flag. Four levels to win."
else
if gAssetsOK is true then
set the text of field "pfHelp" to "KENNEY CHARACTERS LOADED, but: " & gLoadNote & cr & "Missing atlases fall back to plain shapes." & cr & "Shift+Reset re-asks for the Spritesheets folder."
else
set the text of field "pfHelp" to "PLACEHOLDER ART - the atlas load failed: " & gLoadNote & cr & "Press Shift+Reset to pick the Spritesheets folder again (it must contain the" & cr & "spritesheet-*-default.png and .xml files)."
end if
end if
if there is a field "pfSplash" then
set the text of field "pfSplash" to "LEVEL " & gLevel & " / 4" & cr & gLevelName
end if
-- ===== camera-dead fallback: clamp play to the visible screen ====
if gCamOK is not true then
b2kWall 1010, 0, 1010, 640
put empty into tRef
if gAssetsOK is true and b2kSheetHasFrame("tiles", "flag_blue_a") then
b2kSpriteNew "tiles", "flag_blue_a", 950, 544
put the result into tRef
if tRef is not empty then
set the uPfGoalFlag of tRef to true
b2kSpritePlay tRef, "wave"
b2kAddSensor tRef, "ball"
put tRef into gGoalSpr
end if
end if
if tRef is empty then
create graphic "pf_flagnear"
set the style of it to "rectangle"
set the rect of it to 934, 512, 966, 576
set the filled of it to true
set the backgroundColor of it to "70,110,230"
put the long id of graphic "pf_flagnear" into tRef
set the uPfGoalFlag of tRef to true
b2kAddSensor tRef, "box"
end if
set the text of field "pfHelp" to "CAMERA UNAVAILABLE on this engine (" & b2kCamStatus() & ") - the level is clamped" & cr & "to this screen and the flag moved here so it stays completable. Everything else works." & cr & "Please report the quoted reason."
end if
-- diagnostics: prove world membership at a glance (line 3 of the help)
if gCamOK is true and gHeroSpr is not empty then
put "diag: hero-sprite in [" & the short name of the owner of gHeroSpr & "]" into tS
if gGateSprA is not empty then
put " gate-tile in [" & the short name of the owner of gGateSprA & "]" after tS
end if
put tS & " grouped-loc semantics: " & b2kCamLocSemantics() into line 3 of field "pfHelp"
end if
-- ===== input + events ====
b2kInputOn
b2kBindAction "jump", "space,up,w"
b2kFrameTarget the long id of me
b2kContactTarget the long id of me
pfChromeFront
unlock screen
put empty into gHudLast
put true into gStarted
put false into gBuilding
b2kStart
end pfStartGame
-- An invisible static physics slab (shown grey only in fallback mode).
command pfSlab pName, pL, pT, pR, pB
create graphic pName
set the style of it to "rectangle"
set the rect of it to pL, pT, pR, pB
set the filled of it to true
set the backgroundColor of it to "100,105,115"
set the visible of it to false
b2kAddStatic the long id of graphic pName
end pfSlab
-- Skin a STATIC slab with sheet art instead of a flat colour: a COPY of the
-- frame image stretched to the slab's exact rect, so the collision size never
-- changes. The texture is a pf_* image (pfWipeStage clears it next build), laid
-- in at build time only -- the camera scroll is 0 then, so the slab's rect reads
-- as world coords and the adopt preserves the position. No assets or a missing
-- frame: this no-ops and the slab keeps its colour (the placeholder fallback).
command pfTextureSlab pSlab, pFrame
local tSpr, tImg, tName
if gAssetsOK is not true then exit pfTextureSlab
if there is no graphic pSlab then exit pfTextureSlab
if not b2kSheetHasFrame("tiles", pFrame) then exit pfTextureSlab
b2kSpriteNew "tiles", pFrame, -3000, -3000 -- a throwaway sprite carries the sliced frame as its icon
put the result into tSpr
if tSpr is empty then exit pfTextureSlab
put the icon of tSpr into tImg
if tImg is empty or there is no image id tImg then
b2kSpriteRemove tSpr
exit pfTextureSlab
end if
put "pf_tex_" & (the short name of graphic pSlab) into tName
if there is an image tName then delete image tName
lock screen
clone image id tImg
set the name of the last image to tName
set the rect of image tName to the rect of graphic pSlab -- stretch to fit
set the visible of image tName to true -- the sliced frame source is hidden; the copy shows
set the lockLoc of image tName to true
b2kSpriteRemove tSpr
set the visible of graphic pSlab to false -- the texture stands in for the flat fill
b2kCamAdopt the long id of image tName
unlock screen
end pfTextureSlab
command pfShowSlabs pFlag
local tC
set the itemDelimiter to comma
repeat for each item tC in "pf_ground1,pf_ground2,pf_ground3,pf_plat1,pf_plat2"
if there is a graphic tC then
set the visible of graphic tC to pFlag
if pFlag is true then b2kCamAdopt the long id of graphic tC
end if
end repeat
end pfShowSlabs
-- One 64px tile sprite by its top-left corner (sprites centre on x,y).
-- pFlip true mirrors the tile (ramps ascending the other way).
command pfTile pFrame, pX, pY, pFlip
local tRef
b2kSpriteNew "tiles", pFrame, pX + 32, pY + 32
if pFlip is true then
put the result into tRef
if tRef is not empty then b2kSpriteFlipH tRef, true
end if
end pfTile
-- Register a bodiless patroller: the sprite glides on sine paths each
-- frame (b2kSpriteMoveTo, camera-correct), flips to face its travel, and
-- hurts by plain proximity when given a hurt box - no physics involved.
command pfAddMover pSpr, pX, pY, pAmpX, pPerX, pAmpY, pPerY, pHurtW, pHurtH, pFaceTravel
add 1 to gMovN
put pSpr into gMovSpr[gMovN]
put pX into gMovX[gMovN]
put pY into gMovY[gMovN]
put pAmpX into gMovAX[gMovN]
put max(1, pPerX) into gMovPX[gMovN]
put pAmpY into gMovAY[gMovN]
put max(1, pPerY) into gMovPY[gMovN]
put pHurtW into gMovHurtW[gMovN]
put pHurtH into gMovHurtH[gMovN]
put (pFaceTravel is true) into gMovFlip[gMovN]
end pfAddMover
command pfMakeCoin pX, pY
local tRef
add 1 to gCoinsTotal -- the total counts itself as the level builds
put empty into tRef
if gAssetsOK is true and b2kSheetHasFrame("tiles", "coin_gold") then
b2kSpriteNew "tiles", "coin_gold", pX, pY
put the result into tRef
if tRef is not empty then b2kSpritePlay tRef, "spin"
end if
if tRef is empty then
create graphic ("pf_coin" & pX)
set the style of it to "oval"
set the rect of it to pX - 16, pY - 16, pX + 16, pY + 16
set the filled of it to true
set the backgroundColor of it to "240,200,70"
put the long id of it into tRef
b2kCamAdopt tRef
end if
set the uPfCoinFlag of tRef to true
b2kAddSensor tRef, "ball"
end pfMakeCoin
-- A patrolling slime: an invisible dynamic body, art bound on top.
-- pKind "normal" stomps flat; "spike" hurts from EVERY side. pTopY is the
-- surface it stands on; it walks pMinX..pMaxX.
command pfMakeSlime pIdx, pKind, pX, pMinX, pMaxX, pTopY
local tName, tBody, tSpr, tFrame
put "pf_slimeBody" & pIdx into tName
create graphic tName
set the style of it to "rectangle"
set the rect of it to pX - 24, pTopY - 36, pX + 24, pTopY
set the filled of it to true
if pKind is "spike" then
set the backgroundColor of it to "200,80,80"
else
set the backgroundColor of it to "110,190,90"
end if
set the visible of it to false
put the long id of graphic tName into tBody
b2kAddBox tBody
b2kSetFixedRotation tBody, true