Mostrando entradas con la etiqueta Desarrollo de Juegos. Mostrar todas las entradas
Mostrando entradas con la etiqueta Desarrollo de Juegos. Mostrar todas las entradas

martes, 15 de marzo de 2011

“MUCHO TIEMPO SIN VERNOS”

Por los últimos meses he estado inmerso en una maratónica instancia de programación a efectos de publicar mi primera aplicación comercial en la Tienda de Apps de iTunes. Finalmente puedo decir con orgullo que salió al mercado!

http://itunes.apple.com/app/just-survive/id424392950

El nombre dice mucho sobre el juego en sí: “Just Survive!” (sólo sobrevive)

Comencé originalmente a trabajar en él durante 1998 y lo publiqué en Internet durante 1999. En aquel entonces había un único tipo de enemigos y los puntajes se guardaban localmente. El juego no llegó a los ojos de muchas personas puesto que todo era muy diferente al grado de globalización que estamos viviendo ahora, pero recuerdo haber recibido un mensaje de email de alguien que escribió algo así como:

Buen Juego. Lo he estado jugando por 45 minutos y quiero saber cuánto tiempo más tengo que jugar para llegar al primer jefe; por cierto, los puntajes no se están salvando en tu servidor …”

Ese mensaje me ayudó a decidir –algunos años más tarde- si me metía de lleno en la Industria independiente (“inide”). Y hoy, heme aquí …

El juego ha sufrido varias modificaciones desde entonces, 90% de ellas sucediendo durante 2010/2011, por lo que ahora si puedo decir que podrán luchar con varios tipos de enemigo, más de un jefe y hay una tabla de puntajes más altos global!

Aquí está el video promocional en Youtube:

Just Survive! First Trailer

Este es uno de los cuatro juegos que tengo en producción para diferentes plataformas, así que en los próximos meses aguarden novedades sobre ellos.

Espero que les guste … y lo compren!
~Pete

> Vínculo a la versión en inglés.

jueves, 14 de octubre de 2010

REEMPLAZANDO EL API DE GESTION DE CONTENIDO: PRELUDIO

Como mencioné brevemente en una publicación anterior, por un período de tiempo (ya cercano a 2 años) he estado desarrollando mi propio API de gestión de contenido como reemplazo del liberado como parte del Framework de XNA.

¿Por qué? Al comienzo estaba tratando de sortear las limitaciones de este último respecto a su incapacidad de gestionar con propiedad la operación Dispose. Y al hacerlo, noté -cuando miré el código fuente del original con “Reflector”- que había espacio para mejoras (con un rediseño).

Luego de casi dos años de desarrollarlo como parte de mi propio motor de videojuegos basado en XNA (estando este último aún en desarrollo), decidí rediseñar mi gestor de contenido como un módulo independiente para que pueda ser utilizado con XNA sin tener que enchufar mi motor (ni completamente ni en parte).

No voy a liberar el código fuente de mi API, así que en las próximas entregas de esta serie de artículos voy a discutir públicamente lo que mi API tiene para ofrecer, de forma que cualquiera pueda leerlo, enfocándome en “El Qué” y no en “El Cómo”; así que estaré presentando principalmente características y resultados (si … ya se, ya se, … “que plomazo!”).

Entonces, qué sigue? En la parte 1 (y muy posiblemente además en la parte 2) voy a presentar mi API, explicando las diferencias con la que se encuentra por defecto, y luego de ello empezaré a hablar de los resultados obtenidos (sí! con cifras, capturas de pantalla y demás) para las diferentes plataformas donde corrí las pruebas.

Hasta la próxima,
~Pete

> Vínculo a la versión en inglés.

viernes, 10 de septiembre de 2010

OBTENIENDO UNA REFERENCIA AL OBJETO QUE INVOCA A UN METODO EN C#

Ok, luego de tomarme un mes de asueto de cualquier actividad de blogueo, supongo que es tiempo de ponerme al día y publicar de nuevo. Y creo que no hay mejor forma de hacerlo que escribir un artículo explicando una forma de obtener una referencia a un objeto que llama a un método específico.

Durante los últimos dos años he estado trabajando duro en un API de reemplazo al Content Manager del Framework de XNA para usarla en mi propio motor de videojuegos “que nunca finaliza” (voy a escribir sobre esto más adelante). En etapas tempranas de su desarrollo encontré una forma de solucionar esta traba referencial que ha estado creando dolores de cabeza a muchos desarrolladores, globalmente (siendo yo, uno de ellos, por mucho tiempo).

