Compartilhar via


APIs de jogos do iOS no Xamarin.iOS

Este artigo aborda os novos aprimoramentos de jogos fornecidos pelo iOS 9 que podem ser usados para melhorar os recursos gráficos e de áudio do seu jogo Xamarin.iOS.

A Apple fez várias melhorias tecnológicas nas APIs de jogos no iOS 9 que facilitam a implementação de gráficos e áudio de jogos em um aplicativo Xamarin.iOS. Isso inclui facilidade de desenvolvimento por meio de estruturas de alto nível e aproveitar o poder da GPU do dispositivo iOS para melhorar a velocidade e as habilidades gráficas.

Um exemplo de um aplicativo executando o flocking

Isso inclui GameplayKit, ReplayKit, Model I/O, MetalKit e Metal Performance Shaders, juntamente com novos recursos aprimorados de Metal, SceneKit e SpriteKit.

Este artigo apresentará todas as maneiras de melhorar seu jogo Xamarin.iOS com os novos aprimoramentos de jogos do iOS 9:

Apresentando o GameplayKit

A nova estrutura GameplayKit da Apple fornece um conjunto de tecnologias que facilita a criação de jogos para dispositivos iOS, reduzindo a quantidade de código comum e repetitivo necessário para a implementação. O GameplayKit fornece ferramentas para desenvolver a mecânica do jogo que podem ser facilmente combinadas com um motor gráfico (como SceneKit ou SpriteKit) para entregar rapidamente um jogo completo.

O GameplayKit inclui vários algoritmos de jogo comuns, tais como:

  • Uma simulação de agente baseada em comportamento que permite definir movimentos e metas que a IA perseguirá automaticamente.
  • Uma inteligência artificial minmax para jogos por turnos.
  • Um sistema de regras para lógica de jogo orientada por dados com raciocínio difuso para fornecer comportamento emergente.

Além disso, o GameplayKit adota uma abordagem de bloco de construção para o desenvolvimento de jogos usando uma arquitetura modular que fornece os seguintes recursos:

  • Máquina de estado para lidar com sistemas complexos baseados em código processual no jogo.
  • Ferramentas para fornecer jogo aleatório e imprevisibilidade sem causar problemas de depuração.
  • Uma arquitetura baseada em entidade reutilizável e componentizada.

Para saber mais sobre o GameplayKit, consulte o Guia de Programação do Gameplaykit da Apple e a Referência do Framework do GameplayKit.

Exemplos de GameplayKit

Vamos dar uma olhada rápida na implementação de algumas mecânicas de jogo simples em um aplicativo Xamarin.iOS usando o kit de jogo.

Descoberta de caminhos

Pathfinding é a capacidade de um elemento de IA de um jogo encontrar seu caminho ao redor do tabuleiro do jogo. Por exemplo, um inimigo 2D encontrando seu caminho através de um labirinto ou um personagem 3D através de um terreno do mundo de tiro em primeira pessoa.

Considere o seguinte mapa:

Um exemplo de mapa de localização de caminhos

Usando pathfinding este código C# pode encontrar um caminho através do 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 Especialista Clássico

O seguinte trecho de código C# mostra como o GameplayKit pode ser usado para implementar um sistema especialista clássico:

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;
}

Com base em um determinado conjunto de regras (GKRule) e um conjunto conhecido de entradas, o sistema especialista (GKRuleSystem) criará uma saída previsível (fizzbuzz para o nosso exemplo acima).

Reunindo

O bando permite que um grupo de entidades de jogo controladas por IA se comporte como um bando, onde o grupo responde aos movimentos e ações de uma entidade líder, como um bando de pássaros em voo ou um cardume de peixes nadando.

O seguinte trecho de código C# implementa o comportamento de agrupamento usando GameplayKit e SpriteKit para a exibição 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}");
        }
    }
}

Em seguida, implemente essa cena em um controlador de exibição:

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);
    }
}

