What is Dream Factory?

Creación Dinámica de Objetos: Guía de Factories

06/01/2023

Valoración: 4.86 (9958 votos)

En el desarrollo de videojuegos, a menudo nos enfrentamos a la necesidad de crear objetos mientras el juego está en marcha. Piensa en las balas que dispara un personaje, los enemigos que aparecen en oleadas, o las monedas que surgen de un bloque al golpearlo. Colocar cada uno de estos objetos manualmente en el editor es imposible o, como mínimo, tremendamente ineficiente. Aquí es donde entra en juego una herramienta fundamental y poderosa: el Factory Component. Este componente es la clave para dar vida a nuestros mundos de juego, permitiéndonos generar instancias de objetos de forma dinámica, controlada y eficiente.

How do I add a factory component to a game object?
When you add a factory component to a game object you specify in the Prototype property what game object file the factory should use as a prototype (also known as “prefabs” or “blueprints” in other engines) for all new game objects it creates. To trigger the creation of a game object, call factory.create():
Índice de Contenido

¿Qué es un Factory Component y para qué sirve?

Un Factory Component (o Componente de Fábrica) es un elemento que puedes añadir a un objeto de juego (Game Object) con un único y poderoso propósito: crear dinámicamente otros objetos de juego en tiempo de ejecución. Actúa, como su nombre indica, como una fábrica. Tú le proporcionas un plano y él se encarga de producir copias exactas de ese plano cuando se lo pidas.

Este "plano" se conoce como Prototipo (en otros motores de juego podrías conocerlo como "Prefab" o "Blueprint"). Se trata de un archivo de objeto de juego que has diseñado previamente. El Factory Component que agregas a un objeto en tu escena contendrá una referencia a este archivo de prototipo, listo para ser instanciado.

Creando tu Primer Objeto: factory.create() al Rescate

La magia de la creación de objetos reside en una única llamada de función: factory.create(). Para invocarla, necesitas un script que tenga acceso al componente de fábrica.

El uso más básico de esta función es especificar la URL del componente de fábrica que debe realizar la creación. Veamos un ejemplo sencillo donde creamos una estrella en una posición aleatoria en el eje Y:

-- En un script que controla el spawner local p = go.get_position() p.y = vmath.lerp(math.random(), min_y, max_y) -- "#star_factory" es el ID del componente de fábrica en el objeto actual local component = "#star_factory" factory.create(component, p) 

En este código, obtenemos la posición del objeto que contiene la fábrica, ajustamos su coordenada 'y' y luego llamamos a factory.create(), pasándole el ID del componente y la nueva posición. La nueva estrella aparecerá en esa ubicación.

Desglosando los Parámetros de factory.create()

La función factory.create() es muy flexible y acepta hasta cinco parámetros para personalizar el objeto recién creado:

  • url: (Obligatorio) El identificador del componente de fábrica que realizará la creación.
  • [position]: (Opcional) La posición en el mundo donde aparecerá el nuevo objeto. Debe ser un vector3. Si no se especifica, el objeto se crea en la misma posición que el componente de fábrica.
  • [rotation]: (Opcional) La rotación mundial del nuevo objeto. Debe ser un quat (quaternion).
  • [properties]: (Opcional) Una tabla de Lua que permite inicializar propiedades de script del nuevo objeto. ¡Esta es una característica increíblemente útil!
  • [scale]: (Opcional) La escala del objeto generado. Puede ser un número para una escala uniforme o un vector3 para una escala no uniforme en cada eje.

Personalización Avanzada en la Creación

La verdadera potencia de las fábricas se desata cuando usamos los parámetros opcionales. Por ejemplo, podemos no solo crear un objeto, sino también definir el valor inicial de sus variables de script. Imagina que quieres que una estrella recién creada otorgue una puntuación específica.

Primero, en el script de la estrella (star.script), definimos una propiedad:

-- star.script go.property("score", 1) -- Definimos la propiedad "score" con un valor por defecto de 1 function on_message(self, message_id, message, sender) if message_id == hash("collision_response") then -- Al colisionar, enviamos un mensaje con el valor de la puntuación msg.post("main#gui", "add_score", { amount = self.score }) go.delete() end end 

Ahora, al crear la estrella, podemos pasarle un valor para esa propiedad:

-- factory.script local p = go.get_position() p.y = vmath.lerp(math.random(), min_y, max_y) local component = "#star_factory" -- Creamos una estrella en la posición 'p', sin rotación, -- con la propiedad 'score' establecida en 10 y con el doble de su escala. factory.create(component, p, nil, { score = 10 }, 2.0) 

Con esta simple línea, hemos creado una instancia de estrella que vale 10 puntos en lugar de su valor por defecto de 1. Esto abre un abanico de posibilidades para crear enemigos con diferentes niveles de vida, power-ups con distintas duraciones, etc., todo desde un único prototipo.

Nota importante sobre la escala: Ten cuidado con la escala no uniforme (por ejemplo, vmath.vector3(1.0, 2.0, 1.0)). Aunque los componentes visuales como los sprites se escalarán correctamente, las formas de colisión (collision shapes) no lo harán. Esto puede llevar a comportamientos inesperados en la física de tu juego.

How do I add a factory component to a game object?
When you add a factory component to a game object you specify in the Prototype property what game object file the factory should use as a prototype (also known as “prefabs” or “blueprints” in other engines) for all new game objects it creates. To trigger the creation of a game object, call factory.create():

Gestionando los Objetos Creados

Crear objetos es solo la mitad del trabajo. También necesitamos poder interactuar con ellos y, eventualmente, eliminarlos. La función factory.create() nos devuelve el id del objeto de juego recién creado, que es nuestra llave para su gestión.

Comunicación y Direccionamiento

Con el id en mano, podemos enviar mensajes al objeto. Por ejemplo, para decirle a un enemigo recién creado que empiece a cazar a un objetivo:

local function create_hunter(target_id) local id = factory.create("#hunterfactory") msg.post(id, "hunt", { target = target_id }) return id end 

A veces, necesitas comunicarte con un componente específico dentro del objeto creado, como deshabilitar su sprite de arma. Para ello, construyes una URL completa usando msg.url(), que combina el id del objeto con el id del componente:

local function create_guard(unarmed) local id = factory.create("#guardfactory") if unarmed then -- Construimos la URL al componente de sprite llamado "weapon" local weapon_sprite_url = msg.url(nil, id, "weapon") msg.post(weapon_sprite_url, "disable") end end 

Seguimiento y Eliminación

Un patrón de diseño muy común es guardar los IDs de los objetos creados en una tabla. Esto es útil para gestionar grupos de objetos, como por ejemplo, eliminar todas las monedas de un nivel cuando el jugador muere.

-- spawner.script function init(self) self.spawned_coins = {} end -- ... más tarde, al crear una moneda local id = factory.create("#coinfactory", coin_position) table.insert(self.spawned_coins, id) -- ... y para eliminarlas todas function reset_level(self) for _, coin_id in ipairs(self.spawned_coins) do go.delete(coin_id) end -- O más eficientemente, la función go.delete() puede aceptar una tabla de IDs go.delete(self.spawned_coins) self.spawned_coins = {} end 

Optimización y Carga de Recursos

Por defecto, cuando se carga un componente de fábrica, todos los recursos de su prototipo (imágenes, sonidos, etc.) se cargan en memoria, listos para ser usados. Sin embargo, para niveles grandes o prototipos complejos, esto puede no ser ideal. Para ello, existen opciones de carga dinámica.

Carga Dinámica: Síncrona vs. Asíncrona

Al marcar la casilla "Load Dynamically" en las propiedades del componente de fábrica, pospones la carga de sus recursos. Tienes dos formas de proceder:

  1. Carga Síncrona: Simplemente llamas a factory.create() como siempre. El juego se detendrá momentáneamente (causando un "hitch" o tirón) mientras carga los recursos necesarios y luego creará el objeto. Es fácil de implementar, pero puede afectar la fluidez del juego.
  2. Carga Asíncrona: La mejor opción para una experiencia de usuario fluida. Primero llamas a factory.load(). Esta función comienza a cargar los recursos en segundo plano sin detener el juego. Cuando la carga ha finalizado, se llama a una función de callback que tú proporcionas. Dentro de esa función de callback, ya es seguro llamar a factory.create().