Aunque debo admitir que en algún punto del ciclo de desarrollo de mi API decidí abandonar esta solución “parche”, luego de leer uno de los artículos mencionados por Shawn aquí (adivinen cuál), entiendo que es una gran oportunidad para hablar de ello.

Un poco de historia … previo a encontrar esta solución lo intenté todo: desde probar usar, sin suerte, la información contenida en instancias del StackFrame e incluso delegados hasta finalmente “desistir” y utilizar un enfoque ingenuo como ser el pasar al invocador como parámetro en la firma de la operación. Pero, luego vino el Framework .NET 3 y los métodos de extensión al rescate!

Por favor coincidan conmigo que el ejemplo presentado en este artículo es uno simple para mostrar su uso, pero créanme cuando digo que puede extenderse a escenarios complejos.

Dicho eso … si desean saber cuál objeto está invocando a un cierto método de una clase como (que puede esta sellada o nó):

  public sealed class MyCalledClass
  {
    internal bool JustReturnTrue(int myParameter)
    {
      return true;
    }
  }

Siendo el invocador, digamos, una especificación de la siguiente clase:

  public abstract class MyCallingClass
  {
    public void Print()
    {
      Console.WriteLine("I am the object which is just about to call
         the monitored operation named 'JustReturnTrue' …"
);
    }
  }

Entonces todo lo que tienen que hacer es implementar el método de extensión que sigue para los tipos que deseen (en mi ejemplo, “MyCallingClass”):

  public static class MyExtensionMethod
  {
    public static bool ExecuteOperation<T>
      (this T mycallingClass, int myParameter)
      where T: MyCallingClass
    {
      mycallingClass.Print(); // Cámbienlo con sus propias cosas.
 
      return new MyCalledClass().JustReturnTrue(myParameter);
    }
  }

El truco aquí es diseñar y separar ensamblados y espacios de nombre de forma tal que todas las instancias de “MyCallingClass” no puedan ejecutar directamente la operación “JustReturnTrue” (dejo esa tarea como un ejercicio para el lector).

Pero hay un tema a seguir de cerca, sin embargo. Al hacer esto están de hecho agregando una llamada más (cosa que ya saben), lo cual no es en general un problema en Windows, pero para la XBox 360 y todos los dispositivos que utilicen el Compact Framework, podría resultar caro si se utiliza muchas veces en tarea o bucles intensivos o pesados.

Pero si Uds. realmente lo necesitan cuando la velocidad no es un inconveniente o para operaciones donde, por ejemplo, precisen establecer dueños de algo “automágicamente” tras bambalinas y luego evaluar si un dueño llama a una operación restringida antes de ejecutarla, entonces ahí lo tienen!

Sólo úsenlo sabiamente …
~Pete

> Vínculo a la versión en inglés.

martes, 8 de diciembre de 2009

VIDEOS DEL PDC09 DISPONIBLES

Los videos que muestran todas las presentaciones del PDC09 están ahora disponibles para o bien verlos online o bien descargarlos.

En particular, para aquellos interesados en utilizar DX11 para desarrollar con .NET aplicaciones & juegos para Windows 7, miren estos videos:

Que los disfruten,
~Pete

> Vínculo a la versión en inglés.

viernes, 25 de septiembre de 2009

OBSERVANDO AL SOL ARDER - PARTE 2

En la parte 1 de la serie, introduje el motor de iluminación y renderizado llamado “Sunburn”, de Synapse Gaming.

Bien, ya salió la versión 1.2.4 del motor, trayendo consigo un impulso en la performance de la técnica de renderizado "hacia adelante".

Aquí encontrarán mis resultados más recientes para la demo de reflección/refracción (la cual utiliza la técnica antedicha):

1) Plataforma PC:

  • Min: 37 fps -> mirando a las tres orbes casi llegando al techo (cerca de las ventanas de arriba),
  • Max: 60 fps -> mirando a una de las ventanas de arriba, y
  • Promedio: 43 fps -> en general (a veces un poco más en los corredores, sin encarar hacia a las orbes).

2) Consola XBox 360:

  • Min: 28 fps -> mismo caso que para la plataforma PC,
  • Max: 54 fps -> idem,
  • Promedio: 32 fps -> idem.

Es importante notar que, en la demo, la escene es renderizada tres veces por cuadro: una vez para la imagen de reflexión, una vez para la imagen de refracción, y unavez para la salida final. Por qué? A efectos de permitir un comportamiento dinámico. Y qué significa? En vez de simular el efecto con fotos “estáticas”, lo que ven se actualiza y calcula en tiempo real; por lo que objetos en movimientos son captados por el proceso, refractados y reflejados.

Nuevamente, para ambas pruebas dibujé la imagen final sobre un back-buffer de alta resolución de 1280x720, pero esta vez también corrí las pruebas en la XBox 360 (ambas con una marca de tiempo variable).

Dados estos resultados, no puedo esperar a ver una versión con renderizado diferido en la XBox 360!

Ahora bien, cuán difícil es el uso de Sunburn? Averigüémoslo, bien?

I. El Código.

Continuemos utilizando la demo de “Refleccón/Refracción” para demostrar cómo se usa Sunburn. Por favor descarguen el proyecto de demostración de la sección de descargas de SG antes de seguir leyendo.

Cuando abran el proyecto en Visual Studio 2008, van a encontrar la estructura inicial usual: la clase "game" y la carpeta de contenido.

En lo que sigue, estaré explicando que incluyen, basado en el ejemplo, pero únicamente para el código relativo al motor en sí:

i. Declaraciones "Using"

Hay una buena cantidad de espacios de nombrea referenciar, pero para este ejemplo sólo concentraré mis comentarios en dos de ellas:

   1: using SynapseGaming.LightingSystem.Effects.Forward;
   2: ...
   3: using SynapseGaming.LightingSystem.Rendering.Forward;

En caso que decidan usar Renderizado Diferido, tendrán que cambiar “Forward” por “Deferred” aqui (y luego, como verán basados en el chequeo de sintaxis realizado por VS08, deberán modificar algúnas partes del código, de manera acorde).

ii. Campos

Los campos adicionales agregados a la clase "game", en comparación a la estándar, pueden separase en tres categorías: a) el sistema de iluminación, b) los miembros de la escena, y c) los miembros de la técnica.

a) basicamente, deben declarar aquí tanto los gestores de iluminación y renderizado, más los ayudantes que proverán datos específicos al sistema, como ser información del ambinente y preferencias de iluminación más detalle.

   1: LightingSystemManager lightingSystemManager;
   2: RenderManager renderManager;
   3: ...
   4: SceneState sceneState;
   5: SceneEnvironment environment;
   6: LightingSystemPreferences preferences;
   7: ...
   8: DetailPreference renderQuality = DetailPreference.Off;

b) junto con las mallas que conforman el "scenegraph", deben declarar todas las luces que se usarán en la escena más su respectivos “aparejos” (rigs); ahora bien, qué es un aparejo? Es un contenedor que guardará, organizará y ayudarña a comprartir las luces de la escena en dicha escena.

   1: ...
   2: LightRig lightRig;
   3: PointLight keyLight;
   4: PointLight fillLight;
   5: DirectionalLight sunLight;
   6: ...

c) principalmente, deben declarar los efectos de renderizado "hacia adelante" junto con los ayudantes de objetivo de renderizado (los últimos dan soporte para reflección, refracción y las usuales llamadas a la función de dibujo para renderizar en texturas).

   1: SasEffect orbEffect;
   2: SasEffect waterEffect;
   3: ...
   4: RenderTargetHelper refractionTarget;
   5: RenderTargetHelper waterReflectionTarget;

iii. Constructor

Llendo a los miembros de inicialización, deben instanciar la mayoría de los campos antedichos y establecer las preferencias de iluminación y detalle basados en la plataforma objetivo.

   1: // Load the user preferences (example - not required).
   2: preferences = new LightingSystemPreferences();
   3: #if !XBOX
   4: if (File.Exists(userPreferencesFile))
   5:     preferences.LoadFromFile(userPreferencesFile);
   6: else
   7: #endif
   8: {
   9:     preferences.EffectDetail = DetailPreference.High;
  10:     preferences.MaxAnisotropy = 1;
  11:     preferences.PostProcessingDetail = DetailPreference.High;
  12:     preferences.ShadowDetail = DetailPreference.Low;
  13:     preferences.ShadowQuality = 1.0f;
  14:     preferences.TextureQuality = DetailPreference.High;
  15:     preferences.TextureSampling = SamplingPreference.Anisotropic;
  16: }

Es interesante notar que, en caso de la plataforma PC, el ejemplo brinda un medio para seleccionar el nivel de detalle basado en el fabricante de la tarjeta gráfica y el número de modelo de la misma.

   1: // Pick the best performance options based on hardware.
   2:  VideoHardwareHelper hardware = new VideoHardwareHelper();
   3:  
   4:  if (hardware.Manufacturer == VideoHardwareHelper.VideoManufacturer.Nvidia)
   5:  {
   6:      if (hardware.ModelNumber >= 8800  hardware.ModelNumber < 1000)
   7:          renderQuality = DetailPreference.High;
   8:      else if (hardware.ModelNumber >= 7800)
   9:          renderQuality = DetailPreference.Medium;
  10:      else if (hardware.ModelNumber >= 6800)
  11:          renderQuality = DetailPreference.Low;
  12:  }
  13:  else if (hardware.Manufacturer == VideoHardwareHelper.VideoManufacturer.Ati)
  14:  {
  15:      if (hardware.ModelNumber >= 3800)
  16:          renderQuality = DetailPreference.High;
  17:      else if (hardware.ModelNumber >= 3400)
  18:          renderQuality = DetailPreference.Medium;
  19:      else if (hardware.ModelNumber >= 2600)
  20:          renderQuality = DetailPreference.Low;
  21:  }
  22:  
  23:  switch (renderQuality)
  24:  {
  25:      case DetailPreference.High:
  26:          reflectionRefractionTargetSize = 512;
  27:          reflectionRefractionTargetMultiSampleType = MultiSampleType.TwoSamples;
  28:          graphics.PreferMultiSampling = true;
  29:          break;
  30:      case DetailPreference.Medium:
  31:          reflectionRefractionTargetSize = 256;
  32:          reflectionRefractionTargetMultiSampleType = MultiSampleType.TwoSamples;
  33:          graphics.PreferMultiSampling = true;
  34:          break;
  35:      case DetailPreference.Low:
  36:          reflectionRefractionTargetSize = 128;
  37:          reflectionRefractionTargetMultiSampleType = MultiSampleType.TwoSamples;
  38:          graphics.PreferMultiSampling = false;
  39:          break;
  40:      case DetailPreference.Off:
  41:          reflectionRefractionTargetSize = 128;
  42:          reflectionRefractionTargetMultiSampleType = MultiSampleType.None;
  43:          graphics.PreferMultiSampling = false;
  44:          break;
  45:  }

Importante: puesto que el gestor de renderizado utiliza por defecto un tamaño de “página” de 2048 pixeles para mapeo de sombras, en caso de apuntar a la Xbox 360, este valor debe reducirse a 1024 pixeles para que la página encaje completamente dentro de la EDRAM de la 360, evitando así el llamado "mosaico predicado" (predicated tiling).

   1: ...
   2: renderManager.ShadowManager.PageSize = 1024;
   3: ...

iv. Cargando Contenido:

Primero, deben crear los ayudantes de objetivo de renderizado y aplicar las preferencias, en este caso, para renderizar los efectos de refracción y reflección, al ayudante que corresponda.

   1: // Create reflection / refraction targets.  Note the refraction target is using
   2: // the "Standard" type to avoid clipping as the map is used by all refractive
   3: // objects (not just one with a specific surface plane).  See the comments at the
   4: // top of the page for details on why this is done.
   5:  
   6: refractionTarget = new RenderTargetHelper(graphics, RenderTargetHelper.TargetType.Standard,
   7:     reflectionRefractionTargetSize, reflectionRefractionTargetSize, 1, SurfaceFormat.Color,
   8:     reflectionRefractionTargetMultiSampleType, 0, RenderTargetUsage.PlatformContents);
   9:  
  10: waterReflectionTarget = new RenderTargetHelper(graphics, RenderTargetHelper.TargetType.Reflection,
  11:     reflectionRefractionTargetSize, reflectionRefractionTargetSize, 1, SurfaceFormat.Color,
  12:     reflectionRefractionTargetMultiSampleType, 0, RenderTargetUsage.PlatformContents);
  13:  
  14:  
  15: // Setup the refraction and reflection preferences.  These preferences are
  16: // set to a lower quality than the main scene's rendering to increase performance
  17: // and because reflection / refraction distortions from the normal map will
  18: // hide the quality.
  19:  
  20: refractionPreferences = new LightingSystemPreferences();
  21: refractionPreferences.EffectDetail = DetailPreference.Low;
  22: refractionPreferences.MaxAnisotropy = 0;
  23: refractionPreferences.PostProcessingDetail = DetailPreference.Low;
  24: refractionPreferences.ShadowDetail = DetailPreference.Low;
  25: refractionPreferences.ShadowQuality = 0.25f;
  26: refractionPreferences.TextureSampling = SamplingPreference.Trilinear;
  27:  
  28: refractionTarget.ApplyPreferences(refractionPreferences);
  29: waterReflectionTarget.ApplyPreferences(refractionPreferences);

Luego deberán leer del disco los valores que especifican cómo configurar los efectos a fin de utilizarlos como materiales.

   1: // Load the custom materials / effects used by the additional reflection / refraction
   2: // rendering pass.  These materials both use the same FX file with different material options.
   3:  
   4: orbEffect = Content.Load<SasEffect>("Effects/Orb");
   5: waterEffect = Content.Load<SasEffect>("Effects/Water");

El contenido de los archivos “.mat” originales es cómo sigue:

//-----------------------------------------------
// Synapse Gaming - SunBurn Lighting System
// Exported from the SunBurn material editor
//-----------------------------------------------
 
Locale: en-US
 
AffectsRenderStates: False
BlendColor: 0.6 0.6 0.4
BlendColorAmount: 0
BumpAmount: 0.017
BumpTexture: ""
DoubleSided: False
EffectFile: "ReflectionRefraction.fx"
Invariant: False
ReflectAmount: 0.5
ReflectTexture: ""
RefractTexture: ""
ShadowGenerationTechnique: ""
Technique: "Technique1"
Tint: 0.8627451 0.9254902 0.9647059
Transparency: 0.5
TransparencyMapParameterName: "ReflectTexture"
TransparencyMode: None

A continuación, cargan la estructura del aparejo de luz, el cual declara cada luz y su respectiva configuración y recorren dicha estructura para instanciar y configurar a cada luz.

   1: // LightRigs contain many lights and light groups.
   2: lightRig = Content.Load<LightRig>("Lights/Lights");
   3:  
   4: // Need to find the lights for later performance adjustments.
   5: foreach (ILightGroup group in lightRig.LightGroups)
   6: {
   7:     foreach (ILight light in group.Lights)
   8:     {
   9:         if (light is PointLight)
  10:         {
  11:             PointLight pointlight = light as PointLight;
  12:  
  13:             if (pointlight.Name == "FillLight")
  14:                 fillLight = pointlight;
  15:             else if (pointlight.Name == "KeyLight")
  16:                 keyLight = pointlight;
  17:         }
  18:         else if (light is DirectionalLight)
  19:         {
  20:             DirectionalLight dirlight = light as DirectionalLight;
  21:  
  22:             if (dirlight.Name == "Sun")
  23:                 sunLight = dirlight;
  24:         }
  25:     }
  26: }

El contenido del archivo “.rig” original es como sigue:

<root>
  <LightRig>
    <LightGroups>
      <GroupList>
        <item_0>
          <LightGroup>
            <Name>EnvLighting</Name>
            <ShadowType>SceneLifeSpanObjects</ShadowType>
            <Position>
              <Vector3>
                <X>0</X>
                <Y>0</Y>
                <Z>0</Z>
              </Vector3>
            </Position>
            <Radius>0</Radius>
            <ShadowQuality>0.5</ShadowQuality>
            <ShadowPrimaryBias>1</ShadowPrimaryBias>
            <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
            <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
            <ShadowGroup>False</ShadowGroup>
            <Lights>
              <LightList>
                <item_0>
                  <AmbientLight>
                    <Name>Ambient Lighting</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>1</X>
                        <Y>0.6431373</Y>
                        <Z>0.04313726</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>0.3</Intensity>
                  </AmbientLight>
                </item_0>
                <item_1>
                  <DirectionalLight>
                    <Name>Sun</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>1</X>
                        <Y>0.972549</Y>
                        <Z>0.772549</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>2.6</Intensity>
                    <ShadowType>AllObjects</ShadowType>
                    <Direction>
                      <Vector3>
                        <X>-0.5012565</X>
                        <Y>-0.8552828</Y>
                        <Z>-0.1312759</Z>
                      </Vector3>
                    </Direction>
                    <ShadowQuality>2</ShadowQuality>
                    <ShadowPrimaryBias>1.3</ShadowPrimaryBias>
                    <ShadowSecondaryBias>0.01</ShadowSecondaryBias>
                    <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
                  </DirectionalLight>
                </item_1>
              </LightList>
            </Lights>
          </LightGroup>
        </item_0>
        <item_1>
          <LightGroup>
            <Name>OrbLighting</Name>
            <ShadowType>SceneLifeSpanObjects</ShadowType>
            <Position>
              <Vector3>
                <X>0</X>
                <Y>0</Y>
                <Z>0</Z>
              </Vector3>
            </Position>
            <Radius>0</Radius>
            <ShadowQuality>0.5</ShadowQuality>
            <ShadowPrimaryBias>1</ShadowPrimaryBias>
            <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
            <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
            <ShadowGroup>False</ShadowGroup>
            <Lights>
              <LightList>
                <item_0>
                  <PointLight>
                    <Name>FillLight</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>0.3803922</X>
                        <Y>0.8313726</Y>
                        <Z>0.9411765</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>3.8</Intensity>
                    <FillLight>True</FillLight>
                    <FalloffStrength>0</FalloffStrength>
                    <ShadowType>AllObjects</ShadowType>
                    <Position>
                      <Vector3>
                        <X>25.83315</X>
                        <Y>10.99056</Y>
                        <Z>-75.42744</Z>
                      </Vector3>
                    </Position>
                    <Radius>46</Radius>
                    <ShadowQuality>0</ShadowQuality>
                    <ShadowPrimaryBias>1</ShadowPrimaryBias>
                    <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
                    <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
                  </PointLight>
                </item_0>
                <item_1>
                  <PointLight>
                    <Name>KeyLight</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>0.4627451</X>
                        <Y>0.8980392</Y>
                        <Z>1</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>0.6</Intensity>
                    <FillLight>False</FillLight>
                    <FalloffStrength>0</FalloffStrength>
                    <ShadowType>AllObjects</ShadowType>
                    <Position>
                      <Vector3>
                        <X>25.83315</X>
                        <Y>10.99056</Y>
                        <Z>-75.42744</Z>
                      </Vector3>
                    </Position>
                    <Radius>110</Radius>
                    <ShadowQuality>0.25</ShadowQuality>
                    <ShadowPrimaryBias>1</ShadowPrimaryBias>
                    <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
                    <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
                  </PointLight>
                </item_1>
              </LightList>
            </Lights>
          </LightGroup>
        </item_1>
      </GroupList>
    </LightGroups>
  </LightRig>
</root>

Finalmente, cargan los datos que configuran el ambiente.

   1: // Load the scene settings.
   2: environment = Content.Load<SceneEnvironment>("Environment/Environment");

Siendo el contenido de los archivos “.env” originales el siguiente:

<root>
  <SceneEnvironment>
    <VisibleDistance>500</VisibleDistance>
    <FogEnabled>True</FogEnabled>
    <FogColor>
      <Vector3>
        <X>0</X>
        <Y>0</Y>
        <Z>0</Z>
      </Vector3>
    </FogColor>
    <FogStartDistance>70</FogStartDistance>
    <FogEndDistance>200</FogEndDistance>
    <ShadowFadeStartDistance>500</ShadowFadeStartDistance>
    <ShadowFadeEndDistance>5000</ShadowFadeEndDistance>
    <ShadowCasterDistance>5000</ShadowCasterDistance>
    <BloomAmount>2</BloomAmount>
    <BloomThreshold>0.7</BloomThreshold>
    <ExposureAmount>1</ExposureAmount>
    <DynamicRangeTransitionMaxScale>4.5</DynamicRangeTransitionMaxScale>
    <DynamicRangeTransitionMinScale>0.5</DynamicRangeTransitionMinScale>
    <DynamicRangeTransitionTime>0.5</DynamicRangeTransitionTime>
  </SceneEnvironment>
</root>

Las tareas restantes son las usuales con excepción de la relacionada al campo local del tipo “EffectBatchHelper”.

   1: ...
   2: EffectBatchHelper batcher = new EffectBatchHelper();
   3: batcher.CollapseEffects(scene);
   4: ...

Para qué sirve? Ayuda a crear lotes de effectos, analizando los efectos usados por cada modelo en la escena. O en otras palabras, colapsa "materiales" similares a fin de optimizar las llamadas de dibujo.

v. Actualizando:

La única cosa especial a notar aquí tiene que ver con el efecto de agua, ya que en cada llamada de actualización se calcula la textura de normales a establecer para lograr el efecto de animación en la superficie del agua.

   1: // Apply the current water animation "frame" to the water effects.
   2: for (int p = 0; p < water.MeshParts.Count; p++)
   3: {
   4:     ModelMeshPart part = water.MeshParts[p];
   5:     if (part.Effect is LightingEffect)
   6:         (part.Effect as LightingEffect).NormalMapTexture = waternormalmapframe;
   7: }

Más allá de ello, no hay nada adicional que comentar porque todos los gestores son automáticamente actualizados por la propia instancia de la clase "game".

vi. Dibujando:

Cómo dije al comienzo de este artículo, el juego renderiza la textura de refracción, luego la de reflección y finalmente la salida principal.

A fin del lograr este objetivo, primero deben configurar el estado de la escena.

   1: // Setup the scene state.
   2: sceneState.BeginFrameRendering(view, projection, gameTime, environment);
   3: ...

Entonces, para cada mapa “especial” (en este caso, en order, los de refracción y reflexión), seleccionan el objetivo de renderizado, qué luces están activas y que objetos afectan, y dibujan la escena.

   1: //-------------------------------------------
   2: // Generate the refraction map.
   3:  
   4: // Adjust the reflection / refraction lighting based on performance.
   5: if (renderQuality == DetailPreference.High)
   6: {
   7:     keyLight.Enabled = true;
   8:     keyLight.ShadowType = ShadowType.AllObjects;
   9:     fillLight.Enabled = true;
  10:     fillLight.ShadowType = ShadowType.AllObjects;
  11:     sunLight.Enabled = true;
  12: }
  13: else
  14: {
  15:     keyLight.Enabled = false;
  16:     keyLight.ShadowType = ShadowType.None;
  17:     fillLight.Enabled = true;
  18:     fillLight.ShadowType = ShadowType.None;
  19:     sunLight.Enabled = true;
  20: }
  21:  
  22: // Add the light rig.
  23: renderManager.LightManager.SubmitLightRig(lightRig, ObjectLifeSpan.Frame);
  24:  
  25: // Begin generating the refraction map.
  26: refractionTarget.BeginFrameRendering(sceneState);
  27:  
  28: // Clear the depth buffer then render.
  29: graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Gray, 1.0f, 0);
  30: RenderTarget(refractionTarget);
  31: refractionTarget.EndFrameRendering();

   1: //-------------------------------------------
   2: // Generate the water reflection map.
   3:  
   4: // Adjust the reflection / refraction lighting based on performance.
   5: if (renderQuality != DetailPreference.High)
   6:     sunLight.Enabled = false;
   7:  
   8: // Add the light rig.
   9: renderManager.LightManager.SubmitLightRig(lightRig, ObjectLifeSpan.Frame);
  10:  
  11: // The water reflection map includes the orbs so add them as dynamic frame objects.
  12: foreach (Orb orb in orbs)
  13:     renderManager.SubmitRenderableObject(orb.model, orb.mesh, orb.currentMeshToObject, sceneWorld, false, ObjectLifeSpan.Frame);
  14:  
  15: // Begin generating the water reflection map.
  16: waterReflectionTarget.BeginFrameRendering(sceneState, waterWorldPlane);
  17:  
  18: // Clear the depth buffer then render.
  19: graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Gray, 1.0f, 0);
  20: RenderTarget(waterReflectionTarget);
  21: waterReflectionTarget.EndFrameRendering();

