En mi última publicación hablé sobre la diferencia entre usar Lumen o iluminación tradicional y por qué me interesa la opción tradicional.
Además, creé un Blueprint de configuración de escena para controlar el "PostProcessVolume" y cambiar entre Lumen y la iluminación tradicional usando el editor Unreal Engine y dentro del juego.
Ahora profundizaré en mis Blueprints de luces para tomar algunas notas importantes sobre la herencia y las instancias de los componentes porque hay algunos conceptos confusos que quiero aclarar.
HERENCIA
En el desarrollo de software este es un concepto muy importante en la Programación Orientada a Objetos y Unreal Engine puede aplicarlo en Blueprints, lo cual es muy útil.
Imagina que quieres hacer vehículos terrestres de diferentes tipos, por ejemplo, una moto, un coche y un autobús, y quieres hacer su lógica y propiedades. Podrías hacer el código para cada tipo de vehículo, pero tu programa se volverá un infierno porque estás triplicando el trabajo necesario para hacerlos (y el trabajo necesario para hacer mejoras y cambios más adelante en estas lógicas...)
Aquí es donde se necesita la herencia. Con la herencia, puedes especificar un objeto base, por ejemplo, un "vehículo terrestre", y derivar de él todas las variaciones que necesitará: la motocicleta, el automóvil y el autobús.
En este objeto base "vehículo terrestre" podemos especificar algunas lógicas y variables básicas, por ejemplo, cada vehículo terrestre puede transportar personas, tener un motor, moverse, tener luces, etc. Pero cada tipo de vehículo puede tener algunas características especiales. variables y lógicas que no se aplicarían en el resto de tipos de vehículos: por ejemplo, para ir en moto hay que llevar casco, un coche y un autobús tienen puertas, el autobús tiene máquina validadora de billetes, etc.
Entonces tenemos algo como esto:
Objeto base:
- Vehículo terrestre -> puede transportar personas, tiene un motor, luces que se pueden encender o apagar, se puede mover, etc.
Objetos secundarios derivados:
- Moto -> debe usar casco, puede tener un bloqueo de rueda, etc.
- Coche -> tiene puertas, tiene ventanas, etc.
- Autobús -> tiene puertas, tiene ventanas, tiene máquina validadora de boletos, etc.
¿Cómo funciona en Unreal Engine? Veamos un ejemplo con mi sistema de luces.
DIFERENTES TIPOS DE LÁMPARAS
Quiero un Blueprint para controlar una lámpara en el escenario, pero luego necesitaré diferentes tipos de luces. De momento tengo dos: una lámpara redonda y una lámpara alargada. La lámpara verde es la misma que la redonda solo que cambiando el color, luego lo veremos.
La lógica es la misma para cada una: necesito poder cambiar la luz y configurarla para usar Lumen o no.
Entonces debo poder cambiar el chasis de la lámpara y su color e intensidad para personalizar su luz. En un futuro añadiré opciones para que parpadeen y tiren chispas.
Como podéis ver en este diagrama, tendré un Blueprint con la lámpara base y dos derivados:
En "BP_Base_Lamp" habrá casi toda la lógica y las propiedades y luego en "BP_Protoype_LongLamp" y "BP_Prototype_PointLamp" solo habrá algunas lógicas de configuración y la malla.
Cada lámpara tendrá:
- Un "LightComponent" que será la luz principal que tendrá la lámpara.
- Uno o más "LightComponents" que serán las luces ambientales que proyectan la luz de cada lámpara para el modo de iluminación tradicional (Lumen no los necesitará).
- Una "Mesh" que será la bombilla con un material especial que brilla en el color de la luz principal.
Para lograr esto, también tendrá estas propiedades públicas:
- LightColor: una variable "LinearColor" que tendrá el color de la luz.
- LightOnIntensity: una variable "Float" con la intensidad que tendrá la luz.
- LightOffIntensity: otra variable "Float" con la intensidad cuando la luz está apagada. Por lo general, será cero, pero tal vez necesite algunas lámparas que aún funcionen con baja intensidad cuando estén apagadas para iluminar un poco un escenario oscuro.
- IsActive: una variable "boolean" para saber si esta luz está encendida o apagada. En el editor podemos encender o no la luz usando esta variable y ver el resultado.
- HideAmbientLight: este es otro "booleano" y será útil solo con fines de prueba. Ocultará todas las luces ambientales en el modo de iluminación tradicional para ayudarme en el proceso de iluminación del escenario.
Además, habrá algunas variables privadas que serán útiles en la lógica interna de este Blueprint, pero las revisaremos mientras exploramos su lógica.
INICIALIZACIÓN DE LÁMPARA BASE
Primero, necesitamos la lámpara base que contendrá la lógica y las variables principales: nuestro Blueprint base llamado "BP_Base_Lamp".
En el interior no haremos nada en la ventana "Viewport" ya que este Blueprint no necesita ninguna representación gráfica.
Necesitamos todas estas variables:
Es importante hacerlas públicas o privadas, ya que solo veremos las públicas en el editor de Unreal Engine y, en nuestro caso, solo necesitamos hacer públicas las variables de configuración para personalizar los Blueprints instanciados en el nivel.
Entonces necesitamos algunas funciones que llamaremos dentro de nuestra lógica de lámpara. En primer lugar, la función "InitializeLamp":
Esta función recibe tres parámetros:
- Una "Malla" llamada "Bulb" que será la bombilla donde encontrar el material brillante especial.
- Un componente de luz principal llamado "MainLight"
- Una colección de componentes de luces llamada "AmbientLights".
Necesitamos esta función para inicializar el Blueprint derivado.
Lo primero es establecer estos parámetros en variables privadas, ya que necesitaremos recuperarlos en algunos puntos dentro de la lógica de esta lámpara.
Es importante aclarar que estos parámetros recibirán una referencia a objetos que existen en el juego, pero estamos trabajando con un elemento abstracto que realmente no los tiene. Necesitamos instancias vivas de esta bombilla y componentes de luz para jugar con sus propiedades que afectan el mundo que los rodea, para lograrlo, debemos inicializarlos desde afuera en el constructor del Blueprint derivado porque es donde estos elementos cobran vida y podemos asignarlos dentro del Blueprint base usando esta función.
Luego obtenemos la malla de la bombilla y de su material creamos una "DynamicMaterialInstance" y la guardamos en una variable privada para usarla más tarde, este material tiene dos propiedades con las que podemos jugar: su color y brillo.
El siguiente paso es configurar las propiedades de la lámpara, esta es una función llamada "SetLampProperties" que desarrollaremos más adelante y en la que configuramos el color y la intensidad de la luz de la lámpara. Si la lámpara ahora está activa, configuramos "LightOnIntensity", de lo contrario configuraremos "LightOffIntensity".
Y el paso final es obtener "BP_Scene_Config" del nivel y configurar el uso de Lumen en esta lámpara. Para configurar Lumen usamos otra función que puedes ver a continuación llamada "ConfigLumen":
"ConfigLumen" recibe un booleano para saber si está activo o no. Luego hay un "ForEachLoop" en el que desactivamos cada luz ambiental de la lámpara si Lumen está activo o viceversa.
Ya tenemos la lámpara configurada. Esta lógica funciona cada vez que cambiamos una variable en el editor de Unreal Engine o al inicio del juego. Más adelante veremos dónde lanzar la función "InitializeLamp".
Una cosa más sobre la inicialización: en "BP_Scene_Config" tenemos dos eventos para activar o desactivar Lumen, y en "EventGraph" de "BP_Base_Lamp" los usamos para actualizar la configuración de Lumen. Esto ayuda a actualizar el estado de Lumen para cada lampara en el juego.
CONFIGURAR LAS PROPIEDADES DE LA LÁMPARA Y LA LUZ
Para configurar las propiedades de la lámpara tenemos una función llamada "SetLampProperties".
Primero, configuramos algunas variables locales para modificar esta luz. Una variable local es una variable que solo existe dentro de una función. En este caso, configuramos "ActualColor" y "ActualIntensity" para trabajar con ellos fácilmente dentro de esta función.
Configuramos las propiedades de luz de "MainLight" usando la variable privada que configuramos anteriormente en "InitializationLamp". Aquí usamos otra función personalizada llamada "SetLightProperties" donde establecemos el color y la intensidad de la luz, en este caso nuestro color e intensidad de nuestras variables locales.
Luego configuramos el material de la bombilla para que brille con nuestro color y una pequeña fracción de nuestra intensidad de luz. Para lograrlo, usamos "SetVectorParameterValue" para el color de la luz y "SetScalarParameterValue" para la intensidad sobre nuestra variable privada "LightBulbMaterial" que configuramos anteriormente en la función "InitializeLamp". Esto requiere un material especial con parámetros para establecer el color y la intensidad de su brillo, luego lo veremos.
Luego aplicamos las propiedades de la luz sobre todas las luces ambientales usando la misma función "SetLightProperties" pero dividiendo la intensidad porque la luz ambiental brilla menos que la luz principal. En caso de que estemos usando Lumen o queramos ocultar estas luces ponemos la intensidad a cero. Tenemos todas las luces ambientales en una matriz en nuestras variables privadas gracias a la función "InitializationLamp".
La función "SetLightProperties" es esta que puedes ver a continuación:
Es más simple de lo que parece. Si la intensidad de la luz es mayor que cero, establecemos el color y la intensidad y establecemos esta luz como visible, en el caso contrario ocultamos esta luz, porque no queremos hacer trabajo extra mostrando luces que son inútiles.
MATERIAL DE LA BOMBILLA
Anteriormente hemos hablado sobre un material especial que brilla. Es este: el material "M_BulbLight".
Es bastante simple. Creamos un material con dos parámetros llamados "LightColor" y "LightIntensity" que configuran el efecto de brillo de forma dinámica en el juego y en el editor de Unreal Engine. De este material hacemos una instancia de material que es la que usaremos en nuestras bombillas en nuestras lámparas.
DIFERENTES LÁMPARAS
Finalmente, podemos hacer diferentes lámparas usando nuestra base "BP_Base_Lamp". En el editor de Blueprints podemos configurar aquí la clase base para heredar todo: funciones, variables, mallas, etc:
Entonces, en los Blueprints de lámparas secundarias configuraremos la misma "BP_Base_Lamp" y luego usaremos la magia de la herencia para configurar dos lámparas diferentes.
En el "BP_Prototype_LongLamp" he hecho una malla de cubo con forma rectangular y otra más pequeña por dentro con el material de la bombilla "M_BulbLight". Para hacer las luces de ambiente pongo tres puntos de luz y para la luz principal pongo una luz rectangular. Luego podría haber configurado las variables para el color y la intensidad pero las dejé en sus valores predeterminados.
Lo importante es que usamos el constructor para inicializar la lámpara, aquí es donde llamamos a la famosa función "InitializeLamp".
Como puedes ver, configuramos las luces y la bombilla a través de la función "InitializeLamp" porque aquí tenemos las instancias de estos elementos.
Y para la lámpara redonda algo similar pero con diferente malla y menos luces de ambiente:
Puedo hacer más variaciones, esta cambiando el color de la luz cuando instanciamos un "BP_Prototype_PointLight" en el nivel:
Y el resultado es el que has visto al inicio de este post: