Last active
September 14, 2020 09:04
-
-
Save HungryProton/1f386ea42425cedc9ee82c203a48cf7e to your computer and use it in GitHub Desktop.
ECS protoype
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
This is the base Component class. Every component should inherit from this. | |
It only stores data. Its only responsibility is to notify the parent Entity when its | |
created. (Or we could setup something in the Entity to monitor new components | |
but I just find it easier this way) | |
""" | |
extends Node | |
class_name Component | |
func _ready() -> void: | |
get_parent().register_component(self) | |
set_process(false) # Not sure if it's needed if _process is not defined |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
The main class. Don't inherit from this, just attach it to a node and add components as child nodes. | |
""" | |
extends Spatial | |
class_name Entity | |
signal layout_updated | |
# We store all the components here for easy access | |
# {component_type: component_node} | |
var components := {} | |
func _ready() -> void: | |
SystemManager.register_new_entity(self) # Tell the systems this entity exists | |
func register_component(object: Component) -> void: | |
if not object is Component: | |
print_debug("Error: ", object, " is not a component.") | |
return | |
var type = object.get_script() | |
if components.has(type): | |
print_debug("Error: ", self.name, " has multiple components ", type) | |
return | |
components[type] = object | |
emit_signal("layout_updated") # Systems listen to that event | |
func deregister_component(object: Component) -> void: | |
var type = object.get_script() | |
if components.has(type): | |
components.erase(type) | |
object.queue_free() | |
emit_signal("layout_updated") | |
# Check if the entity has a specific component | |
func has(type) -> bool: | |
return components.has(type) | |
# Check if the entity has all the components in that list | |
func has_all(list: Array) -> bool: | |
return components.has_all(list) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Inherit your own systems from this class | |
""" | |
extends Object | |
class_name System | |
# true if the system have to run each frame. Set to false if it only do stuff to initialize entities when they first appear. | |
var process := true | |
# All the relevant entities are stored here. This array is updated when an entity gets or lose components | |
var entities := [] | |
func initialize() -> void: | |
pass | |
# Override this in your system to return the list of components relevant to this systems | |
func get_required_components() -> Array: | |
return [] | |
""" | |
Called when a new entity is created. This checks if the entity has the required components | |
and adds it to the pool if it does. When the components of that entity changes, it is | |
reevaluated again to make sure it is still valid. | |
""" | |
func reference_new_entity(entity: Entity) -> void: | |
if entity.has_all(get_required_components()): | |
entities.append(entity) | |
entity.connect("layout_updated", self, "_on_entity_layout_updated", [entity]) | |
_on_new_entity_registered(entity) | |
""" | |
Loops over all entities and process them. | |
""" | |
func process(delta: float) -> void: | |
for e in entities: | |
execute(e, delta) | |
""" | |
Called for each matching entity. Override this in the inherited system. | |
""" | |
func execute(_entity: Entity, _delta: float) -> void: | |
pass | |
""" | |
Called when an entity looses a component. If it no longer have the required components, it | |
is removed from the local entity list. | |
""" | |
func _on_entity_layout_updated(entity) -> void: | |
if not entity.has(get_required_components()): | |
entity.disconnect("layout_updated", self, "_on_entity_layout_updated") | |
entities.erase(entity) | |
_on_entity_removed(entity) | |
""" | |
Called when a new entity is registered within the system. Override if needed. | |
""" | |
func _on_new_entity_registered(_entity: Entity) -> void: | |
pass | |
""" | |
Called when an entity no longer have the required components to be processed by this system. Override if needed | |
""" | |
func _on_entity_removed(_entity: Entity) -> void: | |
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Define all your systems here. The array order defines the order of execution. | |
""" | |
extends Reference | |
class_name SystemsList | |
var list = [ | |
InputController, | |
MotionTiledSystem, | |
] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
The system manager creates all the systems and call their process function. | |
Make it an autoload called SystemManager | |
""" | |
extends Node | |
var all := [] # Every systems created | |
var process := [] # Systems that are processed every frame | |
func _ready() -> void: | |
_instantiate_all_systems() | |
func _process(delta: float) -> void: | |
for s in process: | |
s.process(delta) | |
# Called from a newly created Entity. Ask all the systems if this new entity is relevant to them | |
func register_new_entity(entity) -> void: | |
for s in all: | |
s.reference_new_entity(entity) | |
""" | |
Instantiate all systems defined in the system list, in order. | |
""" | |
func _instantiate_all_systems() -> void: | |
var systems := SystemsList.new() | |
for system_type in systems.list: | |
var new_system = system_type.new() | |
if not new_system is System: | |
continue | |
new_system.initialize() | |
all.append(new_system) | |
if new_system.process: | |
process.append(new_system) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
A example of system. This one handle the motion on a grid in a turn based roguelike game. | |
""" | |
extends System # Don't forget to extend from system! | |
class_name MotionTiledSystem | |
# Define which components are required for this system to work. This one needs the entities | |
# to have 4 components. | |
func get_required_components() -> Array: | |
return [ActorTiled, MotionTiled, Commands, CharacterState] | |
# Automatically called every frame for each relevant entity | |
func execute(entity: Entity, delta: float) -> void: | |
# You can call get_component() safely because you know these entities have the components you | |
# listed in get_required_components() | |
var commands: Commands = entity.components[Commands] | |
var move = commands.actions["move"] | |
if move == Vector3.ZERO: | |
return # Character didn't try to move, abort | |
var actor: ActorTiled = entity.components[ActorTiled] | |
var motion: MotionTiled = entity.components[MotionTiled] | |
# That's a custom class that holds data about the tiled world | |
# Dimensions, layout, which entity is on which tile and so on | |
var world: WorldTiled = WorldManager.get_world(actor.world_id) | |
# Calculate the new position based on data from the components | |
var new_position = actor.position + (move * motion.tiles_travel) | |
# Perform logic only if the character lands on a valid tile | |
if world.is_position_valid(new_position, actor): | |
motion.set_path(actor.position, new_position) | |
world.set_actor_position(new_position, actor) | |
var state: CharacterState = entity.get_component("CharacterState") | |
state.state = CharacterState.State.MOVE |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment