Share via


Xamarin.iOS 中的 iOS 遊戲 API

本文涵蓋 iOS 9 所提供的新遊戲增強功能,可用來改善 Xamarin.iOS 遊戲的圖形和音訊功能。

Apple 已對 iOS 9 中的遊戲 API 進行了數項技術改進,可讓您更輕鬆地在 Xamarin.iOS 應用程式中實作遊戲圖形和音訊。 這些包括透過高階架構輕鬆開發,以及利用 iOS 裝置 GPU 的強大功能來提升速度和圖形能力。

執行蜂擁的應用程式範例

這包括GamesKit、ReplayKit、Model I/O、MetalKit 和金屬性能著色器,以及金屬、SceneKit 和 SpriteKit 的全新增強功能。

本文將介紹使用 iOS 9 的新遊戲增強功能來改善 Xamarin.iOS 遊戲的所有方式:

GameKit 簡介

Apple 的新 GamesKit 架構提供一組技術,可藉由減少實作所需的重複常見程式代碼量,輕鬆建立 iOS 裝置的遊戲。 GameKit 提供用來開發遊戲機制的工具,然後可以輕鬆地與圖形引擎(例如 SceneKit 或 SpriteKit)結合,以快速提供完整的遊戲。

GameKit 包含數種常見的遊戲演算法,例如:

  • 以行為為基礎的代理程序模擬,可讓您定義 AI 將自動追求的行動和目標。
  • 回合型遊戲的最小人工智慧。
  • 數據驅動遊戲邏輯的規則系統,具有模糊推理來提供新興行為。

此外,GameKit 會使用提供下列功能的模組化架構,採用遊戲開發的建置組塊方法:

  • 用於處理遊戲中複雜程式碼型系統的狀態機器。
  • 提供隨機遊戲和不可預測性的工具,而不會造成偵錯問題。
  • 可重複使用的元件化實體架構。

若要深入瞭解GamesKit,請參閱Apple的 Gameskit 程式設計指南GamesKit架構參考

GameKit 範例

讓我們快速瞭解使用遊戲套件在 Xamarin.iOS 應用程式中實作一些簡單的遊戲機制。

Pathfinding

Pathfinding 是遊戲 AI 元素在遊戲板中尋找其方式的能力。 例如,2D 敵人透過迷宮或 3D 字元透過第一人稱射擊世界地形尋找方向。

請考慮下列對應:

範例路徑尋找對應

使用路徑尋找此 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# 程式代碼段示範如何使用GamesKit 來實作傳統專家系統:

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) 和一組已知的輸入,專家系統 (GKRuleSystem) 會建立可預測的輸出(fizzbuzz 如上例所示)。

植 絨

蜂擁可讓一組 AI 控制的遊戲實體作為一群蜂群,其中該群組會回應領先實體的移動和動作,例如飛行中的鳥類群或魚類游泳學校。

下列 C# 程式代碼代碼段使用GamesKit 和 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」 會蜂擁而至:

小動畫的Boids會蜂擁而至的手指點選

其他 Apple 範例

除了上述範例之外,Apple 還提供下列可轉碼為 C# 和 Xamarin.iOS 的應用程式範例:

Metal

在 iOS 9 中,Apple 已對 Metal 進行了數項變更和新增,以提供 GPU 的低負荷存取。 使用金屬,您可以將 iOS 應用程式的圖形和運算潛力最大化。

Metal 架構包含下列新功能:

  • OS X 的新私人和深度樣板紋理。
  • 使用深度夾住和個別的正面和背面樣板值來改善陰影品質。
  • 金屬底紋語言和金屬標準連結庫改善。
  • 計算著色器支援更廣泛的像素格式。

MetalKit 架構

MetalKit 架構提供一組公用程式類別和功能,可減少 iOS 應用程式中使用 Metal 所需的工作量。 MetalKit 提供三個主要領域的支援:

  1. 從各種來源載入異步紋理,包括一般格式,例如 PNG、JPEG、KTX 和 PVR。
  2. 針對金屬特定模型處理,輕鬆存取模型 I/O 型資產。 這些功能已高度優化,可在模型 I/O 網格與金屬緩衝區之間提供有效率的數據傳輸。
  3. 預先定義的金屬檢視和檢視管理,可大幅減少在iOS應用程式中顯示圖形轉譯所需的程式代碼數量。

若要深入瞭解 MetalKit,請參閱 Apple 的 MetalKit 架構參考金屬程序設計指南金屬架構參考金屬底紋語言指南

金屬效能著色器架構

金屬性能著色器架構提供一組高度優化的圖形和計算型著色器,以用於您的金屬型 iOS 應用程式。 金屬性能著色器架構中的每個著色器都經過特別調整,以在支援金屬的iOS GPU上提供高效能。

