iOS Gaming APIs in Xamarin.iOS

In diesem Artikel werden die neuen Spieleverbesserungen beschrieben, die von iOS 9 bereitgestellt werden, um die Grafik- und Audiofunktionen Ihres Xamarin.iOS-Spiels zu verbessern.

Apple hat in iOS 9 verschiedene technologische Verbesserungen an den Gaming-APIs vorgenommen, welche die Implementierung von Spielgrafiken und Audio in einer Xamarin.iOS-App vereinfachen. Dazu gehören sowohl einfache Entwicklung durch High-Level-Frameworks als auch die Nutzung der Leistung der GPU des iOS-Geräts für verbesserte Geschwindigkeit und Grafikfähigkeiten.

An example of an app running flocking

Dazu gehören GameplayKit, ReplayKit, Model I/O, MetalKit und Metal Performance Shaders sowie neue, erweiterte Features von Metal, SceneKit und SpriteKit.

In diesem Artikel werden alle Möglichkeiten vorgestellt, wie Sie Ihr Xamarin.iOS-Spiel mit den neuen Spielverbesserungen von iOS 9 verbessern können:

Einführung in GameplayKit

Das neue GameplayKit-Framework von Apple bietet eine Reihe von Technologien, die das Erstellen von Spielen für iOS-Geräte vereinfachen, indem die Anzahl der sich wiederholenden, gemeinsamen Code für die Implementierung reduziert wird. GameplayKit bietet Tools für die Entwicklung der Spielmechanik, die dann problemlos mit einer Grafik-Engine (z. B. SceneKit oder SpriteKit) kombiniert werden kann, um schnell ein abgeschlossenes Spiel zu liefern.

GameplayKit enthält mehrere gängige Spielalgorithmen wie:

  • Eine verhaltensbasierte Agentsimulation, mit der Sie Bewegungen und Ziele definieren können, die die KI automatisch verfolgen wird.
  • Eine minmax künstliche Intelligenz für turnbasiertes Spiel.
  • Ein Regelsystem für datengesteuerte Spiellogik mit fuzzy reasoning zur Bereitstellung von emergent Verhalten.

Darüber hinaus nutzt GameplayKit einen Bausteinansatz für die Spieleentwicklung mithilfe einer modularen Architektur, die die folgenden Features bereitstellt:

  • Zustandsautomat für die Verarbeitung komplexer, prozeduraler Codebasierter Systeme im Spiel.
  • Tools für die Bereitstellung zufälliger Spielspiele und Unvorstellbarkeit, ohne dass Debuggingprobleme verursacht werden.
  • Eine wiederverwendbare, komponentenbasierte Entitätsarchitektur.

Weitere Informationen zu GameplayKit finden Sie im Programmierhandbuch für das Gameplaykit von Apple und im GameplayKit-Framework.

GameplayKit-Beispiele

Werfen wir einen schnellen Blick auf die Implementierung einiger einfacher Spielmechaniken in einer Xamarin.iOS-App mit dem Game Play Kit.

Pathfinding

Pathfinding ist die Möglichkeit für ein KI-Element eines Spiels, seinen Weg um das Spielbrett zu finden. Beispielsweise findet ein 2D-Feind seinen Weg durch ein Labyrinth oder einen 3D-Charakter durch ein Gelände der First-Person-Shooter-Welt.

Betrachten Sie die folgende Karte:

An example pathfinding map

Mithilfe des Pfadsuches dieses C#-Codes finden Sie einen Weg durch die Karte:

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

Klassisches Expertensystem

Der folgende Codeausschnitt von C#-Code zeigt, wie GameplayKit verwendet werden kann, um ein klassisches Expertensystem zu implementieren:

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

Basierend auf einem bestimmten Satz von Regeln (GKRule) und einem bekannten Satz von Eingaben erstellt das Expertensystem (GKRuleSystem) vorhersehbare Ausgabe (fizzbuzz für unser Beispiel oben).

Beflockung

Das Flocken ermöglicht es einer Gruppe von KI-gesteuerten Spieleinheiten, sich als Herde zu verhalten, bei der die Gruppe auf die Bewegungen und Aktionen einer Lead-Entität wie eine Vogelherde im Flug oder eine Schule des Fischschwimmens reagiert.

Der folgende Codeausschnitt von C#-Code implementiert das Herdeverhalten mithilfe von GameplayKit und SpriteKit für die Grafikanzeige:

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

Implementieren Sie als Nächstes diese Szene in einem Ansichtscontroller:

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

