Condividi tramite


API di gioco iOS in Xamarin.iOS

Questo articolo illustra i nuovi miglioramenti del gioco forniti da iOS 9 che possono essere usati per migliorare le funzionalità di grafica e audio del gioco Xamarin.iOS.

Apple ha apportato diversi miglioramenti tecnologici alle API di gioco in iOS 9 che semplificano l'implementazione della grafica e dell'audio dei giochi in un'app Xamarin.iOS. Questi includono sia la facilità di sviluppo attraverso framework di alto livello che sfruttano la potenza della GPU del dispositivo iOS per migliorare la velocità e le capacità grafiche.

Esempio di app che esegue flocking

Sono inclusi GameplayKit, ReplayKit, Model I/O, MetalKit e Metal Performance Shader insieme a nuove funzionalità avanzate di Metal, SceneKit e SpriteKit.

Questo articolo presenta tutti i modi per migliorare il gioco Xamarin.iOS con i nuovi miglioramenti di gioco di iOS 9:

Introduzione a GameplayKit

Il nuovo framework GameplayKit di Apple offre un set di tecnologie che semplifica la creazione di giochi per i dispositivi iOS riducendo la quantità di codice comune ripetitivo necessario per l'implementazione. GameplayKit fornisce strumenti per lo sviluppo delle meccaniche di gioco che possono quindi essere facilmente combinate con un motore grafico (ad esempio SceneKit o SpriteKit) per distribuire rapidamente un gioco completato.

GameplayKit include diversi algoritmi di gioco comuni, ad esempio:

  • Simulazione agente basata sul comportamento che consente di definire movimenti e obiettivi che l'intelligenza artificiale inseguirà automaticamente.
  • Un'intelligenza artificiale minmax per il gioco a turni.
  • Un sistema di regole per la logica di gioco basata sui dati con ragionamento fuzzy per fornire un comportamento emergente.

Inoltre, GameplayKit adotta un approccio di blocco predefinito allo sviluppo di giochi usando un'architettura modulare che fornisce le funzionalità seguenti:

  • Macchina a stati per la gestione di sistemi complessi basati su codice procedurale nel gioco.
  • Strumenti per fornire giochi casuali e imprevedibilità senza causare problemi di debug.
  • Architettura basata su entità riutilizzabile e componentizzata.

Per altre informazioni su GameplayKit, vedi guida alla programmazione di Gameplaykit di Apple e Informazioni di riferimento sul framework GameplayKit.

Esempi di GameplayKit

Esaminiamo rapidamente l'implementazione di alcuni semplici meccanismi di gioco in un'app Xamarin.iOS usando il kit di gioco.

Pathfinding

Pathfinding è la capacità di un elemento di intelligenza artificiale di un gioco di trovare il suo modo di aggirare la tavola da gioco. Ad esempio, un nemico 2D trova la sua strada attraverso un labirinto o un personaggio 3D attraverso un terreno del mondo sparatutto in prima persona.

Si consideri la mappa seguente:

Mappa pathfinding di esempio

Usando pathfinding questo codice C# è possibile trovare una strada attraverso la mappa:

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 esperto classico

Il frammento di codice C# seguente mostra come usare GameplayKit per implementare un sistema esperto classico:

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

