-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDOCUMENTATION.html
More file actions
3307 lines (3070 loc) · 207 KB
/
Copy pathDOCUMENTATION.html
File metadata and controls
3307 lines (3070 loc) · 207 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Raylib Engine — Interactive Documentation</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" crossorigin="anonymous" />
<style>
:root {
--bg: #1d2229;
--bg-elev-1: #232932;
--bg-elev-2: #2a313b;
--bg-elev-3: #323a45;
--border: #3a4250;
--border-soft: #2e3540;
--text: #e0e4eb;
--text-dim: #a6adbb;
--text-mute: #6e7684;
--accent: #478cbf;
--accent-soft: #478cbf33;
--accent-glow: #478cbf66;
--warn: #f0a020;
--good: #57b35a;
--bad: #d3614a;
--code-bg: #161a20;
--code-key: #c678dd;
--code-str: #98c379;
--code-com: #6e7684;
--code-type: #61afef;
--code-fn: #e5c07b;
--code-num: #d19a66;
--code-op: #56b6c2;
--sidebar-w: 290px;
--max-content: 1100px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: 'Inter', system-ui, -apple-system, sans-serif; font-size: 15px; line-height: 1.65; scroll-behavior: smooth; }
::selection { background: var(--accent-soft); color: var(--text); }
a { color: var(--accent); text-decoration: none; transition: color .12s ease; }
a:hover { color: #6aa8d6; text-decoration: underline; }
code, pre, .mono { font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; }
p { margin: 0 0 1em 0; }
h1, h2, h3, h4, h5 { font-weight: 600; line-height: 1.3; color: #f0f3f8; scroll-margin-top: 24px; }
h1 { font-size: 2.1rem; margin: 0 0 .6em 0; }
h2 { font-size: 1.6rem; margin: 2.4em 0 .6em 0; padding-bottom: .35em; border-bottom: 1px solid var(--border-soft); }
h2 .anchor, h3 .anchor, h4 .anchor { color: var(--text-mute); margin-left: .4em; font-size: .8em; opacity: 0; transition: opacity .12s; }
h2:hover .anchor, h3:hover .anchor, h4:hover .anchor { opacity: 1; }
h3 { font-size: 1.25rem; margin: 1.8em 0 .5em 0; color: #d4dae6; }
h4 { font-size: 1.05rem; margin: 1.4em 0 .4em 0; color: #c0c7d4; }
ul, ol { margin: 0 0 1em 0; padding-left: 1.4em; }
li { margin: .25em 0; }
strong { color: #f0f3f8; font-weight: 600; }
em { color: #d4dae6; }
blockquote { margin: 1em 0; padding: .6em 1em; border-left: 3px solid var(--accent); background: var(--bg-elev-1); border-radius: 0 4px 4px 0; color: var(--text-dim); }
hr { border: none; border-top: 1px solid var(--border-soft); margin: 2em 0; }
/* ===== Layout ===== */
.app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
.sidebar {
position: sticky; top: 0; height: 100vh; overflow-y: auto;
background: var(--bg-elev-1); border-right: 1px solid var(--border-soft);
padding: 1.4em 1em 4em 1.4em; font-size: 13.5px;
}
.sidebar .brand { font-size: 16px; font-weight: 700; color: #f0f3f8; margin-bottom: .2em; display: flex; align-items: center; gap: .5em; }
.sidebar .brand::before { content: ""; width: 14px; height: 14px; background: var(--accent); border-radius: 3px; box-shadow: 0 0 12px var(--accent-glow); }
.sidebar .brand-sub { color: var(--text-mute); font-size: 11.5px; margin-bottom: 1.4em; letter-spacing: .04em; text-transform: uppercase; }
.sidebar nav h5 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-mute); margin: 1.4em 0 .5em 0; }
.sidebar nav ul { list-style: none; padding: 0; margin: 0; }
.sidebar nav li { margin: 0; }
.sidebar nav a { display: block; padding: .32em .6em; border-radius: 4px; color: var(--text-dim); transition: all .12s; line-height: 1.4; }
.sidebar nav a:hover { background: var(--bg-elev-2); color: var(--text); text-decoration: none; }
.sidebar nav a.active { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
.sidebar nav a.sub { padding-left: 1.4em; font-size: 12.5px; color: var(--text-mute); }
.sidebar nav a.sub:hover { color: var(--text-dim); }
.main { padding: 0; max-width: var(--max-content); margin: 0 auto; width: 100%; }
.content { padding: 2.5em 3em 6em 3em; }
@media (max-width: 980px) {
.app { grid-template-columns: 1fr; }
.sidebar { position: relative; height: auto; max-height: 40vh; border-right: none; border-bottom: 1px solid var(--border-soft); }
.content { padding: 1.5em 1.2em 4em 1.2em; }
}
/* ===== Code blocks ===== */
code { background: var(--bg-elev-2); padding: .12em .4em; border-radius: 3px; font-size: .9em; color: #e5c07b; }
pre { background: var(--code-bg); border: 1px solid var(--border-soft); border-radius: 6px; padding: 1em 1.2em; overflow-x: auto; margin: 1em 0; position: relative; font-size: 13.5px; line-height: 1.55; }
pre code { background: none; padding: 0; color: var(--text); font-size: inherit; }
.code-block { position: relative; margin: 1em 0; }
.code-block pre { margin: 0; }
.code-block .copy-btn {
position: absolute; top: 8px; right: 8px; padding: 4px 10px;
background: var(--bg-elev-3); color: var(--text-dim); border: 1px solid var(--border);
border-radius: 4px; font-family: 'Inter'; font-size: 11px; cursor: pointer;
opacity: 0; transition: opacity .15s, background .12s, color .12s;
}
.code-block:hover .copy-btn { opacity: 1; }
.code-block .copy-btn:hover { background: var(--accent); color: white; }
.code-block .copy-btn.copied { background: var(--good); color: white; opacity: 1; }
.code-block .lang-tag { position: absolute; top: 8px; left: 12px; font-size: 10.5px; color: var(--text-mute); text-transform: uppercase; letter-spacing: .08em; font-family: 'Inter'; }
/* C++ syntax highlighting (manual spans) */
.tok-kw { color: var(--code-key); }
.tok-str { color: var(--code-str); }
.tok-com { color: var(--code-com); font-style: italic; }
.tok-type { color: var(--code-type); }
.tok-fn { color: var(--code-fn); }
.tok-num { color: var(--code-num); }
.tok-op { color: var(--code-op); }
/* ===== Inline UI elements ===== */
.pill { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 11px; font-weight: 500; letter-spacing: .03em; vertical-align: middle; }
.pill.public { background: var(--accent-soft); color: var(--accent); }
.pill.private { background: #d3614a22; color: var(--bad); }
.pill.static { background: #f0a02022; color: var(--warn); }
.pill.virtual { background: #57b35a22; color: var(--good); }
.pill.legacy { background: #6e768422; color: var(--text-mute); }
.note { background: var(--bg-elev-1); border-left: 3px solid var(--accent); padding: .8em 1.1em; border-radius: 0 4px 4px 0; margin: 1em 0; }
.note.warn { border-color: var(--warn); }
.note.tip { border-color: var(--good); }
.note .nb { font-weight: 600; color: var(--accent); }
.note.warn .nb { color: var(--warn); }
.note.tip .nb { color: var(--good); }
table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 13.5px; background: var(--bg-elev-1); border: 1px solid var(--border-soft); border-radius: 6px; overflow: hidden; }
th, td { padding: .55em .9em; text-align: left; border-bottom: 1px solid var(--border-soft); vertical-align: top; }
th { background: var(--bg-elev-2); color: var(--text); font-weight: 600; font-size: 12.5px; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--bg-elev-2); }
td code { font-size: 12.5px; }
/* ===== Class reference cards ===== */
.class-card { background: var(--bg-elev-1); border: 1px solid var(--border-soft); border-radius: 8px; padding: 1.4em 1.6em; margin: 1.4em 0; }
.class-card .class-head { display: flex; align-items: baseline; gap: .8em; flex-wrap: wrap; margin-bottom: .4em; }
.class-card .class-head h3 { margin: 0; font-size: 1.3rem; color: #f0f3f8; }
.class-card .class-head .inherits { color: var(--text-mute); font-size: 13px; font-family: 'JetBrains Mono'; }
.class-card .class-head .file { color: var(--text-mute); font-size: 11.5px; font-family: 'JetBrains Mono'; margin-left: auto; }
.class-card .why { color: var(--text-dim); font-size: 14px; margin: .6em 0 1em 0; padding: .7em .9em; background: var(--bg); border-radius: 4px; border-left: 2px solid var(--accent); }
.class-card .why::before { content: "Why it exists — "; color: var(--accent); font-weight: 600; }
.method { padding: .65em 0; border-bottom: 1px dashed var(--border-soft); }
.method:last-child { border-bottom: none; }
.method .sig { font-family: 'JetBrains Mono'; font-size: 13.5px; color: #e5c07b; display: block; margin-bottom: .3em; }
.method .sig .ret { color: var(--code-type); }
.method .desc { color: var(--text-dim); font-size: 13.5px; }
.method .params { margin: .4em 0 0 0; font-size: 12.5px; color: var(--text-mute); }
.method .params span { display: inline-block; margin-right: 1em; }
.method .params code { color: var(--code-fn); }
/* ===== Interactive demos ===== */
.demo { background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 8px; margin: 1.5em 0; overflow: hidden; }
.demo-head { padding: .8em 1.2em; background: var(--bg-elev-2); border-bottom: 1px solid var(--border-soft); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .6em; }
.demo-head h4 { margin: 0; font-size: 14px; color: #f0f3f8; }
.demo-head .controls { display: flex; gap: .5em; flex-wrap: wrap; align-items: center; font-size: 12.5px; }
.demo-head .controls label { color: var(--text-dim); display: flex; align-items: center; gap: .35em; }
.demo-head .controls input[type="range"] { width: 110px; accent-color: var(--accent); }
.demo-head .controls select, .demo-head .controls button {
background: var(--bg-elev-3); color: var(--text); border: 1px solid var(--border);
border-radius: 4px; padding: 3px 9px; font-size: 12px; font-family: 'Inter'; cursor: pointer;
}
.demo-head .controls button:hover { background: var(--accent); color: white; }
.demo-head .controls button.active { background: var(--accent); color: white; }
.demo-canvas-wrap { padding: 1em; background: #0f1217; }
.demo-canvas-wrap canvas { display: block; max-width: 100%; background: #0a0d12; border-radius: 4px; image-rendering: pixelated; }
.demo-foot { padding: .55em 1.2em; background: var(--bg-elev-2); border-top: 1px solid var(--border-soft); font-size: 12px; color: var(--text-mute); display: flex; gap: 1.5em; flex-wrap: wrap; }
.demo-foot .stat strong { color: var(--accent); font-family: 'JetBrains Mono'; }
.demo-help { padding: .5em 1.2em; background: var(--bg-elev-2); border-top: 1px solid var(--border-soft); font-size: 12px; color: var(--text-dim); }
.demo-help .key { display: inline-block; padding: 1px 6px; background: var(--bg-elev-3); border: 1px solid var(--border); border-radius: 3px; font-family: 'JetBrains Mono'; font-size: 11px; margin: 0 2px; }
/* ===== Scene graph viz ===== */
.scene-graph { background: var(--bg-elev-1); border: 1px solid var(--border-soft); border-radius: 8px; padding: 1.4em; margin: 1em 0; }
.scene-graph .tree { font-family: 'JetBrains Mono'; font-size: 13.5px; line-height: 1.7; }
.scene-graph .tree .node { cursor: pointer; padding: 2px 6px; border-radius: 3px; display: inline-block; transition: background .12s; }
.scene-graph .tree .node:hover { background: var(--bg-elev-2); }
.scene-graph .tree .node.selected { background: var(--accent-soft); color: var(--accent); }
.scene-graph .tree .indent { color: var(--text-mute); }
.scene-graph .detail { margin-top: 1em; padding: 1em; background: var(--bg); border-radius: 6px; border-left: 3px solid var(--accent); }
.scene-graph .detail h5 { margin: 0 0 .5em 0; color: #f0f3f8; font-size: 14px; font-family: 'JetBrains Mono'; }
.scene-graph .detail .members { font-size: 13px; color: var(--text-dim); }
.scene-graph .detail .members .m { display: block; padding: 2px 0; font-family: 'JetBrains Mono'; font-size: 12.5px; }
.scene-graph .detail .members .m .vis { color: var(--text-mute); width: 60px; display: inline-block; }
.scene-graph .detail .members .m .type { color: var(--code-type); }
.scene-graph .detail .members .m .name { color: var(--code-fn); }
/* ===== API playground ===== */
.playground { background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 8px; margin: 1.5em 0; overflow: hidden; }
.playground .editor { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.playground .editor .pane { padding: 1em 1.2em; }
.playground .editor .pane + .pane { border-left: 1px solid var(--border-soft); }
.playground .editor h5 { margin: 0 0 .5em 0; font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-mute); }
.playground .editor textarea { width: 100%; min-height: 220px; background: var(--code-bg); color: var(--text); border: 1px solid var(--border-soft); border-radius: 4px; padding: .8em; font-family: 'JetBrains Mono'; font-size: 12.5px; line-height: 1.55; resize: vertical; }
.playground .editor textarea:focus { outline: none; border-color: var(--accent); }
.playground .run-bar { padding: .6em 1.2em; background: var(--bg-elev-2); border-top: 1px solid var(--border-soft); display: flex; gap: .6em; align-items: center; }
.playground .run-bar button { background: var(--accent); color: white; border: none; padding: 6px 16px; border-radius: 4px; font-family: 'Inter'; font-size: 13px; cursor: pointer; font-weight: 500; }
.playground .run-bar button:hover { background: #5a9dd0; }
.playground .run-bar .status { color: var(--text-mute); font-size: 12px; font-family: 'JetBrains Mono'; }
@media (max-width: 720px) {
.playground .editor { grid-template-columns: 1fr; }
.playground .editor .pane + .pane { border-left: none; border-top: 1px solid var(--border-soft); }
}
/* ===== Hero ===== */
.hero { background: linear-gradient(135deg, #1d2229 0%, #232932 60%, #2a313b 100%); border-bottom: 1px solid var(--border-soft); padding: 3em 0 2.4em 0; margin-bottom: 1.4em; }
.hero .hero-inner { max-width: var(--max-content); padding: 0 3em; }
.hero h1 { font-size: 2.6rem; margin: 0 0 .25em 0; letter-spacing: -.01em; }
.hero h1 .accent { color: var(--accent); }
.hero .tagline { color: var(--text-dim); font-size: 1.05rem; margin-bottom: 1.6em; max-width: 60ch; }
.hero .stats { display: flex; gap: 2em; flex-wrap: wrap; }
.hero .stats .stat .n { font-size: 1.6rem; font-weight: 700; color: var(--accent); font-family: 'JetBrains Mono'; }
.hero .stats .stat .l { font-size: 12px; color: var(--text-mute); text-transform: uppercase; letter-spacing: .08em; }
/* ===== Diagram blocks ===== */
.diagram { background: var(--bg-elev-1); border: 1px solid var(--border-soft); border-radius: 8px; padding: 1.5em; margin: 1.5em 0; overflow-x: auto; }
.diagram svg { display: block; max-width: 100%; height: auto; }
.diagram .caption { font-size: 12.5px; color: var(--text-mute); margin-top: .8em; text-align: center; }
/* ===== Math ===== */
.math-block { background: var(--bg-elev-1); border: 1px solid var(--border-soft); border-radius: 6px; padding: 1em 1.4em; margin: 1em 0; overflow-x: auto; }
.math-block .katex { font-size: 1.05em; }
.math-inline .katex { font-size: 1em; }
/* ===== Quick nav (top of page) ===== */
.quick-nav { display: flex; gap: .4em; flex-wrap: wrap; margin: 1em 0 2em 0; padding: .8em 1em; background: var(--bg-elev-1); border-radius: 6px; border: 1px solid var(--border-soft); }
.quick-nav a { padding: 4px 10px; background: var(--bg-elev-2); border-radius: 4px; font-size: 12.5px; color: var(--text-dim); transition: all .12s; }
.quick-nav a:hover { background: var(--accent); color: white; text-decoration: none; }
.footer { text-align: center; color: var(--text-mute); font-size: 12.5px; padding: 2em 0 1em 0; border-top: 1px solid var(--border-soft); margin-top: 3em; }
</style>
</head>
<body>
<div class="app">
<!-- ============ SIDEBAR ============ -->
<aside class="sidebar">
<div class="brand">Raylib Engine</div>
<div class="brand-sub">Interactive Docs</div>
<nav>
<h5>Overview</h5>
<ul>
<li><a href="#hero" class="active">Architecture</a></li>
<li><a href="#core-concepts" class="sub">Core Concepts</a></li>
</ul>
<h5>Foundation</h5>
<ul>
<li><a href="#foundation" class="sub">Object</a></li>
<li><a href="#foundation" class="sub">Vector2i</a></li>
</ul>
<h5>Node Hierarchy</h5>
<ul>
<li><a href="#node-hierarchy" class="sub">Node</a></li>
<li><a href="#node-hierarchy" class="sub">CanvasItem</a></li>
<li><a href="#node-hierarchy" class="sub">Node2D</a></li>
</ul>
<h5>Visual & UI</h5>
<ul>
<li><a href="#visual-nodes" class="sub">Sprite2D</a></li>
<li><a href="#visual-nodes" class="sub">Control</a></li>
<li><a href="#visual-nodes" class="sub">Button</a></li>
</ul>
<h5>Collision</h5>
<ul>
<li><a href="#collision-system" class="sub">CollisionShape2D</a></li>
<li><a href="#collision-system" class="sub">CollisionObject2D</a></li>
<li><a href="#collision-system" class="sub">Area2D</a></li>
<li><a href="#collision-system" class="sub">PhysicsBody2D</a></li>
<li><a href="#collision-system" class="sub">Particle</a></li>
</ul>
<h5>Shapes</h5>
<ul>
<li><a href="#shapes" class="sub">Shape2D</a></li>
<li><a href="#shapes" class="sub">CircleShape2D</a></li>
<li><a href="#shapes" class="sub">RectangleShape2D</a></li>
</ul>
<h5>Servers</h5>
<ul>
<li><a href="#render-server" class="sub">RenderServer</a></li>
<li><a href="#physics-server" class="sub">PhysicsServer</a></li>
<li><a href="#gravity-server">GravityServer (FMM)</a></li>
</ul>
<h5>Interactive</h5>
<ul>
<li><a href="#demos" class="sub">All Demos</a></li>
<li><a href="#demo-fmm" class="sub">FMM Quadtree</a></li>
<li><a href="#demo-nbody" class="sub">N-Body Sim</a></li>
<li><a href="#demo-scene" class="sub">Scene Graph</a></li>
<li><a href="#demo-hash" class="sub">Spatial Hash</a></li>
<li><a href="#demo-collision" class="sub">Collision Solver</a></li>
<li><a href="#demo-playground" class="sub">API Playground</a></li>
</ul>
<h5>Application</h5>
<ul>
<li><a href="#main-walkthrough" class="sub">main.cpp</a></li>
<li><a href="#cookbook" class="sub">API Cookbook</a></li>
</ul>
</nav>
</aside>
<!-- ============ MAIN ============ -->
<main class="main">
<div class="content">
<!-- ===================== SECTION: HERO ===================== -->
<section id="hero">
<div class="hero">
<div class="hero-inner">
<h1>Raylib <span class="accent">Engine</span> — Interactive Docs</h1>
<div class="tagline">
A Godot-inspired 2D game engine built on top of Raylib 5.5/6.0.
Node-based scene graph, three singleton servers for rendering / physics / gravity,
and an O(n log n) Fast Multipole Method gravity solver for thousands of bodies in real time.
</div>
<div class="stats">
<div class="stat"><div class="n">17</div><div class="l">Classes</div></div>
<div class="stat"><div class="n">3</div><div class="l">Servers</div></div>
<div class="stat"><div class="n">O(n log n)</div><div class="l">Gravity</div></div>
<div class="stat"><div class="n">6</div><div class="l">Live Demos</div></div>
</div>
</div>
</div>
<div class="quick-nav">
<a href="#core-concepts">Core Concepts</a>
<a href="#foundation">Foundation</a>
<a href="#node-hierarchy">Node Hierarchy</a>
<a href="#visual-nodes">Visual & UI</a>
<a href="#collision-system">Collision</a>
<a href="#shapes">Shapes</a>
<a href="#gravity-server">GravityServer FMM</a>
<a href="#demos">Interactive Demos</a>
<a href="#main-walkthrough">main.cpp</a>
<a href="#cookbook">Cookbook</a>
</div>
<h2 id="architecture">Architecture at a Glance</h2>
<p>
The engine is organized around three orthogonal concerns: <strong>identity & ownership</strong> (the <code>Object</code> → <code>Node</code> tree),
<strong>spatial representation</strong> (the <code>Shape2D</code> hierarchy used for both drawing and collision), and
<strong>global services</strong> (three singletons: <code>RenderServer</code>, <code>PhysicsServer</code>, <code>GravityServer</code>).
Every node that should appear on screen inherits from <code>CanvasItem</code>, which auto-registers itself with the <code>RenderServer</code> on construction.
Every collision-capable node inherits from <code>CollisionObject2D</code>, which auto-registers with the <code>PhysicsServer</code>.
Every gravity-affected body inherits from <code>PhysicsBody2D</code>, which auto-registers with the <code>GravityServer</code>.
This means <strong>construction is registration</strong>: <code>new Particle(...)</code> is enough to make a body visible, collidable, and gravitationally active.
</p>
<div class="diagram">
<svg viewBox="0 0 900 560" xmlns="http://www.w3.org/2000/svg" font-family="Inter, sans-serif" font-size="12">
<!-- Title -->
<text x="450" y="22" text-anchor="middle" font-size="14" font-weight="600" fill="#f0f3f8">Class Hierarchy & Server Registrations</text>
<!-- Object root -->
<rect x="380" y="44" width="140" height="40" rx="6" fill="#2a313b" stroke="#478cbf" stroke-width="1.5"/>
<text x="450" y="68" text-anchor="middle" fill="#f0f3f8" font-weight="600">Object</text>
<!-- Three branches from Object -->
<line x1="450" y1="84" x2="450" y2="110" stroke="#4a5260" stroke-width="1.2"/>
<line x1="120" y1="110" x2="780" y2="110" stroke="#4a5260" stroke-width="1.2"/>
<line x1="120" y1="110" x2="120" y2="135" stroke="#4a5260" stroke-width="1.2"/>
<line x1="450" y1="110" x2="450" y2="135" stroke="#4a5260" stroke-width="1.2"/>
<line x1="780" y1="110" x2="780" y2="135" stroke="#4a5260" stroke-width="1.2"/>
<!-- Branch 1: Servers -->
<text x="120" y="128" text-anchor="middle" fill="#6e7684" font-size="10" font-weight="600">SERVERS (singletons)</text>
<rect x="40" y="135" width="160" height="36" rx="5" fill="#2a313b" stroke="#57b35a" stroke-width="1.2"/>
<text x="120" y="157" text-anchor="middle" fill="#e0e4eb">RenderServer</text>
<rect x="40" y="180" width="160" height="36" rx="5" fill="#2a313b" stroke="#57b35a" stroke-width="1.2"/>
<text x="120" y="202" text-anchor="middle" fill="#e0e4eb">PhysicsServer</text>
<rect x="40" y="225" width="160" height="36" rx="5" fill="#2a313b" stroke="#f0a020" stroke-width="1.5"/>
<text x="120" y="247" text-anchor="middle" fill="#f0a020" font-weight="600">GravityServer</text>
<text x="120" y="280" text-anchor="middle" fill="#6e7684" font-size="10">FMM · O(n log n)</text>
<!-- Branch 2: Node tree -->
<text x="450" y="128" text-anchor="middle" fill="#6e7684" font-size="10" font-weight="600">NODE TREE</text>
<rect x="380" y="135" width="140" height="36" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="450" y="157" text-anchor="middle" fill="#e0e4eb">Node</text>
<line x1="450" y1="171" x2="450" y2="190" stroke="#4a5260" stroke-width="1.2"/>
<rect x="380" y="190" width="140" height="36" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="450" y="212" text-anchor="middle" fill="#e0e4eb">CanvasItem</text>
<text x="450" y="240" text-anchor="middle" fill="#57b35a" font-size="10">→ regs w/ RenderServer</text>
<line x1="450" y1="226" x2="280" y2="260" stroke="#4a5260" stroke-width="1.2"/>
<line x1="450" y1="226" x2="620" y2="260" stroke="#4a5260" stroke-width="1.2"/>
<rect x="200" y="260" width="160" height="36" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="280" y="282" text-anchor="middle" fill="#e0e4eb">Node2D</text>
<rect x="540" y="260" width="160" height="36" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="620" y="282" text-anchor="middle" fill="#e0e4eb">Control</text>
<!-- Node2D children -->
<line x1="280" y1="296" x2="280" y2="320" stroke="#4a5260" stroke-width="1.2"/>
<line x1="160" y1="320" x2="400" y2="320" stroke="#4a5260" stroke-width="1.2"/>
<line x1="160" y1="320" x2="160" y2="340" stroke="#4a5260" stroke-width="1.2"/>
<line x1="280" y1="320" x2="280" y2="340" stroke="#4a5260" stroke-width="1.2"/>
<line x1="400" y1="320" x2="400" y2="340" stroke="#4a5260" stroke-width="1.2"/>
<rect x="110" y="340" width="100" height="32" rx="5" fill="#2a313b" stroke="#4a5260" stroke-width="1"/>
<text x="160" y="360" text-anchor="middle" fill="#a6adbb">Sprite2D</text>
<rect x="230" y="340" width="100" height="32" rx="5" fill="#2a313b" stroke="#4a5260" stroke-width="1"/>
<text x="280" y="360" text-anchor="middle" fill="#a6adbb">CollisionShape2D</text>
<rect x="350" y="340" width="100" height="32" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="400" y="360" text-anchor="middle" fill="#e0e4eb">CollisionObject2D</text>
<text x="400" y="388" text-anchor="middle" fill="#57b35a" font-size="10">→ regs w/ PhysicsServer</text>
<line x1="400" y1="372" x2="400" y2="402" stroke="#4a5260" stroke-width="1.2"/>
<line x1="320" y1="402" x2="480" y2="402" stroke="#4a5260" stroke-width="1.2"/>
<line x1="320" y1="402" x2="320" y2="420" stroke="#4a5260" stroke-width="1.2"/>
<line x1="480" y1="402" x2="480" y2="420" stroke="#4a5260" stroke-width="1.2"/>
<rect x="280" y="420" width="80" height="32" rx="5" fill="#2a313b" stroke="#4a5260" stroke-width="1"/>
<text x="320" y="440" text-anchor="middle" fill="#a6adbb">Area2D</text>
<rect x="420" y="420" width="120" height="32" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="480" y="440" text-anchor="middle" fill="#e0e4eb">PhysicsBody2D</text>
<text x="480" y="468" text-anchor="middle" fill="#f0a020" font-size="10">→ regs w/ GravityServer</text>
<line x1="480" y1="452" x2="480" y2="478" stroke="#4a5260" stroke-width="1.2"/>
<rect x="425" y="478" width="110" height="32" rx="5" fill="#2a313b" stroke="#f0a020" stroke-width="1.5"/>
<text x="480" y="498" text-anchor="middle" fill="#f0a020" font-weight="600">Particle</text>
<!-- Control children -->
<line x1="620" y1="296" x2="620" y2="340" stroke="#4a5260" stroke-width="1.2"/>
<rect x="570" y="340" width="100" height="32" rx="5" fill="#2a313b" stroke="#4a5260" stroke-width="1"/>
<text x="620" y="360" text-anchor="middle" fill="#a6adbb">Button</text>
<!-- Branch 3: Shapes -->
<text x="780" y="128" text-anchor="middle" fill="#6e7684" font-size="10" font-weight="600">SHAPES</text>
<rect x="710" y="135" width="140" height="36" rx="5" fill="#2a313b" stroke="#478cbf" stroke-width="1.2"/>
<text x="780" y="157" text-anchor="middle" fill="#e0e4eb">Shape2D</text>
<line x1="780" y1="171" x2="780" y2="195" stroke="#4a5260" stroke-width="1.2"/>
<line x1="720" y1="195" x2="840" y2="195" stroke="#4a5260" stroke-width="1.2"/>
<line x1="720" y1="195" x2="720" y2="215" stroke="#4a5260" stroke-width="1.2"/>
<line x1="840" y1="195" x2="840" y2="215" stroke="#4a5260" stroke-width="1.2"/>
<rect x="670" y="215" width="100" height="32" rx="5" fill="#2a313b" stroke="#4a5260" stroke-width="1"/>
<text x="720" y="235" text-anchor="middle" fill="#a6adbb">CircleShape2D</text>
<rect x="790" y="215" width="100" height="32" rx="5" fill="#2a313b" stroke="#4a5260" stroke-width="1"/>
<text x="840" y="235" text-anchor="middle" fill="#a6adbb">RectangleShape2D</text>
<!-- Legend -->
<g transform="translate(40, 510)">
<rect x="0" y="0" width="14" height="10" fill="#2a313b" stroke="#478cbf"/>
<text x="20" y="9" fill="#a6adbb" font-size="11">Node</text>
<rect x="80" y="0" width="14" height="10" fill="#2a313b" stroke="#57b35a"/>
<text x="100" y="9" fill="#a6adbb" font-size="11">Server (singleton)</text>
<rect x="230" y="0" width="14" height="10" fill="#2a313b" stroke="#f0a020"/>
<text x="250" y="9" fill="#a6adbb" font-size="11">FMM-critical</text>
</g>
</svg>
<div class="caption">Class hierarchy. Green outlines indicate singleton servers; the orange GravityServer uses the Fast Multipole Method. Dashed "→ regs w/" labels show the auto-registration side effects performed by each constructor.</div>
</div>
<h3 id="quick-start">Quick Start</h3>
<p>The smallest possible application boots the three servers, spawns one particle, and runs the main loop. Note how <code>new Particle(...)</code> alone is sufficient to make the body visible, collidable, and gravitationally active — all three servers learn about it automatically through constructor side-effects.</p>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-kw">#include</span> <span class="tok-str">"../classes/gravity_server/GravityServer.hpp"</span>
<span class="tok-kw">#include</span> <span class="tok-str">"../classes/node/canvas_item/node2d/collision_object2d/physics_body2d/particle/Particle.hpp"</span>
<span class="tok-kw">#include</span> <span class="tok-str">"../classes/physics_server/PhysicsServer.hpp"</span>
<span class="tok-kw">#include</span> <span class="tok-str">"../classes/render_server/RenderServer.hpp"</span>
<span class="tok-kw">#include</span> <span class="tok-str">"../include/raylib-cpp.hpp"</span>
<span class="tok-type">int</span> <span class="tok-fn">main</span>(<span class="tok-type">void</span>) {
<span class="tok-com">// 1. Boot all three singletons</span>
<span class="tok-type">GravityServer</span>::<span class="tok-fn">getInstance</span>();
<span class="tok-type">PhysicsServer</span>::<span class="tok-fn">getInstance</span>();
<span class="tok-type">RenderServer</span>::<span class="tok-fn">getInstance</span>();
<span class="tok-com">// 2. Spawn a particle — auto-regs with all 3 servers</span>
<span class="tok-type">Particle</span>* p = <span class="tok-kw">new</span> <span class="tok-type">Particle</span>(<span class="tok-str">"P0"</span>,
<span class="tok-type">raylib</span>::<span class="tok-type">Vector2</span>(<span class="tok-num">400</span>, <span class="tok-num">300</span>),
<span class="tok-type">raylib</span>::<span class="tok-type">Color</span>::<span class="tok-fn">White</span>());
p-><span class="tok-fn">setMass</span>(<span class="tok-num">2.0f</span>);
<span class="tok-com">// 3. Window + main loop</span>
<span class="tok-type">raylib</span>::<span class="tok-type">Window</span> window(<span class="tok-num">800</span>, <span class="tok-num">600</span>);
<span class="tok-kw">while</span> (!window.<span class="tok-fn">ShouldClose</span>()) {
window.<span class="tok-fn">BeginDrawing</span>();
window.<span class="tok-fn">ClearBackground</span>();
<span class="tok-type">PhysicsServer</span>::<span class="tok-fn">getInstance</span>().<span class="tok-fn">rebuild</span>();
<span class="tok-type">GravityServer</span>::<span class="tok-fn">getInstance</span>().<span class="tok-fn">rebuild</span>();
<span class="tok-type">GravityServer</span>::<span class="tok-fn">getInstance</span>().<span class="tok-fn">applyGravity</span>();
p-><span class="tok-fn">updatePhysics</span>();
<span class="tok-type">RenderServer</span>::<span class="tok-fn">getInstance</span>().<span class="tok-fn">render</span>(window);
window.<span class="tok-fn">EndDrawing</span>();
}
<span class="tok-kw">delete</span> p;
<span class="tok-kw">return</span> <span class="tok-num">0</span>;
}</code></pre>
</div>
</section>
<!-- ===================== SECTION: CORE CONCEPTS ===================== -->
<section id="core-concepts">
<h2>Core Concepts</h2>
<p>Before diving into individual classes, four foundational patterns are worth understanding because every other class in the engine is built on top of them. These patterns explain <em>why</em> the API looks the way it does, and once you internalize them, the rest of the engine reads almost mechanically.</p>
<h3 id="concept-identity">1. Object Identity</h3>
<p>Every entity in the engine — every node, every shape, every server — inherits from <code>Object</code>. The <code>Object</code> base class hands out a monotonically-increasing <code>unsigned long _instanceID</code> at construction time. This ID is the <strong>only</strong> stable handle you can hold across copies, moves, and pointer reassignments: the copy constructor assigns a fresh ID rather than duplicating the source's, so two objects that compare unequal via <code>operator==</code> are guaranteed to have different IDs even if every other field is identical.</p>
<p>This matters because the three singleton servers all key their internal maps on <code>_instanceID</code>. When you call <code>PhysicsServer::removeCollisionObject(this)</code> from the <code>CollisionObject2D</code> destructor, the lookup is an O(1) hash on the ID — no pointer comparisons, no name lookups, no ambiguity. The ID is also what makes the <code>GravityServer</code>'s acceleration map work: during <code>applyGravity()</code> the FMM accumulates per-body accelerations in an <code>unordered_map<unsigned long, Vector2></code>, then a second pass dispatches <code>applyForce()</code> back to each body by ID.</p>
<h3 id="concept-tree">2. Node Tree & Ownership</h3>
<p>The <code>Node</code> class extends <code>Object</code> with a parent pointer and a vector of child pointers. This is a classic Godot-style scene graph: a parent owns its children, the destructor recursively <code>delete</code>s every child, and child positions/rotations inherit (and propagate deltas to) their parent's transform. When you write <code>particle.pushBackChild(sprite)</code>, you are transferring ownership of <code>sprite</code>'s lifetime to the particle — destroying the particle will destroy the sprite automatically.</p>
<p>The tree is intentionally <strong>not</strong> reference-counted. This keeps the hot path (the per-frame update loop over 2000+ particles) free of atomic refcount operations, at the cost of requiring the user to pick exactly one owner for each node. The <code>setParent</code> method auto-removes the node from its previous parent, so reparenting is safe but the previous parent must still be alive.</p>
<h3 id="concept-auto-reg">3. Constructor Side-Effects (Auto-Registration)</h3>
<p>This is the single most important pattern in the engine. The constructor of <code>CanvasItem</code> calls <code>RenderServer::addCanvasItem(this)</code>. The constructor of <code>CollisionObject2D</code> calls <code>PhysicsServer::addCollisionObject(this)</code>. The constructor of <code>PhysicsBody2D</code> calls <code>GravityServer::addBody(this)</code>. The destructors all call the matching <code>remove</code>. The practical consequence is that <strong>creating a node is sufficient to make it part of the engine's update loops</strong> — there is no separate "register" or "spawn" call to forget.</p>
<div class="note tip"><span class="nb">Tip.</span> Because registration happens in the base-class constructor, you never need to call <code>addBody</code> / <code>addCollisionObject</code> / <code>addCanvasItem</code> yourself for any subclass. The only time you'd call them directly is if you were writing a brand-new base class outside the existing hierarchy.</div>
<h3 id="concept-singletons">4. Singleton Servers</h3>
<p><code>RenderServer</code>, <code>PhysicsServer</code>, and <code>GravityServer</code> all follow the same pattern: a private default constructor, a static <code>_instance</code> pointer, a public <code>getInstance()</code> that lazily allocates on first call, and a <code>deleteInstance()</code> for shutdown. The bodies/canvas-items/collision-objects they track are stored in <strong>static</strong> maps keyed by instance ID, which means the maps survive even if you momentarily delete the singleton instance — but since the maps are only ever read from inside <code>getInstance()</code>-returned references, this is mostly an implementation detail.</p>
<p>The singleton pattern is a deliberate tradeoff: it removes the need to thread a "world" or "scene" object through every function call, at the cost of making the engine fundamentally single-scene. For the particle-simulator use case this engine was built for, that tradeoff is a clear win.</p>
<h3 id="concept-fluent">5. Fluent Setter API</h3>
<p>Almost every setter in the engine returns <code>*this</code> by reference, so calls chain naturally:</p>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code>particle.<span class="tok-fn">setMass</span>(<span class="tok-num">2.0f</span>)
.<span class="tok-fn">setLockRotation</span>(<span class="tok-kw">true</span>)
.<span class="tok-fn">setLinearVel</span>(<span class="tok-type">raylib</span>::<span class="tok-type">Vector2</span>(<span class="tok-num">10</span>, <span class="tok-num">0</span>));</code></pre>
</div>
<p>This is borrowed from Godot's convention and works particularly well when configuring a node immediately after construction, as seen in <code>main.cpp</code>'s <code>spawnParticles</code> helper.</p>
</section>
<!-- ===================== SECTION: FOUNDATION ===================== -->
<section id="foundation">
<h2>Foundation: Object & Vector2i</h2>
<p>These two types are the bedrock of the engine. <code>Object</code> is the universal base class — everything inherits from it, directly or indirectly. <code>Vector2i</code> is a tiny integer vector used as a hash key by <code>PhysicsServer</code>'s spatial grid.</p>
<div class="class-card">
<div class="class-head">
<h3 id="cls-object">Object</h3>
<span class="inherits">: (root base class)</span>
<span class="file">classes/Object.hpp</span>
</div>
<div class="why">
<code>Object</code> exists to give every entity in the engine a stable, unique identity (the <code>_instanceID</code>) and a uniform introspection surface (<code>getClassName()</code>, <code>getInstanceName()</code>, <code>operator<<</code>). The ID is what the three singleton servers use as a hash key — without it, the engine would need pointer-keyed maps (which break when objects move in memory) or string-keyed maps (which are slow and ambiguous). Because the copy constructor allocates a new ID, two objects are never accidentally aliased.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">explicit</span> Object(const std::string &instanceName = "")</span>
<div class="desc">Default constructor. Assigns a fresh <code>_instanceID</code> by post-incrementing the static counter, stores the optional human-readable name.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">explicit</span> Object(const Object &other)</span>
<div class="desc">Copy constructor. Gets a <em>new</em> ID (does not copy <code>other._instanceID</code>) but copies the name. This guarantees two distinct objects always have distinct IDs.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">unsigned long</span> getInstanceID() <span class="ret">const noexcept</span></span>
<div class="desc">Returns the unique identifier. Stable for the lifetime of the object. Used as the hash key in all three server maps.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">const std::string &</span> getInstanceName() <span class="ret">const noexcept</span></span>
<div class="desc">Returns the human-readable name. Non-unique; multiple objects can share a name. Used by <code>Node::findChild(name)</code>.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Object &</span> setInstanceName(const std::string &instanceName = "") <span class="ret">noexcept</span></span>
<div class="desc">Mutator (fluent). Returns <code>*this</code> so it can chain with other setters.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">virtual const std::string &</span> getClassName() <span class="ret">const noexcept</span></span>
<div class="desc">Virtual — overridden by every subclass to return a static string like <code>"Particle"</code> or <code>"GravityServer"</code>. Used by <code>Node::findClass()</code> and by the collision dispatcher in <code>PhysicsBody2D::collideWith</code> to identify shape types via string comparison.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> operator==(const Object &other) <span class="ret">const noexcept</span></span>
<div class="desc">Identity comparison — true iff <code>_instanceID</code> matches. Two objects with the same name but different IDs compare unequal.</div>
</div>
<h4>Example</h4>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-type">Object</span> a(<span class="tok-str">"hero"</span>), b(<span class="tok-str">"hero"</span>);
<span class="tok-com">// a and b have the same name but different IDs</span>
<span class="tok-fn">assert</span>(a != b); <span class="tok-com">// true: IDs differ</span>
<span class="tok-fn">assert</span>(a.<span class="tok-fn">getInstanceName</span>() == b.<span class="tok-fn">getInstanceName</span>()); <span class="tok-com">// true: same name</span>
std::cout << a; <span class="tok-com">// prints "Object#0: \"hero\""</span></code></pre>
</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-vector2i">Vector2i</h3>
<span class="inherits">: struct (no base)</span>
<span class="file">classes/Vector2i.hpp</span>
</div>
<div class="why">
<code>Vector2i</code> is a 2D integer vector used as a hash key by <code>PhysicsServer</code>'s spatial grid. Raylib's <code>Vector2</code> is float-based and has no <code>std::hash</code> specialization, so it cannot be used as an <code>unordered_map</code> key directly. Rather than specializing <code>std::hash</code> for <code>raylib::Vector2</code> (which would be invasive), the engine introduces a tiny standalone integer vector with a companion <code>Vector2iHash</code> functor that uses the classic Boost-style <code>0x9e3779b9</code> mix.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">constexpr</span> Vector2i(int x = 0, int y = 0) <span class="ret">noexcept</span></span>
<div class="desc">Default/component constructor. <code>constexpr</code> so it can be used in compile-time contexts.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">constexpr</span> Vector2i(const raylib::Vector2 &v) <span class="ret">noexcept</span></span>
<div class="desc">Implicit conversion from <code>raylib::Vector2</code>. Truncates (does not round). Used by <code>PhysicsServer::hashFunction</code> which divides a float position by <code>CELL_SIZE</code> and relies on the implicit conversion.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">constexpr bool</span> operator==(const Vector2i &other) <span class="ret">const noexcept</span></span>
<div class="desc">Required for <code>unordered_map</code> key equality.</div>
</div>
<h4>Vector2iHash</h4>
<p>The companion hash functor. Implements the Boost hash-combine idiom: <code>h ^= v.y + 0x9e3779b9 + (h << 6) + (h >> 2)</code>. The constant <code>0x9e3779b9</code> is <code>2^32 / φ</code> (golden ratio), which distributes bits well. Used as the third template argument to <code>std::unordered_map<Vector2i, ..., Vector2iHash></code>.</p>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-com">// How PhysicsServer uses it:</span>
<span class="tok-type">std::unordered_map</span><<span class="tok-type">Vector2i</span>, <span class="tok-type">std::vector</span><<span class="tok-type">CollisionObject2D</span>*>, <span class="tok-type">Vector2iHash</span>> _hashMap;
<span class="tok-type">Vector2i</span> <span class="tok-fn">hashFunction</span>(<span class="tok-kw">const</span> <span class="tok-type">raylib</span>::<span class="tok-type">Vector2</span>& p) <span class="tok-kw">const</span> <span class="tok-kw">noexcept</span> {
<span class="tok-kw">return</span> p / <span class="tok-num">10.0f</span>; <span class="tok-com">// implicit Vector2 -> Vector2i via constructor</span>
}</code></pre>
</div>
</div>
</section>
<!-- ===================== SECTION: NODE HIERARCHY ===================== -->
<section id="node-hierarchy">
<h2>Node Hierarchy: Node → CanvasItem → Node2D</h2>
<p>These three classes form the spine of the scene graph. <code>Node</code> adds the parent/child tree, <code>CanvasItem</code> adds visibility and auto-registration with <code>RenderServer</code>, and <code>Node2D</code> adds a 2D transform (position + rotation) that propagates deltas to its children.</p>
<div class="class-card">
<div class="class-head">
<h3 id="cls-node">Node</h3>
<span class="inherits">: public Object</span>
<span class="file">classes/node/Node.hpp</span>
</div>
<div class="why">
<code>Node</code> is the engine's scene-graph primitive. It owns its children via raw pointers and <code>delete</code>s them in its destructor — this avoids refcounting overhead in the hot loop and matches Godot's ownership model. The class also provides virtual <code>ready()</code>, <code>update()</code>, <code>updatePhysics()</code> hooks that subclasses override to plug into the frame loop.
</div>
<h4>Tree operations</h4>
<table>
<thead><tr><th>Method</th><th>Purpose</th><th>Notes</th></tr></thead>
<tbody>
<tr><td><code>setParent(Node*)</code></td><td>Reparent this node. Auto-removes from old parent.</td><td>Returns <code>*this</code> (fluent).</td></tr>
<tr><td><code>getParent()</code></td><td>Read-only parent pointer.</td><td>Returns <code>nullptr</code> for root.</td></tr>
<tr><td><code>getChildren()</code></td><td>Copy of the children vector.</td><td>By value — safe to iterate even if tree mutates.</td></tr>
<tr><td><code>pushBackChild(Node&)</code></td><td>Append child. Sets child's parent to this.</td><td>No-op if child is already a child of this.</td></tr>
<tr><td><code>pushFrontChild(Node&)</code></td><td>Prepend child.</td><td>Useful for draw-order overrides.</td></tr>
<tr><td><code>popFrontChild() / popBackChild()</code></td><td>Detach (not delete) the front/back child.</td><td>Sets child's <code>_parent = nullptr</code>.</td></tr>
<tr><td><code>removeChild(iterator|name|id)</code></td><td>Detach matching child/children.</td><td>Does not free memory.</td></tr>
<tr><td><code>deleteChild(iterator|name|id)</code></td><td>Detach AND <code>delete</code> matching child/children.</td><td>Frees memory.</td></tr>
<tr><td><code>findChild(name|id)</code></td><td>Linear scan returning all matches.</td><td>Returns <code>vector<Node*></code> — non-unique names can match many.</td></tr>
<tr><td><code>findClass(className)</code></td><td>Find children whose <code>getClassName()</code> matches.</td><td>Used by <code>PhysicsBody2D::collideWith</code> to find <code>CollisionShape2D</code> children.</td></tr>
</tbody>
</table>
<h4>Virtual frame hooks</h4>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> ready() <span class="ret">noexcept</span></span>
<div class="desc">Called once when the node enters the tree. Default impl is a no-op. (Currently not auto-invoked by the engine — reserved for future use.)</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> update() <span class="ret">noexcept</span></span>
<div class="desc">Per-frame logic hook. <code>Button</code> overrides this to poll mouse state and fire <code>onClick</code>.</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> updatePhysics() <span class="ret">noexcept</span></span>
<div class="desc">Per-frame physics integration hook. <code>PhysicsBody2D</code> overrides this to integrate velocity & acceleration.</div>
</div>
<h4>Example: build a small subtree</h4>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-type">Node</span> root(<span class="tok-str">"root"</span>);
<span class="tok-type">Node</span>* child1 = <span class="tok-kw">new</span> <span class="tok-type">Node</span>(<span class="tok-str">"c1"</span>);
<span class="tok-type">Node</span>* child2 = <span class="tok-kw">new</span> <span class="tok-type">Node</span>(<span class="tok-str">"c2"</span>);
root.<span class="tok-fn">pushBackChild</span>(*child1);
root.<span class="tok-fn">pushBackChild</span>(*child2);
<span class="tok-com">// Find by name</span>
<span class="tok-kw">auto</span> matches = root.<span class="tok-fn">findChild</span>(<span class="tok-str">"c1"</span>); <span class="tok-com">// [child1]</span>
<span class="tok-com">// Deleting a child frees memory and removes from parent</span>
root.<span class="tok-fn">deleteChild</span>(<span class="tok-str">"c1"</span>); <span class="tok-com">// child1 is now deleted</span>
<span class="tok-com">// root's destructor will delete child2 automatically</span></code></pre>
</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-canvasitem">CanvasItem</h3>
<span class="inherits">: public Node</span>
<span class="file">classes/node/canvas_item/CanvasItem.hpp</span>
</div>
<div class="why">
<code>CanvasItem</code> is the bridge between the scene graph and the render pipeline. Its constructor calls <code>RenderServer::addCanvasItem(this)</code> and its destructor calls <code>removeCanvasItem</code>, so any <code>CanvasItem</code> subclass is automatically drawn every frame by the <code>RenderServer</code>. It also owns the visibility flags (<code>_visible</code>, <code>_visibleDebug</code>) and the render layer, plus the <code>isInView()</code> culling tests that <code>RenderServer</code> uses to skip off-screen items.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> isVisible() <span class="ret">const noexcept</span></span>
<div class="desc">Whether <code>draw()</code> will be called by <code>RenderServer</code>. Default <code>true</code>.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">CanvasItem &</span> setVisible(bool) <span class="ret">noexcept</span></span>
<div class="desc">Toggle visibility. Items with <code>_visible=false</code> are skipped entirely by the render loop.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> isVisibleDebug() <span class="ret">const noexcept</span></span>
<div class="desc">Whether <code>drawDebug()</code> will also be called. Default <code>false</code>. Useful for visualizing collision shapes, velocities, etc.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">CanvasItem &</span> setVisibleDebug(bool) <span class="ret">noexcept</span></span>
<div class="desc">Enable debug overlay drawing. <code>Node2D::drawDebug</code> draws a small red+green axis gizmo and a blue origin dot.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">int</span> getLayer() / setLayer(int) <span class="ret">noexcept</span></span>
<div class="desc">Render layer (reserved for future z-ordering; currently not used by <code>RenderServer</code>).</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> isInView(const Window&, const Vector2& pos) <span class="ret">const noexcept</span></span>
<div class="desc">Returns true if <code>pos</code> is inside the window's screen-space rectangle. Used by <code>RenderServer</code> for culling.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> isInView(const Window&, const Camera2D&, const Vector2& pos) <span class="ret">const noexcept</span></span>
<div class="desc">Camera-aware variant. Computes the visible world rectangle from the camera's target & zoom, then tests <code>pos</code> against it.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> isInView(const Window&, const Shape2D&, const Vector2& shapePos) <span class="ret">const noexcept</span></span>
<div class="desc">Shape-aware variant. Builds a screen-sized <code>RectangleShape2D</code> and tests collision with the given shape — useful for large sprites that may straddle the screen edge.</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> draw() <span class="ret">const noexcept = 0</span></span>
<div class="desc">Pure virtual — every subclass implements its own rendering. <code>Node2D</code>'s impl is empty (invisible); <code>Sprite2D</code> draws texture or shape; <code>Button</code> draws a rounded rectangle with text.</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> drawDebug() <span class="ret">const noexcept = 0</span></span>
<div class="desc">Pure virtual debug overlay. Called only if <code>_visibleDebug</code> is true.</div>
</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-node2d">Node2D</h3>
<span class="inherits">: public CanvasItem</span>
<span class="file">classes/node/canvas_item/node2d/Node2D.hpp</span>
</div>
<div class="why">
<code>Node2D</code> adds a 2D transform (<code>_pos</code>, <code>_rot</code>) and the <strong>delta-propagation rule</strong>: when you call <code>setPos(newPos)</code>, the node computes <code>deltaPos = newPos - _pos</code> and then recursively calls <code>setPos(child.getPos() + deltaPos)</code> on every <code>Node2D</code> child. The same is true for <code>setRotation</code>. This means moving a parent moves the entire subtree rigidly, which is exactly the behavior you want for a <code>Particle</code> that owns its <code>Sprite2D</code> and <code>CollisionShape2D</code> children.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">const Vector2 &</span> getPos() <span class="ret">const noexcept</span></span>
<div class="desc">Const accessor for <code>_pos</code>. Returns by reference — no copy.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Vector2 &</span> getPos() <span class="ret">noexcept</span></span>
<div class="desc">Non-const accessor. <strong>Warning</strong>: bypasses delta-propagation. Use <code>setPos()</code> if you have children that should follow.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Node2D &</span> setPos(const Vector2 &newPos) <span class="ret">noexcept</span></span>
<div class="desc">Set position and propagate the delta to all <code>Node2D</code> children. <code>dynamic_cast</code>s each child — non-<code>Node2D</code> children are skipped.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">float</span> getRotation() <span class="ret">const noexcept</span></span>
<div class="desc">Rotation in radians (despite the field name, no implicit unit conversion is done — callers must convert from degrees if needed).</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Node2D &</span> setRotation(float newRot) <span class="ret">noexcept</span></span>
<div class="desc">Set rotation (radians) and propagate delta to <code>Node2D</code> children.</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> drawDebug() <span class="ret">const noexcept override</span></span>
<div class="desc">Draws a tiny axis gizmo: 10-pixel red line for +X, 10-pixel green line for +Y, and a small blue circle at the origin. The lines are rotated by <code>_rot</code> so you can visually confirm the orientation.</div>
</div>
<h4>Delta-propagation example</h4>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-type">Node2D</span> parent(<span class="tok-str">"parent"</span>);
<span class="tok-type">Node2D</span>* child = <span class="tok-kw">new</span> <span class="tok-type">Node2D</span>(<span class="tok-str">"child"</span>);
child-><span class="tok-fn">setPos</span>(<span class="tok-type">Vector2</span>(<span class="tok-num">5</span>, <span class="tok-num">0</span>)); <span class="tok-com">// child at (5,0), parent at (0,0)</span>
parent.<span class="tok-fn">pushBackChild</span>(*child);
parent.<span class="tok-fn">setPos</span>(<span class="tok-type">Vector2</span>(<span class="tok-num">100</span>, <span class="tok-num">100</span>));
<span class="tok-com">// parent is now at (100,100), child is at (105,100) — moved by delta</span></code></pre>
</div>
</div>
</section>
<!-- ===================== SECTION: VISUAL & UI NODES ===================== -->
<section id="visual-nodes">
<h2>Visual & UI Nodes: Sprite2D, Control, Button</h2>
<p>This group covers everything you actually <em>see</em> on screen. <code>Sprite2D</code> renders a texture or procedural shape; <code>Control</code> is the base class for UI widgets (currently a thin marker class awaiting future UI primitives); <code>Button</code> is a fully-functional clickable button with hover/press state, rounded borders, and an <code>onClick</code> callback.</p>
<div class="class-card">
<div class="class-head">
<h3 id="cls-sprite2d">Sprite2D</h3>
<span class="inherits">: public Node2D</span>
<span class="file">classes/node/canvas_item/node2d/sprite2d/Sprite2D.hpp</span>
</div>
<div class="why">
<code>Sprite2D</code> is the universal visual primitive. It can render either a GPU texture (<code>_texture</code>) or a procedural <code>Shape2D</code> (circle/rectangle) — if the texture's <code>id != 0</code>, the texture wins; otherwise the shape is drawn. This dual-mode design lets you use the same class for both image-based sprites and procedural particles, which is why <code>Particle</code> uses a <code>Sprite2D</code> child with a <code>CircleShape2D</code> rather than needing its own draw code.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Sprite2D &</span> setShape(float width, float height) <span class="ret">noexcept</span></span>
<div class="desc">Set a rectangle shape. If a <code>RectangleShape2D</code> already exists, mutates its size; otherwise allocates a new one. If a circle is currently set, replaces it.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Sprite2D &</span> setShape(float radius) <span class="ret">noexcept</span></span>
<div class="desc">Set a circle shape. Same replace-if-different-type logic as the rectangle variant.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Sprite2D &</span> setColor(const Color &) <span class="ret">noexcept</span></span>
<div class="desc">Tint color for both texture and shape rendering.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Sprite2D &</span> setTexture(Texture2D &&) <span class="ret">noexcept</span></span>
<div class="desc">Move-assign a texture. The texture's <code>id != 0</code> check in <code>draw()</code> is what flips rendering into texture mode.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Sprite2D &</span> setOffset(const Vector2 &) / setScale(float) / setRotation(float) <span class="ret">noexcept</span></span>
<div class="desc">Local transform relative to the parent <code>Node2D</code>. The sprite's effective rotation is <code>_rot + _rotation</code> (parent + local).</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> draw() <span class="ret">const noexcept override</span></span>
<div class="desc">Renders either texture (via <code>Texture2D::Draw</code>) or shape (circle via <code>DrawCircle</code>, rectangle via <code>Rectangle::Draw</code> with a center offset for proper rotation pivot). Combines parent rotation + local rotation, applies scale, applies tint color.</div>
</div>
<h4>Texture vs Shape dispatch</h4>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-kw">void</span> <span class="tok-type">Sprite2D</span>::<span class="tok-fn">draw</span>() <span class="tok-kw">const</span> <span class="tok-kw">noexcept</span> {
<span class="tok-kw">if</span> (_texture.id != <span class="tok-num">0</span>) {
_texture.<span class="tok-fn">Draw</span>(_pos + _offset, (_rot + _rotation) * RAD2DEG, _scale, _color);
} <span class="tok-kw">else if</span> (_shape != <span class="tok-kw">nullptr</span>) {
<span class="tok-com">// ...circle or rectangle branch on _shape->getClassName()</span>
}
}</code></pre>
</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-control">Control</h3>
<span class="inherits">: public CanvasItem</span>
<span class="file">classes/node/canvas_item/control/Control.hpp</span>
</div>
<div class="why">
<code>Control</code> is the UI counterpart to <code>Node2D</code>: just as <code>Node2D</code> adds a 2D transform to <code>CanvasItem</code> for world-space entities, <code>Control</code> is intended as the base class for screen-space UI widgets. In the current codebase it's a thin marker class — the hierarchy slot is reserved so that future UI primitives (Label, LineEdit, Slider, etc.) have a natural place to live and can share anchor/margin logic. <code>Button</code> already inherits from it.
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> Control(const std::string &instanceName = "")</span>
<div class="desc">Constructor — just forwards to <code>CanvasItem</code>. No additional state yet.</div>
</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-button">Button</h3>
<span class="inherits">: public Control</span>
<span class="file">classes/node/canvas_item/control/button/Button.hpp</span>
</div>
<div class="why">
<code>Button</code> is a self-contained, immediate-mode-style clickable widget. Unlike the rest of the engine (which is retained-mode via the scene graph), the button polls raylib's mouse state inside <code>update()</code> and fires its <code>onClick</code> callback the moment a click is detected. This makes it trivial to use — no event bus, no signal system, just <code>setOnClick([]{...})</code>.
</div>
<h4>Constructors (5 overloads for convenience)</h4>
<table>
<thead><tr><th>Constructor</th><th>Use case</th></tr></thead>
<tbody>
<tr><td><code>Button(x, y, w, h, text)</code></td><td>Scalar components — most common.</td></tr>
<tr><td><code>Button(x, y, Vector2 size, text)</code></td><td>Size as a vector.</td></tr>
<tr><td><code>Button(Vector2 pos, w, h, text)</code></td><td>Position as a vector.</td></tr>
<tr><td><code>Button(Vector2 pos, Vector2 size, text)</code></td><td>Both as vectors.</td></tr>
<tr><td><code>Button(Rectangle bounds, text)</code></td><td>From an existing rectangle.</td></tr>
</tbody>
</table>
<h4>State machine</h4>
<p>The button tracks three boolean flags: <code>isHovered</code> (mouse inside <code>bounds</code>), <code>isPressed</code> (mouse inside bounds AND left button down), and <code>wasPressed</code> (previous-frame <code>isPressed</code>). A click is detected as the rising edge: <code>wasPressed && !isPressed && isHovered</code>. The color picker selects among <code>normalColor</code>, <code>hoverColor</code>, <code>pressedColor</code> based on the current state.</p>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> update() <span class="ret">noexcept override</span></span>
<div class="desc">Polls mouse, updates <code>isHovered</code> / <code>isPressed</code>, fires <code>onClick</code> on rising-edge release. Must be called every frame for the button to work.</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> draw() <span class="ret">const noexcept override</span></span>
<div class="desc">Renders background (rounded or rectangular), optional border, centered text label, and a 40%-alpha black overlay if disabled.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Button &</span> setOnClick(std::function<void()>) </span>
<div class="desc">Register the click callback. Uses <code>std::function</code> so lambdas with captures work.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Button &</span> setColors(normal, hover, pressed) </span>
<div class="desc">Triple-color setter for the three states. Defaults are <code>(100,100,100)</code>, <code>(130,130,130)</code>, <code>(70,70,70)</code>.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Button &</span> setRoundness(float r) / setBorderWidth(float) / setBorderColor(Color) </span>
<div class="desc">Visual customization. <code>roundness</code> is clamped to <code>[0,1]</code>. Border is only drawn if <code>borderWidth > 0</code>.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">Button &</span> setEnabled(bool) / bool isEnabled() </span>
<div class="desc">Disabled buttons don't poll mouse, render with a dark overlay, and never fire <code>onClick</code>.</div>
</div>
<h4>Full fluent-API example</h4>
<div class="code-block">
<span class="lang-tag">C++</span>
<button class="copy-btn">Copy</button>
<pre><code><span class="tok-type">Button</span> btn(<span class="tok-num">50</span>, <span class="tok-num">50</span>, <span class="tok-num">200</span>, <span class="tok-num">50</span>, <span class="tok-str">"Spawn Particle"</span>);
btn.<span class="tok-fn">setColors</span>(<span class="tok-type">Color</span>(<span class="tok-num">60</span>,<span class="tok-num">120</span>,<span class="tok-num">200</span>),
<span class="tok-type">Color</span>(<span class="tok-num">80</span>,<span class="tok-num">150</span>,<span class="tok-num">230</span>),
<span class="tok-type">Color</span>(<span class="tok-num">40</span>,<span class="tok-num">90</span>,<span class="tok-num">160</span>))
.<span class="tok-fn">setRoundness</span>(<span class="tok-num">0.3f</span>)
.<span class="tok-fn">setBorderWidth</span>(<span class="tok-num">2.0f</span>)
.<span class="tok-fn">setBorderColor</span>(<span class="tok-type">Color</span>::<span class="tok-fn">White</span>())
.<span class="tok-fn">setOnClick</span>([&](){
particles.<span class="tok-fn">push_back</span>(<span class="tok-kw">new</span> <span class="tok-type">Particle</span>(<span class="tok-str">"P"</span>, <span class="tok-fn">randomPos</span>(), <span class="tok-type">Color</span>::<span class="tok-fn">Red</span>()));
});
<span class="tok-com">// In main loop:</span>
btn.<span class="tok-fn">update</span>(); <span class="tok-com">// polls mouse + may fire onClick</span>
btn.<span class="tok-fn">draw</span>(); <span class="tok-com">// renders the button</span></code></pre>
</div>
</div>
</section>
<!-- ===================== SECTION: COLLISION SYSTEM ===================== -->
<section id="collision-system">
<h2>Collision System: CollisionShape2D → CollisionObject2D → PhysicsBody2D → Particle</h2>
<p>This is the deepest branch of the node tree, and the most algorithmically interesting. <code>CollisionShape2D</code> wraps a <code>Shape2D</code> (circle or rectangle). <code>CollisionObject2D</code> adds the collision-layer/mask machinery and auto-registration with the <code>PhysicsServer</code>. <code>PhysicsBody2D</code> adds mass, velocity, acceleration, and the impulse-based collision solver. <code>Particle</code> is a concrete circle-body that pre-assembles a <code>Sprite2D</code> + <code>CollisionShape2D</code> child pair so you can spawn one with a single constructor call.</p>
<div class="class-card">
<div class="class-head">
<h3 id="cls-collisionshape2d">CollisionShape2D</h3>
<span class="inherits">: public Node2D</span>
<span class="file">classes/node/canvas_item/node2d/collision_shape2d/CollisionShape2D.hpp</span>
</div>
<div class="why">
<code>CollisionShape2D</code> is a <code>Node2D</code> wrapper around a heap-allocated <code>Shape2D</code>. It is intended to be added as a child of a <code>CollisionObject2D</code> (or subclass). The <code>PhysicsBody2D::collideWith</code> dispatcher uses <code>findClass("CollisionShape2D")</code> on its children to locate the shape — this indirection lets a single body carry multiple shapes (although only the first is currently used) and lets shapes be swapped at runtime without touching the body.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">CollisionShape2D &</span> setShape(float width, float height) <span class="ret">noexcept</span></span>
<div class="desc">Allocate or mutate a <code>RectangleShape2D</code>. If the current shape is a circle, replaces it (frees old shape, allocates new).</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">CollisionShape2D &</span> setShape(float radius) <span class="ret">noexcept</span></span>
<div class="desc">Allocate or mutate a <code>CircleShape2D</code>. Same replace-if-different-type logic.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">const Shape2D *</span> getShape() <span class="ret">const noexcept</span></span>
<div class="desc">Const accessor. Returns <code>nullptr</code> if no shape has been set yet.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">CollisionShape2D &</span> setParentPos(const Vector2 &) / setParentRotation(float) <span class="ret">noexcept</span></span>
<div class="desc">Convenience: forward a position/rotation change to the parent <code>Node2D</code>. Useful when you've grabbed a <code>CollisionShape2D*</code> handle and want to move the owning body.</div>
</div>
<div class="method">
<span class="sig"><span class="pill virtual">virtual</span> <span class="ret">void</span> drawDebug() <span class="ret">const noexcept override</span></span>
<div class="desc">Renders the underlying shape with a semi-transparent dark-blue fill and a solid outline. The rectangle variant uses <code>Rectangle::Draw</code> with a center pivot so it rotates correctly with <code>_rot</code>.</div>
</div>
<div class="note"><span class="nb">Design note.</span> <code>CollisionShape2D</code> deliberately owns its <code>Shape2D</code> via a raw pointer with manual <code>delete</code> in the destructor. <code>Shape2D</code> is a small polymorphic type and using <code>unique_ptr</code> here would add an extra indirection on every collision query, which the engine tries to avoid.</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-collisionobject2d">CollisionObject2D</h3>
<span class="inherits">: public Node2D</span>
<span class="file">classes/node/canvas_item/node2d/collision_object2d/CollisionObject2D.hpp</span>
</div>
<div class="why">
<code>CollisionObject2D</code> is the registration boundary for the collision system: its constructor calls <code>PhysicsServer::addCollisionObject(this)</code>, its destructor calls <code>remove</code>. It also stores the layer/mask pair (currently informational — the engine does not yet filter collisions by layer/mask in <code>getCollisions</code>, but the fields exist for future use). <code>Area2D</code> (sensor) and <code>PhysicsBody2D</code> (rigid body) both inherit from here.
</div>
<h4>Public API</h4>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">int</span> getCollisionLayer() / setCollisionLayer(int) <span class="ret">noexcept</span></span>
<div class="desc">Bitmask describing which layers this object lives on. Default <code>1</code> (layer 0).</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">int</span> getCollisionMask() / setCollisionMask(int) <span class="ret">noexcept</span></span>
<div class="desc">Bitmask describing which layers this object scans against. Default <code>1</code>.</div>
</div>
<div class="method">
<span class="sig"><span class="pill public">public</span> <span class="ret">bool</span> isEnabled() / setEnabled(bool) <span class="ret">noexcept</span></span>
<div class="desc">Master toggle. Disabled objects still exist in the server's map but are excluded from collision queries (reserved for future use; <code>getCollisions</code> currently does not check this flag).</div>
</div>
</div>
<div class="class-card">
<div class="class-head">
<h3 id="cls-area2d">Area2D</h3>