Xamarin.iOS 中的 iOS 游戏 API
本文介绍 iOS 9 提供的新游戏增强功能,可用于改进 Xamarin.iOS 游戏的图形和音频功能。
Apple 对 iOS 9 中的游戏 API 进行了多项技术改进,使 Xamarin.iOS 应用更容易实现游戏图形和音频。 这包括通过高级框架简化开发,以及利用 iOS 设备 GPU 的强大功能提高速度和图形能力。
其中包括 GameplayKit、ReplayKit、Model I/O、MetalKit 和 Metal Performance Shader,以及 Metal、SceneKit 和 SpriteKit 的新增强功能。
本文将介绍使用 iOS 9 的新游戏增强功能改进 Xamarin.iOS 游戏的所有方法:
游戏工具包简介
Apple 的新 GamesKit 框架提供了一组技术,通过减少实现所需的重复通用代码量,可以轻松地为 iOS 设备创建游戏。 GameKit 提供了用于开发游戏机制的工具,然后可以轻松地与图形引擎(如 SceneKit 或 SpriteKit)结合使用,以快速交付已完成的游戏。
GameKit 包括多个常见的游戏算法,例如:
- 基于行为的代理模拟,可用于定义 AI 将自动追求的动作和目标。
- 用于回合游戏的 minmax 人工智能。
- 数据驱动游戏逻辑的规则系统,具有模糊推理以提供新兴行为。
此外,GameKit 使用提供以下功能的模块化体系结构,采用游戏开发构建基块方法:
- 用于在游戏中处理基于过程代码的复杂系统的状态机。
- 用于提供随机游戏和不可预测性的工具,而不会导致调试问题。
- 基于可重用的组件化实体体系结构。
若要了解有关 GameplayKit 的详细信息,请参阅 Apple 的 游戏工具包编程指南 和 GameplayKit 框架参考。
GameplayKit 示例
让我们快速了解如何使用游戏工具包在 Xamarin.iOS 应用中实现一些简单的游戏机制。
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# 代码片段演示了如何使用 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# 代码片段使用游戏玩法工具包和 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” 将蜂拥在我们的手指点击周围:
其他 Apple 示例
除了上述示例外,Apple 还提供了可转码为 C# 和 Xamarin.iOS 的以下示例应用:
- FourInARow:将 GameplayKit Minmax 策略师用于对手 AI
- AgentsCatalog:在 GameplayKit 中使用代理系统
- DemoBots:使用 SpriteKit 和 GameKit 构建跨平台游戏
Metal
在 iOS 9 中,Apple 对 Metal 进行了多项更改和补充,以提供对 GPU 的低开销访问。 使用 Metal 可以最大化 iOS 应用的图形和计算潜力。
Metal 框架包含以下新功能:
- OS X 的新专用和深度模具纹理。
- 改进了阴影质量,并具有深度固定和单独的正面和背面模具值。
- 金属底纹语言和金属标准库改进。
- 计算着色器支持更广泛的像素格式。
MetalKit 框架
MetalKit 框架提供了一组实用工具类和功能,可减少 iOS 应用中使用 Metal 所需的工作量。 MetalKit 在三个关键领域提供支持:
- 从各种源进行异步纹理加载,包括 PNG、JPEG、KTX 和 PVR 等常见格式。
- 轻松访问基于模型 I/O 的资产进行金属特定的模型处理。 这些功能经过高度优化,可以在模型 I/O 网格与金属缓冲区之间提供高效的数据传输。
- 预定义的金属视图和视图管理,大大减少了在 iOS 应用中显示图形呈现所需的代码量。
若要了解有关 MetalKit 的详细信息,请参阅 Apple 的 MetalKit 框架参考、 金属编程指南、 金属框架参考 和 金属底纹语言指南。
金属性能着色器框架
金属性能着色器框架提供一组高度优化的图形和基于计算的着色器,用于基于金属的 iOS 应用。 已专门优化金属性能着色器框架中的每个着色器,以在支持金属的 iOS GPU 上提供高性能。
通过使用金属性能着色器类,可以在每个特定的 iOS GPU 上实现最高性能,而无需定位和维护单个代码库。 金属性能着色器可用于任何金属资源,例如纹理和缓冲区。
金属性能着色器框架提供一组常见的着色器,例如:
- 高斯·布鲁 尔(
MPSImageGaussianBlur
) - 索贝尔边缘检测 (
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
场景工具包是一个 3D 场景图形 API,可简化使用 3D 图形。 它首次在 OS X 10.8 中引入,现已推出 iOS 8。 使用场景工具包创建沉浸式 3D 可视化效果和休闲 3D 游戏不需要 OpenGL 的专业知识。 基于常见的场景图概念构建,场景工具包将 OpenGL 和 OpenGL ES 的复杂性抽象化,使向应用程序添加 3D 内容变得非常简单。 但是,如果你是 OpenGL 专家,场景工具包也非常支持直接与 OpenGL 搭配使用。 它还包括许多功能,这些功能补充了 3D 图形(如物理),并与其他几个 Apple 框架(如核心动画、核心图像和子画面工具包)完美集成。
SceneKit 更改
Apple 已将以下新功能添加到适用于 iOS 9 的 SceneKit:
- Xcode 现在提供了一个场景编辑器,允许你直接从 Xcode 中编辑场景来快速生成游戏和交互式 3D 应用。
- 这些
SCNView
和SCNSceneRenderer
类可用于启用金属渲染(在受支持的 iOS 设备上)。 - 这些
SCNAudioPlayer
和SCNNode
类可用于添加空间音频效果,这些效果可自动跟踪玩家位置到 iOS 应用。
有关详细信息,请参阅 SceneKit 文档 和 Apple 的 SceneKit 框架参考 和 Fox:使用 Xcode 场景编辑器 示例项目构建 SceneKit 游戏。
SpriteKit
Apple 的 2D 游戏框架 Sprite Kit 在 iOS 8 和 OS X Yosemite 中具有一些有趣的新功能。 其中包括与场景工具包的集成、着色器支持、照明、阴影、约束、普通地图生成和物理增强功能。 特别是,新的物理功能使它很容易向游戏添加现实的效果。
SpriteKit 更改
Apple 已将以下新功能添加到适用于 iOS 9 的 SpriteKit:
- 使用类自动跟踪玩家位置
SKAudioNode
的空间音频效果。 - Xcode 现在提供一个场景编辑器和操作编辑器,用于轻松创建 2D 游戏和应用。
- 使用新的 相机 Nodes (
SKCameraNode
) 对象轻松滚动游戏支持。 - 在支持 Metal 的 iOS 设备上,SpriteKit 会自动使用它进行呈现,即使你已在使用自定义 OpenGL ES 着色器。
有关详细信息,请参阅 SpriteKit 文档 Apple 的 SpriteKit 框架参考 及其 DemoBot:使用 SpriteKit 和 GameKit 示例应用构建跨平台游戏。
总结
本文介绍了 iOS 9 为 Xamarin.iOS 应用提供的新游戏功能。 它引入了游戏工具包和模型 I/O;金属的主要增强功能;和 SceneKit 和 SpriteKit 的新功能。