Wenn sie laufen, wird das kleine animierte "Boids" um unsere Finger tippen:

The little animated Boids will flock around the finger taps

Andere Apple-Beispiele

Zusätzlich zu den oben vorgestellten Beispielen hat Apple die folgenden Beispiel-Apps bereitgestellt, die in C# und Xamarin.iOS transcodiert werden können:

Metal

In iOS 9 hat Apple mehrere Änderungen und Ergänzungen zu Metal vorgenommen, um den Zugriff auf die GPU mit geringem Aufwand zu ermöglichen. Mit Metal können Sie die Grafik- und Computerpotenziale Ihrer iOS-Apps maximieren.

Das Metal-Framework umfasst die folgenden neuen Features:

  • Neue private und Tiefenschablonentexturen für OS X.
  • Verbesserte Schattenqualität mit Tiefenklemmung und separaten Front- und Rückenschablonenwerten.
  • Verbesserungen der Metal Shading Language und der Metal Standard Library.
  • Rechen-Shader unterstützen einen größeren Bereich von Pixelformaten.

Das MetalKit Framework

Das MetalKit-Framework bietet eine Reihe von Hilfsklassen und -features, die die Arbeitsmenge verringern, die für die Verwendung von Metal in einer iOS-App erforderlich ist. MetalKit bietet Unterstützung in drei Schlüsselbereichen:

  1. Asynchrones Laden von Texturen aus einer Vielzahl von Quellen, einschließlich gängiger Formate wie PNG, JPEG, KTX und PVR.
  2. Einfacher Zugriff auf Modell-E/A-basierte Ressourcen für die Metallspezifische Modellverarbeitung. Diese Features wurden sehr optimiert, um eine effiziente Datenübertragung zwischen Modell-E/A-Gittern und Metallpuffern zu ermöglichen.
  3. Vordefinierte Metallansichten und Ansichtsverwaltung, die die Menge an Code erheblich reduzieren, der zum Anzeigen von Grafikrenderings in einer iOS-App erforderlich ist.

Weitere Informationen zu MetalKit finden Sie in der Apple MetalKit Framework-Referenz, dem Metal Programming Guide, dem Metal Framework Reference und dem Metal Shading Language Guide.

Metal Performance Shader Framework

Das Metal Performance Shader-Framework bietet einen hochoptimierten Satz von Grafiken und rechenbasierten Shadern für die Verwendung in Ihren Metal-basierten iOS-Apps. Jeder Shader im Metal Performance Shader-Framework wurde speziell abgestimmt, um eine hohe Leistung auf von Metal unterstützten iOS-GPUs bereitzustellen.

Mithilfe von Metal Performance Shader-Klassen können Sie die höchste Leistung für jede bestimmte iOS-GPU erzielen, ohne einzelne Codebasen gezielt und Standard enthalten zu müssen. Metal Performance Shader können mit einer beliebigen Metal-Ressource wie Texturen und Puffern verwendet werden.

Das Metal Performance Shader-Framework stellt eine Reihe allgemeiner Shader bereit, z. B.:

  • Gaussischer Weichzeichner (MPSImageGaussianBlur)
  • Sobel Edge Detection (MPSImageSobel)
  • Bild histogramm (MPSImageHistogram)

Weitere Informationen finden Sie im Apple Metal Shading Language Guide.

Einführung in Modell-E/A

Das Modell-E/A-Framework von Apple bietet ein tiefes Verständnis für 3D-Ressourcen (z. B. Modelle und deren zugehörige Ressourcen). Model I/O bietet Ihre iOS-Spiele mit physischen Materialien, Modellen und Beleuchtungen, die mit GameplayKit, Metal und SceneKit verwendet werden können.

Mit Modell-E/A können Sie die folgenden Aufgabentypen unterstützen:

  • Importieren Sie Beleuchtung, Materialien, Gitterdaten, Kameraeinstellungen und andere szenenbasierte Informationen aus einer Vielzahl beliebter Software- und Spielemodulformate.
  • Verarbeiten oder generieren Sie szenenbasierte Informationen, z. B. erstellen Sie prozedural strukturierte Himmelskuppeln oder backen Sie Beleuchtung in ein Gitter.
  • Funktioniert mit MetalKit, SceneKit und GLKit, um Spielressourcen effizient in GPU-Puffer zum Rendern zu laden.
  • Exportieren Sie szenenbasierte Informationen in eine Vielzahl beliebter Software- und Spielemodulformate.

Weitere Informationen zu Modell-E/A finden Sie in der Apple Model I/O Framework-Referenz

Einführung in ReplayKit

Das neue ReplayKit-Framework von Apple ermöglicht es Ihnen, dem iOS-Spiel ganz einfach aufzeichnungen hinzuzufügen und dem Benutzer das schnelle und einfache Bearbeiten und Freigeben dieses Videos aus der App zu ermöglichen.

Weitere Informationen finden Sie unter Apples Going Social mit ReplayKit- und Game Center-Video und ihren DemoBots: Erstellen eines plattformübergreifenden Spiels mit SpriteKit- und GameplayKit-Beispiel-App .

SceneKit

Scene Kit ist eine 3D-Szenendiagramm-API, die das Arbeiten mit 3D-Grafiken vereinfacht. Es wurde erstmals in OS X 10.8 eingeführt und ist jetzt zu iOS 8 gekommen. Mit dem Scene Kit ist die Erstellung immersiver 3D-Visualisierungen und lässiger 3D-Spiele in OpenGL nicht erforderlich. Basierend auf gängigen Szenendiagrammkonzepten abstrahiert Scene Kit die Komplexitäten von OpenGL und OpenGL ES, wodurch es sehr einfach ist, einer Anwendung 3D-Inhalte hinzuzufügen. Wenn Sie jedoch ein OpenGL-Experte sind, hat Scene Kit auch eine hervorragende Unterstützung für die direkte Bindung mit OpenGL. Es enthält auch zahlreiche Features, die 3D-Grafiken ergänzen, z. B. Physik, und integriert sich sehr gut in verschiedene andere Apple-Frameworks wie Core Animation, Core Image und Sprite Kit.

Weitere Informationen finden Sie in unserer SceneKit-Dokumentation .

SceneKit-Änderungen

Apple hat die folgenden neuen Features zu SceneKit für iOS 9 hinzugefügt:

  • Xcode bietet jetzt einen Szenen-Editor, mit dem Sie schnell Spiele und interaktive 3D-Apps erstellen können, indem Sie Szenen direkt aus Xcode bearbeiten.
  • Die SCNView Klassen SCNSceneRenderer können zum Aktivieren des Metal-Renderings (auf unterstützten iOS-Geräten) verwendet werden.
  • Die SCNAudioPlayer Klassen SCNNode können verwendet werden, um räumliche Audioeffekte hinzuzufügen, die eine Playerposition automatisch einer iOS-App nachverfolgen.

Weitere Informationen finden Sie in unserer SceneKit-Dokumentation und der SceneKit-Framework-Referenz von Apple und Fox: Erstellen eines SceneKit-Spiels mit dem Xcode Scene Editor-Beispielprojekt.

SpriteKit

Sprite Kit, das 2D-Spielframework von Apple, verfügt über einige interessante neue Features in iOS 8 und OS X Yosemite. Dazu gehören integration in Scene Kit, Shaderunterstützung, Beleuchtung, Schatten, Einschränkungen, normale Kartengenerierung und physikalische Verbesserungen. Insbesondere die neuen Physikfeatures machen es sehr einfach, einem Spiel realistische Effekte hinzuzufügen.

Weitere Informationen finden Sie in unserer SpriteKit-Dokumentation .

SpriteKit-Änderungen

Apple hat die folgenden neuen Features zu SpriteKit für iOS 9 hinzugefügt:

  • Räumlicher Audioeffekt, der die Position des Spielers automatisch mit der SKAudioNode Klasse nachverfolgt.
  • Xcode verfügt jetzt über einen Szenen-Editor und einen Aktions-Editor für einfache 2D-Spiele- und App-Erstellung.
  • Einfache Bildlauf-Spielunterstützung mit neuen Kamera Knotenobjekten (SKCameraNode).
  • Auf iOS-Geräten, die Metal unterstützen, verwendet SpriteKit diese automatisch zum Rendern, auch wenn Sie bereits benutzerdefinierte OpenGL ES-Shader verwendet haben.

Weitere Informationen finden Sie in unserer SpriteKit-Dokumentation von Apples SpriteKit Framework-Referenz und ihren DemoBots: Erstellen eines plattformübergreifenden Spiels mit SpriteKit- und GameplayKit-Beispiel-App .

Zusammenfassung

In diesem Artikel werden die neuen Gaming-Features behandelt, die iOS 9 für Ihre Xamarin.iOS-Apps bereitstellt. Es führte GameplayKit und Model I/O ein; die wichtigsten Verbesserungen an Metall; und die neuen Features von SceneKit und SpriteKit.