Xamarin.iOS의 iOS 게임 API

이 문서에서는 Xamarin.iOS 게임의 그래픽 및 오디오 기능을 개선하는 데 사용할 수 있는 iOS 9에서 제공하는 새로운 게임 기능을 다룹니다.

Apple은 xamarin.iOS 앱에서 게임 그래픽 및 오디오를 보다 쉽게 구현할 수 있도록 iOS 9의 게임 API를 몇 가지 기술로 개선했습니다. 여기에는 고급 프레임워크를 통한 개발 용이성과 향상된 속도 및 그래픽 기능을 위해 iOS 디바이스의 GPU 기능을 활용하는 기능이 모두 포함됩니다.

무리가 실행되는 앱의 예

여기에는 GameplayKit, ReplayKit, 모델 I/O, MetalKit 및 금속 성능 셰이더와 Metal, SceneKit 및 SpriteKit의 새로운 향상된 기능이 포함됩니다.

이 문서에서는 iOS 9의 새로운 게임 향상된 기능을 사용하여 Xamarin.iOS 게임을 개선하는 모든 방법을 소개합니다.

GameplayKit 소개

Apple의 새로운 GameplayKit 프레임워크는 구현에 필요한 반복적이고 일반적인 코드의 양을 줄여 iOS 디바이스용 게임을 쉽게 만들 수 있는 일련의 기술을 제공합니다. GameplayKit은 그래픽 엔진(예: SceneKit 또는 SpriteKit)과 쉽게 결합하여 완성된 게임을 신속하게 제공할 수 있는 게임 메커니즘을 개발하기 위한 도구를 제공합니다.

GameplayKit에는 다음과 같은 몇 가지 일반적인 게임 플레이 알고리즘이 포함되어 있습니다.

  • AI가 자동으로 추구하는 움직임과 목표를 정의할 수 있는 동작 기반 에이전트 시뮬레이션입니다.
  • 턴 기반 게임 플레이에 대한 최소 인공 지능.
  • 새로운 동작을 제공하기 위해 유사 추론이 있는 데이터 기반 게임 논리에 대한 규칙 시스템입니다.

또한 GameplayKit는 다음과 같은 기능을 제공하는 모듈식 아키텍처를 사용하여 게임 개발에 대한 구성 요소 접근 방식을 사용합니다.

  • 게임 플레이에서 복잡한 절차적 코드 기반 시스템을 처리하기 위한 상태 컴퓨터입니다.
  • 디버깅 문제를 일으키지 않고 임의 게임 플레이 및 예측 불가능성을 제공하는 도구입니다.
  • 재사용 가능한 구성 요소화된 엔터티 기반 아키텍처입니다.

GameplayKit에 대한 자세한 내용은 Apple의 Gameplaykit 프로그래밍 가이드GameplayKit 프레임워크 참조를 참조하세요.

GameplayKit 예제

게임 플레이 키트를 사용하여 Xamarin.iOS 앱에서 간단한 게임 플레이 메커니즘을 구현하는 방법에 대해 간략하게 살펴보겠습니다.

경로 정의

경로 정의는 게임의 AI 요소가 게임 보드에서 길을 찾을 수 있는 기능입니다. 예를 들어, 1인칭 슈팅 게임 세계 지형을 통해 미로 또는 3D 문자를 통해 길을 찾는 2D 적.

다음 맵을 고려합니다.

경로 정의 맵 예제

이 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) 및 알려진 입력 집합에 따라 전문가 시스템(GKRuleSystem)은 예측 가능한 출력을 만듭니다(fizzbuzz 위 예제).

몰려

무리는 AI 제어 게임 엔터티의 그룹이 무리로 행동 할 수 있습니다, 그룹은 비행 조류의 무리 또는 물고기 수영의 학교와 같은 리드 엔터티의 움직임과 행동에 응답.

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

실행할 때, 작은 애니메이션 "보이드" 는 우리의 손가락 탭 주위에 무리 것입니다 :

