Capítulo 6: GDScript Avanzado
Capítulo 6: GDScript Avanzado
Introducción
En este capítulo exploraremos las características más avanzadas de GDScript, incluyendo metaprogramación, reflexión, optimización de rendimiento, integración con C++, y patrones arquitectónicos complejos.
1. Metaprogramación y Reflexión
1.1 Reflexión en GDScript
# Inspección de objetos en tiempo de ejecución
class_name MetaExample
extends Node
func _ready():
# Obtener información de la clase
var class_info = get_class()
print("Clase: ", class_info)
# Listar todos los métodos
var methods = get_method_list()
for method in methods:
print("Método: ", method.name)
# Listar todas las propiedades
var properties = get_property_list()
for prop in properties:
if prop.name.begins_with("_"):
continue
print("Propiedad: %s - Tipo: %s" % [prop.name, prop.type])
# Verificar si tiene un método
if has_method("custom_function"):
call("custom_function")
# Obtener y establecer propiedades dinámicamente
set("position", Vector2(100, 100))
var pos = get("position")
# Conectar señales dinámicamente
if has_signal("custom_signal"):
connect("custom_signal", self, "_on_custom_signal")
1.2 Creación Dinámica de Objetos
# Factory dinámico con reflexión
class_name DynamicFactory
extends Node
# Registro de clases
var registered_classes = {}
func register_class(class_name: String, class_script: Script):
registered_classes[class_name] = class_script
func create_instance(class_name: String, params: Dictionary = {}):
if not registered_classes.has(class_name):
push_error("Clase no registrada: " + class_name)
return null
var script = registered_classes[class_name]
var instance = script.new()
# Configurar propiedades dinámicamente
for key in params:
if instance.has_method("set_" + key):
instance.call("set_" + key, params[key])
elif key in instance:
instance.set(key, params[key])
return instance
# Uso
func _ready():
# Registrar clases
register_class("Enemy", preload("res://Enemy.gd"))
register_class("PowerUp", preload("res://PowerUp.gd"))
# Crear instancias dinámicamente
var enemy = create_instance("Enemy", {
"health": 100,
"damage": 20,
"position": Vector2(500, 300)
})
1.3 Evaluación Dinámica de Expresiones
# Sistema de expresiones dinámicas
class_name ExpressionEvaluator
extends Node
var expression = Expression.new()
func evaluate_expression(expr_string: String, variables: Dictionary = {}):
var error = expression.parse(expr_string, variables.keys())
if error != OK:
push_error("Error parseando expresión: " + expr_string)
return null
var result = expression.execute(variables.values(), self)
if expression.has_execute_failed():
push_error("Error ejecutando expresión: " + expr_string)
return null
return result
# Uso para sistema de condiciones dinámicas
func check_condition(condition: String, context: Dictionary) -> bool:
var result = evaluate_expression(condition, context)
return result if result is bool else false
func _ready():
# Evaluar condiciones complejas
var player_stats = {
"health": 75,
"mana": 50,
"level": 10
}
var can_cast = check_condition(
"health > 50 and mana >= 30 and level >= 5",
player_stats
)
print("Puede lanzar hechizo: ", can_cast)
2. Sistemas de Plugins y Extensiones
2.1 Sistema de Plugins Modular
# Sistema de plugins base
class_name PluginSystem
extends Node
# Plugin base
class Plugin:
var name: String
var version: String
var enabled: bool = true
func initialize(host):
pass
func on_enable():
pass
func on_disable():
pass
func process(delta: float):
pass
# Gestor de plugins
var plugins: Array[Plugin] = []
var plugin_directory = "user://plugins/"
func load_plugins():
var dir = DirAccess.open(plugin_directory)
if not dir:
push_error("No se puede abrir directorio de plugins")
return
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if file_name.ends_with(".gd"):
_load_plugin(plugin_directory + file_name)
file_name = dir.get_next()
func _load_plugin(path: String):
var script = load(path)
if not script:
push_error("No se puede cargar plugin: " + path)
return
var plugin = script.new()
if plugin is Plugin:
plugin.initialize(self)
plugins.append(plugin)
print("Plugin cargado: ", plugin.name)
func enable_plugin(plugin_name: String):
for plugin in plugins:
if plugin.name == plugin_name:
plugin.enabled = true
plugin.on_enable()
return
func disable_plugin(plugin_name: String):
for plugin in plugins:
if plugin.name == plugin_name:
plugin.enabled = false
plugin.on_disable()
return
func _process(delta):
for plugin in plugins:
if plugin.enabled:
plugin.process(delta)
2.2 Sistema de Hooks y Eventos
# Sistema avanzado de hooks
class_name HookSystem
extends Node
# Diccionario de hooks registrados
var hooks = {}
# Registrar un hook
func register_hook(hook_name: String, callback: Callable, priority: int = 0):
if not hooks.has(hook_name):
hooks[hook_name] = []
hooks[hook_name].append({
"callback": callback,
"priority": priority
})
# Ordenar por prioridad
hooks[hook_name].sort_custom(_sort_by_priority)
func _sort_by_priority(a, b):
return a.priority > b.priority
# Ejecutar hooks
func execute_hook(hook_name: String, args: Array = []):
if not hooks.has(hook_name):
return
var results = []
for hook in hooks[hook_name]:
var result = hook.callback.callv(args)
results.append(result)
# Si un hook retorna false, detener la cadena
if result is bool and not result:
break
return results
# Filtro con hooks
func apply_filter(filter_name: String, value, args: Array = []):
if not hooks.has(filter_name):
return value
var filtered_value = value
for hook in hooks[filter_name]:
var new_args = [filtered_value] + args
filtered_value = hook.callback.callv(new_args)
return filtered_value
# Ejemplo de uso
func _ready():
# Registrar hooks
register_hook("player_damaged", _on_player_damaged, 10)
register_hook("player_damaged", _check_armor, 20)
register_hook("player_damaged", _play_damage_sound, 5)
# Registrar filtros
register_hook("calculate_damage", _apply_resistance, 10)
register_hook("calculate_damage", _apply_buffs, 5)
# Ejecutar hook
var damage = 50
damage = apply_filter("calculate_damage", damage, ["fire"])
execute_hook("player_damaged", [damage])
func _on_player_damaged(damage):
print("Jugador recibió daño: ", damage)
func _check_armor(damage):
print("Verificando armadura...")
return damage > 10 # Continuar si el daño es significativo
func _play_damage_sound(damage):
print("Reproduciendo sonido de daño")
func _apply_resistance(damage, damage_type):
if damage_type == "fire":
return damage * 0.5 # 50% resistencia al fuego
return damage
func _apply_buffs(damage, damage_type):
# Aplicar buffs activos
return damage * 1.2 # 20% más daño
3. Optimización Avanzada
3.1 Profiling y Métricas
# Sistema de profiling personalizado
class_name Profiler
extends Node
static var instances = {}
static var metrics = {}
class ProfileTimer:
var name: String
var start_time: int
var total_time: int = 0
var call_count: int = 0
var min_time: int = 9223372036854775807 # MAX_INT
var max_time: int = 0
func _init(p_name: String):
name = p_name
start_time = Time.get_ticks_usec()
func stop():
var elapsed = Time.get_ticks_usec() - start_time
total_time += elapsed
call_count += 1
min_time = min(min_time, elapsed)
max_time = max(max_time, elapsed)
return elapsed
# Comenzar profiling
static func begin(name: String) -> ProfileTimer:
var timer = ProfileTimer.new(name)
if not instances.has(name):
instances[name] = timer
metrics[name] = {
"total_time": 0,
"call_count": 0,
"min_time": timer.min_time,
"max_time": 0,
"avg_time": 0
}
return timer
# Finalizar profiling
static func end(timer: ProfileTimer):
var elapsed = timer.stop()
if metrics.has(timer.name):
metrics[timer.name].total_time += elapsed
metrics[timer.name].call_count += 1
metrics[timer.name].min_time = min(metrics[timer.name].min_time, elapsed)
metrics[timer.name].max_time = max(metrics[timer.name].max_time, elapsed)
metrics[timer.name].avg_time = metrics[timer.name].total_time / metrics[timer.name].call_count
# Reportar métricas
static func report():
print("\n=== PROFILING REPORT ===")
for name in metrics:
var m = metrics[name]
print("%s:" % name)
print(" Calls: %d" % m.call_count)
print(" Total: %.2f ms" % (m.total_time / 1000.0))
print(" Avg: %.3f ms" % (m.avg_time / 1000.0))
print(" Min: %.3f ms" % (m.min_time / 1000.0))
print(" Max: %.3f ms" % (m.max_time / 1000.0))
# Uso
func complex_operation():
var timer = Profiler.begin("complex_operation")
# Operación costosa
for i in range(10000):
var result = sqrt(i) * PI
Profiler.end(timer)
func _ready():
for i in range(100):
complex_operation()
Profiler.report()
3.2 Object Pooling Avanzado
# Pool genérico con warmup y estadísticas
class_name AdvancedPool
extends Node
var pool_scene: PackedScene
var available: Array = []
var active: Array = []
var max_size: int = 100
var warmup_size: int = 20
var statistics = {
"total_created": 0,
"reuses": 0,
"peak_usage": 0,
"current_usage": 0
}
func _init(scene: PackedScene, p_max_size: int = 100, p_warmup: int = 20):
pool_scene = scene
max_size = p_max_size
warmup_size = p_warmup
func warmup():
for i in range(warmup_size):
var instance = _create_instance()
available.append(instance)
print("Pool warmed up with %d instances" % warmup_size)
func _create_instance():
statistics.total_created += 1
var instance = pool_scene.instantiate()
instance.set_process(false)
instance.set_physics_process(false)
instance.visible = false
add_child(instance)
return instance
func get_instance():
var instance
if available.size() > 0:
instance = available.pop_back()
statistics.reuses += 1
elif active.size() < max_size:
instance = _create_instance()
else:
push_warning("Pool exhausted! Max size: %d" % max_size)
return null
active.append(instance)
statistics.current_usage = active.size()
statistics.peak_usage = max(statistics.peak_usage, statistics.current_usage)
instance.set_process(true)
instance.set_physics_process(true)
instance.visible = true
if instance.has_method("reset"):
instance.reset()
return instance
func return_instance(instance):
if not instance in active:
push_error("Instance not from this pool")
return
active.erase(instance)
available.append(instance)
statistics.current_usage = active.size()
instance.set_process(false)
instance.set_physics_process(false)
instance.visible = false
if instance.has_method("on_return_to_pool"):
instance.on_return_to_pool()
func get_statistics() -> Dictionary:
statistics.efficiency = float(statistics.reuses) / float(max(statistics.total_created, 1))
return statistics
func clear_pool():
for instance in available + active:
instance.queue_free()
available.clear()
active.clear()
statistics.current_usage = 0
4. Integración con C++
4.1 GDExtension Básico
# Uso de GDExtension desde GDScript
class_name NativeIntegration
extends Node
# Cargar extensión nativa
var native_class
func _ready():
# Verificar si la extensión está disponible
if ClassDB.class_exists("MyNativeClass"):
native_class = ClassDB.instantiate("MyNativeClass")
# Llamar métodos nativos
var result = native_class.complex_calculation(1000000)
print("Resultado nativo: ", result)
# Conectar señales desde código nativo
if native_class.has_signal("calculation_complete"):
native_class.connect("calculation_complete", _on_calculation_complete)
func _on_calculation_complete(result):
print("Cálculo completado: ", result)
# Interfaz para comunicación bidireccional
func send_data_to_native(data: Dictionary):
if native_class and native_class.has_method("process_data"):
return native_class.process_data(data)
return null
4.2 Optimización con Threading
# Sistema de threading avanzado
class_name ThreadPool
extends Node
var threads: Array[Thread] = []
var tasks: Array = []
var results: Dictionary = {}
var mutex: Mutex
var semaphore: Semaphore
var running: bool = true
var num_threads: int = OS.get_processor_count()
func _ready():
mutex = Mutex.new()
semaphore = Semaphore.new()
# Crear threads del pool
for i in range(num_threads):
var thread = Thread.new()
thread.start(_worker_thread.bind(i))
threads.append(thread)
print("Thread pool iniciado con %d threads" % num_threads)
func _worker_thread(thread_id: int):
while running:
semaphore.wait() # Esperar por trabajo
if not running:
break
mutex.lock()
if tasks.size() == 0:
mutex.unlock()
continue
var task = tasks.pop_front()
mutex.unlock()
# Ejecutar tarea
var result = task.callable.call()
mutex.lock()
results[task.id] = result
mutex.unlock()
func submit_task(callable: Callable, task_id: String = "") -> String:
if task_id == "":
task_id = "task_" + str(Time.get_unix_time_from_system())
mutex.lock()
tasks.append({
"id": task_id,
"callable": callable
})
mutex.unlock()
semaphore.post() # Señalar que hay trabajo
return task_id
func get_result(task_id: String, wait: bool = false):
while wait:
mutex.lock()
if results.has(task_id):
var result = results[task_id]
results.erase(task_id)
mutex.unlock()
return result
mutex.unlock()
OS.delay_msec(10)
mutex.lock()
if results.has(task_id):
var result = results[task_id]
results.erase(task_id)
mutex.unlock()
return result
mutex.unlock()
return null
func shutdown():
running = false
# Despertar todos los threads
for i in range(num_threads):
semaphore.post()
# Esperar que terminen
for thread in threads:
thread.wait_to_finish()
print("Thread pool cerrado")
func _exit_tree():
shutdown()
5. Arquitecturas Complejas
5.1 Sistema ECS (Entity Component System)
# Implementación de ECS en GDScript
class_name ECS
extends Node
# Componente base
class Component:
var entity_id: int
var type: String
# Entidad
class Entity:
var id: int
var components: Dictionary = {}
var active: bool = true
func add_component(component: Component):
components[component.type] = component
component.entity_id = id
func get_component(type: String) -> Component:
return components.get(type)
func has_component(type: String) -> bool:
return components.has(type)
func remove_component(type: String):
components.erase(type)
# Sistema base
class System:
var required_components: Array[String] = []
var ecs: ECS
func process_entity(entity: Entity, delta: float):
pass
func can_process(entity: Entity) -> bool:
for comp_type in required_components:
if not entity.has_component(comp_type):
return false
return true
# Core ECS
var entities: Dictionary = {}
var systems: Array[System] = []
var next_entity_id: int = 0
var component_pools: Dictionary = {}
func create_entity() -> Entity:
var entity = Entity.new()
entity.id = next_entity_id
entities[next_entity_id] = entity
next_entity_id += 1
return entity
func destroy_entity(entity_id: int):
if entities.has(entity_id):
var entity = entities[entity_id]
# Retornar componentes a sus pools
for comp_type in entity.components:
return_component_to_pool(entity.components[comp_type])
entities.erase(entity_id)
func add_system(system: System):
system.ecs = self
systems.append(system)
func _process(delta):
for system in systems:
for entity in entities.values():
if entity.active and system.can_process(entity):
system.process_entity(entity, delta)
# Pool de componentes para reutilización
func get_component_from_pool(type: String) -> Component:
if not component_pools.has(type):
component_pools[type] = []
if component_pools[type].size() > 0:
return component_pools[type].pop_back()
# Crear nuevo componente según tipo
match type:
"Transform":
return TransformComponent.new()
"Velocity":
return VelocityComponent.new()
"Health":
return HealthComponent.new()
_:
return Component.new()
func return_component_to_pool(component: Component):
if not component_pools.has(component.type):
component_pools[component.type] = []
component_pools[component.type].append(component)
# Componentes ejemplo
class TransformComponent extends Component:
var position: Vector2
var rotation: float
func _init():
type = "Transform"
class VelocityComponent extends Component:
var velocity: Vector2
var max_speed: float = 500.0
func _init():
type = "Velocity"
class HealthComponent extends Component:
var current: int = 100
var maximum: int = 100
func _init():
type = "Health"
# Sistema de movimiento
class MovementSystem extends System:
func _init():
required_components = ["Transform", "Velocity"]
func process_entity(entity: Entity, delta: float):
var transform = entity.get_component("Transform") as TransformComponent
var velocity = entity.get_component("Velocity") as VelocityComponent
transform.position += velocity.velocity * delta
5.2 Sistema de Comandos (Command Pattern)
# Sistema de comandos con undo/redo
class_name CommandSystem
extends Node
# Comando base
class Command:
var timestamp: float
func execute():
timestamp = Time.get_unix_time_from_system()
func undo():
pass
func redo():
execute()
func can_merge_with(other: Command) -> bool:
return false
func merge(other: Command):
pass
# Gestor de comandos
var command_history: Array[Command] = []
var current_index: int = -1
var max_history: int = 100
var command_groups: Dictionary = {}
func execute_command(command: Command):
# Eliminar comandos posteriores al índice actual
if current_index < command_history.size() - 1:
command_history.resize(current_index + 1)
# Intentar fusionar con el comando anterior
if command_history.size() > 0:
var last_command = command_history[-1]
if last_command.can_merge_with(command):
last_command.merge(command)
return
# Agregar nuevo comando
command.execute()
command_history.append(command)
current_index += 1
# Limitar tamaño del historial
if command_history.size() > max_history:
command_history.pop_front()
current_index -= 1
func undo() -> bool:
if current_index < 0:
return false
command_history[current_index].undo()
current_index -= 1
return true
func redo() -> bool:
if current_index >= command_history.size() - 1:
return false
current_index += 1
command_history[current_index].redo()
return true
# Comando compuesto
class CompositeCommand extends Command:
var commands: Array[Command] = []
func add_command(command: Command):
commands.append(command)
func execute():
super.execute()
for cmd in commands:
cmd.execute()
func undo():
# Deshacer en orden inverso
for i in range(commands.size() - 1, -1, -1):
commands[i].undo()
func redo():
for cmd in commands:
cmd.redo()
# Ejemplo: Comando de movimiento
class MoveCommand extends Command:
var target: Node2D
var old_position: Vector2
var new_position: Vector2
func _init(p_target: Node2D, p_new_position: Vector2):
target = p_target
new_position = p_new_position
func execute():
super.execute()
old_position = target.position
target.position = new_position
func undo():
target.position = old_position
func can_merge_with(other: Command) -> bool:
if not other is MoveCommand:
return false
var other_move = other as MoveCommand
# Fusionar si es el mismo objeto y los comandos están cerca en tiempo
return other_move.target == target and \
abs(other_move.timestamp - timestamp) < 0.5
func merge(other: Command):
var other_move = other as MoveCommand
new_position = other_move.new_position
timestamp = other_move.timestamp
6. Networking Avanzado
6.1 Sistema de Replicación
# Sistema de replicación de estado
class_name NetworkReplication
extends Node
# Propiedades replicadas
class ReplicatedProperty:
var name: String
var value
var last_value
var interpolate: bool = false
var reliable: bool = true
var update_rate: float = 0.1
var last_update: float = 0.0
func should_replicate() -> bool:
return value != last_value and \
Time.get_ticks_msec() / 1000.0 - last_update >= update_rate
func update_sent():
last_value = value
last_update = Time.get_ticks_msec() / 1000.0
# Objeto replicado
class ReplicatedObject:
var id: int
var owner_id: int
var properties: Dictionary = {}
var node: Node
func add_property(prop_name: String, initial_value, config: Dictionary = {}):
var prop = ReplicatedProperty.new()
prop.name = prop_name
prop.value = initial_value
prop.last_value = initial_value
prop.interpolate = config.get("interpolate", false)
prop.reliable = config.get("reliable", true)
prop.update_rate = config.get("update_rate", 0.1)
properties[prop_name] = prop
func get_replication_data() -> Dictionary:
var data = {}
for prop_name in properties:
var prop = properties[prop_name]
if prop.should_replicate():
data[prop_name] = prop.value
prop.update_sent()
return data
func apply_replication_data(data: Dictionary):
for prop_name in data:
if properties.has(prop_name):
var prop = properties[prop_name]
if prop.interpolate and node:
# Interpolar suavemente
var tween = node.create_tween()
tween.tween_property(node, prop_name, data[prop_name], 0.1)
else:
prop.value = data[prop_name]
if node:
node.set(prop_name, data[prop_name])
# Gestor de replicación
var replicated_objects: Dictionary = {}
var next_object_id: int = 0
var local_player_id: int = 1
func register_object(node: Node, owner_id: int = -1) -> ReplicatedObject:
var obj = ReplicatedObject.new()
obj.id = next_object_id
obj.owner_id = owner_id if owner_id != -1 else local_player_id
obj.node = node
replicated_objects[obj.id] = obj
next_object_id += 1
return obj
func unregister_object(object_id: int):
replicated_objects.erase(object_id)
func get_replication_snapshot() -> Dictionary:
var snapshot = {}
for obj_id in replicated_objects:
var obj = replicated_objects[obj_id]
var data = obj.get_replication_data()
if data.size() > 0:
snapshot[obj_id] = data
return snapshot
func apply_replication_snapshot(snapshot: Dictionary):
for obj_id in snapshot:
if replicated_objects.has(obj_id):
replicated_objects[obj_id].apply_replication_data(snapshot[obj_id])
# RPC optimizado
@rpc("any_peer", "unreliable_ordered")
func replicate_snapshot(snapshot: Dictionary):
apply_replication_snapshot(snapshot)
# Enviar actualizaciones
func _physics_process(delta):
if multiplayer.is_server():
var snapshot = get_replication_snapshot()
if snapshot.size() > 0:
replicate_snapshot.rpc(snapshot)
7. Generación Procedural Avanzada
7.1 Wave Function Collapse
# Implementación de Wave Function Collapse
class_name WaveFunctionCollapse
extends Node
# Celda del grid
class Cell:
var possibilities: Array = [] # Tiles posibles
var collapsed: bool = false
var value: int = -1
func entropy() -> float:
return log(possibilities.size()) if possibilities.size() > 0 else 0.0
# Reglas de adyacencia
var adjacency_rules: Dictionary = {}
var tile_weights: Dictionary = {}
var grid: Array[Array] = []
var width: int
var height: int
func initialize(p_width: int, p_height: int, tiles: Array):
width = p_width
height = p_height
# Inicializar grid
grid.clear()
for y in range(height):
var row: Array[Cell] = []
for x in range(width):
var cell = Cell.new()
cell.possibilities = tiles.duplicate()
row.append(cell)
grid.append(row)
func add_adjacency_rule(tile_a: int, tile_b: int, direction: Vector2i):
if not adjacency_rules.has(tile_a):
adjacency_rules[tile_a] = {}
var dir_key = "%d,%d" % [direction.x, direction.y]
if not adjacency_rules[tile_a].has(dir_key):
adjacency_rules[tile_a][dir_key] = []
adjacency_rules[tile_a][dir_key].append(tile_b)
func collapse():
while not is_fully_collapsed():
# Encontrar celda con menor entropía
var min_entropy_cell = find_minimum_entropy_cell()
if not min_entropy_cell:
push_error("No se puede colapsar más")
break
# Colapsar la celda
collapse_cell(min_entropy_cell)
# Propagar restricciones
propagate(min_entropy_cell)
func find_minimum_entropy_cell() -> Vector2i:
var min_entropy = INF
var min_cell = Vector2i(-1, -1)
for y in range(height):
for x in range(width):
var cell = grid[y][x]
if not cell.collapsed:
var entropy = cell.entropy()
# Agregar ruido para romper empates
entropy += randf() * 0.001
if entropy < min_entropy:
min_entropy = entropy
min_cell = Vector2i(x, y)
return min_cell
func collapse_cell(pos: Vector2i):
var cell = grid[pos.y][pos.x]
if cell.possibilities.size() == 0:
return
# Seleccionar tile basado en pesos
var selected = select_weighted(cell.possibilities)
cell.value = selected
cell.possibilities = [selected]
cell.collapsed = true
func select_weighted(possibilities: Array) -> int:
var total_weight = 0.0
for tile in possibilities:
total_weight += tile_weights.get(tile, 1.0)
var random_value = randf() * total_weight
var accumulated = 0.0
for tile in possibilities:
accumulated += tile_weights.get(tile, 1.0)
if accumulated >= random_value:
return tile
return possibilities[0]
func propagate(start_pos: Vector2i):
var stack = [start_pos]
while stack.size() > 0:
var current = stack.pop_back()
var current_cell = grid[current.y][current.x]
# Revisar vecinos
var directions = [
Vector2i(0, -1), # Arriba
Vector2i(1, 0), # Derecha
Vector2i(0, 1), # Abajo
Vector2i(-1, 0) # Izquierda
]
for dir in directions:
var neighbor_pos = current + dir
if neighbor_pos.x < 0 or neighbor_pos.x >= width or \
neighbor_pos.y < 0 or neighbor_pos.y >= height:
continue
var neighbor = grid[neighbor_pos.y][neighbor_pos.x]
if neighbor.collapsed:
continue
var old_possibilities = neighbor.possibilities.size()
constrain_neighbor(current_cell, neighbor, dir)
if neighbor.possibilities.size() < old_possibilities:
stack.append(neighbor_pos)
func constrain_neighbor(source: Cell, neighbor: Cell, direction: Vector2i):
var valid_tiles = []
for source_tile in source.possibilities:
var dir_key = "%d,%d" % [direction.x, direction.y]
if adjacency_rules.has(source_tile) and \
adjacency_rules[source_tile].has(dir_key):
for valid_tile in adjacency_rules[source_tile][dir_key]:
if valid_tile not in valid_tiles:
valid_tiles.append(valid_tile)
# Filtrar posibilidades del vecino
var new_possibilities = []
for tile in neighbor.possibilities:
if tile in valid_tiles:
new_possibilities.append(tile)
neighbor.possibilities = new_possibilities
func is_fully_collapsed() -> bool:
for row in grid:
for cell in row:
if not cell.collapsed:
return false
return true
func get_result() -> Array[Array]:
var result: Array[Array] = []
for row in grid:
var result_row: Array[int] = []
for cell in row:
result_row.append(cell.value)
result.append(result_row)
return result
Ejercicios Avanzados
Ejercicio 1: Sistema de Modificadores
Implementa un sistema de modificadores (buffs/debuffs) que:
- Soporte modificadores apilables y únicos
- Tenga duración y efectos periódicos
- Permita modificadores condicionales
- Incluya sistema de inmunidades
Ejercicio 2: IA con Behavior Trees
Crea un sistema de Behavior Trees que:
- Soporte nodos composite (Sequence, Selector, Parallel)
- Implemente decoradores (Inverter, Repeater, Conditional)
- Tenga blackboard para compartir datos
- Incluya depuración visual
Ejercicio 3: Sistema de Crafting
Diseña un sistema de crafting que:
- Soporte recetas con múltiples ingredientes
- Tenga sistema de calidad de items
- Implemente descubrimiento de recetas
- Incluya crafting en cadena
Recursos Avanzados
- GDExtension: Documentación oficial para crear extensiones en C++
- Godot Source Code: Estudiar el código fuente para entender internals
- Performance Monitors: Herramientas de profiling integradas
- Remote Debugger: Depuración de builds en dispositivos
- Custom Modules: Crear módulos personalizados para el editor
Conclusión
Has explorado las características más avanzadas de GDScript, desde metaprogramación hasta integración con código nativo. Estas técnicas te permiten crear sistemas complejos y optimizados para proyectos profesionales.
Recuerda que la complejidad debe justificarse por las necesidades del proyecto. No todas las técnicas avanzadas son necesarias para todos los juegos.
¡Continúa experimentando y creando sistemas innovadores con GDScript!