Por último, se renderiza la escena por tercera vez combinando ambos mapas previos y con ello, se obtiene la salida final de la escena.

   1: //-------------------------------------------
   2: // Render the main scene.
   3:  
   4: // Adjust the lighting based on performance.
   5: if (renderQuality == DetailPreference.High  renderQuality == DetailPreference.Medium)
   6: {
   7:     keyLight.Enabled = true;
   8:     keyLight.ShadowType = ShadowType.AllObjects;
   9:     fillLight.Enabled = true;
  10:     fillLight.ShadowType = ShadowType.AllObjects;
  11:     sunLight.Enabled = true;
  12: }
  13: else
  14: {
  15:     keyLight.Enabled = renderQuality != DetailPreference.Off;
  16:     keyLight.ShadowType = ShadowType.AllObjects;
  17:     fillLight.Enabled = true;
  18:     fillLight.ShadowType = ShadowType.None;
  19:     sunLight.Enabled = true;
  20: }
  21:  
  22: // Add the light rig.
  23: renderManager.LightManager.SubmitLightRig(lightRig, ObjectLifeSpan.Frame);
  24:  
  25: // The main rendering pass includes all objects so add the water and orbs as dynamic frame objects.
  26: foreach (Orb orb in orbs)
  27:     renderManager.SubmitRenderableObject(orb.model, orb.mesh, orb.currentMeshToObject, sceneWorld, false, ObjectLifeSpan.Frame);
  28: renderManager.SubmitRenderableObject(scene, water, waterMeshToObject, sceneWorld, false, ObjectLifeSpan.Frame);
  29:  
  30:  
  31: // Apply main scene preferences (higher quality than reflection / refraction).
  32: renderManager.ApplyPreferences(preferences);
  33:  
  34: // Begin main frame rendering.
  35: editor.BeginFrameRendering(sceneState);
  36: renderManager.BeginFrameRendering(sceneState);
  37:  
  38: // Clear the depth buffer then render.
  39: graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
  40: renderManager.Render();
  41:  
  42:  
  43: // Setup and render reflective / refractive pass for water and orbs using additive blending.
  44: GraphicsDevice.RenderState.AlphaBlendEnable = true;
  45: GraphicsDevice.RenderState.SourceBlend = Blend.One;
  46: GraphicsDevice.RenderState.DestinationBlend = Blend.One;
  47:  
  48: foreach (Orb orb in orbs)
  49:     RenderMesh(orb.mesh, orb.currentMeshToObject * sceneWorld, sceneState, orbEffect, null, refractionTarget.GetTexture());
  50:  
  51: GraphicsDevice.RenderState.CullMode = CullMode.None;
  52:  
  53: RenderMesh(water, waterMeshToObject * sceneWorld, sceneState, waterEffect,
  54:     waterReflectionTarget.GetTexture(), refractionTarget.GetTexture());
  55:  
  56: GraphicsDevice.RenderState.AlphaBlendEnable = false;
  57: GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
  58:  
  59:  
  60: // Done rendering main frame.
  61: renderManager.EndFrameRendering();
  62: editor.EndFrameRendering();

Cada paso de renderizado comienza y finaliza de manera similar a como lo hace la clase “SpriteBatch”, así que no hay sorpresa aquí.

Finalmente, terminamos con el renderizado del cuadro de la escena.

   1: sceneState.EndFrameRendering();
   2: ...

vii. En Suma:

Todas las demos prvistas incluyen una explicación detallada sobre lo que ocurre en cada ejemplo, lo que les ayuda mucho a entender la lógica detrás de los procesos del motor.

II. El Editor.

Bien pueden estarse preguntando: “cómo puedo acelerar el proceso de modelado? Si tengo que escribir a mano los archivos de materiales y demás, sería incómodo!”.

Tienen razón, pero, por fortuna el motor viene con un editor que los salva de realizar dicha tarea.

Cómo? Simple, utilizando la clase "game" provista por Synapse Gaming, Uds. pueden cambiar los materiales, y las posiciones de las luces, entre otras cosas.

Nota: a fin de utilizar el editor deberán agregar un campo más al código: un campo de tipo "LightingSystemEditor".

El siguiente video lo dice todo:

Por más videos mostrando el motor Sunburn por favor visiten este sitio:

http://www.youtube.com/user/bobthecbuilder

Bueno, eso es todo. Ahora es el turno de Uds. para probar y compartir su experiencia usando el motor Sunburn!

Vamos a la pausa,
~Pete

 

> Vínculo a la versión en inglés.