Quando executados, os pequenos "Boids" animados se agruparão em torno de nossos toques de dedo:

Os pequenos Boids animados se agruparão em torno das torneiras dos dedos

Outros exemplos da Apple

Além dos exemplos apresentados acima, a Apple forneceu os seguintes aplicativos de exemplo que podem ser transcodificados para C# e Xamarin.iOS:

Metal

No iOS 9, a Apple fez várias mudanças e adições ao Metal para fornecer acesso de baixa sobrecarga à GPU. Usando o Metal, você pode maximizar o potencial gráfico e de computação de seus aplicativos iOS.

A estrutura Metal inclui os seguintes novos recursos:

  • Novas texturas de estêncil privado e de profundidade para OS X.
  • Qualidade de sombra melhorada com fixação de profundidade e valores de estêncil frontal e traseiro separados.
  • Melhorias na Linguagem de Sombreamento de Metal e na Biblioteca Padrão de Metal.
  • Os sombreadores computacionais suportam uma gama mais ampla de formatos de pixel.

A estrutura MetalKit

A estrutura MetalKit fornece um conjunto de classes de utilitários e recursos que reduzem a quantidade de trabalho necessária para usar o Metal em um aplicativo iOS. A MetalKit oferece suporte em três áreas principais:

  1. Carregamento assíncrono de textura de uma variedade de fontes, incluindo formatos comuns, como PNG, JPEG, KTX e PVR.
  2. Fácil acesso aos ativos baseados em E/S do modelo para manuseio do modelo específico do Metal. Esses recursos foram altamente otimizados para fornecer transferência de dados eficiente entre malhas de E/S de modelo e buffers metálicos.
  3. Visualizações e gerenciamento de exibição predefinidos do Metal que reduzem consideravelmente a quantidade de código necessária para exibir renderizações gráficas em um aplicativo iOS.

Para saber mais sobre o MetalKit, consulte o MetalKit Framework Reference da Apple, o Guia de Programação de Metal, o Metal Framework Reference e o Guia de Linguagem de Sombreamento de Metal.

Estrutura de sombreadores de desempenho de metal

A estrutura Metal Performance Shader fornece um conjunto altamente otimizado de sombreadores gráficos e computacionais para uso em seus aplicativos iOS baseados em Metal. Cada sombreador na estrutura Metal Performance Shader foi especificamente ajustado para fornecer alto desempenho em GPUs iOS suportadas por Metal.

Usando classes Metal Performance Shader, você pode obter o mais alto desempenho possível em cada GPU iOS específica sem ter que direcionar e manter bases de código individuais. Os Sombreadores de Desempenho de Metal podem ser usados com qualquer recurso de Metal, como texturas e buffers.

A estrutura Metal Performance Shader fornece um conjunto de sombreadores comuns, como:

  • Desfoque Gaussiano (MPSImageGaussianBlur)
  • Detecção de borda Sobel (MPSImageSobel)
  • Histograma da imagem (MPSImageHistogram)

Para obter mais informações, consulte o Guia de linguagem de sombreamento de metal da Apple.

Introdução à E/S do modelo

A estrutura de E/S de modelo da Apple fornece uma compreensão profunda dos ativos 3D (como modelos e seus recursos relacionados). O Model I/O fornece aos seus jogos iOS materiais, modelos e iluminação baseados em física que podem ser usados com GameplayKit, Metal e SceneKit.

Com a E/S de modelo, você pode oferecer suporte aos seguintes tipos de tarefas:

  • Importe iluminação, materiais, dados de malha, configurações de câmera e outras informações baseadas em cena de uma variedade de formatos populares de software e mecanismo de jogo.
  • Processe ou gere informações baseadas em cena, como criar cúpulas de céu processualmente texturizadas ou assar iluminação em uma malha.
  • Funciona com MetalKit, SceneKit e GLKit para carregar eficientemente ativos de jogos em buffers de GPU para renderização.
  • Exporte informações baseadas em cenas para uma variedade de softwares populares e formatos de mecanismos de jogos.

Para saber mais sobre a E/S de modelo, consulte a Referência da estrutura de E/S de modelo da Apple

Apresentando o ReplayKit

A nova estrutura ReplayKit da Apple permite que você adicione facilmente a gravação do jogo ao seu jogo iOS e permita que o usuário edite e compartilhe este vídeo de forma rápida e fácil de dentro do aplicativo.

Para obter mais informações, consulte o vídeo Going Social with ReplayKit e Game Center da Apple e seu aplicativo de exemplo DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit.

SceneKit

O Scene Kit é uma API de gráfico de cena 3D que simplifica o trabalho com gráficos 3D. Ele foi introduzido pela primeira vez no OS X 10.8 e agora chegou ao iOS 8. Com o Scene Kit, criar visualizações 3D imersivas e jogos 3D casuais não requer experiência em OpenGL. Com base em conceitos comuns de gráficos de cena, o Scene Kit abstrai as complexidades do OpenGL e do OpenGL ES, tornando muito fácil adicionar conteúdo 3D a um aplicativo. No entanto, se você é um especialista em OpenGL, o Scene Kit tem um ótimo suporte para vincular diretamente com o OpenGL também. Ele também inclui inúmeros recursos que complementam os gráficos 3D, como física, e se integra muito bem com vários outros frameworks da Apple, como Core Animation, Core Image e Sprite Kit.

Para obter mais informações, consulte nossa documentação do SceneKit .

Alterações no SceneKit

A Apple adicionou os seguintes novos recursos ao SceneKit para iOS 9:

  • O Xcode agora fornece um Editor de Cena que permite que você crie rapidamente jogos e aplicativos 3D interativos editando cenas diretamente do Xcode.
  • As SCNView classes e SCNSceneRenderer podem ser usadas para habilitar a renderização Metal (em dispositivos iOS compatíveis).
  • As SCNAudioPlayer classes e SCNNode podem ser usadas para adicionar efeitos de áudio espaciais que rastreiam automaticamente a posição do jogador em um aplicativo iOS.

Para obter mais informações, consulte nossa Documentação do SceneKit e a Referência do Framework SceneKit da Apple e o projeto de exemplo Fox: Building a SceneKit Game with the Xcode Scene Editor.

SpriteKit

Sprite Kit, a estrutura de jogos 2D da Apple, tem alguns novos recursos interessantes no iOS 8 e OS X Yosemite. Isso inclui integração com o Scene Kit, suporte a sombreador, iluminação, sombras, restrições, geração normal de mapas e aprimoramentos de física. Em particular, os novos recursos de física tornam muito fácil adicionar efeitos realistas a um jogo.

Para obter mais informações, consulte nossa documentação do SpriteKit .

Alterações no SpriteKit

A Apple adicionou os seguintes novos recursos ao SpriteKit para iOS 9:

  • Efeito de áudio espacial que rastreia automaticamente a posição do jogador com a SKAudioNode classe.
  • O Xcode agora possui um Editor de Cena e Editor de Ação para facilitar a criação de jogos e aplicativos em 2D.
  • Suporte a jogos de rolagem fácil com novos objetos Camera Nodes (SKCameraNode).
  • Em dispositivos iOS que suportam Metal, o SpriteKit o usará automaticamente para renderização, mesmo se você já estiver usando sombreadores OpenGL ES personalizados.

Para obter mais informações, consulte nossa Documentação do SpriteKit Referência do Framework da Apple e seus DemoBots: Construindo um jogo de plataforma cruzada com o aplicativo de exemplo SpriteKit e GameplayKit .

Resumo

Este artigo abordou os novos recursos de jogos que o iOS 9 fornece para seus aplicativos Xamarin.iOS. Introduziu o GameplayKit e o Model I/O; as principais melhorias no Metal; e os novos recursos do SceneKit e SpriteKit.