藉由使用金屬效能著色器類別,您可以在每個特定的 iOS GPU 上達到最高效能,而不需要設定目標並維護個別程式碼基底。 金屬性能著色器可以搭配任何金屬資源使用,例如紋理和緩衝區。

金屬效能著色器架構提供一組常見的著色器,例如:

  • 高斯布盧 爾 (MPSImageGaussianBlur
  • Sobel Edge 偵測MPSImageSobel
  • 影像直方圖MPSImageHistogram

如需詳細資訊,請參閱Apple的 金屬底紋語言指南

模型 I/O 簡介

Apple 的模型 I/O 架構可讓您深入瞭解 3D 資產(例如模型及其相關資源)。 模型 I/O 為您的 iOS 遊戲提供實體材質、模型和光源,可與 GamesKit、金屬和 SceneKit 搭配使用。

使用模型 I/O,您可以支援下列類型的工作:

  • 從各種熱門軟體和遊戲引擎格式匯入光源、材質、網格數據、相機設定和其他場景型資訊。
  • 處理或產生場景資訊,例如建立程式紋理的天空圓頂或將光源烤成網格。
  • 與 MetalKit、SceneKit 和 GLKit 搭配運作,以有效率地將遊戲資產載入 GPU 緩衝區以進行轉譯。
  • 將場景型信息導出至各種熱門軟體和遊戲引擎格式。

若要深入瞭解模型 I/O,請參閱 Apple 的 模型 I/O 架構參考

ReplayKit 簡介

Apple 的新 ReplayKit 架構可讓您輕鬆地將遊戲播放的錄製新增至 iOS 遊戲,並讓使用者從應用程式內快速輕鬆地編輯和共用這段影片。

如需詳細資訊,請參閱Apple的 ReplayKit 和 Game Center 影片 及其 DemoBots:使用 SpriteKit 和 GameKit 建置跨平台遊戲範例應用程式。

SceneKit

Scene Kit 是 3D 場景圖形 API,可簡化使用 3D 圖形。 它最初是在OS X 10.8中引進,現在已推出iOS 8。 使用 Scene Kit 建立沉浸式 3D 視覺效果和休閒 3D 遊戲不需要 OpenGL 的專業知識。 在常見的場景圖形概念上建置,Scene Kit 會抽象化 OpenGL 和 OpenGL ES 的複雜度,讓您輕鬆地將 3D 內容新增至應用程式。 不過,如果您是OpenGL專家,Scene Kit也非常支援直接與OpenGL搭配使用。 它也包含許多功能,可補充 3D 圖形,例如物理,並與數個其他 Apple 架構非常整合,例如 Core Animation、Core Image 和 Sprite Kit。

如需詳細資訊,請參閱我們的 SceneKit 檔。

SceneKit 變更

Apple 已將下列新功能新增至適用於 iOS 9 的 SceneKit:

  • Xcode 現在提供場景編輯器,可讓您直接從 Xcode 內編輯場景,快速建置遊戲和互動式 3D 應用程式。
  • SCNViewSCNSceneRenderer 類別可用來啟用金屬轉譯(在支援的 iOS 裝置上)。
  • SCNAudioPlayerSCNNode 類別可用來將空間音訊效果自動追蹤播放程式位置新增至iOS應用程式。

如需詳細資訊,請參閱我們的 SceneKit 檔和 Apple 的 SceneKit 架構參考Fox:使用 Xcode 場景編輯器 範例專案建置 SceneKit 遊戲。

SpriteKit

來自 Apple 的 2D 遊戲架構 Sprite Kit 在 iOS 8 和 OS X Yosemite 中有一些有趣的新功能。 其中包括與 Scene Kit、著色器支援、光源、陰影、條件約束、一般地圖產生和物理增強功能整合。 特別是,新的物理特徵可讓您輕鬆地將現實效果新增至遊戲。

如需詳細資訊,請參閱我們的 SpriteKit 檔。

SpriteKit 變更

Apple 已將下列新功能新增至 SpriteKit for iOS 9:

  • 使用類別自動追蹤播放程式位置 SKAudioNode 的空間音訊效果。
  • Xcode 現在提供場景編輯器和動作編輯器,以便輕鬆建立 2D 遊戲和應用程式。
  • 使用新的 相機 Nodes (SKCameraNode) 物件輕鬆捲動遊戲支援。
  • 在支援 Metal 的 iOS 裝置上,即使您已經使用自定義 OpenGL ES 著色器,SpriteKit 仍會自動使用它進行轉譯。

如需詳細資訊,請參閱我們的 SpriteKit檔 Apple的 SpriteKit架構參考 及其 DemoBots:使用SpriteKit和GameKit 建置跨平臺遊戲範例應用程式。

摘要

本文涵蓋 iOS 9 為您的 Xamarin.iOS 應用程式提供的新遊戲功能。 它引進了GamesKit 和模型 I/O;金屬的主要增強功能;和 SceneKit 和 SpriteKit 的新功能。