In base a un determinato set di regole (GKRule) e a un set noto di input, il sistema esperto (GKRuleSystem) creerà un output prevedibile (fizzbuzz per l'esempio precedente).

Affollamento

Flocking consente a un gruppo di entità di gioco controllate dall'IA di comportarsi come un gregge, dove il gruppo risponde ai movimenti e alle azioni di un'entità lead come un gregge di uccelli in volo o una scuola di nuoto di pesci.

Il frammento di codice C# seguente implementa il comportamento flocking usando GameplayKit e SpriteKit per la visualizzazione grafica:

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

Implementare quindi questa scena in un controller di visualizzazione:

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

Durante l'esecuzione, i piccoli "Boid" animati si affollano intorno ai tap di dito:

I piccoli Boid animati si affollano intorno ai tap di dito

Altri esempi di Apple

Oltre agli esempi presentati in precedenza, Apple ha fornito le app di esempio seguenti che possono essere transcodificate in C# e Xamarin.iOS:

Metallo

In iOS 9, Apple ha apportato diverse modifiche e aggiunte a Metal per fornire un accesso a basso sovraccarico alla GPU. Usando Metal è possibile ottimizzare la grafica e il potenziale di calcolo delle app iOS.

Il framework Metal include le nuove funzionalità seguenti:

  • Nuove trame stencil private e depth per OS X.
  • Miglioramento della qualità dell'ombreggiatura con stencil di profondità e valori di stencil anteriore e posteriore separati.
  • Miglioramenti del linguaggio di ombreggiatura metal e della libreria standard metal.
  • Gli shader computazionali supportano una gamma più ampia di formati di pixel.

Il framework MetalKit

Il framework MetalKit fornisce un set di classi di utilità e funzionalità che riducono la quantità di lavoro necessaria per usare Metal in un'app iOS. MetalKit offre supporto in tre aree chiave:

  1. Caricamento asincrono delle trame da un'ampia gamma di origini, tra cui formati comuni, ad esempio PNG, JPEG, KTX e PVR.
  2. Facile accesso agli asset basati su I/O del modello per la gestione di modelli specifici del metallo. Queste funzionalità sono state altamente ottimizzate per offrire un trasferimento efficiente dei dati tra mesh di I/O del modello e buffer di metallo.
  3. Visualizzazioni e gestione delle visualizzazioni metal predefinite che riducono notevolmente la quantità di codice necessaria per visualizzare i rendering grafici all'interno di un'app iOS.

Per altre informazioni su MetalKit, vedere La guida di riferimento al framework MetalKit di Apple, la Guida alla programmazione metal, il riferimento a Metal Framework e la Guida al linguaggio metal shading.

Framework degli shader per le prestazioni metal

Il framework Metal Performance Shader offre un set altamente ottimizzato di shader grafici e basati sul calcolo per l'uso nelle app iOS basate su Metal. Ogni shader nel framework Metal Performance Shader è stato ottimizzato in modo specifico per offrire prestazioni elevate nelle GPU iOS supportate da Metal.

Usando le classi Metal Performance Shader, è possibile ottenere le prestazioni più elevate possibili in ogni GPU iOS specifica senza dover impostare come destinazione e gestire singole codebase. Gli shader di prestazioni metal possono essere usati con qualsiasi risorsa metal, ad esempio trame e buffer.

Il framework Metal Performance Shader offre un set di shader comuni, ad esempio:

  • Sfocatura gaussiana (MPSImageGaussianBlur)
  • Sobel Edge Detection (MPSImageSobel)
  • Istogramma immagine (MPSImageHistogram)

Per altre informazioni, vedere la Guida al linguaggio Metal Shading di Apple.

Introduzione all'I/O del modello

Il framework di I/O del modello di Apple offre una conoscenza approfondita degli asset 3D , ad esempio modelli e risorse correlate. L'I/O del modello fornisce i tuoi giochi iOS con materiali fisici, modelli e illuminazione che possono essere usati con GameplayKit, Metal e SceneKit.

Con l'I/O del modello è possibile supportare i tipi di attività seguenti:

  • Importare illuminazione, materiali, dati mesh, impostazioni della fotocamera e altre informazioni basate sulla scena da un'ampia gamma di formati diffusi di software e motore di gioco.
  • Elaborare o generare informazioni basate sulla scena, ad esempio creare cupole del cielo con trame procedurali o cuocere l'illuminazione in una mesh.
  • Funziona con MetalKit, SceneKit e GLKit per caricare in modo efficiente gli asset del gioco in buffer GPU per il rendering.
  • Esportare le informazioni basate sulla scena in un'ampia gamma di formati diffusi di software e motore di gioco.

Per altre informazioni sull'I/O del modello, vedere Le informazioni di riferimento sul framework di I/O del modello di Apple

Introduzione a ReplayKit

Il nuovo framework ReplayKit di Apple consente di aggiungere facilmente la registrazione del gioco al gioco iOS e consentire all'utente di modificare e condividere facilmente questo video dall'interno dell'app.

Per altre informazioni, vedi il video di Apple Going Social with ReplayKit e Game Center e i relativi DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit sample app (DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit sample app).

SceneKit

Scene Kit è un'API grafo della scena 3D che semplifica l'uso della grafica 3D. È stato introdotto per la prima volta in OS X 10.8 e ora è arrivato a iOS 8. Con Scene Kit che crea visualizzazioni 3D immersive e giochi 3D casuali non richiede competenze in OpenGL. Basandosi sui concetti comuni del grafico della scena, Scene Kit astrae le complessità di OpenGL e OpenGL ES, semplificando l'aggiunta di contenuto 3D a un'applicazione. Tuttavia, se sei un esperto OpenGL, Scene Kit ha un grande supporto per legarsi direttamente con OpenGL. Include anche numerose funzionalità che integrano la grafica 3D, come la fisica, e si integra molto bene con diversi altri framework Apple, ad esempio Animazione core, Core Image e Sprite Kit.

Per altre informazioni, vedere la documentazione di SceneKit .

Modifiche di SceneKit

Apple ha aggiunto le nuove funzionalità seguenti a SceneKit per iOS 9:

  • Xcode offre ora un editor di scene che consente di creare rapidamente giochi e app 3D interattive modificando scene direttamente da Xcode.
  • Le SCNView classi e SCNSceneRenderer possono essere usate per abilitare il rendering metal (nei dispositivi iOS supportati).
  • Le SCNAudioPlayer classi e SCNNode possono essere usate per aggiungere effetti audio spaziali che tengono traccia automatica di una posizione del lettore a un'app iOS.

Per altre informazioni, vedi la documentazione di SceneKit e le informazioni di riferimento sul framework SceneKit di Apple e Fox: Building a SceneKit Game with the Xcode Scene Editor sample project (Guida di esempio di SceneKit di Apple e Fox: Building a SceneKit Game with the Xcode Scene Editor).

SpriteKit

Sprite Kit, il framework di gioco 2D di Apple, ha alcune nuove funzionalità interessanti in iOS 8 e OS X Yosemite. Questi includono l'integrazione con Scene Kit, supporto shader, illuminazione, ombreggiature, vincoli, generazione normale della mappa e miglioramenti della fisica. In particolare, le nuove caratteristiche della fisica rendono molto facile aggiungere effetti realistici a un gioco.

Per altre informazioni, vedere la documentazione di SpriteKit .

Modifiche di SpriteKit

Apple ha aggiunto le nuove funzionalità seguenti a SpriteKit per iOS 9:

  • Effetto audio spaziale che tiene traccia automaticamente della posizione del lettore con la SKAudioNode classe .
  • Xcode ora include un editor di scene e un editor di azioni per facilitare la creazione di giochi e app 2D.
  • Supporto semplice del gioco di scorrimento con nuovi oggetti nodi di Fotocamera (SKCameraNode).
  • Nei dispositivi iOS che supportano Metal, SpriteKit lo userà automaticamente per il rendering, anche se si usavano già shader OpenGL ES personalizzati.

Per altre informazioni, vedi le informazioni di riferimento sul framework SpriteKit della documentazione di SpriteKit di Apple e sui relativi DemoBot: Building a Cross Platform Game with SpriteKit and GameplayKit sample app (Informazioni di riferimento sul framework SpriteKit di SpriteKit e demobot: creazione di un gioco multipiattaforma con SpriteKit e app di esempio GameplayKit).

Riepilogo

Questo articolo ha illustrato le nuove funzionalità di gioco offerte da iOS 9 per le app Xamarin.iOS. Ha introdotto GameplayKit e I/O del modello; i principali miglioramenti a Metal; e le nuove funzionalità di SceneKit e SpriteKit.