Skip to content

Instantly share code, notes, and snippets.

@malj
Created January 29, 2021 17:06
Show Gist options
  • Save malj/3416b0d6dc01eb277e401c7ea917900f to your computer and use it in GitHub Desktop.
Save malj/3416b0d6dc01eb277e401c7ea917900f to your computer and use it in GitHub Desktop.
Godot ECS
"""
Base class for components.
Components are nodes which only store state data. They belong to an entity, and
can optionally provide accessor methods, but don't contain any behavior logic.
"""
class_name Component extends "./ecs_node.gd"
func _enter_tree() -> void:
if owner != null:
# Register entity
var entities_group := get_entities_group(get_script())
if owner.is_in_group(entities_group):
printerr("Entity %s can't have multiple components of the same type" % owner.name)
queue_free()
return
owner.add_to_group(entities_group)
# Register component (automatically unregistered when exiting tree)
var components_group := get_components_group(owner)
add_to_group(components_group)
else:
printerr("Component %s must be assigned to an entity" % name)
queue_free()
func _exit_tree() -> void:
if owner != null:
# Unregister entity
var entities_group := get_entities_group(get_script())
if owner.is_in_group(entities_group):
owner.remove_from_group(entities_group)
"""
Base class for all ECS nodes.
Sorts the nodes into worlds and provides standardized opaque group names
for its descendants to use.
"""
extends Node
export var world := 1
func get_entities_group(component_type: Script) -> String:
return "world_%d_component_type_%d_entities" % [world, component_type.get_instance_id()]
func get_components_group(entity: Node) -> String:
return "_world_%d_entity_%d_components" % [world, entity.get_instance_id()]
func get_singleton_components_group() -> String:
return "_world_%d_singleton_components" % world
"""
Base class for singleton components.
Singleton components are components without an associated entity. Only one
singleton component of the same type can exist in each world.
"""
class_name SingletonComponent extends "./ecs_node.gd"
func _enter_tree() -> void:
# Register component (automatically unregistered when exiting tree)
var component_type := get_script() as Script
var components_group := get_singleton_components_group()
for component in get_tree().get_nodes_in_group(components_group):
if component is component_type:
printerr("World %d can't have multiple singleton components of the same type" % world)
queue_free()
return
add_to_group(components_group)
"""
Base class for systems.
Systems are nodes which operate on sets of components. They provide behavior
logic but don't contain any state data.
"""
class_name System extends "./ecs_node.gd"
func get_entities(component_types: Array) -> Array:
var entities := []
if component_types:
var first_entities_group := get_entities_group(component_types[0])
for entity in get_tree().get_nodes_in_group(first_entities_group):
if has_components(entity, component_types):
entities.append(entity)
return entities
func has_component(entity: Node, component_type: Script) -> bool:
var entities_group := get_entities_group(component_type)
return entity.is_in_group(entities_group)
func has_components(entity: Node, component_types: Array) -> bool:
for component_type in component_types:
if not has_component(entity, component_type):
return false
return true
func get_component(entity: Node, component_type: Script) -> Component:
if has_component(entity, component_type):
var components_group := get_components_group(entity)
for component in get_tree().get_nodes_in_group(components_group):
if component is component_type:
return component
return null
func add_component(entity: Node, component_type: Script) -> bool:
if not has_component(entity, component_type):
var component := component_type.new() as Component
if component != null:
component.world = world
component.owner = entity
entity.add_child(component)
return true
return false
func remove_component(entity: Node, component_type: Script) -> bool:
if has_component(entity, component_type):
var component := get_component(entity, component_type)
component.get_parent().remove_child(component)
component.queue_free()
return true
return false
func get_singleton_component(component_type: Script) -> SingletonComponent:
var components_group := get_singleton_components_group()
for component in get_tree().get_nodes_in_group(components_group):
if component is component_type:
return component
return null
func add_singleton_component(component_type: Script) -> bool:
if get_singleton_component(component_type) == null:
var component := component_type.new() as SingletonComponent
if component != null:
component.world = world
get_tree().get_root().add_child(component)
return true
return false
func remove_singleton_component(component_type: Script) -> bool:
var components_group := get_singleton_components_group()
for component in get_tree().get_nodes_in_group(components_group):
if component is component_type:
component.get_parent().remove_child(component)
component.queue_free()
return true
return false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment