API de Juegos de iOS en Xamarin.iOS

En este artículo se tratan las nuevas mejoras de juego proporcionadas por iOS 9 que se pueden usar para mejorar las características de audio y gráficos del juego de Xamarin.iOS.

Apple ha realizado varias mejoras tecnológicas en las API de juegos en iOS 9 que facilitan la implementación de gráficos y audio de juegos en una aplicación de Xamarin.iOS. Estos incluyen la facilidad de desarrollo a través de marcos de alto nivel y aprovechar la potencia de la GPU del dispositivo iOS para mejorar la velocidad y las capacidades gráficas.

Ejemplo de una aplicación que ejecuta flocking

Esto incluye GameplayKit, ReplayKit, Model I/O, MetalKit y Metal Performance Shaders, junto con nuevas características mejoradas de Metal, SceneKit y SpriteKit.

En este artículo se presentan todas las formas de mejorar el juego de Xamarin.iOS con las nuevas mejoras de juego de iOS 9:

Presentación de GameplayKit

El nuevo marco de GameplayKit de Apple proporciona un conjunto de tecnologías que facilita la creación de juegos para dispositivos iOS reduciendo la cantidad de código común repetitivo necesario para su implementación. GameplayKit proporciona herramientas para desarrollar la mecánica del juego que después pueden combinarse fácilmente con un motor gráfico (como SceneKit o SpriteKit) para obtener rápidamente un juego finalizado.

GameplayKit incluye varios algoritmos de juego comunes, como:

  • Una simulación de agente basada en el comportamiento que le permite definir movimientos y objetivos que la inteligencia artificial perseguirá automáticamente.
  • Una inteligencia artificial de minmax para el juego por turnos.
  • Un sistema de reglas para la lógica del juego controlada por datos con razonamiento aproximado para proporcionar un comportamiento emergente.

Además, GameplayKit adopta un enfoque de bloque de creación para el desarrollo de juegos mediante una arquitectura modular que proporciona las siguientes características:

  • Máquina de estados para el control de sistemas complejos basados en código de procedimiento en el juego.
  • Herramientas para proporcionar juegos aleatorios e imprevisibles sin causar problemas de depuración.
  • Una arquitectura reutilizable basada en entidades con componentes.

Para más información sobre GameplayKit, consulte la Guía de programación de Gameplaykit y la Referencia del marco de trabajo de GameplayKit de Apple.

Ejemplos de GameplayKit

Echemos un vistazo rápido a la implementación de algunos sencillos mecanismos de juego en una aplicación de Xamarin.iOS mediante el kit de juegos.

Pathfinding

Pathfinding es la capacidad de un elemento de IA de un juego para encontrar su camino en el tablero de juego. Por ejemplo, un enemigo en 2D encontrando su camino a través de un laberinto o un personaje en 3D a través del terreno de un mundo de disparos en primera persona.

Considere el siguiente mapa:

Mapa de trazado de ejemplo

Usando pathfinding este código C# puede encontrar un camino a través del mapa:

var a = GKGraphNode2D.FromPoint (new Vector2 (0, 5));
var b = GKGraphNode2D.FromPoint (new Vector2 (3, 0));
var c = GKGraphNode2D.FromPoint (new Vector2 (2, 6));
var d = GKGraphNode2D.FromPoint (new Vector2 (4, 6));
var e = GKGraphNode2D.FromPoint (new Vector2 (6, 5));
var f = GKGraphNode2D.FromPoint (new Vector2 (6, 0));

a.AddConnections (new [] { b, c }, false);
b.AddConnections (new [] { e, f }, false);
c.AddConnections (new [] { d }, false);
d.AddConnections (new [] { e, f }, false);

var graph = GKGraph.FromNodes(new [] { a, b, c, d, e, f });

var a2e = graph.FindPath (a, e); // [ a, c, d, e ]
var a2f = graph.FindPath (a, f); // [ a, b, f ]

Console.WriteLine(String.Join ("->", (object[]) a2e));
Console.WriteLine(String.Join ("->", (object[]) a2f));

Sistema experto clásico

El siguiente fragmento de código de C# muestra cómo se puede usar GameplayKit para implementar un sistema experto clásico:

string output = "";
bool reset = false;
int input = 15;

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    /*
    If reset is true, clear the output and set reset to false
    */
    var clearRule = GKRule.FromPredicate ((rules) => reset, rules => {
        output = "";
        reset = false;
    });
    clearRule.Salience = 1;

    var fizzRule = GKRule.FromPredicate (mod (3), rules => {
        output += "fizz";
    });
    fizzRule.Salience = 2;

    var buzzRule = GKRule.FromPredicate (mod (5), rules => {
        output += "buzz";
    });
    buzzRule.Salience = 2;

    /*
    This *always* evaluates to true, but is higher Salience, so evaluates after lower-salience items
    (which is counter-intuitive). Print the output, and reset (thus triggering "ResetRule" next time)
    */
    var outputRule = GKRule.FromPredicate (rules => true, rules => {
        System.Console.WriteLine(output == "" ? input.ToString() : output);
        reset = true;
    });
    outputRule.Salience = 3;

    var rs = new GKRuleSystem ();
    rs.AddRules (new [] {
        clearRule,
        fizzRule,
        buzzRule,
        outputRule
    });

    for (input = 1; input < 16; input++) {
        rs.Evaluate ();
        rs.Reset ();
    }
}

protected Func<GKRuleSystem, bool> mod(int m)
{
    Func<GKRuleSystem,bool> partiallyApplied = (rs) => input % m == 0;
    return partiallyApplied;
}

En función de un conjunto determinado de reglas (GKRule) y un conjunto conocido de entradas, el sistema experto (GKRuleSystem) creará una salida predecible (fizzbuzz para nuestro ejemplo anterior).

Flocking

Flocking permite que un grupo de entidades de juego controladas por IA se comporte como un grupo, donde el grupo responde a los movimientos y acciones de una entidad líder como una bandada de pájaros en vuelo o un banco de peces nadando.

El siguiente fragmento de código C# implementa el comportamiento de flocking usando GameplayKit y SpriteKit para la visualización de gráficos:

using System;
using SpriteKit;
using CoreGraphics;
using UIKit;
using GameplayKit;
using Foundation;
using System.Collections.Generic;
using System.Linq;
using OpenTK;

namespace FieldBehaviorExplorer
{
    public static class FlockRandom
    {
        private static GKARC4RandomSource rand = new GKARC4RandomSource ();

        static FlockRandom ()
        {
            rand.DropValues (769);
        }

        public static float NextUniform ()
        {
            return rand.GetNextUniform ();
        }
    }

    public class FlockingScene : SKScene
    {
        List<Boid> boids = new List<Boid> ();
        GKComponentSystem componentSystem;
        GKAgent2D trackingAgent; //Tracks finger on screen
        double lastUpdateTime = Double.NaN;
        //Hold on to behavior so it doesn't get GC'ed
        static GKBehavior flockingBehavior;
        static GKGoal seekGoal;

        public FlockingScene (CGSize size) : base (size)
        {
            AddRandomBoids (20);

            var scale = 0.4f;
            //Flocking system
            componentSystem = new GKComponentSystem (typeof(GKAgent2D));
            var behavior = DefineFlockingBehavior (boids.Select (boid => boid.Agent).ToArray<GKAgent2D>(), scale);
            boids.ForEach (boid => {
                boid.Agent.Behavior = behavior;
                componentSystem.AddComponent(boid.Agent);
            });

            trackingAgent = new GKAgent2D ();
            trackingAgent.Position = new Vector2 ((float) size.Width / 2.0f, (float) size.Height / 2.0f);
            seekGoal = GKGoal.GetGoalToSeekAgent (trackingAgent);
        }

        public override void TouchesBegan (NSSet touches, UIEvent evt)
        {
            boids.ForEach(boid => boid.Agent.Behavior.SetWeight(1.0f, seekGoal));
        }

        public override void TouchesEnded (NSSet touches, UIEvent evt)
        {
            boids.ForEach (boid => boid.Agent.Behavior.SetWeight (0.0f, seekGoal));
        }

        public override void TouchesMoved (NSSet touches, UIEvent evt)
        {
            var touch = (UITouch) touches.First();
            var loc = touch.LocationInNode (this);
            trackingAgent.Position = new Vector2((float) loc.X, (float) loc.Y);
        }

        private void AddRandomBoids (int count)
        {
            var scale = 0.4f;
            for (var i = 0; i < count; i++) {
                var b = new Boid (UIColor.Red, this.Size, scale);
                boids.Add (b);
                this.AddChild (b);
            }
        }

        internal static GKBehavior DefineFlockingBehavior(GKAgent2D[] boidBrains, float scale)
        {
            if (flockingBehavior == null) {
                var flockingGoals = new GKGoal[3];
                flockingGoals [0] = GKGoal.GetGoalToSeparate (boidBrains, 100.0f * scale, (float)Math.PI * 8.0f);
                flockingGoals [1] = GKGoal.GetGoalToAlign (boidBrains, 40.0f * scale, (float)Math.PI * 8.0f);
                flockingGoals [2] = GKGoal.GetGoalToCohere (boidBrains, 40.0f * scale, (float)Math.PI * 8.0f);

                flockingBehavior = new GKBehavior ();
                flockingBehavior.SetWeight (25.0f, flockingGoals [0]);
                flockingBehavior.SetWeight (10.0f, flockingGoals [1]);
                flockingBehavior.SetWeight (10.0f, flockingGoals [2]);
            }
            return flockingBehavior;
        }

        public override void Update (double currentTime)
        {
            base.Update (currentTime);
            if (Double.IsNaN(lastUpdateTime)) {
                lastUpdateTime = currentTime;
            }
            var delta = currentTime - lastUpdateTime;
            componentSystem.Update (delta);
        }
    }

    public class Boid : SKNode, IGKAgentDelegate
    {
        public GKAgent2D Agent { get { return brains; } }
        public SKShapeNode Sprite { get { return sprite; } }

        class BoidSprite : SKShapeNode
        {
            public BoidSprite (UIColor color, float scale)
            {
                var rot = CGAffineTransform.MakeRotation((float) (Math.PI / 2.0f));
                var path = new CGPath ();
                path.MoveToPoint (rot, new CGPoint (10.0, 0.0));
                path.AddLineToPoint (rot, new CGPoint (0.0, 30.0));
                path.AddLineToPoint (rot, new CGPoint (10.0, 20.0));
                path.AddLineToPoint (rot, new CGPoint (20.0, 30.0));
                path.AddLineToPoint (rot, new CGPoint (10.0, 0.0));
                path.CloseSubpath ();

                this.SetScale (scale);
                this.Path = path;
                this.FillColor = color;
                this.StrokeColor = UIColor.White;

            }
        }

        private GKAgent2D brains;
        private BoidSprite sprite;
        private static int boidId = 0;

        public Boid (UIColor color, CGSize size, float scale)
        {
            brains = BoidBrains (size, scale);
            sprite = new BoidSprite (color, scale);
            sprite.Position = new CGPoint(brains.Position.X, brains.Position.Y);
            sprite.ZRotation = brains.Rotation;
            sprite.Name = boidId++.ToString ();

            brains.Delegate = this;

            this.AddChild (sprite);
        }

        private GKAgent2D BoidBrains(CGSize size, float scale)
        {
            var brains = new GKAgent2D ();
            var x = (float) (FlockRandom.NextUniform () * size.Width);
            var y = (float) (FlockRandom.NextUniform () * size.Height);
            brains.Position = new Vector2 (x, y);

            brains.Rotation = (float)(FlockRandom.NextUniform () * Math.PI * 2.0);
            brains.Radius = 30.0f * scale;
            brains.MaxSpeed = 0.5f;
            return brains;
        }

        [Export ("agentDidUpdate:")]
        public void AgentDidUpdate (GameplayKit.GKAgent agent)
        {
        }

        [Export ("agentWillUpdate:")]
        public void AgentWillUpdate (GameplayKit.GKAgent agent)
        {
            var brainsIn = (GKAgent2D) agent;
            sprite.Position = new CGPoint(brainsIn.Position.X, brainsIn.Position.Y);
            sprite.ZRotation = brainsIn.Rotation;
            Console.WriteLine ($"{sprite.Name} -> [{sprite.Position}], {sprite.ZRotation}");
        }
    }
}

A continuación, implemente esta escena en un controlador de vista:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();
        // Perform any additional setup after loading the view, typically from a nib.
        this.View = new SKView {
        ShowsFPS = true,
        ShowsNodeCount = true,
        ShowsDrawCount = true
    };
}

public override void ViewWillLayoutSubviews ()
{
    base.ViewWillLayoutSubviews ();

    var v = (SKView)View;
    if (v.Scene == null) {
        var scene = new FlockingScene (View.Bounds.Size);
        scene.ScaleMode = SKSceneScaleMode.AspectFill;
        v.PresentScene (scene);
    }
}

Cuando se ejecuta, los pequeños "Boids" animados se agruparán alrededor de las pulsaciones de nuestros dedos:

Los pequeños Boid animados se agolparán alrededor de las pulsaciones del dedo

Otros ejemplos de Apple

Además de los ejemplos presentados anteriormente, Apple ha proporcionado las siguientes aplicaciones de ejemplo que se pueden transcodificar a C# y Xamarin.iOS:

Metálico

En iOS 9, Apple ha realizado varios cambios y adiciones a Metal para proporcionar acceso de baja sobrecarga a la GPU. Con Metal puede maximizar el potencial gráfico y de procesamiento de sus aplicaciones para iOS.

El marco Metal incluye las siguientes características nuevas:

  • Nuevas texturas de galería de símbolos privadas y de profundidad para OS X.
  • Mejora en la calidad de sombra con sujeción de profundidad y valores de estarcido frontal y posterior separados.
  • Mejoras en el Lenguaje de sombreado de Metal y en la Biblioteca estándar de Metal.
  • Los sombreadores computacionales son compatibles con una gama más amplia de formatos de píxeles.

El marco MetalKit

El marco MetalKit proporciona un conjunto de clases y características de utilidad que reducen la cantidad de trabajo necesario para usar Metal en una aplicación iOS. MetalKit proporciona compatibilidad en tres áreas clave:

  1. Carga asincrónica de texturas desde una variedad de orígenes, incluidos formatos comunes, como PNG, JPEG, KTX y PVR.
  2. Acceso sencillo a los recursos basados en Model I/O para el control de modelos específicos de Metal. Estas características han sido altamente optimizadas para proporcionar transferencia de datos eficiente entre mallas de Model I/O y búferes de Metal.
  3. Vistas de Metal predefinidas y administración de vistas que reducen en gran medida la cantidad de código necesario para mostrar representaciones gráficas dentro de una aplicación de iOS.

Para más información sobre MetalKit, consulte la Referencia del marco de MetalKit, la Guía de programación de Metal, la Referencia del marco de Metal y la Guía del Lenguaje de sombreado de Metal de Apple.

Marco de sombreadores de rendimiento de Metal

El marco de Metal Performance Shader proporciona un conjunto altamente optimizado de sombreadores basados en gráficos y cálculo para usar en sus aplicaciones de iOS basadas en Metal. Cada sombreador del marco Metal Performance Shader se ha ajustado específicamente para proporcionar un alto rendimiento en las GPU de iOS compatibles con Metal.

El uso de las clases de Metal Performance Shader permite alcanzar el máximo rendimiento posible en cada GPU específica de iOS sin tener que usar como destino y mantener bases de código individuales. Metal Performance Shader se puede usar con cualquier recurso de Metal, como texturas y buffers.

El marco de Metal Performance Shader proporciona un conjunto de sombreadores comunes como:

  • Desenfoque gaussiano (MPSImageGaussianBlur)
  • Detección de bordes de Sobel (MPSImageSobel)
  • Histograma de imagen (MPSImageHistogram)

Para más información, consulte la Guía del Lenguaje de sombreado de Metal de Apple.

Presentación de Model I/O

El marco Model I/O de Apple proporciona un profundo conocimiento de los recursos 3D (como los modelos y sus recursos relacionados). Model I/O proporciona a sus juegos de iOS materiales, modelos e iluminación basados en la física que pueden usarse con GameplayKit, Metal y SceneKit.