작은 애니메이션 보이드는 손가락 탭 주위에 무리 것입니다

기타 Apple 예제

위에서 제시한 샘플 외에도 Apple은 C# 및 Xamarin.iOS로 트랜스코딩할 수 있는 다음 샘플 앱을 제공했습니다.

Metal

iOS 9에서 Apple은 GPU에 대한 오버헤드가 낮은 액세스를 제공하기 위해 Metal을 몇 가지 변경하고 추가했습니다. Metal을 사용하면 iOS 앱의 그래픽 및 컴퓨팅 잠재력을 최대화할 수 있습니다.

Metal 프레임워크에는 다음과 같은 새로운 기능이 포함되어 있습니다.

  • OS X에 대한 새로운 프라이빗 및 깊이 스텐실 텍스처.
  • 깊이 클램핑과 별도의 전면 및 후면 스텐실 값으로 섀도 품질이 향상되었습니다.
  • 금속 음영 언어 및 금속 표준 라이브러리 개선.
  • 계산 셰이더는 더 넓은 범위의 픽셀 형식을 지원합니다.

MetalKit 프레임워크

MetalKit 프레임워크는 iOS 앱에서 Metal을 사용하는 데 필요한 작업량을 줄이는 유틸리티 클래스 및 기능 집합을 제공합니다. MetalKit는 다음 세 가지 주요 영역에서 지원을 제공합니다.

  1. PNG, JPEG, KTX 및 PVR과 같은 일반적인 형식을 포함하여 다양한 소스에서 비동기 텍스처 로드
  2. 금속별 모델 처리를 위해 모델 I/O 기반 자산에 쉽게 액세스할 수 있습니다. 이러한 기능은 모델 I/O 메시와 금속 버퍼 간에 효율적인 데이터 전송을 제공하도록 고도로 최적화되었습니다.
  3. iOS 앱 내에서 그래픽 렌더링을 표시하는 데 필요한 코드 양을 크게 줄이는 미리 정의된 금속 보기 및 뷰 관리

MetalKit에 대한 자세한 내용은 Apple의 MetalKit 프레임워크 참조, 금속 프로그래밍 가이드, 금속 프레임워크 참조금속 음영 언어 가이드를 참조하세요.

금속 성능 셰이더 프레임워크

금속 성능 셰이더 프레임워크는 Metal 기반 iOS 앱에서 사용할 수 있는 고도로 최적화된 그래픽 및 계산 기반 셰이더 집합을 제공합니다. 금속 성능 셰이더 프레임워크의 각 셰이더는 금속 지원 iOS GPU에서 고성능을 제공하도록 특별히 조정되었습니다.

Metal Performance Shader 클래스를 사용하면 개별 코드 베이스를 대상으로 지정하고 기본 않고도 각 특정 iOS GPU에서 가능한 최고 성능을 달성할 수 있습니다. 금속 성능 셰이더는 텍스처 및 버퍼와 같은 모든 금속 리소스와 함께 사용할 수 있습니다.

금속 성능 셰이더 프레임워크는 다음과 같은 일반적인 셰이더 집합을 제공합니다.

  • 가우시안 흐림 (MPSImageGaussianBlur)
  • Sobel Edge 검색 (MPSImageSobel)
  • 이미지 히스토그램 (MPSImageHistogram)

자세한 내용은 Apple의 금속 음영 언어 가이드를 참조하세요.

모델 I/O 소개

Apple의 모델 I/O 프레임워크는 3D 자산(예: 모델 및 관련 리소스)에 대한 깊은 이해를 제공합니다. 모델 I/O는 GameplayKit, Metal 및 SceneKit에서 사용할 수 있는 물리적 기반 재질, 모델 및 조명을 iOS 게임에 제공합니다.

모델 I/O를 사용하면 다음 유형의 작업을 지원할 수 있습니다.

  • 다양한 인기 소프트웨어 및 게임 엔진 형식에서 조명, 재질, 메시 데이터, 카메라 설정 및 기타 장면 기반 정보를 가져옵니다.
  • 절차적으로 질감이 있는 하늘 돔을 만들거나 조명을 메시에 굽는 것과 같은 장면 기반 정보를 처리하거나 생성합니다.
  • MetalKit, SceneKit 및 GLKit과 협력하여 렌더링을 위해 GPU 버퍼에 게임 자산을 효율적으로 로드합니다.
  • 장면 기반 정보를 다양한 인기 있는 소프트웨어 및 게임 엔진 형식으로 내보냅니다.

모델 I/O에 대한 자세한 내용은 Apple의 모델 I/O 프레임워크 참조를 참조하세요.

ReplayKit 소개

Apple의 새로운 ReplayKit 프레임워크를 사용하면 iOS 게임에 게임 플레이 기록을 쉽게 추가하고 사용자가 앱 내에서 이 비디오를 빠르고 쉽게 편집하고 공유할 수 있습니다.

자세한 내용은 ReplayKit 및 Game Center 비디오와 데모봇을 사용하여 Apple의 Going Social을 참조하세요. SpriteKit 및 GameplayKit 샘플 앱을 사용하여 플랫폼 간 게임 빌드

SceneKit

장면 키트는 3D 그래픽 작업을 간소화하는 3D 장면 그래프 API입니다. OS X 10.8에서 처음 도입되었으며 이제 iOS 8에 도입되었습니다. 장면 키트를 사용하여 몰입형 3D 시각화 및 캐주얼 3D 게임을 만들 때 OpenGL에 대한 전문 지식이 필요하지 않습니다. 일반적인 장면 그래프 개념을 바탕으로 장면 키트는 OpenGL 및 OpenGL ES의 복잡성을 추상화하여 애플리케이션에 3D 콘텐츠를 매우 쉽게 추가할 수 있도록 합니다. 그러나 OpenGL 전문가인 경우 장면 키트는 OpenGL과 직접 연결하는 데도 큰 지원을 제공합니다. 또한 물리학과 같은 3D 그래픽을 보완하는 수많은 기능이 포함되어 있으며 코어 애니메이션, 코어 이미지 및 스프라이트 키트와 같은 다른 여러 Apple 프레임워크와 매우 잘 통합됩니다.

자세한 내용은 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의 몇 가지 흥미로운 새로운 기능이 있습니다. 여기에는 장면 키트와의 통합, 셰이더 지원, 조명, 그림자, 제약 조건, 일반 지도 생성 및 물리학 향상이 포함됩니다. 특히, 새로운 물리학 기능은 매우 쉽게 게임에 사실적인 효과를 추가 할 수 있습니다.

자세한 내용은 SpriteKit 설명서를 참조하세요.

SpriteKit 변경 내용

Apple은 iOS 9용 SpriteKit에 다음과 같은 새로운 기능을 추가했습니다.

  • 클래스를 사용하여 플레이어의 위치를 SKAudioNode 자동으로 추적하는 공간 오디오 효과입니다.
  • Xcode는 이제 손쉬운 2D 게임 및 앱 생성을 위한 장면 편집기 및 작업 편집기를 제공합니다.
  • 새 카메라 Nodes(SKCameraNode) 개체를 사용하여 게임 지원을 쉽게 스크롤할 수 있습니다.
  • Metal을 지원하는 iOS 디바이스에서 SpriteKit는 사용자 지정 OpenGL ES 셰이더를 이미 사용했더라도 자동으로 렌더링에 사용합니다.

자세한 내용은 SpriteKit 설명서 Apple의 SpriteKit 프레임워크 참조DemoBots: SpriteKit 및 GameplayKit 샘플 앱을 사용하여 플랫폼 간 게임 빌드를 참조하세요.

요약

이 문서에서는 iOS 9에서 Xamarin.iOS 앱에 제공하는 새로운 게임 기능에 대해 설명했습니다. GameplayKit 및 모델 I/O를 도입했습니다. 금속의 주요 향상된 기능; 및 SceneKit 및 SpriteKit의 새로운 기능