Архітектура ігрового движка на C — Частина 2b: Спадкування префабів і завантаження сцен

Вступ

Добре, цей розділ буде дещо схожий на частину 2. Спочатку я планував опублікувати це через тиждень, але на його створення пішло всього кілька годин, тому я вирішив випустити його як доповнення до попередньої частини і зробити його коротким.

Спадкування префабів

Щоб зменшити дублювання коду між схожими об'єктами (наприклад, персонажами гравця з різними поведінками), я впровадив систему спадкування для моїх префабів. Простими словами, будь-який префаб може вказати базовий префаб, від якого він успадковується. Він отримає всі компоненти та поведінки від базового префабу, але може перевизначити значення базового префабу, а також додати нові компоненти чи поведінки. Ось приклад із гравцем:

"Prefabs": [  
{  
 "Name": "Player",  
 "Components": {  
 "Transform": {},  
 "Physics": {  
 "Gravity": 9.8  
 },  
 "PlayerController": {  
 "WalkSpeed": 2.0,  
 "JumpSpeed": 2.0,  
 "PlayerNum": 1  
 },  
 "RectangleRender": {  
 "Width": 1.5,  
 "Height": 2.0,  
 "Color": "FF0000FF"  
 }  
 },  
 "Events": {  
 "Init": [  
 "RectangleRenderComponent_Init"  
 ],  
 "LogicUpdate": [  
 "PlayerControllerComponent_LogicUpdate",  
 "Physics_Gravity_Update"  
 ],  
 "PhysicsUpdate": [  
 "PhysicsComponent_PhysicsUpdate"  
 ],  
 "PostUpdate": [  
 "Physics_CollideGround_PostUpdate"  
 ],  
 "Render": [  
 "RectangleRenderComponent_RenderUpdate"  
 ]  
 }  
},  
{  
 "Name": "Player1",  
 "Inherits": "Player",  
 "Components": {  
 "PlayerController": {  
 "WalkSpeed": 4.0,  
 "JumpSpeed": 5.0,  
 "PlayerNum": 1  
 },  
 "ProjectileThrower": {  
 "Prefab": "Boomerang"  
 }  
 },  
 "Events": {  
 "Init": [  
 "...",  
 "ProjectileThrowerComponent_Init"  
 ],  
 "LogicUpdate": [  
 "...",  
 "ProjectileThrowerComponent_LogicUpdate"  
 ]  
 }  
},

У цьому прикладі гравець оновлює значення швидкості в контролері гравця та додає компонент ProjectileThrower і його поведінку. Цікаво, як тут структуровані події. Кожен список подій у Player1 починається з “…”. Коли система спадкування бачить це в масиві, вона сигналізує, щоб скопіювати весь оригінальний масив на цьому індексі. Це дає перевагу тим, що користувач може контролювати, чи будуть додаватися додаткові поведінки до або після оригінальних.

Ще одна перевага цього полягає в тому, що “…” можна повністю опустити. У цьому випадку успадкований префаб може видаляти поведінки з базового префабу. Приклад цього можна побачити у префабах проектилів:

{  
 "Name": "Projectile",  
 "Components": {  
 "Transform": {},  
 "Projectile": {},  
 "Physics": {  
 "Gravity": 9.8  
 },  
 "RectangleRender": {  
 "Width": 0.5,  
 "Height": 0.5,  
 "Color": "222222FF"  
 }  
 },  
 "Events": {  
 "Init": [  
 "RectangleRenderComponent_Init"  
 ],  
 "Activate": [  
 "ProjectileComponent_Activate"  
 ],  
 "PhysicsUpdate": [  
 "Physics_Gravity_Update",  
 "PhysicsComponent_PhysicsUpdate"  
 ],  
 "PostUpdate": [  
 "ProjectileComponent_CheckBounds"  
 ],  
 "Render": [  
 "RectangleRenderComponent_RenderUpdate"  
 ]  
 }  
},  
{  
 "Name": "Boomerang",  
 "Inherits": "Projectile",  
 "Components": {  
 "Projectile": {  
 "InitialSpeedX": 10.0,  
 "InitialSpeedY": 0.0  
 },  
 "Boomerang": {  
 "Acceleration": 10.0,  
 "CurveSpeedThreshold": 5.0,  
 "CurveSpeed": 2.0  
 },  
 "RectangleRender": {  
 "Color": "00FF00FF"  
 }  
 },  
 "Events": {  
 "LogicUpdate": [  
 "BoomerangComponent_LogicUpdate"  
 ],  
 "PhysicsUpdate": [  
 "PhysicsComponent_PhysicsUpdate"  
 ]  
 }  
},

У цьому прикладі префаб Boomerang перевизначає PhysicsUpdate, щоб включити лише поведінку PhysicsUpdate, що видаляє поведінку Gravity з оригінального префабу.
Це велика перевага порівняно з традиційним об'єктно-орієнтованим спадкуванням, оскільки вона дозволяє надзвичайно просто видаляти небажану поведінку з успадкованого класу без необхідності спеціальної підтримки в базовому класі.

Завантаження сцен

Для підтримки спадкування префабів була додана нова функція Prefab_Inherit, яка приймає два об'єкти json, базовий об'єкт і успадкований об'єкт, і повертає новий комбінований об'єкт json, який можна використовувати для інстанціювання префабу. Виявилося, що саме ця функціональність була необхідна для реалізації завантаження сцен.

Сцени завантажуються в гру за допомогою файлу json, що має майже такий самий формат, як і файл префабу. Це означає, що спадкування префабів також можна використовувати для поєднання даних сцени з даними префабу перед інстанціюванням об'єктів, щоб встановити такі параметри, як позиція чи специфічні для сцени параметри.

{  
 "Name": "Scene1",  
 "Objects": [  
 {  
 "Prefab": "Player1",  
 "Components": {  
 "Transform": {  
 "X": 5.0,  
 "Y": 0.0  
 }  
 }  
 },  
 {  
 "Prefab": "Player2",  
 "Components": {  
 "Transform": {  
 "X": 10.0,  
 "Y": -5.0  
 }  
 }  
 },  
 {  
 "Prefab": "Ground",  
 "Components": {  
 "Transform": {  
 "X": -15,  
 "Y": 6  
 },  
 "RectangleRender": {  
 "Width": 30,  
 "Height": 6  
 }  
 }  
 }  
 ]  
}

Хоча це зазвичай використовуватиметься для простіших змін, таких як встановлення стартової позиції, сцени насправді підтримують 100% функціональності файлів префабів. Це означає, що ви можете додати одноразову поведінку до об'єкта в конкретній сцені або побудувати цілий об'єкт без префабу для одноразового елемента, який з'являється лише в одній сцені.

Висновок

Я не показував жодного коду в цій статті, оскільки, на відміну від попередніх систем, я не вважав, що показ коду насправді допоможе краще продемонструвати функціональність. Думаю, що специфіка системи префабів була цікавішою за саму реалізацію в даному випадку. Але якщо вам дуже хочеться побачити код, ось як значно компактніше тепер стало завантаження та оновлення основної сцени!

Scene *scene = Scene_Create(SCENE_MAX_OBJECTS);  
Scene_Load(scene, "resources/scene_flat.json");  

while (!WindowShouldClose())  
{  
 BeginDrawing();  
 BeginMode2D(camera);  
 {  
 ClearBackground(LIGHTGRAY);  

 Scene_UpdateLogic(scene);  
 Scene_Render(scene);  
 }  
 EndMode2D();  
 EndDrawing();  
}

Тепер набагато зручніше, коли я більше не ініціалізую всі об'єкти в коді.

Перекладено з: Game Engine Architecture in C — Part 2b: Prefab Inheritance and Scene Loading

Leave a Reply

Your email address will not be published. Required fields are marked *