-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathClrHost.cpp
More file actions
2874 lines (2347 loc) · 110 KB
/
Copy pathClrHost.cpp
File metadata and controls
2874 lines (2347 loc) · 110 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
#include "ClrHost.h"
#include "PathFinder.h"
#include "Entity.h"
#include "Transform.h"
#include "SceneManager.h"
#include "Scene.h"
#include "PrefabUtility.h"
#include "SoundComponent.h"
#include "Animator.h"
#include "ConditionParameter.h"
#include "CharacterControllerComponent.h"
#include "RectTransformComponent.h"
#include "ImageComponent.h"
#include "TextComponent.h"
#include "Canvas.h"
#include "UIButton.h"
#include "CameraComponent.h"
#include "CameraSystem.h"
#include "MeshRenderer.h"
#include "../RenderEngine/Material.h"
#include "InputManager.h"
#include "PhysicsManager.h"
#include "RigidBodyComponent.h"
#include "SphereColliderComponent.h"
#include "BoxColliderComponent.h"
#include "CapsuleColliderComponent.h"
#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>
#include <windows.h>
#include <cstring>
// nethost.lib는 get_hostfxr_path 하나만 제공하는 얇은 import 라이브러리다.
// 실제 팩 경로는 EngineOutput.props의 DotNetHostPackDir 한 곳에서 관리한다.
#pragma comment(lib, "nethost.lib")
namespace
{
// ── 관리 코드에 넘기는 엔진 API 표 ──
//
// C# ScriptApiTable과 필드 순서·타입이 정확히 같아야 한다.
// 어긋나면 엉뚱한 함수를 호출하게 되므로 버전과 크기를 함께 넘겨 초기화 때 검사한다.
// 필드를 추가하면 kApiVersion을 반드시 올린다.
constexpr int kApiVersion = 19;
struct Float3 { float x, y, z; };
// 물리 질의 결과 하나. 관리 측 RaycastHit과 배치가 같아야 한다.
//
// 개수가 정해지지 않은 결과는 호출자가 준 버퍼에 채운다 — 경계 너머로 컨테이너를
// 넘기지 않고, 관리 측이 스택 버퍼를 쓰면 할당도 없다.
struct ScriptHitResult
{
ScriptObjectHandle object;
unsigned int layer;
Float3 point;
Float3 normal;
float distance;
};
// 쿼터니언 전용. math::vector4와 배치가 같아 그대로 오간다.
struct Float4 { float x, y, z, w; };
// UI 좌표·화면 크기용. math::vector2와 배치가 같다.
struct Float2 { float x, y; };
struct ScriptApiTable
{
int version;
int structSize;
void (__stdcall* Log)(int level, const char* message);
ScriptObjectHandle (__stdcall* Entity_FindByName)(const char* name);
int (__stdcall* Entity_IsAlive)(ScriptObjectHandle handle);
int (__stdcall* Entity_GetName)(ScriptObjectHandle handle, char* buffer, int capacity);
void (__stdcall* Entity_SetEnabled)(ScriptObjectHandle handle, int enabled);
// 계층 접근. 실측에서 m_childrenIndices 76 · Entity::FindIndex 100 ·
// m_parentIndex 32로, 남은 어떤 컴포넌트 래퍼보다 큰 표면이다.
int (__stdcall* Entity_GetChildCount)(ScriptObjectHandle handle);
ScriptObjectHandle (__stdcall* Entity_GetChild)(ScriptObjectHandle handle, int index);
ScriptObjectHandle (__stdcall* Entity_GetParent)(ScriptObjectHandle handle);
ScriptObjectHandle (__stdcall* Entity_FindByIndex)(int index);
int (__stdcall* Entity_GetIndex)(ScriptObjectHandle handle);
Float3 (__stdcall* Transform_GetLocalPosition)(ScriptObjectHandle handle);
void (__stdcall* Transform_SetLocalPosition)(ScriptObjectHandle handle, Float3 position);
Float3 (__stdcall* Transform_GetWorldPosition)(ScriptObjectHandle handle);
// 회전·스케일·방향축. 실측에서 Transform 획득이 154회로 전체 1위인데
// 위치만 열려 있어 이동·조준 코드가 통째로 막혀 있었다.
Float4 (__stdcall* Transform_GetLocalRotation)(ScriptObjectHandle handle);
void (__stdcall* Transform_SetLocalRotation)(ScriptObjectHandle handle, Float4 rotation);
Float3 (__stdcall* Transform_GetLocalScale)(ScriptObjectHandle handle);
void (__stdcall* Transform_SetLocalScale)(ScriptObjectHandle handle, Float3 scale);
void (__stdcall* Transform_AddLocalPosition)(ScriptObjectHandle handle, Float3 delta);
void (__stdcall* Transform_AddLocalRotation)(ScriptObjectHandle handle, Float4 delta);
void (__stdcall* Transform_SetWorldPosition)(ScriptObjectHandle handle, Float3 position);
Float4 (__stdcall* Transform_GetWorldRotation)(ScriptObjectHandle handle);
void (__stdcall* Transform_SetWorldRotation)(ScriptObjectHandle handle, Float4 rotation);
Float3 (__stdcall* Transform_GetWorldScale)(ScriptObjectHandle handle);
void (__stdcall* Transform_SetWorldScale)(ScriptObjectHandle handle, Float3 scale);
Float3 (__stdcall* Transform_GetForward)(ScriptObjectHandle handle);
Float3 (__stdcall* Transform_GetRight)(ScriptObjectHandle handle);
Float3 (__stdcall* Transform_GetUp)(ScriptObjectHandle handle);
int (__stdcall* Prefab_Exists)(const char* name);
ScriptObjectHandle (__stdcall* Prefab_Instantiate)(const char* prefabName, const char* instanceName);
void (__stdcall* Entity_Destroy)(ScriptObjectHandle handle);
unsigned long long (__stdcall* Engine_GetFrameCount)();
int (__stdcall* Sound_Exists)(ScriptObjectHandle handle);
void (__stdcall* Sound_Play)(ScriptObjectHandle handle);
void (__stdcall* Sound_Stop)(ScriptObjectHandle handle);
void (__stdcall* Sound_Pause)(ScriptObjectHandle handle, int pause);
int (__stdcall* Sound_IsPlaying)(ScriptObjectHandle handle);
void (__stdcall* Sound_PlayOneShot)(ScriptObjectHandle handle);
int (__stdcall* Sound_GetClipKey)(ScriptObjectHandle handle, char* buffer, int capacity);
void (__stdcall* Sound_SetClipKey)(ScriptObjectHandle handle, const char* value);
float (__stdcall* Sound_GetVolume)(ScriptObjectHandle handle);
void (__stdcall* Sound_SetVolume)(ScriptObjectHandle handle, float value);
float (__stdcall* Sound_GetPitch)(ScriptObjectHandle handle);
void (__stdcall* Sound_SetPitch)(ScriptObjectHandle handle, float value);
// Animator (실측 122회 — 그 중 SetParameter만 94회다)
int (__stdcall* Animator_Exists)(ScriptObjectHandle handle);
int (__stdcall* Animator_HasParameter)(ScriptObjectHandle handle, const char* name);
void (__stdcall* Animator_SetBool)(ScriptObjectHandle handle, const char* name, int value);
void (__stdcall* Animator_SetFloat)(ScriptObjectHandle handle, const char* name, float value);
void (__stdcall* Animator_SetInt)(ScriptObjectHandle handle, const char* name, int value);
void (__stdcall* Animator_SetTrigger)(ScriptObjectHandle handle, const char* name);
void (__stdcall* Animator_ResetTrigger)(ScriptObjectHandle handle, const char* name);
int (__stdcall* Animator_GetBool)(ScriptObjectHandle handle, const char* name);
float (__stdcall* Animator_GetFloat)(ScriptObjectHandle handle, const char* name);
int (__stdcall* Animator_GetInt)(ScriptObjectHandle handle, const char* name);
void (__stdcall* Animator_SetUseLayer)(ScriptObjectHandle handle, int layerIndex, int useLayer);
void (__stdcall* Animator_StopAnimation)(ScriptObjectHandle handle, float duration);
// CharacterControllerComponent (획득 47회 · 호출 약 60회 — 이동의 뼈대)
int (__stdcall* Cct_Exists)(ScriptObjectHandle handle);
void (__stdcall* Cct_Move)(ScriptObjectHandle handle, float inputX, float inputY);
void (__stdcall* Cct_TriggerForcedMove)(ScriptObjectHandle handle, Float3 velocity, float duration);
void (__stdcall* Cct_StopForcedMove)(ScriptObjectHandle handle);
int (__stdcall* Cct_IsInForcedMove)(ScriptObjectHandle handle);
void (__stdcall* Cct_SetAutomaticRotation)(ScriptObjectHandle handle, int useAuto);
void (__stdcall* Cct_SetLookDirection)(ScriptObjectHandle handle, Float3 direction);
void (__stdcall* Cct_ClearLookDirection)(ScriptObjectHandle handle);
void (__stdcall* Cct_ForcedSetPosition)(ScriptObjectHandle handle, Float3 position);
float (__stdcall* Cct_GetBaseSpeed)(ScriptObjectHandle handle);
void (__stdcall* Cct_SetBaseSpeed)(ScriptObjectHandle handle, float speed);
int (__stdcall* Cct_IsOnMove)(ScriptObjectHandle handle);
void (__stdcall* Cct_SetOnMove)(ScriptObjectHandle handle, int isMove);
int (__stdcall* Cct_IsFalling)(ScriptObjectHandle handle);
float (__stdcall* Cct_GetRadius)(ScriptObjectHandle handle);
float (__stdcall* Cct_GetHeight)(ScriptObjectHandle handle);
unsigned int (__stdcall* Cct_GetId)(ScriptObjectHandle handle);
// RectTransformComponent (획득 28회 · SetAnchoredPosition만 39회)
int (__stdcall* Rect_Exists)(ScriptObjectHandle handle);
Float2 (__stdcall* Rect_GetAnchoredPosition)(ScriptObjectHandle handle);
void (__stdcall* Rect_SetAnchoredPosition)(ScriptObjectHandle handle, Float2 position);
Float2 (__stdcall* Rect_GetSizeDelta)(ScriptObjectHandle handle);
void (__stdcall* Rect_SetSizeDelta)(ScriptObjectHandle handle, Float2 size);
Float2 (__stdcall* Rect_GetPivot)(ScriptObjectHandle handle);
void (__stdcall* Rect_SetPivot)(ScriptObjectHandle handle, Float2 pivot);
// ImageComponent (획득 48회 · SetTexture 39 · color 46)
int (__stdcall* Image_Exists)(ScriptObjectHandle handle);
void (__stdcall* Image_SetTexture)(ScriptObjectHandle handle, int index);
int (__stdcall* Image_GetTextureCount)(ScriptObjectHandle handle);
Float4 (__stdcall* Image_GetColor)(ScriptObjectHandle handle);
void (__stdcall* Image_SetColor)(ScriptObjectHandle handle, Float4 color);
float (__stdcall* Image_GetClipPercent)(ScriptObjectHandle handle);
void (__stdcall* Image_SetClipPercent)(ScriptObjectHandle handle, float percent);
void (__stdcall* Image_SetNativeSize)(ScriptObjectHandle handle);
// 카메라. 월드→스크린 변환을 손으로 하는 파일이 11개나 되어 한 번에 접는다.
int (__stdcall* Camera_Exists)();
Float2 (__stdcall* Camera_GetScreenSize)();
Float3 (__stdcall* Camera_WorldToScreenPoint)(Float3 world);
// MeshRenderer + Material. 스크립트가 만지는 것은 사실상 셋뿐이다 —
// 재질 사본 만들기(6회) · 셰이더 상수 넣기(24회) · 베이스 색 알파.
int (__stdcall* Mesh_Exists)(ScriptObjectHandle handle);
void (__stdcall* Mesh_InstantiateMaterial)(ScriptObjectHandle handle, const char* newName);
int (__stdcall* Mesh_GetMaterialName)(ScriptObjectHandle handle, char* buffer, int capacity);
int (__stdcall* Mesh_SetMaterialFloat)(ScriptObjectHandle handle, const char* buffer, const char* name, float value);
int (__stdcall* Mesh_SetMaterialInt)(ScriptObjectHandle handle, const char* buffer, const char* name, int value);
Float4 (__stdcall* Mesh_GetBaseColor)(ScriptObjectHandle handle);
void (__stdcall* Mesh_SetBaseColor)(ScriptObjectHandle handle, Float4 color);
// 입력. 실측 43회인데 스크립트에서 아무것도 받을 수 없던 표면이다.
//
// 개별 술어(Down/Pressed/Released)를 따로 열지 않고 상태값을 통째로 넘긴다.
// 경계 왕복이 한 번으로 줄고, 무엇보다 엔진의 Pressed가 첫 프레임을 제외한다는
// 함정을 C# 쪽에서 정확히 조합해 감출 수 있다.
int (__stdcall* Input_GetKeyState)(int key);
int (__stdcall* Input_GetMouseButtonState)(int button);
int (__stdcall* Input_GetControllerButtonState)(int index, int button);
int (__stdcall* Input_IsAnyKeyPressed)();
Float2 (__stdcall* Input_GetMousePosition)();
Float2 (__stdcall* Input_GetMouseDelta)();
int (__stdcall* Input_GetWheelDelta)();
void (__stdcall* Input_SetCursorVisible)(int visible);
int (__stdcall* Input_IsControllerConnected)(int index);
int (__stdcall* Input_IsControllerTriggerL)(int index);
int (__stdcall* Input_IsControllerTriggerR)(int index);
Float2 (__stdcall* Input_GetControllerThumbL)(int index);
Float2 (__stdcall* Input_GetControllerThumbR)(int index);
// 물리 질의 (Raycast 19 · SphereOverlap 16 — 타격 판정과 탐지의 뼈대)
int (__stdcall* Physics_Raycast)(Float3 origin, Float3 direction, float distance,
unsigned int layerMask, ScriptHitResult* hit);
int (__stdcall* Physics_RaycastAll)(Float3 origin, Float3 direction, float distance,
unsigned int layerMask, ScriptHitResult* buffer, int capacity);
int (__stdcall* Physics_OverlapSphere)(Float3 position, float radius,
unsigned int layerMask, ScriptHitResult* buffer, int capacity);
// RigidBodyComponent (실측 34회)
int (__stdcall* Rigid_Exists)(ScriptObjectHandle handle);
Float3 (__stdcall* Rigid_GetLinearVelocity)(ScriptObjectHandle handle);
void (__stdcall* Rigid_SetLinearVelocity)(ScriptObjectHandle handle, Float3 velocity);
void (__stdcall* Rigid_AddLinearVelocity)(ScriptObjectHandle handle, Float3 velocity);
Float3 (__stdcall* Rigid_GetAngularVelocity)(ScriptObjectHandle handle);
void (__stdcall* Rigid_SetAngularVelocity)(ScriptObjectHandle handle, Float3 velocity);
void (__stdcall* Rigid_AddForce)(ScriptObjectHandle handle, Float3 force, int forceMode);
void (__stdcall* Rigid_SetBodyType)(ScriptObjectHandle handle, int bodyType);
int (__stdcall* Rigid_IsKinematic)(ScriptObjectHandle handle);
void (__stdcall* Rigid_SetKinematic)(ScriptObjectHandle handle, int kinematic);
int (__stdcall* Rigid_IsTrigger)(ScriptObjectHandle handle);
void (__stdcall* Rigid_SetIsTrigger)(ScriptObjectHandle handle, int isTrigger);
int (__stdcall* Rigid_IsColliderEnabled)(ScriptObjectHandle handle);
void (__stdcall* Rigid_SetColliderEnabled)(ScriptObjectHandle handle, int enabled);
int (__stdcall* Rigid_IsUsingGravity)(ScriptObjectHandle handle);
void (__stdcall* Rigid_UseGravity)(ScriptObjectHandle handle, int useGravity);
float (__stdcall* Rigid_GetMass)(ScriptObjectHandle handle);
void (__stdcall* Rigid_SetMass)(ScriptObjectHandle handle, float mass);
void (__stdcall* Rigid_SetLinearDamping)(ScriptObjectHandle handle, float damping);
void (__stdcall* Rigid_SetAngularDamping)(ScriptObjectHandle handle, float damping);
void (__stdcall* Rigid_SetScale)(ScriptObjectHandle handle, Float3 scale);
void (__stdcall* Rigid_SetLockLinear)(ScriptObjectHandle handle, int x, int y, int z);
void (__stdcall* Rigid_SetLockAngular)(ScriptObjectHandle handle, int x, int y, int z);
// 콜라이더 3종. 표면이 거의 같아 종류를 인자로 받아 디스패치한다
// (kind: 0=Sphere 1=Box 2=Capsule).
int (__stdcall* Collider_Exists)(ScriptObjectHandle handle, int kind);
float (__stdcall* Collider_GetRadius)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetRadius)(ScriptObjectHandle handle, int kind, float radius);
float (__stdcall* Collider_GetHeight)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetHeight)(ScriptObjectHandle handle, int kind, float height);
Float3 (__stdcall* Collider_GetExtents)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetExtents)(ScriptObjectHandle handle, int kind, Float3 extents);
Float3 (__stdcall* Collider_GetPositionOffset)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetPositionOffset)(ScriptObjectHandle handle, int kind, Float3 offset);
float (__stdcall* Collider_GetRestitution)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetRestitution)(ScriptObjectHandle handle, int kind, float value);
float (__stdcall* Collider_GetStaticFriction)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetStaticFriction)(ScriptObjectHandle handle, int kind, float value);
float (__stdcall* Collider_GetDynamicFriction)(ScriptObjectHandle handle, int kind);
void (__stdcall* Collider_SetDynamicFriction)(ScriptObjectHandle handle, int kind, float value);
// TextComponent (SetMessage 16 · SetAlpha 6)
int (__stdcall* Text_Exists)(ScriptObjectHandle handle);
int (__stdcall* Text_GetMessage)(ScriptObjectHandle handle, char* buffer, int capacity);
void (__stdcall* Text_SetMessage)(ScriptObjectHandle handle, const char* message);
Float4 (__stdcall* Text_GetColor)(ScriptObjectHandle handle);
void (__stdcall* Text_SetColor)(ScriptObjectHandle handle, Float4 color);
float (__stdcall* Text_GetAlpha)(ScriptObjectHandle handle);
void (__stdcall* Text_SetAlpha)(ScriptObjectHandle handle, float alpha);
float (__stdcall* Text_GetFontSize)(ScriptObjectHandle handle);
void (__stdcall* Text_SetFontSize)(ScriptObjectHandle handle, float size);
Float2 (__stdcall* Text_GetRelativePosition)(ScriptObjectHandle handle);
void (__stdcall* Text_SetRelativePosition)(ScriptObjectHandle handle, Float2 position);
// UIComponent 공통. Image·Text가 함께 쓰므로 기반 타입으로 찾는다.
int (__stdcall* Ui_GetOrder)(ScriptObjectHandle handle);
void (__stdcall* Ui_SetOrder)(ScriptObjectHandle handle, int order);
// Canvas
int (__stdcall* Canvas_Exists)(ScriptObjectHandle handle);
int (__stdcall* Canvas_GetOrder)(ScriptObjectHandle handle);
void (__stdcall* Canvas_SetOrder)(ScriptObjectHandle handle, int order);
int (__stdcall* Canvas_GetName)(ScriptObjectHandle handle, char* buffer, int capacity);
void (__stdcall* Canvas_SetName)(ScriptObjectHandle handle, const char* name);
// UI 내비게이션·버튼·Image 잔여 (IsNavigationThis 5 · SetNavLock 5 · curindex 5 · rotate 4)
int (__stdcall* Ui_IsSelected)(ScriptObjectHandle handle);
int (__stdcall* Ui_IsNavLocked)(ScriptObjectHandle handle);
void (__stdcall* Ui_SetNavLock)(ScriptObjectHandle handle, int locked);
ScriptObjectHandle (__stdcall* UiNav_GetSelected)();
void (__stdcall* UiNav_SetSelected)(ScriptObjectHandle handle);
int (__stdcall* Button_Exists)(ScriptObjectHandle handle);
int (__stdcall* Button_ConsumeClicked)(ScriptObjectHandle handle);
int (__stdcall* Image_GetTextureIndex)(ScriptObjectHandle handle);
float (__stdcall* Image_GetRotation)(ScriptObjectHandle handle);
void (__stdcall* Image_SetRotation)(ScriptObjectHandle handle, float rotation);
// 레이아웃 검증용 — 계산된 최종 사각형(x, y, width, height)을 그대로 읽는다.
Float4 (__stdcall* Rect_GetWorldRect)(ScriptObjectHandle handle);
// Camera.WorldToScreenPoint와 같은 좌상단 원점 화면 픽셀 좌표.
Float2 (__stdcall* Rect_GetScreenPosition)(ScriptObjectHandle handle);
void (__stdcall* Rect_SetScreenPosition)(ScriptObjectHandle handle, Float2 position);
};
ScriptApiTable g_apiTable{};
// ── API 구현 ──
// 전부 게임 스레드에서만 불린다(관리 코드 호출을 게임 스레드로 한정했으므로).
void __stdcall Api_Log(int level, const char* message)
{
if (nullptr == message) return;
switch (level)
{
case 0: Debug->LogDebug(message); break;
case 2: Debug->LogWarning(message); break;
case 3: Debug->LogError(message); break;
default: Debug->Log(message); break;
}
}
ScriptObjectHandle __stdcall Api_Entity_FindByName(const char* name)
{
if (nullptr == name) return {};
Scene* scene = SceneManagers->GetActiveScene();
if (nullptr == scene) return {};
auto object = scene->GetEntity(name);
if (!object) return {};
return ScriptObjectRegistry::Get().Register(object);
}
int __stdcall Api_Entity_IsAlive(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object && !object->IsDestroyMark()) ? 1 : 0;
}
int __stdcall Api_Entity_GetName(ScriptObjectHandle handle, char* buffer, int capacity)
{
if (nullptr == buffer || capacity <= 0) return 0;
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return 0;
const std::string name = object->m_name.ToString();
const int length = static_cast<int>(std::min<size_t>(name.size(), static_cast<size_t>(capacity)));
std::memcpy(buffer, name.data(), length);
return length;
}
void __stdcall Api_Entity_SetEnabled(ScriptObjectHandle handle, int enabled)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr != object)
{
object->SetEnabled(0 != enabled);
}
}
/// 월드 값을 읽기 전에 캐시를 맞춰 둔다.
///
/// m_worldPosition·m_worldQuaternion·m_worldMatrix는 프레임당 한 번
/// Scene::AllUpdateWorldMatrix가 채우는 캐시다. 그런데 스크립트에서는
/// "방금 내 Transform을 고치고 바로 월드 값을 읽는" 흐름이 흔하고,
/// 그대로 두면 한 프레임 전 값을 보게 된다(실측으로 확인한 문제).
/// 조상까지 훑어 하나라도 더러우면 갱신한다 — 깨끗하면 bool 몇 번 읽고 끝난다.
void EnsureWorldMatrix(Entity* object)
{
if (nullptr == object) return;
// 계층이 꼬여 순환이 생기면 여기서 멈춘다(엔진 재귀도 같은 위험이 있다).
constexpr int kMaxDepth = 64;
bool dirty = false;
Entity* node = object;
for (int depth = 0; nullptr != node && depth < kMaxDepth; ++depth)
{
if (node->Transform_().IsDirty())
{
dirty = true;
break;
}
const Entity::Index parentIndex = node->GetParentIndex();
if (Entity::INVALID_INDEX == parentIndex) break;
node = node->OwnerSceneFindIndex(parentIndex);
}
if (!dirty) return;
// UpdateWorldMatrix가 조상까지 거슬러 올라가며 갱신한다. 다만 부모가 없는
// 오브젝트는 월드 행렬만 세우고 분해는 하지 않으므로 여기서 마저 시킨다
// (행렬이 그대로면 SetAndDecomposeMatrix가 곧바로 빠져나간다).
Transform& transform = object->Transform_();
transform.SetAndDecomposeMatrix(transform.UpdateWorldMatrix());
}
// ── 계층 ──
//
// 엔진은 오브젝트를 인덱스로 잇는다(m_childrenIndices·m_parentIndex).
// 관리 측에는 인덱스 대신 세대 핸들로 바꿔 넘긴다 — 인덱스는 슬롯이 재사용되면
// 다른 오브젝트를 가리키게 되지만 핸들은 세대 비교로 걸러진다.
int __stdcall Api_Entity_GetChildCount(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object) ? static_cast<int>(object->GetChildrenIndices().size()) : 0;
}
ScriptObjectHandle __stdcall Api_Entity_GetChild(ScriptObjectHandle handle, int index)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return {};
const auto& children = object->GetChildrenIndices();
if (index < 0 || static_cast<size_t>(index) >= children.size()) return {};
Entity* child = object->OwnerSceneFindIndex(children[index]);
return (nullptr != child) ? ScriptObjectRegistry::Get().Register(child) : ScriptObjectHandle{};
}
ScriptObjectHandle __stdcall Api_Entity_GetParent(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return {};
const Entity::Index parentIndex = object->GetParentIndex();
if (Entity::INVALID_INDEX == parentIndex) return {};
Entity* parent = object->OwnerSceneFindIndex(parentIndex);
return (nullptr != parent) ? ScriptObjectRegistry::Get().Register(parent) : ScriptObjectHandle{};
}
ScriptObjectHandle __stdcall Api_Entity_FindByIndex(int index)
{
if (index < 0) return {};
Entity* object = Entity::FindIndex(index);
return (nullptr != object) ? ScriptObjectRegistry::Get().Register(object) : ScriptObjectHandle{};
}
int __stdcall Api_Entity_GetIndex(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object) ? static_cast<int>(object->m_index) : -1;
}
Float3 __stdcall Api_Transform_GetLocalPosition(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return {};
const auto& p = object->Transform_().position;
return { p.x, p.y, p.z };
}
void __stdcall Api_Transform_SetLocalPosition(ScriptObjectHandle handle, Float3 position)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
object->Transform_().SetPosition({ position.x, position.y, position.z });
}
Float3 __stdcall Api_Transform_GetWorldPosition(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return {};
EnsureWorldMatrix(object);
const math::vector3 world = object->Transform_().GetWorldPosition();
return { world.x, world.y, world.z };
}
// 아래 Transform API는 전부 같은 모양이다 — 핸들을 풀고, 없으면 무해한 기본값.
// 엔진 setter들이 내부에서 SetDirty를 부르므로 여기서 따로 표시할 것은 없다.
Float4 __stdcall Api_Transform_GetLocalRotation(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 0.f, 0.f, 0.f, 1.f }; // 단위 쿼터니언
const auto& r = object->Transform_().rotation;
return { r.x, r.y, r.z, r.w };
}
void __stdcall Api_Transform_SetLocalRotation(ScriptObjectHandle handle, Float4 rotation)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
object->Transform_().SetRotation({ rotation.x, rotation.y, rotation.z, rotation.w });
}
Float3 __stdcall Api_Transform_GetLocalScale(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 1.f, 1.f, 1.f };
const auto& s = object->Transform_().scale;
return { s.x, s.y, s.z };
}
void __stdcall Api_Transform_SetLocalScale(ScriptObjectHandle handle, Float3 scale)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
object->Transform_().SetScale({ scale.x, scale.y, scale.z });
}
void __stdcall Api_Transform_AddLocalPosition(ScriptObjectHandle handle, Float3 delta)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
object->Transform_().AddPosition({ delta.x, delta.y, delta.z });
}
void __stdcall Api_Transform_AddLocalRotation(ScriptObjectHandle handle, Float4 delta)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
object->Transform_().AddRotation({ delta.x, delta.y, delta.z, delta.w });
}
void __stdcall Api_Transform_SetWorldPosition(ScriptObjectHandle handle, Float3 position)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
EnsureWorldMatrix(object);
object->Transform_().SetWorldPosition({ position.x, position.y, position.z });
}
Float4 __stdcall Api_Transform_GetWorldRotation(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 0.f, 0.f, 0.f, 1.f };
EnsureWorldMatrix(object);
const math::quaternion q = object->Transform_().GetWorldQuaternion();
return { q.x, q.y, q.z, q.w };
}
void __stdcall Api_Transform_SetWorldRotation(ScriptObjectHandle handle, Float4 rotation)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
EnsureWorldMatrix(object);
object->Transform_().SetWorldRotation({ rotation.x, rotation.y, rotation.z, rotation.w });
}
Float3 __stdcall Api_Transform_GetWorldScale(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 1.f, 1.f, 1.f };
EnsureWorldMatrix(object);
const math::vector3 s = object->Transform_().GetWorldScale();
return { s.x, s.y, s.z };
}
void __stdcall Api_Transform_SetWorldScale(ScriptObjectHandle handle, Float3 scale)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
EnsureWorldMatrix(object);
object->Transform_().SetWorldScale({ scale.x, scale.y, scale.z });
}
Float3 __stdcall Api_Transform_GetForward(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 0.f, 0.f, 1.f };
EnsureWorldMatrix(object);
const math::vector3 v = object->Transform_().GetForward();
return { v.x, v.y, v.z };
}
Float3 __stdcall Api_Transform_GetRight(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 1.f, 0.f, 0.f };
EnsureWorldMatrix(object);
const math::vector3 v = object->Transform_().GetRight();
return { v.x, v.y, v.z };
}
Float3 __stdcall Api_Transform_GetUp(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return { 0.f, 1.f, 0.f };
EnsureWorldMatrix(object);
const math::vector3 v = object->Transform_().GetUp();
return { v.x, v.y, v.z };
}
int __stdcall Api_Prefab_Exists(const char* name)
{
if (nullptr == name) return 0;
return (nullptr != PrefabUtilitys->LoadPrefab(name)) ? 1 : 0;
}
ScriptObjectHandle __stdcall Api_Prefab_Instantiate(const char* prefabName, const char* instanceName)
{
if (nullptr == prefabName) return {};
Prefab* prefab = PrefabUtilitys->LoadPrefab(prefabName);
if (nullptr == prefab)
{
Debug->LogWarning(std::string("[스크립트] 프리팹을 찾을 수 없습니다: ") + prefabName);
return {};
}
const std::string name = (nullptr != instanceName) ? instanceName : "";
Entity* instance = PrefabUtilitys->InstantiatePrefab(prefab, name);
if (nullptr == instance)
{
Debug->LogWarning(std::string("[스크립트] 프리팹 인스턴스 생성 실패: ") + prefabName);
return {};
}
// 이 호출이 반환되기 전에 새 오브젝트의 스크립트를 깨운다.
//
// 그러지 않으면 다음 프레임의 드레인까지 밀려서, 스폰 직후 대상을 초기화하는
// 흔한 패턴이 한 프레임 늦게 동작한다(실측으로 확인한 문제).
// DrainPendingLifecycle은 이미 깨운 컴포넌트를 플래그로 건너뛰므로 전체를 다시
// 돌아도 안전하고, 프리팹이 자식을 여럿 두더라도 한 번에 처리된다.
if (Scene* scene = SceneManagers->GetActiveScene())
{
scene->DrainPendingLifecycle();
}
return ScriptObjectRegistry::Get().Register(instance);
}
void __stdcall Api_Entity_Destroy(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
if (nullptr == object) return;
// 즉시 지우지 않는다. 엔진의 지연 파괴 규약을 그대로 따라야
// 순회 중 컨테이너가 바뀌는 문제가 생기지 않는다.
//
// 핸들 무효화는 여기서 다시 부르지 않는다 — object->Destroy() 안에서
// ScriptObjectRegistry::Unregister가 이미 불린다(Entity.cpp, 트랙 E4).
// 예전에는 여기서 한 번 더 불렀는데, Destroy()가 스스로 Unregister하지
// 않던 시절의 흔적이라 지금은 같은 객체를 두 번 Unregister하는 중복
// 호출이었다(idempotent라 해는 없었지만 정본 지점이 둘로 보이는 문제였다).
object->Destroy();
}
unsigned long long __stdcall Api_Engine_GetFrameCount()
{
return static_cast<unsigned long long>(Time->GetFrameCount());
}
// ── SoundComponent ──
//
// 컴포넌트에 별도 핸들을 주지 않고 오브젝트 핸들로 매번 찾는다.
// 사운드 호출은 프레임당 수십 회 수준이라 이 조회가 병목이 되지 않고,
// 컴포넌트용 슬롯 테이블을 따로 유지하는 쪽이 오히려 비싸다.
SoundComponent* ResolveSound(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object) ? object->GetComponent<SoundComponent>() : nullptr;
}
int __stdcall Api_Sound_Exists(ScriptObjectHandle handle)
{
return (nullptr != ResolveSound(handle)) ? 1 : 0;
}
void __stdcall Api_Sound_Play(ScriptObjectHandle handle)
{
if (auto* sound = ResolveSound(handle)) sound->Play();
}
void __stdcall Api_Sound_Stop(ScriptObjectHandle handle)
{
if (auto* sound = ResolveSound(handle)) sound->Stop();
}
void __stdcall Api_Sound_Pause(ScriptObjectHandle handle, int pause)
{
if (auto* sound = ResolveSound(handle)) sound->Pause(0 != pause);
}
int __stdcall Api_Sound_IsPlaying(ScriptObjectHandle handle)
{
auto* sound = ResolveSound(handle);
return (nullptr != sound && sound->IsPlaying()) ? 1 : 0;
}
void __stdcall Api_Sound_PlayOneShot(ScriptObjectHandle handle)
{
if (auto* sound = ResolveSound(handle)) sound->PlayOneShot();
}
int __stdcall Api_Sound_GetClipKey(ScriptObjectHandle handle, char* buffer, int capacity)
{
if (nullptr == buffer || capacity <= 0) return 0;
auto* sound = ResolveSound(handle);
if (nullptr == sound) return 0;
const int length = static_cast<int>(std::min<size_t>(sound->clipKey.size(), static_cast<size_t>(capacity)));
std::memcpy(buffer, sound->clipKey.data(), length);
return length;
}
void __stdcall Api_Sound_SetClipKey(ScriptObjectHandle handle, const char* value)
{
if (nullptr == value) return;
if (auto* sound = ResolveSound(handle)) sound->clipKey = value;
}
float __stdcall Api_Sound_GetVolume(ScriptObjectHandle handle)
{
auto* sound = ResolveSound(handle);
return (nullptr != sound) ? sound->volume : 0.f;
}
void __stdcall Api_Sound_SetVolume(ScriptObjectHandle handle, float value)
{
if (auto* sound = ResolveSound(handle)) sound->volume = value;
}
float __stdcall Api_Sound_GetPitch(ScriptObjectHandle handle)
{
auto* sound = ResolveSound(handle);
return (nullptr != sound) ? sound->pitch : 0.f;
}
void __stdcall Api_Sound_SetPitch(ScriptObjectHandle handle, float value)
{
if (auto* sound = ResolveSound(handle)) sound->pitch = value;
}
// ── Animator ──
//
// 파라미터는 이름으로 찾는다. 엔진이 Parameters를 선형 탐색하지만 개수가 십여 개
// 수준이고, 인덱스를 캐시하려면 컨트롤러 교체·핫리로드마다 무효화 처리가 필요해서
// 지금 단계에서는 값어치가 없다.
Animator* ResolveAnimator(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object) ? object->GetComponent<Animator>() : nullptr;
}
int __stdcall Api_Animator_Exists(ScriptObjectHandle handle)
{
return (nullptr != ResolveAnimator(handle)) ? 1 : 0;
}
int __stdcall Api_Animator_HasParameter(ScriptObjectHandle handle, const char* name)
{
if (nullptr == name) return 0;
Animator* animator = ResolveAnimator(handle);
return (nullptr != animator && nullptr != animator->FindParameter(name)) ? 1 : 0;
}
void __stdcall Api_Animator_SetBool(ScriptObjectHandle handle, const char* name, int value)
{
if (nullptr == name) return;
if (auto* animator = ResolveAnimator(handle)) animator->SetParameter(name, 0 != value);
}
void __stdcall Api_Animator_SetFloat(ScriptObjectHandle handle, const char* name, float value)
{
if (nullptr == name) return;
if (auto* animator = ResolveAnimator(handle)) animator->SetParameter(name, value);
}
void __stdcall Api_Animator_SetInt(ScriptObjectHandle handle, const char* name, int value)
{
if (nullptr == name) return;
if (auto* animator = ResolveAnimator(handle)) animator->SetParameter(name, value);
}
void __stdcall Api_Animator_SetTrigger(ScriptObjectHandle handle, const char* name)
{
if (nullptr == name) return;
if (auto* animator = ResolveAnimator(handle)) animator->SetParameter(name, true);
}
void __stdcall Api_Animator_ResetTrigger(ScriptObjectHandle handle, const char* name)
{
if (nullptr == name) return;
Animator* animator = ResolveAnimator(handle);
if (nullptr == animator) return;
// ResetTrigger는 tValue만 되돌린다. SetParameter로 false를 넣는 것과 결과가
// 같지만 트리거 전용 경로라 의도가 드러난다.
if (ConditionParameter* parameter = animator->FindParameter(name)) parameter->ResetTrigger();
}
int __stdcall Api_Animator_GetBool(ScriptObjectHandle handle, const char* name)
{
if (nullptr == name) return 0;
Animator* animator = ResolveAnimator(handle);
if (nullptr == animator) return 0;
ConditionParameter* parameter = animator->FindParameter(name);
return (nullptr != parameter && parameter->GetValue<bool>()) ? 1 : 0;
}
float __stdcall Api_Animator_GetFloat(ScriptObjectHandle handle, const char* name)
{
if (nullptr == name) return 0.f;
Animator* animator = ResolveAnimator(handle);
if (nullptr == animator) return 0.f;
ConditionParameter* parameter = animator->FindParameter(name);
return (nullptr != parameter) ? parameter->GetValue<float>() : 0.f;
}
int __stdcall Api_Animator_GetInt(ScriptObjectHandle handle, const char* name)
{
if (nullptr == name) return 0;
Animator* animator = ResolveAnimator(handle);
if (nullptr == animator) return 0;
ConditionParameter* parameter = animator->FindParameter(name);
return (nullptr != parameter) ? parameter->GetValue<int>() : 0;
}
void __stdcall Api_Animator_SetUseLayer(ScriptObjectHandle handle, int layerIndex, int useLayer)
{
if (auto* animator = ResolveAnimator(handle)) animator->SetUseLayer(layerIndex, 0 != useLayer);
}
void __stdcall Api_Animator_StopAnimation(ScriptObjectHandle handle, float duration)
{
if (auto* animator = ResolveAnimator(handle)) animator->StopAnimation(duration);
}
// ── CharacterControllerComponent ──
//
// 이동 입력은 Vector2를 그대로 넘기지 않고 float 둘로 편다 — 8바이트 구조체를
// ABI마다 다르게 다루는 위험을 굳이 떠안을 이유가 없다.
CharacterControllerComponent* ResolveCct(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object) ? object->GetComponent<CharacterControllerComponent>() : nullptr;
}
int __stdcall Api_Cct_Exists(ScriptObjectHandle handle)
{
return (nullptr != ResolveCct(handle)) ? 1 : 0;
}
void __stdcall Api_Cct_Move(ScriptObjectHandle handle, float inputX, float inputY)
{
if (auto* cct = ResolveCct(handle)) cct->Move({ inputX, inputY });
}
void __stdcall Api_Cct_TriggerForcedMove(ScriptObjectHandle handle, Float3 velocity, float duration)
{
// 이징 곡선 인자는 넘기지 않는다 — 게임 스크립트 8곳 어디도 쓰지 않아서
// 엔진 기본값(None)으로 둔다. 필요해지면 enum을 C#에 미러링해 추가한다.
if (auto* cct = ResolveCct(handle))
{
cct->TriggerForcedMove({ velocity.x, velocity.y, velocity.z }, duration);
}
}
void __stdcall Api_Cct_StopForcedMove(ScriptObjectHandle handle)
{
if (auto* cct = ResolveCct(handle)) cct->StopForcedMove();
}
int __stdcall Api_Cct_IsInForcedMove(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct && cct->IsInForcedMove()) ? 1 : 0;
}
void __stdcall Api_Cct_SetAutomaticRotation(ScriptObjectHandle handle, int useAuto)
{
if (auto* cct = ResolveCct(handle)) cct->SetAutomaticRotation(0 != useAuto);
}
void __stdcall Api_Cct_SetLookDirection(ScriptObjectHandle handle, Float3 direction)
{
if (auto* cct = ResolveCct(handle)) cct->SetLookDirection({ direction.x, direction.y, direction.z });
}
void __stdcall Api_Cct_ClearLookDirection(ScriptObjectHandle handle)
{
if (auto* cct = ResolveCct(handle)) cct->ClearLookDirection();
}
void __stdcall Api_Cct_ForcedSetPosition(ScriptObjectHandle handle, Float3 position)
{
if (auto* cct = ResolveCct(handle)) cct->ForcedSetPosition({ position.x, position.y, position.z });
}
float __stdcall Api_Cct_GetBaseSpeed(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct) ? cct->GetBaseSpeed() : 0.f;
}
void __stdcall Api_Cct_SetBaseSpeed(ScriptObjectHandle handle, float speed)
{
if (auto* cct = ResolveCct(handle)) cct->SetBaseSpeed(speed);
}
int __stdcall Api_Cct_IsOnMove(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct && cct->IsOnMove()) ? 1 : 0;
}
void __stdcall Api_Cct_SetOnMove(ScriptObjectHandle handle, int isMove)
{
if (auto* cct = ResolveCct(handle)) cct->SetOnMove(0 != isMove);
}
int __stdcall Api_Cct_IsFalling(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct && cct->IsFalling()) ? 1 : 0;
}
// 아래 셋은 CharacterControllerInfo 구조체를 통째로 넘기는 대신 필드만 꺼낸다.
// 스크립트가 실제로 읽는 것은 radius(3회)와 id(2회)뿐이라, 구조체를 경계에
// 노출하면 필드가 하나 바뀔 때마다 양쪽 배치를 맞춰야 하는 부담만 남는다.
float __stdcall Api_Cct_GetRadius(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct) ? cct->GetControllerInfo().radius : 0.f;
}
float __stdcall Api_Cct_GetHeight(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct) ? cct->GetControllerInfo().height : 0.f;
}
unsigned int __stdcall Api_Cct_GetId(ScriptObjectHandle handle)
{
auto* cct = ResolveCct(handle);
return (nullptr != cct) ? cct->GetControllerInfo().id : 0u;
}
// ── RectTransformComponent ──
RectTransformComponent* ResolveRect(ScriptObjectHandle handle)
{
Entity* object = ScriptObjectRegistry::Get().Resolve(handle);
return (nullptr != object) ? object->GetComponent<RectTransformComponent>() : nullptr;
}
int __stdcall Api_Rect_Exists(ScriptObjectHandle handle)
{
return (nullptr != ResolveRect(handle)) ? 1 : 0;
}