如何在加载.DAE环境和角色时显示加载屏幕?

问题描述 投票:0回答:1

我正在使用 SceneKit 和 DAE 环境以及 mixamo 中的角色,我一直在寻找方法来显示加载屏幕,同时所有内容都最好在 SwiftUI 中加载,而不是在整个 GameView 加载之前冻结 UI 并部分显示环境

这是保存场景的 GameSceneView

import SwiftUI
import SceneKit

///UIView representable for the GameScene and Fighters
struct GameSceneView: UIViewRepresentable {
    let fighter: Fighter
    let enemyFighter: Fighter
    let isPracticeMode: Bool

    typealias UIViewType = SCNView
    let scene = SCNScene(named: "3DAssets.scnassets/GameScene.scn")!
    let cameraNode = SCNNode()
    let lightNode = SCNNode()

    func makeUIView(context: Context) -> UIViewType {
        setUpCamera()
        setUpLight()
        setUpFighters()
        let sceneView = SCNView()
        sceneView.allowsCameraControl = true
        sceneView.autoenablesDefaultLighting = true
        sceneView.scene = scene
        return sceneView
    }

    func updateUIView(_ sceneView: UIViewType, context: Context) {}
}

private extension GameSceneView {
    func setUpFighters() {
        scene.rootNode.addChildNode(fighter.daeHolderNode)
        scene.rootNode.addChildNode(enemyFighter.daeHolderNode)
    }

    func setUpCamera() {
        // create and add a camera to the scene
        cameraNode.camera = SCNCamera()
        cameraNode.position = .init(x: -6, y: 4, z: isPracticeMode ? 2.5 : 3.2) //X: zooms in and out, Y: shifts vertically, Z: horizontal changes
        cameraNode.eulerAngles = .init(x: 0, y: -89.5, z: 0) //X: zooms in and out, Y: rotates horizontally, Z: rotates vertically
        scene.rootNode.addChildNode(cameraNode)
    }

    func setUpLight() {
        lightNode.light = SCNLight()
        lightNode.position = .init(x: -5, y: 3.5, z: 3.2)
        lightNode.eulerAngles = .init(x: 0, y: -89.5, z: 0)
        scene.rootNode.addChildNode(lightNode)
    }
}

还有战斗机类

import SceneKit

///3D model of the fighter
struct Fighter {
    //MARK: Properties
    var fighterType: FighterType

    ///The parent node which contain all of the nodes
    private(set) var daeHolderNode = SCNNode()
    ///Bone node that contains the animation players
    private(set) var animationsNode: SCNNode?

    //MARK: Get-only properties
    private(set) lazy var textNode: SCNNode = {
        let node = SCNNode(geometry: damageText)
        node.name = "textNode"
        node.position = textPosition
        let scale: Float = 1.5
        node.scale = SCNVector3(x: scale, y: scale, z: scale)
        let yAngle: Float = 0
        node.eulerAngles = .init(x: 0, y: yAngle, z: 0)
        node.runAction(SCNAction.hide())
        return node
    }()
    private lazy var damageText: SCNText = {
        let depth: CGFloat = 2
        var text = SCNText(string: "", extrusionDepth: depth)
        let font = UIFont.systemFont(ofSize: 13, weight: .black)
        text.font = font
        text.alignmentMode = CATextLayerAlignmentMode.center.rawValue
        text.firstMaterial?.diffuse.contents = UIColor.white
        return text
    }()
    private lazy var textPosition: SCNVector3 = {
        return SCNVector3(x: isEnemy ? -37 : -2, y:120, z: isEnemy ? 10 : -30)
    }()
    private lazy var textNodeAction: SCNAction = {
        let showAction = SCNAction.unhide()
        let zoomInAction = SCNAction.scale(to: 3, duration: 0.15)
        let zoomOutAction = SCNAction.scale(to: 1.5, duration: 0.1)
        let initialActions = SCNAction.sequence([showAction, zoomInAction, zoomOutAction])
        //From current node's location, go up by 100
        let upwardAction = SCNAction.move(by: SCNVector3(x:0, y:100, z:0), duration: 2)
        //Keep going up but slower and begin hiding the node
        let hideAction = SCNAction.hide()
        let upwardAction2 = SCNAction.move(by: SCNVector3(x:0, y:50, z:0), duration: 2)
        let hidingActions = SCNAction.group([hideAction, upwardAction2])
        //Return to original position
        let returnAction = SCNAction.move(to: textPosition, duration: 0)
        let sequence = SCNAction.sequence([initialActions, upwardAction, hidingActions, returnAction])
        return sequence
    }()

