【visionOS】GameOverを実装する
はじめに
今回は衝突判定を使ってGameOverを実装したので紹介します。
衝突判定の方法は前回の【visionOS】衝突判定してEntityをシーンから削除するを参考にしてください。
また、以前デバイスの位置の取得方法も紹介しましたが、今回リファクタリングでまとめたので改めて紹介しようと思います。
つくったもの
※ゲーム内では鬼から攻撃されたときにGameOverになるように実装していますが、今回はわかりやすいようにシンプルにデバイスと鬼が衝突したときの実装にしています。
環境
- Xcode Version 15.3
- visionOS 1.1
デバイスの位置を取得
【Swift】visionOSで鬼をカメラの方向へ動かすでデバイスの位置を取得する方法を紹介しましたが、他のSystemクラスから取得するケースが出てきたのでDeviceSystem
としてまとめることにしました。
onReceive(timer)
で取得する方法も可能ですが、別のクラスから取得しようとするとうまくいかなかったのでSystemを採用しています。
まずは、デバイスを識別するため、ModelEntityを作成するメソッドを準備します。
衝突判定も必要なのでCollisionComponent
を付与しています。
func generateDevice() -> Entity {
let device = ModelEntity()
device.name = "Device"
device.components.set(CollisionComponent(
shapes: [.generateSphere(radius: 0.5)],
mode: .default
))
return device
}
次に、DeviceSystemクラスのupdate関数でtransformを更新します。
こうすることでdeviceEntity
の位置をフレームごとに更新することが可能になります。
class DeviceSystem : System {
private let arkitSession = ARKitSession()
private let worldTrackingProvider = WorldTrackingProvider()
required init(scene: RealityKit.Scene) {
setUpSession()
}
func setUpSession() {
Task {
do {
try await arkitSession.run([worldTrackingProvider])
} catch {
print("Error: \(error)")
}
}
}
func update(context: SceneUpdateContext) {
guard let device = context.scene.findEntity(named: "Device") else { return }
guard let deviceAnchor = worldTrackingProvider.queryDeviceAnchor(atTimestamp: CACurrentMediaTime()) else { return }
device.transform = Transform(matrix: deviceAnchor.originFromAnchorTransform)
}
}
最後に、RealityViewでdeviceEntity
をSceneに追加します。
RealityView { content in
// カメラをシーンに追加
deviceEntity = device.generateDevice()
content.add(deviceEntity)
}
デバイスと鬼が衝突したらGameOver
デバイスと鬼が当たったら目の前を赤くしてImmersiveSpaceを非表示にするようにしました。ただし、真っ赤にしてしまうと周りが全く見えなくなって危ないので半透明にしています。
まずは、ダメージを受けたときに目の間を赤く表示したいのでダメージ用のEntityを作成するメソッドを準備します。
Opacityをつけるには.transparent(opacity: PhysicallyBasedMaterial.Opacity(floatLiteral: 0.5))
でつけることが可能です。
func generateDamage() -> Entity {
// ダメージ時は目の前を赤くする
var material = UnlitMaterial(color: .red, applyPostProcessToneMap: false)
let damage = ModelEntity(
mesh: .generateSphere(radius: 0.5),
materials: [material]
)
damage.components.set(OpacityComponent(opacity: 0.5))
// カメラの内側を見せたいのでxを-1
damage.scale.x = -1
return damage
}
次に、衝突したらdeviceEntity
の子にdamageEntity
をaddChild()
することでダメージを受けたような演出にしています。
// 衝突判定
subscription = content.subscribe(to: CollisionEvents.Began.self) { collisionEvent in
// 鬼とデバイスが衝突したらゲームオーバー
if collisionEvent.entityA.name == "Oni" && collisionEvent.entityB.name == "Device" {
let oni = collisionEvent.entityA
oni.isEnabled = false
Task {
try await Task.sleep(for: .milliseconds(1000))
gameModel.playSoundEffect(fileName: "GameOver", entity: deviceEntity)
let damege = device.generateDamage()
deviceEntity.addChild(damege)
try await Task.sleep(for: .milliseconds(1600))
gameModel.isFinished = true
await dismissImmersiveSpace()
}
}
}
最後に、Sceneをリセットし、ImmersiveSpaceを非表示にしています。
GameModelで状態管理し、RootであるcontentEntityからSceneのEntityをすべて削除しています。
var isFinished = false {
didSet {
if isFinished == true {
contentEntity.children.removeAll()
}
}
}
おわりに
以上、「GameOverを実装する」でした。
ダメージを受けた時によくありそうな目の前を赤くしてGameOverを表現してみました。
デバイス位置の取得は他にも使えると思うのでぜひ参考にしてみてください。
また、今回の記事で紹介している空間シューティングゲーム「妖怪バスターZ」を日本発売前の2024年5月3日にリリースしました。
ぜひインストールして遊んでみてください。
今後もvisionOSについて発信していきますので、この記事が参考になったと思ったらぜひ♡をお願いします。
Discussion