Con Model I/O, puede dar soporte a los siguientes tipos de tareas:

  • Importar la iluminación, los materiales, los datos de malla, la configuración de la cámara y otra información basada en la escena a partir de una gran variedad de formatos populares de software y motores de juego.
  • Procesar o generar información basada en la escena, como crear cúpulas celestes con texturas procedurales o hacer "bake lighting" en una malla.
  • Funciona con MetalKit, SceneKit y GLKit para cargar eficazmente los recursos del juego en búferes de GPU para su representación.
  • Exportar información basada en escenas a una variedad de formatos populares de software y motores de juego.

Para más información sobre Model I/O, consulte la Referencia del marco Model I/O de Apple.

Presentación de ReplayKit

El nuevo marco ReplayKit de Apple le permite agregar fácilmente la grabación del juego a su juego de iOS y permitir al usuario editar y compartir este vídeo de forma rápida y sencilla desde dentro de la aplicación.

Para más información, consulte el vídeo de Apple Going Social with ReplayKit and Game Center ("Socializar con ReplayKit y Game Center") y su aplicación de muestra DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit (DemoBots: Desarrollo de un juego multiplataforma con SpriteKit y GameplayKit).

SceneKit

Scene Kit es una API de grafos de escena 3D que simplifica el trabajo con gráficos 3D. Se introdujo por primera vez en OS X 10.8 y ahora ha llegado a iOS 8. Con Scene Kit crear visualizaciones 3D inmersivas y juegos 3D casuales no requiere conocimientos de OpenGL. Basándose en conceptos comunes de grafos de escena, Scene Kit abstrae las complejidades de OpenGL y OpenGL ES, lo que facilita la adición de contenido 3D a una aplicación. Sin embargo, si es un experto en OpenGL, Scene Kit también tiene un gran soporte para asociarse directamente con OpenGL. También incluye numerosas características que complementan gráficos 3D, como las físicas, y se integra muy bien con otros marcos de Apple, como Core Animation, Core Image y Sprite Kit.

Para más información, consulte nuestra documentación de SceneKit.

Cambios de SceneKit

Apple ha agregado las siguientes características nuevas a SceneKit para iOS 9:

  • Xcode ahora proporciona un Editor de escenas que permite crear rápidamente juegos y aplicaciones 3D interactivas editando escenas directamente desde Xcode.
  • Las clases SCNView y SCNSceneRenderer pueden usarse para habilitar la representación de Metal (en dispositivos iOS compatibles).
  • Las clases SCNAudioPlayer y SCNNode pueden usarse para agregar a una aplicación de iOS efectos espaciales de audio que sigan automáticamente la posición del jugador.

Para más información, consulte nuestra Documentación de SceneKit, la Referencia del marco de SceneKit de Apple y el proyecto de muestra Fox: Desarrollo de un juego de SceneKit con el Editor de escenas de Xcode.

SpriteKit

Sprite Kit, el marco de juego 2D de Apple, tiene algunas características nuevas interesantes en iOS 8 y OS X Yosemite. Entre ellas se incluyen la integración con Scene Kit, la compatibilidad con sombreador, la iluminación, las sombras, las restricciones, la generación normal de mapas y las mejoras físicas. En concreto, las nuevas características físicas facilitan la adición de efectos realistas a un juego.

Para más información, consulte nuestra documentación de SpriteKit.

Cambios de SpriteKit

Apple ha agregado las siguientes características nuevas a SpriteKit para iOS 9:

  • Efecto de audio espacial que sigue automáticamente la posición del jugador con la clase SKAudioNode.
  • Xcode ahora incluye un Editor de escenas y un Editor de acciones para facilitar la creación de aplicaciones y juegos 2D.
  • Soporte para juegos de desplazamiento fácil con nuevos objetos de nodos de cámara (SKCameraNode).
  • En dispositivos iOS compatibles con Metal, SpriteKit lo usará automáticamente para la representación, incluso si ya estaba usando sombreadores personalizados de OpenGL ES.

Para más información, consulte nuestra Documentación sobre SpriteKit, la Referencia del marco de SpriteKit de Apple y su aplicación de muestra DemoBots: Desarrollo de un juego multiplataforma con SpriteKit y GameplayKit.

Resumen

En este artículo se han tratado las nuevas características de juegos que iOS 9 proporciona para las aplicaciones de Xamarin.iOS. Se han presentado GameplayKit y Model I/O; las principales mejoras de Metal; y las nuevas características de SceneKit y SpriteKit.