    //MARK: - Initializers
    init(type: FighterType, isEnemy: Bool) {
        self.fighterType = type
        self.isEnemy = isEnemy
        defaultAnimation = .idleFight
        createNode()
    }

    //MARK: - Public Methods
    mutating func switchFighter(to nextFighterType: FighterType) {
        fighterType = nextFighterType
    }

    mutating func positionNode(asHorizontal: Bool = false) {
        daeHolderNode.scale = SCNVector3Make(fighterType.scale, fighterType.scale, fighterType.scale)
        var xPosition: Float = isEnemy ? 1.5 : 0    //further
        let yPosition: Float = 0.5                  //vertical position
        var zPosition: Float = isEnemy ? 2.7 : 3    //horizontal position
        var angle: Float = isEnemy ? -89.5 : 90
        if asHorizontal {
            xPosition = 2
            zPosition = isEnemy ? 3 : 1.5
            angle = isEnemy ? 180 : 0
        }
        daeHolderNode.position = SCNVector3Make(xPosition, yPosition, zPosition)
        daeHolderNode.eulerAngles = .init(x: 0, y: angle, z: 0)
        //Add textNode to the parent node holder
        daeHolderNode.addChildNode(textNode)
    }

    mutating func showResult(_ attackResult: AttackResult) {
        damageText.string = attackResult.damageText
        textNode.runAction(textNodeAction)
    }

    mutating func loadAnimations(animations: Set<AnimationType>) {
        for animationType in animations {
            addAnimationPlayer(animationType)
        }
    }

    ///Plays an animation if animationType is new
    func playAnimation(_ animationType: AnimationType) {
        //Get and stop the default animation
        guard let defaultAnimationPlayer = animationsNode?.animationPlayer(forKey: defaultAnimation.rawValue) else {
            LOGE("No default animation player found for \(defaultAnimation)")
            return
        }
        let blendDuration: CGFloat = 0.3
        if animationType != defaultAnimation {
            //Stopping withBlendOutDuration prevents node from going back to T-position before playing the next animation
            defaultAnimationPlayer.stop(withBlendOutDuration: blendDuration)
        }
        //Play next animation
        guard let player = animationsNode?.animationPlayer(forKey: animationType.rawValue) else {
            LOGDE("Animation played not loaded \(animationType)")
            return
        }
        player.play()
        //Handle end of animation
        if animationType.isKillAnimationType {
            //Pause fighter's animations after playing the kill animation. Reduce duration by 0.2 to not let the fighter stand back up
            runAfterDelay(delay: player.animation.duration - blendDuration) {
                pauseAnimations()
            }
        } else {
            //At the end of the played animation, resume to default animation if we're not playing it already
            if animationType != defaultAnimation {
                runAfterDelay(delay: player.animation.duration - blendDuration) {
                    defaultAnimationPlayer.play()
                }
            }
        }
    }

    func resumeAnimations() {
        animationsNode?.isPaused = false
    }

    func pauseAnimations() {
        animationsNode?.isPaused = true
    }

    func stopAnimations() {
        animationsNode?.removeAllAnimations()
    }
}

extension Fighter: Hashable {
    //MARK: - Hashable Required Methods
    static func == (lhs: Fighter, rhs: Fighter) -> Bool {
        return lhs.fighterType == rhs.fighterType
    }

    public func hash(into hasher: inout Hasher) {
        return hasher.combine(fighterType)
    }
}

extension Fighter {
    ///Create node from default animation
    mutating func createNode() {
        daeHolderNode = SCNNode(daePath: fighterType.defaultDaePath)
        //Add materials and images programmatically
        for child in daeHolderNode.childNodes {
            if let partName = child.name,
               let part = SkeletonType(rawValue: partName) {
                child.geometry?.firstMaterial = fighterType.getMaterial(for: part)
            }
        }
        //Assign the daeHolderNode's bones as the node to animate
        animationsNode = daeHolderNode.childNode(withName: fighterType.bonesName, recursively: true)
    }
}
ios swift scenekit game-development collada
1个回答
0
投票

我创建了一个自定义 UIKit 进度条来添加一些更好的效果,并调用 ProgressUpdate 来移动进度条。 一个 gameNodes (sharedInstance) 类,我在其中缓存节点信息并与其他类共享节点信息。 gameNodes->loadModels() 每 N/Total 回调一次以更新栏。 在 gameViewController->viewDidLoad 中,我在创建 UIKit 组件并对齐它们后加载了模型。 我尝试使用启动屏幕来提供帮助,但我一定做错了,因为它从未真正起作用。 由于必须加载 scenekit(就我而言),因此需要一些等待时间,因此它在所有设备上并不完美。 然而,在模型加载时,它确实产生了我认为合理的反馈。

© www.soinside.com 2019 - 2024. All rights reserved.