-- Ejemplo de carga asíncrona function load_complete(self, url, result) -- La carga ha terminado, ahora podemos crear objetos sin tirones self.go_id = factory.create(url) end function init(self) -- Iniciamos la carga asíncrona y le decimos qué función llamar cuando termine factory.load("#factory", load_complete) end 

No olvides llamar a factory.unload() cuando ya no necesites la fábrica para liberar la memoria de los recursos.

Flexibilidad Máxima: Prototipos Dinámicos

¿Y si quisieras que una misma fábrica pudiera crear diferentes tipos de enemigos? ¡Es posible! Activando la opción "Dynamic Prototype" en las propiedades del componente, puedes cambiar el prototipo que utiliza en tiempo de ejecución con la función factory.set_prototype().

-- Cambiar el prototipo de la fábrica para que ahora cree enemigos de tipo A factory.unload("#factory") -- Descargamos recursos anteriores factory.set_prototype("#factory", "/main/levels/enemyA.goc") local enemy_id = factory.create("#factory") 

Esto proporciona una flexibilidad increíble, aunque tiene un pequeño coste: la colección que contiene esta fábrica no podrá optimizar el conteo de componentes y usará los valores por defecto del proyecto.


Tabla Comparativa de Escenarios de Creación

EscenarioFunción ClaveParámetros RelevantesCaso de Uso Típico
Creación simple en una posiciónfactory.create()url, positionGenerar una moneda en una ubicación específica.
Creación con propiedades inicialesfactory.create()propertiesGenerar un enemigo con 100 puntos de vida iniciales.
Creación sin tirones (lag)factory.load()url, callbackCargar un jefe final complejo antes de que aparezca en pantalla.
Fábrica multi-propósitofactory.set_prototype()url, prototype_urlUn único spawner que genera diferentes tipos de power-ups.

Preguntas Frecuentes (FAQ)

¿Puedo crear un objeto exactamente en la misma posición que otro objeto existente?
Sí. Simplemente obtén la posición del objeto de referencia usando go.get_position() y pásala como el segundo parámetro a factory.create().

¿Qué sucede si intento crear más objetos del límite de instancias (max_instances) de mi proyecto?
La llamada a factory.create() fallará y no se creará ningún objeto. El juego no se bloqueará, pero es importante gestionar el número de objetos activos para no alcanzar este límite, que se configura en los ajustes del proyecto.

¿Debo implementar mi propio sistema de "Object Pooling"?
Generalmente, no es necesario. El motor ya realiza una optimización de pooling de objetos internamente. En la mayoría de los casos, es más rápido y limpio simplemente eliminar objetos con go.delete() y crear nuevos con factory.create() cuando los necesites. Crear tu propio sistema de pooling en Lua podría, de hecho, ralentizar el proceso.

¿Cómo puedo cambiar la animación de un sprite en un objeto que acabo de crear?
Primero, obtén el ID del objeto al crearlo. Luego, construye la URL completa al componente de sprite usando msg.url(nil, id, "id_del_sprite"). Finalmente, envía un mensaje "play_animation" a esa URL con el ID de la animación que deseas reproducir.

Conclusión

Los Factory Components son una piedra angular en el desarrollo de juegos dinámicos y complejos. Ofrecen un control granular sobre la creación, personalización y gestión de objetos en tiempo de ejecución. Desde generar simples proyectiles hasta orquestar complejas oleadas de enemigos con diferentes atributos, dominar las fábricas te permitirá construir mundos más vivos, reactivos y, en definitiva, más divertidos. Ahora que conoces sus secretos, ¡es hora de empezar a fabricar!

Si quieres conocer otros artículos parecidos a Creación Dinámica de Objetos: Guía de Factories puedes visitar la categoría Juegos.

Subir