24-苹果官方AR多人弹弓射击Demo解读(下)

今年的 Session 605 - Inside SwiftShot: Creating an AR Game 上演示了一个叫SwiftShot的多人游戏Demo
其中涉及到的内容非常多,上篇是对wwdc605内容的简单总结,而本篇是对官方未说明的部分进行补充说明。
注释版代码

说明

ARKit文章目录

本文是对官方 demo 内容的讲解。是对上一篇官方解读的补充。


文件结构

其他技术点

WWDC605 中对 GameplayKit,metal 旗帜模拟,AVAudio 处理 Midi 音频都只是简单几句带过,相信你和我一样都对此很感兴趣,我们就一起研究一下代码吧。

GameplayKit

GamePlayKit给游戏开发者带来了全新的游戏架构(“实体组件系统”)和一些通用模式(比如:状态机,Goal-agent-behavior系统等),同时它还提供了大量游戏算法,比如寻路算法,模糊逻辑和规则系统等。

在这个官方 Demo 中没有用到人工智能策略和复杂逻辑,所以我们专注于了解GKEntityGKComponent两个类:

GKEntity类(实体):可以容纳很多组件的容器,根据自己的需求来加入相应的Component组件。
GKComponent类(组件):代表一种功能和行为,不同的组件代表不同的功能。

实用功能:
(1)方便通过聚合多种组件,构建复杂的新实体Entity。
(2)不同的实体GKEntity,通过聚合不同种类的组件GKComponent来实现。
(3)不必重复写组件,组件可以复用,也易于扩展。
(4)实体可以实现动态添加组件,以动态获得或者删除某些功能。

创建组件时,通过继承可复用部分进行子类化创建,然后创建GKEntity并使用addComponent:方法进行组件捆绑。

在runtime运行时,基于组件开发的游戏需要周期性调用逻辑方法,所以我们可以使用update/render loop相关方法,比如Spritekit中的update:方法或SceneKit中的renderer: updateAtTime:方法,或基于CADisplayLink的timer自定义一个渲染器为每一个组件进行更新。Gameplaykit提供了两种机制去调用update更新:

• 实体:调用每个实体的updateWithDeltaTime:方法,去更新每一个组件,如果游戏中实体和组件较少,这个方法会很快速。

• 组件:通过GKComponentSystem来处理特定的组件类实例,当你调用updateWithDeltaTime:方法时,它会更新它所管理的所有组件。因为一个组件系统(GKComponentSystem)不需要了解你的游戏层次结构,这个功能可以很好地在复杂的对象关系中运行。


整个逻辑是:GameViewController是最上层,各种Components(GKComponent)是最低层组件,GameObject(GKEntity)对应游戏中的实体:
GameViewController-->GameManager-->GameObjectManager-->GameObject-->Components

metal 实现旗帜模拟

实现是在Flag.swiftFlag.metal两个文件里面。


先看Flag.swift文件中,有两个类ClothSimMetalNodeMetalClothSimulator

ClothSimMetalNode只有一个作用:在init方法中创建一个指定尺寸的几何体,并创建顶点,法线,uv,还有顶点索引。

MetalClothSimulator两个作用:在init方法中创建管线并关联三个 shader;传递数据给管线中 shader。

// 与 Flag.metal 中的三个 shader 关联起来
functionClothSim = defaultLibrary.makeFunction(name: "updateVertex")!
functionNormalUpdate = defaultLibrary.makeFunction(name: "updateNormal")!
functionNormalSmooth = defaultLibrary.makeFunction(name: "smoothNormal")!

// 创建计算管线
do {
    pipelineStateClothSim = try device.makeComputePipelineState(function: functionClothSim)
    pipelineStateNormalUpdate = try device.makeComputePipelineState(function: functionNormalUpdate)
    pipelineStateNormalSmooth = try device.makeComputePipelineState(function: functionNormalSmooth)
} catch {
     fatalError("\(error)")
}

最终,deform(_ mesh: ClothSimMetalNode, simData: SimulationData)将所有数据传递给管线及对应的 shader 中。

三个着色器中updateVertex计算旗帜的力和变形;
updateNormal计算每个点的法线;
smoothNormal取每个点与周围点的法线,取平均值得到平滑的法线;

所以最终得到一面平滑的旗帜,而又无需大量多边形.每一帧画面中,shader会匹配几何体的位置,这样就充分发挥了GPU的潜力,不会影响CPU.

updateNormalsmoothNormal只是求向量叉乘再累加的,比较容易理解。

updateVertex涉及了物理学,受力与加速度,速度,位移之间的关系。而且内部布料拉力的计算是调用ApplyForce(inVertices, inVelocities, pos, vel, x, y, dx, dy, str)函数获取的。

```c++
// 定义的宏,以方便调用,当满足condition条件时,调用ApplyForce(inVertices, inVelocities, pos, vel, x, y, dx, dy, str)并累加到 force 上

define APPLY_FORCE(condition, dx, dy, str) \

if(condition) \
{ \
force += ApplyForce(inVertices, inVelocities, pos, vel, x, y, dx, dy, str); \
}

// 计算内部弹力(拉力)的函数
float3 ApplyForce(const device float3* positions,
const device float3* velocities,
const float3 pos,
const float3 vel,
const uint x,
const uint y,
const uint dx,
const uint dy,
const float constraintStrength)
{
// Get the distance between the point and its neighbour of interest
// 获取点与其邻近点之间的距离(3D 空间中的距离,即变形程度向量)
float3 deltaP = GetPosition(positions, x+dx, y+dy) - pos;

top Created with Sketch.