diff --git a/assets/game_projects/fighter/scenes/main.json b/assets/game_projects/fighter/scenes/main.json index 511fc61d..860b082d 100644 --- a/assets/game_projects/fighter/scenes/main.json +++ b/assets/game_projects/fighter/scenes/main.json @@ -223,7 +223,50 @@ } } ], - "children": [] + "children": [ + { + "name": "PlayerOneHitBox", + "type": "CollisionShape2D", + "tags": [], + "external_scene_source": "", + "components": [ + { + "transform2D": { + "position": { + "x": 0, + "y": 0 + }, + "scale": { + "x": 4, + "y": 4 + }, + "rotation": 0, + "z_index": 0, + "z_index_relative_to_parent": false, + "ignore_camera": false + } + }, + { + "collider": { + "rectangle": { + "x": 0.0, + "y": 0.0, + "width": 4.0, + "height": 4.0 + } + } + }, + { + "scriptable_class": { + "class_path": "assets.game_projects.fighter.src.hit_box", + "class_name": "HitBox" + } + } + ], + "children": [] + } + + ] }, { "name": "PlayerTwo", @@ -420,7 +463,49 @@ } } ], - "children": [] + "children": [ + { + "name": "PlayerTwoHitBox", + "type": "CollisionShape2D", + "tags": [], + "external_scene_source": "", + "components": [ + { + "transform2D": { + "position": { + "x": 0, + "y": 0 + }, + "scale": { + "x": 4, + "y": 4 + }, + "rotation": 0, + "z_index": 0, + "z_index_relative_to_parent": false, + "ignore_camera": false + } + }, + { + "collider": { + "rectangle": { + "x": 0.0, + "y": 0.0, + "width": 4.0, + "height": 4.0 + } + } + }, + { + "scriptable_class": { + "class_path": "assets.game_projects.fighter.src.hit_box", + "class_name": "HitBox" + } + } + ], + "children": [] + } + ] } ] } \ No newline at end of file diff --git a/assets/game_projects/fighter/src/hit_box.py b/assets/game_projects/fighter/src/hit_box.py new file mode 100644 index 00000000..3458fbfd --- /dev/null +++ b/assets/game_projects/fighter/src/hit_box.py @@ -0,0 +1,17 @@ +import roll_engine_api + +from roll.node import CollisionShape2D + + +class HitBox(CollisionShape2D): + # TODO: Investigate way to make child not have to implement + @classmethod + def new(cls): + return roll_engine_api.node_new( + class_path=f"{__name__}", + class_name=f"{cls.__name__}", + node_type=f"{cls.extract_valid_inheritance_node()}", + ) + + def _start(self) -> None: + pass diff --git a/assets/game_projects/fighter/src/init.py b/assets/game_projects/fighter/src/init.py index d1adeded..e5f2605c 100644 --- a/assets/game_projects/fighter/src/init.py +++ b/assets/game_projects/fighter/src/init.py @@ -1,8 +1,8 @@ -from roll.node import Node +from roll.node import Node2D from roll.scene import SceneTree -class Init(Node): +class Init(Node2D): def _start(self) -> None: SceneTree.change_scene( scene_path="assets/game_projects/fighter/scenes/title_screen.json" diff --git a/assets/game_projects/fighter/src/main.py b/assets/game_projects/fighter/src/main.py index 9ab3bca7..93774b98 100644 --- a/assets/game_projects/fighter/src/main.py +++ b/assets/game_projects/fighter/src/main.py @@ -160,11 +160,19 @@ def _physics_process(self, delta_time: float) -> None: def _process_inputs(self) -> None: if Input.is_action_just_pressed(action_name="quit"): # Go back to main menu - if self.game_properties.player_opponent_mode == PropertyValue.PLAYER_OPPONENT_MODE_HOST_PLAYER_VS_PLAYER: + if ( + self.game_properties.player_opponent_mode + == PropertyValue.PLAYER_OPPONENT_MODE_HOST_PLAYER_VS_PLAYER + ): Server.stop() - elif self.game_properties.player_opponent_mode == PropertyValue.PLAYER_OPPONENT_MODE_CLIENT_PLAYER_VS_PLAYER: + elif ( + self.game_properties.player_opponent_mode + == PropertyValue.PLAYER_OPPONENT_MODE_CLIENT_PLAYER_VS_PLAYER + ): Client.disconnect() - SceneTree.change_scene(scene_path="assets/game_projects/fighter/scenes/title_screen.json") + SceneTree.change_scene( + scene_path="assets/game_projects/fighter/scenes/title_screen.json" + ) # Engine.exit() for input_buffer in self.input_buffers: diff --git a/assets/game_projects/fighter/src/test.py b/assets/game_projects/fighter/src/test.py new file mode 100644 index 00000000..c75c01ac --- /dev/null +++ b/assets/game_projects/fighter/src/test.py @@ -0,0 +1,21 @@ +import roll_engine_api +from roll.math import Rect2 +from roll.node import CollisionShape2D + + +class Test(CollisionShape2D): + @classmethod + def new(cls): + return roll_engine_api.node_new( + class_path=f"{__name__}", + class_name=f"{__class__.__name__}", + node_type=f"{cls.extract_valid_inheritance_node()}", + ) + + def _start(self) -> None: + self.collider_rect = Rect2(x=300, y=300, w=40, h=40) + print(f"Start called! Rect = {self.collider_rect}") + + def _physics_process(self, delta_time: float) -> None: + # print("Process!") + pass diff --git a/assets/game_projects/fighter/src/title_screen.py b/assets/game_projects/fighter/src/title_screen.py index 51a61a8c..a8517587 100644 --- a/assets/game_projects/fighter/src/title_screen.py +++ b/assets/game_projects/fighter/src/title_screen.py @@ -1,10 +1,14 @@ from enum import auto +import roll_engine_api + from roll.node import Node2D from roll.scene import SceneTree from roll.input import Input from roll.engine import Engine from roll.color import Color +from roll.math import Vector2 + from assets.game_projects.fighter.src.auto_enum import AutoName @@ -12,6 +16,7 @@ GameProperties, PropertyValue, ) +from assets.game_projects.fighter.src.test import Test class MenuSelection(AutoName): diff --git a/docs/general/project_properties.md b/docs/general/project_properties.md new file mode 100644 index 00000000..b318f6c3 --- /dev/null +++ b/docs/general/project_properties.md @@ -0,0 +1,72 @@ +# Project Properties + +Project properties define how the game will be configured before running. An example of the file is found below: + +### Format + +```json +{ + "game_title": "Fighting Game Proto", + "initial_scene": "scenes/init.json", + "base_resolution": { + "width": 800, + "height": 600 + }, + "colliders_visible": true, + "assets": [ + { + "type": "texture", + "file_path": "assets/game_projects/fighter/assets/fighters/puncher/puncher_basic_sheet.png" + }, + { + "type": "font", + "file_path": "assets/fonts/bruh.ttf", + "size": 60 + }, + { + "type": "music", + "file_path": "assets/audio/music/test_music.wav" + }, + { + "type": "sound", + "file_path": "assets/audio/sound/test_sound_effect.wav" + } + ], + "input_actions": [ + { + "name": "quit", + "values": ["esc"] + }, + { + "name": "confirm", + "values": ["return"] + } + ] +} +``` + +### Properties + +`game_title` + +Title of the game window. + +`initial_scene` + +First scene loaded for the game. + +`base_resolution` + +Base resolution of the game + +`colliders_visible` + +If true, will render a visible box for colliders. + +`assets` + +Textures, fonts, music, and sound effect assets are defined here. + +`input_actions` + +Key bindings are defined here. diff --git a/docs/python_api/node.md b/docs/python_api/node.md index 8762e4da..c211f2ab 100644 --- a/docs/python_api/node.md +++ b/docs/python_api/node.md @@ -134,6 +134,8 @@ Add to node's current position. For example, if this line of code is within the **Inherits**: [Node2D](#node2d) -> [Node](#node) +Class used to render a sprite entity. + ### Variables ```python @@ -167,6 +169,8 @@ None. **Inherits**: [Node2D](#node2d) -> [Node](#node) +Class used to render an animated sprite entity. + ### Variables ```python @@ -225,6 +229,8 @@ Stops currently playing animation. **Inherits**: [Node2D](#node2d) -> [Node](#node) +Class used to render font. + ### Variables ```python @@ -262,3 +268,39 @@ set_text(value: str) -> None: Set node's label text. --- + +--- + +## CollisionShape2D + +**Inherits**: [Node2D](#node2d) -> [Node](#node) + +Class used to define collision shapes defined as rectangles. May add other collision shapes in the future. + +### Variables + +```python +collider_rect: roll.math.Rect2 +``` + +Collision shape's colliding rectangle. + +```python +nodes_to_exclude: roll.color.Node +``` + +Nodes that should be excluded from collision checks. + +--- + +### Signals + +None. + +--- + +### Methods + +None. + +--- diff --git a/mkdocs.yml b/mkdocs.yml index 0294fb66..f045bed8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,6 +4,7 @@ nav: - General: - Home: index.md - Core Concepts: general/core_concepts.md + - Project Properties: general/project_properties.md - Python API: - Index: python_api/index.md - Node: python_api/node.md diff --git a/src/core/ecs/component/components/collider_component.h b/src/core/ecs/component/components/collider_component.h index 971197c1..fe5ce39d 100644 --- a/src/core/ecs/component/components/collider_component.h +++ b/src/core/ecs/component/components/collider_component.h @@ -7,7 +7,7 @@ struct ColliderComponent { public: - Rect2 collider; + Rect2 collider = Rect2(); }; #endif //COLLIDER_COMPONENT_H diff --git a/src/core/ecs/component/components/node_component.h b/src/core/ecs/component/components/node_component.h index 3631e037..190a2e02 100644 --- a/src/core/ecs/component/components/node_component.h +++ b/src/core/ecs/component/components/node_component.h @@ -7,14 +7,26 @@ #include "../component.h" using NodeType = std::uint32_t; +using NodeTypeInheritance = std::uint32_t; enum _NodeType { NodeType_INVALID = 0, - NodeType_NODE = 1, - NodeType_NODE2D = 2, - NodeType_SPRITE = 3, - NodeType_ANIMATED_SPRITE = 4, - NodeType_TEXT_LABEL = 5, + NodeType_NODE = 2, + NodeType_NODE2D = 4, + NodeType_SPRITE = 8, + NodeType_ANIMATED_SPRITE = 16, + NodeType_TEXT_LABEL = 32, + NodeType_COLLISION_SHAPE2D = 64, +}; + +enum _NodeTypeInheritance { + NodeTypeInheritance_INVALID = NodeType_INVALID, + NodeTypeInheritance_NODE = NodeType_NODE, + NodeTypeInheritance_NODE2D = NodeType_NODE | NodeType_NODE2D, + NodeTypeInheritance_SPRITE = NodeType_NODE | NodeType_NODE2D | NodeType_SPRITE, + NodeTypeInheritance_ANIMATED_SPRITE = NodeType_NODE | NodeType_NODE2D | NodeType_ANIMATED_SPRITE, + NodeTypeInheritance_TEXT_LABEL = NodeType_NODE | NodeType_NODE2D | NodeType_TEXT_LABEL, + NodeTypeInheritance_COLLISION_SHAPE2D = NodeType_NODE | NodeType_NODE2D | NodeType_COLLISION_SHAPE2D, }; struct NodeComponent { diff --git a/src/core/ecs/entity/system/animated_sprite_rendering_entity_system.h b/src/core/ecs/entity/system/animated_sprite_rendering_entity_system.h index b33ac638..3b72a165 100644 --- a/src/core/ecs/entity/system/animated_sprite_rendering_entity_system.h +++ b/src/core/ecs/entity/system/animated_sprite_rendering_entity_system.h @@ -11,17 +11,30 @@ class AnimatedSpriteRenderingEntitySystem : public EntitySystem { Renderer *renderer = nullptr; ComponentManager *componentManager = nullptr; CameraManager *cameraManager = nullptr; + SceneManager *sceneManager = nullptr; + + Vector2 GetParentPosition(Entity entity) { + Entity parentEntity = sceneManager->GetParent(entity); + if (parentEntity == NO_ENTITY) { + return {0, 0}; + } else { + Transform2DComponent transform2DComponent = componentManager->GetComponent(parentEntity); + return transform2DComponent.position; + } + } public: + AnimatedSpriteRenderingEntitySystem() { renderer = GD::GetContainer()->renderer; componentManager = GD::GetContainer()->componentManager; cameraManager = GD::GetContainer()->cameraManager; + sceneManager = GD::GetContainer()->sceneManager; enabled = true; } - void Initialize() override {} void Enable() override {} void Disable() override {} + void UnregisterEntity(Entity entity) override {} void Render() { @@ -44,7 +57,8 @@ class AnimatedSpriteRenderingEntitySystem : public EntitySystem { } } Camera camera = cameraManager->GetCurrentCamera(); - Vector2 drawDestinationPosition = SpaceHandler::WorldToScreen(transform2DComponent.position, transform2DComponent.ignoreCamera); + Vector2 parentPosition = GetParentPosition(entity); + Vector2 drawDestinationPosition = SpaceHandler::WorldToScreen(transform2DComponent.position + parentPosition, transform2DComponent.ignoreCamera); Vector2 drawScale = !transform2DComponent.ignoreCamera ? transform2DComponent.scale * camera.zoom : transform2DComponent.scale; Vector2 drawDestinationSize = Vector2(currentFrame.drawSource.w * drawScale.x, currentFrame.drawSource.h * drawScale.y); Rect2 drawDestination = Rect2(drawDestinationPosition, drawDestinationSize); diff --git a/src/core/ecs/entity/system/collision_entity_system.h b/src/core/ecs/entity/system/collision_entity_system.h index 0a712ec2..a8399585 100644 --- a/src/core/ecs/entity/system/collision_entity_system.h +++ b/src/core/ecs/entity/system/collision_entity_system.h @@ -11,15 +11,27 @@ class CollisionEntitySystem : public EntitySystem { CollisionContext *collisionContext = nullptr; ComponentManager *componentManager = nullptr; CameraManager *cameraManager = nullptr; + SceneManager *sceneManager = nullptr; Renderer *renderer = nullptr; Texture2D *colliderTexture = nullptr; Rect2 colliderDrawSource = Rect2(0.0f, 0.0f, 4.0f, 4.0f); + Vector2 GetParentPosition(Entity entity) { + Entity parentEntity = sceneManager->GetParent(entity); + if (parentEntity == NO_ENTITY) { + return {0, 0}; + } else { + Transform2DComponent transform2DComponent = componentManager->GetComponent(parentEntity); + return transform2DComponent.position; + } + } + Rect2 GetCollisionRectangle(Entity entity) { Transform2DComponent transform2DComponent = componentManager->GetComponent(entity); ColliderComponent colliderComponent = componentManager->GetComponent(entity); - return Rect2(transform2DComponent.position.x + (colliderComponent.collider.x * transform2DComponent.scale.x), - transform2DComponent.position.y + (colliderComponent.collider.y * transform2DComponent.scale.y), + Vector2 parentPosition = GetParentPosition(entity); + return Rect2(transform2DComponent.position.x + parentPosition.x + (colliderComponent.collider.x * transform2DComponent.scale.x), + transform2DComponent.position.y + parentPosition.y + (colliderComponent.collider.y * transform2DComponent.scale.y), transform2DComponent.scale.x + colliderComponent.collider.w, transform2DComponent.scale.y + colliderComponent.collider.h); } @@ -28,6 +40,7 @@ class CollisionEntitySystem : public EntitySystem { collisionContext = GD::GetContainer()->collisionContext; componentManager = GD::GetContainer()->componentManager; cameraManager = GD::GetContainer()->cameraManager; + sceneManager = GD::GetContainer()->sceneManager; renderer = GD::GetContainer()->renderer; enabled = true; } @@ -71,9 +84,10 @@ class CollisionEntitySystem : public EntitySystem { for (Entity entity : entities) { Transform2DComponent transform2DComponent = componentManager->GetComponent(entity); ColliderComponent colliderComponent = componentManager->GetComponent(entity); + Vector2 parentPosition = GetParentPosition(entity); Vector2 drawDestinationPosition = SpaceHandler::WorldToScreen(Vector2( - transform2DComponent.position.x + (colliderComponent.collider.x * transform2DComponent.scale.x), - transform2DComponent.position.y + (colliderComponent.collider.y * transform2DComponent.scale.y)), + transform2DComponent.position.x + parentPosition.x + (colliderComponent.collider.x * transform2DComponent.scale.x), + transform2DComponent.position.y + parentPosition.y + (colliderComponent.collider.y * transform2DComponent.scale.y)), transform2DComponent.ignoreCamera); Rect2 colliderDrawDestination = Rect2(drawDestinationPosition, Vector2(transform2DComponent.scale.x * colliderComponent.collider.w, diff --git a/src/core/ecs/entity/system/sprite_rendering_entity_system.h b/src/core/ecs/entity/system/sprite_rendering_entity_system.h index 56dffb71..2bbf8712 100644 --- a/src/core/ecs/entity/system/sprite_rendering_entity_system.h +++ b/src/core/ecs/entity/system/sprite_rendering_entity_system.h @@ -11,11 +11,23 @@ class SpriteRenderingEntitySystem : public EntitySystem { Renderer *renderer = nullptr; ComponentManager *componentManager = nullptr; CameraManager *cameraManager = nullptr; + SceneManager *sceneManager = nullptr; + + Vector2 GetParentPosition(Entity entity) { + Entity parentEntity = sceneManager->GetParent(entity); + if (parentEntity == NO_ENTITY) { + return {0, 0}; + } else { + Transform2DComponent transform2DComponent = componentManager->GetComponent(parentEntity); + return transform2DComponent.position; + } + } public: SpriteRenderingEntitySystem() { renderer = GD::GetContainer()->renderer; componentManager = GD::GetContainer()->componentManager; cameraManager = GD::GetContainer()->cameraManager; + sceneManager = GD::GetContainer()->sceneManager; enabled = true; } @@ -30,7 +42,8 @@ class SpriteRenderingEntitySystem : public EntitySystem { Transform2DComponent transform2DComponent = componentManager->GetComponent(entity); SpriteComponent spriteComponent = componentManager->GetComponent(entity); Camera camera = cameraManager->GetCurrentCamera(); - Vector2 drawDestinationPosition = SpaceHandler::WorldToScreen(transform2DComponent.position, transform2DComponent.ignoreCamera); + Vector2 parentPosition = GetParentPosition(entity); + Vector2 drawDestinationPosition = SpaceHandler::WorldToScreen(transform2DComponent.position + parentPosition, transform2DComponent.ignoreCamera); Vector2 drawScale = !transform2DComponent.ignoreCamera ? transform2DComponent.scale * camera.zoom : transform2DComponent.scale; Vector2 drawDestinationSize = Vector2(spriteComponent.drawSource.w * drawScale.x, spriteComponent.drawSource.h * drawScale.y); spriteComponent.drawDestination = Rect2(drawDestinationPosition, drawDestinationSize); diff --git a/src/core/ecs/entity_component_orchestrator.h b/src/core/ecs/entity_component_orchestrator.h index 9e095ff1..ce492bf9 100644 --- a/src/core/ecs/entity_component_orchestrator.h +++ b/src/core/ecs/entity_component_orchestrator.h @@ -31,9 +31,6 @@ class EntityComponentOrchestrator { // Will be the function to initialize stuff for a scene void RegisterSceneNodeInstances(SceneNode sceneNode) { - for (SceneNode childSceneNode : sceneNode.children) { - RegisterSceneNodeInstances(childSceneNode); - } AddChildToEntityScene(sceneNode.parent, sceneNode.entity); if (componentManager->HasComponent(sceneNode.entity)) { ScriptEntitySystem *scriptEntitySystem = (ScriptEntitySystem*) entitySystemManager->GetEntitySystem(); @@ -41,6 +38,10 @@ class EntityComponentOrchestrator { } NodeComponent nodeComponent = componentManager->GetComponent(sceneNode.entity); nodeNameToEntityMap.emplace(nodeComponent.name, sceneNode.entity); + + for (SceneNode childSceneNode : sceneNode.children) { + RegisterSceneNodeInstances(childSceneNode); + } } void CallStartOnScriptInstances(SceneNode sceneNode) { @@ -62,6 +63,21 @@ class EntityComponentOrchestrator { return entityManager->CreateEntity(); } + void NewEntity(SceneNode sceneNode) { + if (componentManager->HasComponent(sceneNode.entity)) { + ScriptEntitySystem *scriptEntitySystem = (ScriptEntitySystem*) entitySystemManager->GetEntitySystem(); + scriptEntitySystem->CreateEntityInstance(sceneNode.entity); + } + } + + void NewEntityAddChild(Entity parent, Entity child) { + SceneNode childNode = SceneNode{.entity = child, .parent = parent}; + AddChildToEntityScene(childNode.parent, childNode.entity); + NodeComponent nodeComponent = componentManager->GetComponent(childNode.entity); + nodeNameToEntityMap.emplace(nodeComponent.name, childNode.entity); + CallStartOnScriptInstances(childNode); + } + void QueueEntityForDeletion(Entity entity) { entitiesQueuedForDeletion.emplace_back(entity); } diff --git a/src/core/ecs/node_type_helper.cpp b/src/core/ecs/node_type_helper.cpp index 05a2ed80..9c2b3634 100644 --- a/src/core/ecs/node_type_helper.cpp +++ b/src/core/ecs/node_type_helper.cpp @@ -6,3 +6,31 @@ const std::string NodeTypeHelper::NODE_TYPE_NODE2D = "Node2D"; const std::string NodeTypeHelper::NODE_TYPE_SPRITE = "Sprite"; const std::string NodeTypeHelper::NODE_TYPE_ANIMATED_SPRITE = "AnimatedSprite"; const std::string NodeTypeHelper::NODE_TYPE_TEXT_LABEL = "TextLabel"; +const std::string NodeTypeHelper::NODE_TYPE_COLLISION_SHAPE2D = "CollisionShape2D"; + +std::map NodeTypeHelper::NODE_TYPE_TO_STRING_MAP = { + {NodeType_NODE, NODE_TYPE_NODE}, + {NodeType_NODE2D, NODE_TYPE_NODE2D}, + {NodeType_SPRITE, NODE_TYPE_SPRITE}, + {NodeType_ANIMATED_SPRITE, NODE_TYPE_ANIMATED_SPRITE}, + {NodeType_TEXT_LABEL, NODE_TYPE_TEXT_LABEL}, + {NodeType_COLLISION_SHAPE2D, NODE_TYPE_COLLISION_SHAPE2D}, +}; + +std::map NodeTypeHelper::NODE_STRING_TO_TYPE_MAP = { + {NODE_TYPE_NODE, NodeType_NODE}, + {NODE_TYPE_NODE2D, NodeType_NODE2D}, + {NODE_TYPE_SPRITE, NodeType_SPRITE}, + {NODE_TYPE_ANIMATED_SPRITE, NodeType_ANIMATED_SPRITE}, + {NODE_TYPE_TEXT_LABEL, NodeType_TEXT_LABEL}, + {NODE_TYPE_COLLISION_SHAPE2D, NodeType_COLLISION_SHAPE2D}, +}; + +std::map NodeTypeHelper::NODE_TYPE_TO_INHERITANCE_MAP = { + {NodeType_NODE, NodeTypeInheritance_NODE}, + {NodeType_NODE2D, NodeTypeInheritance_NODE2D}, + {NodeType_SPRITE, NodeTypeInheritance_SPRITE}, + {NodeType_ANIMATED_SPRITE, NodeTypeInheritance_ANIMATED_SPRITE}, + {NodeType_TEXT_LABEL, NodeTypeInheritance_TEXT_LABEL}, + {NodeType_COLLISION_SHAPE2D, NodeTypeInheritance_COLLISION_SHAPE2D}, +}; diff --git a/src/core/ecs/node_type_helper.h b/src/core/ecs/node_type_helper.h index 89399115..3226fe01 100644 --- a/src/core/ecs/node_type_helper.h +++ b/src/core/ecs/node_type_helper.h @@ -3,6 +3,8 @@ #include "component/components/node_component.h" +#include + class NodeTypeHelper { private: static const std::string NODE_TYPE_INVALID; @@ -11,38 +13,38 @@ class NodeTypeHelper { static const std::string NODE_TYPE_SPRITE; static const std::string NODE_TYPE_ANIMATED_SPRITE; static const std::string NODE_TYPE_TEXT_LABEL; + static const std::string NODE_TYPE_COLLISION_SHAPE2D; + + static std::map NODE_TYPE_TO_STRING_MAP; + static std::map NODE_STRING_TO_TYPE_MAP; + static std::map NODE_TYPE_TO_INHERITANCE_MAP; public: static std::string GetNodeTypeString(NodeType nodeType) { - switch (nodeType) { - case NodeType_NODE: - return NODE_TYPE_NODE; - case NodeType_NODE2D: - return NODE_TYPE_NODE2D; - case NodeType_SPRITE: - return NODE_TYPE_SPRITE; - case NodeType_ANIMATED_SPRITE: - return NODE_TYPE_ANIMATED_SPRITE; - case NodeType_TEXT_LABEL: - return NODE_TYPE_TEXT_LABEL; - default: - return NODE_TYPE_INVALID; + if (NODE_TYPE_TO_STRING_MAP.count(nodeType) > 0) { + return NODE_TYPE_TO_STRING_MAP[nodeType]; } + return NODE_TYPE_INVALID; } static NodeType GetNodeTypeInt(const std::string &nodeTypeString) { - if (nodeTypeString == NODE_TYPE_NODE) { - return NodeType_NODE; - } else if(nodeTypeString == NODE_TYPE_NODE2D) { - return NodeType_NODE2D; - } else if(nodeTypeString == NODE_TYPE_SPRITE) { - return NodeType_SPRITE; - } else if(nodeTypeString == NODE_TYPE_ANIMATED_SPRITE) { - return NodeType_ANIMATED_SPRITE; - } else if(nodeTypeString == NODE_TYPE_TEXT_LABEL) { - return NodeType_TEXT_LABEL; + if (NODE_STRING_TO_TYPE_MAP.count(nodeTypeString) > 0) { + return NODE_STRING_TO_TYPE_MAP[nodeTypeString]; } - return NodeType_INVALID; } + + static NodeTypeInheritance GetNodeTypeInheritanceInt(NodeType nodeType) { + if (NODE_TYPE_TO_INHERITANCE_MAP.count(nodeType) > 0) { + return NODE_TYPE_TO_INHERITANCE_MAP[nodeType]; + } + return NodeTypeInheritance_INVALID; + } + + static bool IsNameDefaultNodeClass(const std::string &className) { + if (GetNodeTypeInt(className) != NodeType_INVALID) { + return true; + } + return false; + } }; #endif //NODE_TYPE_HELPER_H diff --git a/src/core/scene/scene.h b/src/core/scene/scene.h index 8cf7b940..b0704e5e 100644 --- a/src/core/scene/scene.h +++ b/src/core/scene/scene.h @@ -43,8 +43,14 @@ class SceneManager { AssetManager *assetManager = nullptr; TimerManager *timerManager = nullptr; - SceneNode ParseSceneJson(nlohmann::json nodeJson) { - SceneNode sceneNode = SceneNode{.entity = entityManager->CreateEntity()}; + SceneNode ParseSceneJson(nlohmann::json nodeJson, bool isRoot) { + SceneNode sceneNode; + if (isRoot) { + sceneNode = SceneNode{.entity = entityManager->CreateEntity()}; + } else { + sceneNode = SceneNode{.entity = entityManager->CreateEntity(), .parent = nodeJson["parent_entity_id"].get()}; + } + std::string nodeName = nodeJson["name"].get(); std::string nodeType = nodeJson["type"].get(); nlohmann::json nodeTagsJsonArray = nodeJson["tags"].get(); @@ -53,7 +59,6 @@ class SceneManager { nlohmann::json nodeChildrenJsonArray = nodeJson["children"].get(); // Configure node type component - // TODO: Figure out if node type info should go into a component within scene json std::vector nodeTags; for (const std::string &nodeTag : nodeTagsJsonArray) { nodeTags.emplace_back(nodeTag); @@ -212,7 +217,8 @@ class SceneManager { } for (nlohmann::json nodeChildJson : nodeChildrenJsonArray) { - SceneNode childNode = ParseSceneJson(nodeChildJson); + nodeChildJson["parent_entity_id"] = sceneNode.entity; + SceneNode childNode = ParseSceneJson(nodeChildJson, false); sceneNode.children.emplace_back(childNode); } @@ -257,14 +263,17 @@ class SceneManager { } void AddChild(Entity parent, Entity child) { + SceneNode childNode = SceneNode{.entity = child, .parent = parent}; if (parent != NO_ENTITY) { assert((entityToSceneNodeMap.count(parent) > 0) && "Parent scene node doesn't exist!"); SceneNode parentNode = entityToSceneNodeMap[parent]; - parentNode.children.emplace_back(SceneNode{.entity = child}); + parentNode.children.emplace_back(childNode); entityToSceneNodeMap[parent] = parentNode; + if (parentNode.entity == currentScene.rootNode.entity) { + currentScene.rootNode.children.emplace_back(childNode); + } } // assert((entityToSceneNodeMap.count(child) <= 0) && "Child already exists!"); - SceneNode childNode = SceneNode{.entity = child, .parent = parent}; entityToSceneNodeMap.emplace(childNode.entity, childNode); } @@ -305,7 +314,7 @@ class SceneManager { Scene LoadSceneFromFile(const std::string &filePath) { nlohmann::json sceneJson = JsonFileHelper::LoadJsonFile(filePath); - SceneNode rootNode = ParseSceneJson(sceneJson); + SceneNode rootNode = ParseSceneJson(sceneJson, true); Scene loadedScene = Scene{.rootNode = rootNode}; ChangeToScene(loadedScene); return loadedScene; diff --git a/src/core/scripting/python/python_modules.cpp b/src/core/scripting/python/python_modules.cpp index b686b710..0ade8456 100644 --- a/src/core/scripting/python/python_modules.cpp +++ b/src/core/scripting/python/python_modules.cpp @@ -100,6 +100,99 @@ PyObject* PythonModules::camera_set_viewport_position(PyObject *self, PyObject * } // NODE +PyObject* PythonModules::node_new(PyObject *self, PyObject *args, PyObject *kwargs) { + static EntityComponentOrchestrator *entityComponentOrchestrator = GD::GetContainer()->entityComponentOrchestrator; + static PythonCache *pythonCache = PythonCache::GetInstance(); + static ComponentManager *componentManager = GD::GetContainer()->componentManager; + static EntityManager *entityManager = GD::GetContainer()->entityManager; + char *pyClassPath; + char *pyClassName; + char *pyNodeType; + if (PyArg_ParseTupleAndKeywords(args, kwargs, "sss", nodeNewKWList, &pyClassPath, &pyClassName, &pyNodeType)) { + SceneNode sceneNode = SceneNode{.entity = entityComponentOrchestrator->CreateEntity()}; + + componentManager->AddComponent(sceneNode.entity, NodeComponent{ + .type = NodeTypeHelper::GetNodeTypeInt(std::string(pyNodeType)), + .name = std::string(pyClassName) + }); + + NodeComponent nodeComponent = componentManager->GetComponent(sceneNode.entity); + NodeTypeInheritance nodeTypeInheritance = NodeTypeHelper::GetNodeTypeInheritanceInt(nodeComponent.type); + + if ((NodeType_NODE2D & nodeTypeInheritance) == NodeType_NODE2D) { + componentManager->AddComponent(sceneNode.entity, Transform2DComponent{}); + auto signature = entityManager->GetSignature(sceneNode.entity); + signature.set(componentManager->GetComponentType(), true); + entityManager->SetSignature(sceneNode.entity, signature); + } + + if ((NodeTypeInheritance_SPRITE & nodeTypeInheritance) == NodeTypeInheritance_SPRITE) { + componentManager->AddComponent(sceneNode.entity, SpriteComponent{}); + auto signature = entityManager->GetSignature(sceneNode.entity); + signature.set(componentManager->GetComponentType(), true); + entityManager->SetSignature(sceneNode.entity, signature); + } + + if ((NodeTypeInheritance_ANIMATED_SPRITE & nodeTypeInheritance) == NodeTypeInheritance_ANIMATED_SPRITE) { + componentManager->AddComponent(sceneNode.entity, AnimatedSpriteComponent{}); + auto signature = entityManager->GetSignature(sceneNode.entity); + signature.set(componentManager->GetComponentType(), true); + entityManager->SetSignature(sceneNode.entity, signature); + } + + if ((NodeTypeInheritance_TEXT_LABEL & nodeTypeInheritance) == NodeTypeInheritance_TEXT_LABEL) { + componentManager->AddComponent(sceneNode.entity, TextLabelComponent{}); + auto signature = entityManager->GetSignature(sceneNode.entity); + signature.set(componentManager->GetComponentType(), true); + entityManager->SetSignature(sceneNode.entity, signature); + } + + if ((NodeTypeInheritance_COLLISION_SHAPE2D & nodeTypeInheritance) == NodeTypeInheritance_COLLISION_SHAPE2D) { + componentManager->AddComponent(sceneNode.entity, ColliderComponent{}); + auto signature = entityManager->GetSignature(sceneNode.entity); + signature.set(componentManager->GetComponentType(), true); + entityManager->SetSignature(sceneNode.entity, signature); + } + + if (NodeTypeHelper::IsNameDefaultNodeClass(std::string(pyClassName))) { + pythonCache->CreateClassInstance(std::string(pyClassPath), std::string(pyClassName), sceneNode.entity); + } else { + componentManager->AddComponent(sceneNode.entity, ScriptableClassComponent{ + .classPath = std::string(pyClassPath), + .className = std::string(pyClassName) + }); + auto scriptClassSignature = entityManager->GetSignature(sceneNode.entity); + scriptClassSignature.set(componentManager->GetComponentType(), true); + entityManager->SetSignature(sceneNode.entity, scriptClassSignature); + + entityComponentOrchestrator->NewEntity(sceneNode); + } + + + if (pythonCache->HasActiveInstance(sceneNode.entity)) { + CPyObject &pClassInstance = pythonCache->GetClassInstance(sceneNode.entity); + pClassInstance.AddRef(); + return pClassInstance; + } + Logger::GetInstance()->Debug("failed to add new python instance"); + Py_RETURN_NONE; + } + return nullptr; +} + +PyObject* PythonModules::node_add_child(PyObject *self, PyObject *args, PyObject *kwargs) { + static EntityComponentOrchestrator *entityComponentOrchestrator = GD::GetContainer()->entityComponentOrchestrator; + Entity parentEntity; + Entity childEntity; + if (PyArg_ParseTupleAndKeywords(args, kwargs, "ii", nodeAddChildKWList, &parentEntity, &childEntity)) { + Logger::GetInstance()->Debug("Attempting to add child"); + entityComponentOrchestrator->NewEntityAddChild(parentEntity, childEntity); + Logger::GetInstance()->Debug("Child added!"); + Py_RETURN_NONE; + } + return nullptr; +} + PyObject* PythonModules::node_get_node(PyObject *self, PyObject *args, PyObject *kwargs) { static EntityComponentOrchestrator *entityComponentOrchestrator = GD::GetContainer()->entityComponentOrchestrator; static PythonCache *pythonCache = PythonCache::GetInstance(); @@ -403,6 +496,30 @@ PyObject* PythonModules::text_label_set_color(PyObject *self, PyObject *args, Py return nullptr; } +// COLLISION_SHAPE2D +PyObject* PythonModules::collision_shape2d_get_collider_rect(PyObject *self, PyObject *args, PyObject *kwargs) { + static EntityComponentOrchestrator *entityComponentOrchestrator = GD::GetContainer()->entityComponentOrchestrator; + Entity entity; + if (PyArg_ParseTupleAndKeywords(args, kwargs, "i", nodeGetEntityKWList, &entity)) { + ColliderComponent colliderComponent = entityComponentOrchestrator->GetComponent(entity); + return Py_BuildValue("(ffff)", colliderComponent.collider.x, colliderComponent.collider.y, colliderComponent.collider.w, colliderComponent.collider.h); + } + return nullptr; +} + +PyObject* PythonModules::collision_shape2d_set_collider_rect(PyObject *self, PyObject *args, PyObject *kwargs) { + static EntityComponentOrchestrator *entityComponentOrchestrator = GD::GetContainer()->entityComponentOrchestrator; + Entity entity; + float x, y, w, h; + if (PyArg_ParseTupleAndKeywords(args, kwargs, "iffff", collisionShape2DSetColliderRectKWList, &entity, &x, &y, &w, &h)) { + ColliderComponent colliderComponent = entityComponentOrchestrator->GetComponent(entity); + colliderComponent.collider = Rect2(x, y, w, h); + entityComponentOrchestrator->UpdateComponent(entity, colliderComponent); + Py_RETURN_NONE; + } + return nullptr; +} + // COLLISION PyObject* PythonModules::collision_check(PyObject *self, PyObject *args, PyObject *kwargs) { static CollisionContext *collisionContext = GD::GetContainer()->collisionContext; diff --git a/src/core/scripting/python/python_modules.h b/src/core/scripting/python/python_modules.h index c27bc9e4..eeb5358f 100644 --- a/src/core/scripting/python/python_modules.h +++ b/src/core/scripting/python/python_modules.h @@ -21,6 +21,8 @@ class PythonModules { static PyObject* camera_set_zoom(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* camera_set_viewport_position(PyObject* self, PyObject* args, PyObject* kwargs); + static PyObject* node_new(PyObject* self, PyObject* args, PyObject* kwargs); + static PyObject* node_add_child(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* node_get_node(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* node_queue_deletion(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* node_signal_create(PyObject* self, PyObject* args, PyObject* kwargs); @@ -49,6 +51,9 @@ class PythonModules { static PyObject* text_label_get_color(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* text_label_set_color(PyObject* self, PyObject* args, PyObject* kwargs); + static PyObject* collision_shape2d_get_collider_rect(PyObject* self, PyObject* args, PyObject* kwargs); + static PyObject* collision_shape2d_set_collider_rect(PyObject* self, PyObject* args, PyObject* kwargs); + static PyObject* collision_check(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* collision_get_collided_nodes(PyObject* self, PyObject* args, PyObject* kwargs); @@ -116,6 +121,14 @@ static struct PyMethodDef rollApiMethods[] = { METH_VARARGS | METH_KEYWORDS, "Set viewport's position." }, // NODE + { + "node_new", (PyCFunction) PythonModules::node_new, + METH_VARARGS | METH_KEYWORDS, "Creates new node." + }, + { + "node_add_child", (PyCFunction) PythonModules::node_add_child, + METH_VARARGS | METH_KEYWORDS, "Adds a child to a parent node." + }, { "node_get_node", (PyCFunction) PythonModules::node_get_node, METH_VARARGS | METH_KEYWORDS, "Gets a node if name exists." @@ -212,6 +225,15 @@ static struct PyMethodDef rollApiMethods[] = { "text_label_set_color", (PyCFunction) PythonModules::text_label_set_color, METH_VARARGS | METH_KEYWORDS, "Sets a text label's color." }, + // COLLISION SHAPE2D + { + "collision_shape2d_get_collider_rect", (PyCFunction) PythonModules::collision_shape2d_get_collider_rect, + METH_VARARGS | METH_KEYWORDS, "Get collider's rectangle." + }, + { + "collision_shape2d_set_collider_rect", (PyCFunction) PythonModules::collision_shape2d_set_collider_rect, + METH_VARARGS | METH_KEYWORDS, "Set collider's rectangle." + }, // COLLISION { "collision_check", (PyCFunction) PythonModules::collision_check, @@ -296,6 +318,8 @@ static char *audioSetVolumeKWList[] = {"volume", nullptr}; static char *cameraVector2SetKWList[] = {"x", "y", nullptr}; static char *nodeGetEntityKWList[] = {"entity_id", nullptr}; +static char *nodeNewKWList[] = {"class_path", "class_name", "node_type", nullptr}; +static char *nodeAddChildKWList[] = {"parent_entity_id", "child_entity_id", nullptr}; static char *nodeGetNodeKWList[] = {"name", nullptr}; static char *nodeSignalCreateKWList[] = {"entity_id", "signal_id", nullptr}; static char *nodeSignalConnectKWList[] = {"entity_id", "signal_id", "listener_entity_id", "function_name", nullptr}; @@ -311,6 +335,8 @@ static char *animatedSpritePlayKWList[] = {"entity_id", "animation_name", nullpt static char *textLabelSetTextKWList[] = {"entity_id", "text", nullptr}; static char *textLabelSetColorKWList[] = {"entity_id", "red", "green", "blue", "alpha", nullptr}; +static char *collisionShape2DSetColliderRectKWList[] = {"entity_id", "x", "y", "w", "h", nullptr}; + static char *inputAddActionKWList[] = {"action_name", "value", nullptr}; static char *inputActionCheckKWList[] = {"action_name", nullptr}; diff --git a/src/core/scripting/python/python_source.h b/src/core/scripting/python/python_source.h index a3a98db7..bf3e3a92 100644 --- a/src/core/scripting/python/python_source.h +++ b/src/core/scripting/python/python_source.h @@ -113,6 +113,37 @@ static PythonSource PYTHON_SOURCE_MATH_MODULE = " self.y = y\n" " self.z = z\n" "\n" + "class Rect2:\n" + " def __init__(self, x=0.0, y=0.0, w=0.0, h=0.0):\n" + " self.x = x\n" + " self.y = y\n" + " self.w = w\n" + " self.h = h\n" + "\n" + " def __str__(self):\n" + " return f\"({self.x}, {self.y}, {self.w}, {self.h})\"\n" + "\n" + " def __repr__(self):\n" + " return f\"({self.x}, {self.y}, {self.w}, {self.h})\"\n" + "\n" + " @property\n" + " def position(self) -> Vector2:\n" + " return Vector2(self.x, self.y)\n" + "\n" + " @position.setter\n" + " def position(self, value: Vector2) -> None:\n" + " self.x = value.x\n" + " self.y = value.y\n" + "\n" + " @property\n" + " def size(self) -> Vector2:\n" + " return Vector2(self.w, self.h)\n" + "\n" + " @size.setter\n" + " def size(self, value: Vector2) -> None:\n" + " self.w = value.x\n" + " self.h = value.y\n" + "\n" ""; static PythonSource PYTHON_SOURCE_COLOR_MODULE = @@ -232,11 +263,20 @@ static PythonSource PYTHON_SOURCE_CAMERA_MODULE = ""; static PythonSource PYTHON_SOURCE_NODE_MODULE = + "from enum import Enum\n" "from typing import Optional\n" "import roll_engine_api\n" - "from roll.math import Vector2\n" + "from roll.math import Vector2, Rect2\n" "from roll.color import Color\n" "\n" + "class NodeType(str, Enum):\n" + " NODE = \"Node\"\n" + " NODE2D = \"Node2D\"\n" + " SPRITE = \"Sprite\"\n" + " ANIMATED_SPRITE = \"AnimatedSprite\"\n" + " TEXT_LABEL = \"TextLabel\"\n" + " COLLISION_SHAPE2D = \"CollisionShape2D\"\n" + "\n" "class Node:\n" " def __init__(self, entity_id: int) -> None:\n" " self.entity_id = entity_id\n" @@ -247,6 +287,14 @@ static PythonSource PYTHON_SOURCE_NODE_MODULE = " else:\n" " return False\n" "\n" + " @classmethod\n" + " def extract_valid_inheritance_node(cls) -> str:\n" + " valid_node_type_list = [e.value for e in NodeType]\n" + " for class_path in cls.__mro__:\n" + " if class_path.__name__ in valid_node_type_list:\n" + " return class_path.__name__\n" + " return \"\"\n" + "\n" " @staticmethod\n" " def parse_scene_node_from_engine(scene_node):\n" " if isinstance(scene_node, Node):\n" @@ -258,6 +306,17 @@ static PythonSource PYTHON_SOURCE_NODE_MODULE = " instance = node_class(entity_id=entity_id)\n" " return instance\n" "\n" + " @classmethod\n" + " def new(cls):\n" + " return roll_engine_api.node_new(\n" + " class_path=f\"{__name__}\",\n" + " class_name=f\"{cls.__name__}\",\n" + " node_type=f\"{cls.extract_valid_inheritance_node()}\",\n" + " )\n" + "\n" + " def add_child(self, child_node) -> None:\n" + " roll_engine_api.node_add_child(parent_entity_id=self.entity_id, child_entity_id=child_node.entity_id)\n" + "\n" " def get_node(self, name: str):\n" " node = roll_engine_api.node_get_node(name=name)\n" " if not node:\n" @@ -441,6 +500,20 @@ static PythonSource PYTHON_SOURCE_NODE_MODULE = " alpha=value.a,\n" " )\n" "\n" + "class CollisionShape2D(Node2D):\n" + " @property\n" + " def collider_rect(self) -> Rect2:\n" + " x, y, w, h = roll_engine_api.collision_shape2d_get_collider_rect(\n" + " entity_id=self.entity_id\n" + " )\n" + " return Rect2(x=x, y=y, w=w, h=h)\n" + "\n" + " @collider_rect.setter\n" + " def collider_rect(self, value: Rect2) -> None:\n" + " roll_engine_api.collision_shape2d_set_collider_rect(\n" + " entity_id=self.entity_id, x=value.x, y=value.y, w=value.w, h=value.h\n" + " )\n" + "\n" ""; static PythonSource PYTHON_SOURCE_PHYSICS_MODULE =