API игр iOS в Xamarin.iOS

В этой статье рассматриваются новые улучшения игр, предоставляемые iOS 9, которые можно использовать для улучшения графических и звуковых функций игры Xamarin.iOS.

Apple сделал несколько технологических улучшений в игровых API в iOS 9, что упрощает реализацию игровой графики и звука в приложении Xamarin.iOS. К ним относятся как простота разработки с помощью высокоуровневых платформ, так и использование возможностей GPU устройства iOS для повышения скорости и графических возможностей.

An example of an app running flocking

К ним относятся шейдеры игрового процесса, ReplayKit, модели ввода-вывода, MetalKit и металлических шейдеров производительности, а также новые, улучшенные функции Metal, SceneKit и SpriteKit.

В этой статье представлены все способы улучшения игры Xamarin.iOS с помощью новых улучшений игр iOS 9:

Знакомство с игровой игройKit

Новая платформа GameplayKit Apple предоставляет набор технологий, которые упрощают создание игр для устройств iOS, уменьшая количество повторяющихся и общих кодов, необходимых для реализации. GameKit предоставляет инструменты для разработки игровой механики, которые затем можно легко сочетать с графическим механизмом (например, SceneKit или SpriteKit), чтобы быстро доставить завершенную игру.

GameKit включает несколько распространенных алгоритмов игры, таких как:

  • На основе поведения, имитация агента, которая позволяет определять движения и цели, которые ИИ будет выполнять автоматически.
  • Минмакс искусственный интеллект для игры на основе поворота.
  • Система правил для логики игры на основе данных с нечеткими причинами для обеспечения формирующегося поведения.

Кроме того, GameKit принимает стандартный подход к разработке игр с помощью модульной архитектуры, которая предоставляет следующие функции:

  • Государственный компьютер для обработки сложных процедурных систем на основе кода в игре.
  • Средства для обеспечения случайной игры и непредсказуемости без возникновения проблем с отладкой.
  • Повторно используемый компонентной архитектуры на основе сущностей.

Дополнительные сведения об GameplayKit см. в руководстве по программированию в Apple и руководстве по программированию в игровом интерфейсе и справочнике по игровому процессу.

Примеры игрового элемента

Давайте рассмотрим краткое руководство по реализации некоторых простых игровых механик в приложении Xamarin.iOS с помощью набора игр.

Определение пути

Pathfinding — это способность элемента ИИ игры найти свой путь вокруг игровой доски. Например, 2D-враг находит свой путь через лабиринт или трехмерный символ через первый человек-стреляющий мир местности.

Рассмотрим следующую карту:

An example pathfinding map

С помощью пути, определенного в этом коде C#, можно найти способ по карте:

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

Классическая экспертная система

В следующем фрагменте кода C# показано, как с помощью GameplayKit можно реализовать классическую экспертную систему:

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

На основе заданного набора правил () и известного набора входных данных система экспертов (GKRule) создаст прогнозируемый результат (GKRuleSystemfizzbuzzнапример, приведенный выше).

Стекаются

Flocking позволяет группе управляемых ИИ игровых сущностей вести себя как стая, где группа реагирует на движения и действия свинца сущности, как стая птиц в полете или школе рыбы плавать.

Следующий фрагмент кода C# реализует поведение стека с помощью GameplayKit и SpriteKit для графического отображения:

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

Затем реализуйте эту сцену в контроллере представления:

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

При запуске маленький анимированный "Boids" будет стекаться вокруг наших пальцев касания:

The little animated Boids will flock around the finger taps

Другие примеры Apple

Помимо приведенных выше примеров Apple предоставила следующие примеры приложений, которые можно перекодировать на C# и Xamarin.iOS:

Металл

В iOS 9 Apple внесла несколько изменений и дополнений к Metal, чтобы обеспечить низкоуровневый доступ к GPU. Используя металл, вы можете максимально расширить возможности графики и вычислений приложений iOS.

Платформа Metal включает следующие новые возможности:

  • Новые текстуры наборов элементов для ОС X и глубины.
  • Улучшено качество тени с зажимом глубины и отдельными значениями переднего и заднего элементов.
  • Улучшения языка заливки металла и стандартной библиотеки металла.
  • Вычислительные шейдеры поддерживают более широкий диапазон форматов пикселей.

Платформа MetalKit Framework

Платформа MetalKit предоставляет набор служебных классов и функций, которые снижают объем работы, необходимой для использования Metal в приложении iOS. MetalKit обеспечивает поддержку в трех ключевых областях:

  1. Асинхронная загрузка текстур из различных источников, включая распространенные форматы, такие как PNG, JPEG, KTX и PVR.
  2. Простой доступ к ресурсам на основе операций ввода-вывода модели для обработки конкретных моделей. Эти функции оптимизированы для обеспечения эффективной передачи данных между сетками ввода-вывода модели и буферами металла.
  3. Предопределенные представления и управление представлениями металла, которые значительно снижают объем кода, необходимого для отображения графических отрисовок в приложении iOS.

Дополнительные сведения о MetalKit см. в справочнике apple MetalKit Framework, руководстве по программированию металла, справочнике по метал-платформе и языковом руководстве по затенениям.

Платформа шейдеров производительности металла

Платформа шейдера производительности металла предоставляет высокооптимизированный набор графических и вычислительных шейдеров для использования в приложениях iOS на основе металла. Каждая шейдера в платформе шейдера производительности металла была специально настроена для обеспечения высокой производительности на поддерживаемых gpu iOS для Метал.

Используя классы шейдера производительности металла, вы можете достичь максимальной производительности на каждом конкретном GPU iOS, не нацеливаясь на отдельные базы кода. Шейдеры производительности металла можно использовать с любым ресурсом металла, например текстурами и буферами.

Платформа шейдера производительности металла предоставляет набор распространенных шейдеров, таких как:

  • Gaussian Blur (MPSImageGaussianBlur)
  • Обнаружение пограничных вычислений Sobel (MPSImageSobel)
  • Гистограмма изображения (MPSImageHistogram)

Дополнительные сведения см. в руководстве по языку заливки металла Apple.

Введение в модель ввода-вывода

Платформа ввода-вывода модели Apple обеспечивает глубокое понимание трехмерных ресурсов (таких как модели и связанные с ними ресурсы). Модель ввода-вывода предоставляет игры iOS с физическими материалами, моделями и освещением, которые можно использовать с Игровой игройKit, Metal и SceneKit.

С помощью модели ввода-вывода можно поддерживать следующие типы задач:

  • Импорт освещения, материалов, данных сетки, параметров камеры и других сведений на основе сцены из различных популярных форматов программного обеспечения и игрового двигателя.
  • Обработка или создание сведений на основе сцены, таких как создание процедурно текстурированных куполов неба или выпечки освещения в сетку.
  • Работает с MetalKit, SceneKit и GLKit для эффективной загрузки игровых ресурсов в буферы GPU для отрисовки.
  • Экспортируйте сведения на основе сцены в различные популярные форматы программного обеспечения и игрового ядра.

Дополнительные сведения о модели ввода-вывода см. в справочнике по модели ввода-вывода Apple

Знакомство с ReplayKit

Новая платформа ReplayKit Apple позволяет легко добавлять запись игры в игру iOS и позволяет пользователю быстро и легко редактировать и предоставлять общий доступ к этому видео из приложения.

Дополнительные сведения см. в видео Apple's Going Social с ReplayKit и Game Center и их DemoBots: создание кроссплатформенной игры с помощью SpriteKit и GameKit пример приложения.

SceneKit

Набор сцен — это API графа трехмерных сцен, упрощающий работу с трехмерной графикой. Он был впервые представлен в OS X 10.8 и теперь пришел в iOS 8. При создании иммерсивных трехмерных визуализаций и случайных трехмерных игр не требуется опыт работы в OpenGL. Опираясь на общие понятия графа сцены, Набор сцен абстрагирует сложности OpenGL и OpenGL ES, что упрощает добавление трехмерного содержимого в приложение. Тем не менее, если вы эксперт OpenGL, Scene Kit имеет большую поддержку связывания напрямую с OpenGL, а также. Он также включает в себя множество функций, которые дополняют трехмерную графику, такие как физика, и интегрируются очень хорошо с несколькими другими платформами Apple, такими как Core Animation, Core Image и Sprite Kit.

Дополнительные сведения см. в документации по SceneKit .

Изменения SceneKit

Apple добавила следующие новые функции в SceneKit для iOS 9:

  • Xcode теперь предоставляет редактор сцен, который позволяет быстро создавать игры и интерактивные трехмерные приложения, изменяя сцены непосредственно из Xcode.
  • SCNSceneRenderer Классы SCNView можно использовать для включения отрисовки металла (на поддерживаемых устройствах iOS).
  • SCNNode Классы SCNAudioPlayer можно использовать для добавления пространственных звуковых эффектов, которые автоматически отслеживают положение проигрывателя в приложении iOS.

Дополнительные сведения см. в документации по SceneKit и Справочнике по СценамApple и Fox: создание игры SceneKit с помощью примера проекта редактора сцен Xcode.

SpriteKit

Sprite Kit, 2D игровой платформы от Apple, имеет некоторые интересные новые возможности в iOS 8 и OS X Yosemite. К ним относятся интеграция с Набором сцен, поддержкой шейдера, освещением, тенями, ограничениями, нормальным созданием карт и улучшениями физики. В частности, новые функции физики делают его очень легко добавить реалистичные эффекты в игру.

Дополнительные сведения см. в нашей документации spriteKit .

Изменения SpriteKit

Apple добавила следующие новые возможности в SpriteKit для iOS 9:

  • Пространственный звуковой эффект, который автоматически отслеживает положение проигрывателя с классом SKAudioNode .
  • Xcode теперь включает редактор сцен и редактор действий для простого создания трехмерных игр и приложений.
  • Простая поддержка игр прокрутки с новыми объектами Камера Nodes (SKCameraNode).
  • На устройствах iOS, поддерживающих Metal, SpriteKit автоматически будет использовать его для отрисовки, даже если вы уже использовали пользовательские шейдеры OpenGL ES.

Дополнительные сведения см. в нашей документации SpriteKit Framework Apple и их DemoBots: создание кроссплатформенной игры с помощью SpriteKit и GameKit пример приложения.

Итоги

В этой статье рассматриваются новые функции игр, предоставляемые iOS 9 для приложений Xamarin.iOS. В ней появились игровые наборы и модели ввода-вывода; основные улучшения в Металле; и новые возможности SceneKit и SpriteKit.