🎮

【visionOS】GameOverを実装する

2024/06/04に公開

はじめに

今回は衝突判定を使ってGameOverを実装したので紹介します。

衝突判定の方法は前回の【visionOS】衝突判定してEntityをシーンから削除するを参考にしてください。

また、以前デバイスの位置の取得方法も紹介しましたが、今回リファクタリングでまとめたので改めて紹介しようと思います。

つくったもの

※ゲーム内では鬼から攻撃されたときにGameOverになるように実装していますが、今回はわかりやすいようにシンプルにデバイスと鬼が衝突したときの実装にしています。

環境

  1. Xcode Version 15.3
  2. visionOS 1.1

デバイスの位置を取得

【Swift】visionOSで鬼をカメラの方向へ動かすでデバイスの位置を取得する方法を紹介しましたが、他のSystemクラスから取得するケースが出てきたのでDeviceSystemとしてまとめることにしました。
onReceive(timer)で取得する方法も可能ですが、別のクラスから取得しようとするとうまくいかなかったのでSystemを採用しています。

まずは、デバイスを識別するため、ModelEntityを作成するメソッドを準備します。
衝突判定も必要なのでCollisionComponentを付与しています。

Device.swift
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の位置をフレームごとに更新することが可能になります。

DeviceSystem.swift
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に追加します。

ImmersiveView.swift
RealityView { content in
    // カメラをシーンに追加
    deviceEntity = device.generateDevice()
    content.add(deviceEntity)
}

デバイスと鬼が衝突したらGameOver

デバイスと鬼が当たったら目の前を赤くしてImmersiveSpaceを非表示にするようにしました。ただし、真っ赤にしてしまうと周りが全く見えなくなって危ないので半透明にしています。

まずは、ダメージを受けたときに目の間を赤く表示したいのでダメージ用のEntityを作成するメソッドを準備します。
Opacityをつけるには.transparent(opacity: PhysicallyBasedMaterial.Opacity(floatLiteral: 0.5))でつけることが可能です。

Device.swift
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の子にdamageEntityaddChild()することでダメージを受けたような演出にしています。

// 衝突判定
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をすべて削除しています。

GameModel.swift
var isFinished = false {
    didSet {
        if isFinished == true {
            contentEntity.children.removeAll()
        }
    }
}

おわりに

以上、「GameOverを実装する」でした。
ダメージを受けた時によくありそうな目の前を赤くしてGameOverを表現してみました。
デバイス位置の取得は他にも使えると思うのでぜひ参考にしてみてください。

また、今回の記事で紹介している空間シューティングゲーム「妖怪バスターZ」を日本発売前の2024年5月3日にリリースしました。
ぜひインストールして遊んでみてください。

https://apps.apple.com/us/app/yokai-buster-z/id6502185713

今後もvisionOSについて発信していきますので、この記事が参考になったと思ったらぜひ♡をお願いします。

Discussion