聚焦 SKNode

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

我有以下代码,可以在 tvOS 17.5 的模拟器中构建和运行。 我正在尝试在

SKNode
内的两个
GameScene
(即
SKScene
)之间导航焦点。 我无法让两个
FocusableNode
中的任何一个变得明显聚焦。 我能够聚焦场景(周围有一个蓝色矩形),但不能将焦点转移到两个可聚焦子节点中的任何一个。 一个问题是
pressesBegan
永远不会被调用,
preferredFocusEnvironments
也不会被调用。

[编辑]一旦我注释掉与焦点相关的视图修饰符并删除了正文中的按钮,我就可以将其中一个可聚焦节点聚焦(其颜色变为蓝色),但无法导航焦点,它只是停留在该节点上第一个按钮。 我还收到此运行时警告:

Using legacy initializer -[UIFocusRegion initWithFrame:] for region <_UIFocusItemRegion: 0x6000017529c0> - if this region is initialized by a client, please move over to using the UIFocusItem API. If this region is coming from UIKit, this is a UIKit bug.

struct ContentView: View {
    //@FocusState private var isSpriteViewFocused: Bool
    
    var body: some View {
        VStack {
            SpriteView(scene: GameScene())
                .frame(width: 600, height: 400)
                //.focusable(true)
                //.focused($isSpriteViewFocused)
                //.overlay(
                //    RoundedRectangle(cornerRadius: 10)
                //        .stroke(isSpriteViewFocused ? Color.blue : Color.clear, lineWidth: 5)
                //)
            
            //Button("Focus SpriteView [\(isSpriteViewFocused)]") {
            //    isSpriteViewFocused = true //.toggle()
            //}
            //.padding()
        }
        .padding()
    }
}

class GameScene: SKScene {
    
    override func didMove(to view: SKView) {
        backgroundColor = .gray
        
        let size1 = view.bounds.size
        self.size = size1
        
            // Create a focusable SKNode
        let focusableNode1 = FocusableNode()
        focusableNode1.position = CGPoint(x: size1.width / 2, y: size1.height / 2 + 100)
        focusableNode1.name = "Focusable Node 1"
        addChild(focusableNode1)
        
        let focusableNode2 = FocusableNode()
        focusableNode2.position = CGPoint(x: size1.width / 2, y: size1.height / 2 - 100)
        focusableNode2.name = "Focusable Node 2"
        addChild(focusableNode2)
        
            // Create a non-focusable SKNode
        let nonFocusableNode = SKLabelNode(text: "Non-Focusable Node".uppercased())
        nonFocusableNode.fontSize = 15
        nonFocusableNode.fontColor = .white
        nonFocusableNode.position = CGPoint(x: size1.width / 2, y: size1.height / 2)
        nonFocusableNode.isUserInteractionEnabled = false // Disable focus
        addChild(nonFocusableNode)
        
        if false {
            addFocusGuides()
        } else {
            self.scene?.view?.window?.setNeedsFocusUpdate()
            self.scene?.view?.window?.updateFocusIfNeeded()
        }
    }
    
    private func addFocusGuides() {
        guard let view = view else { return }
        
        let guide1 = UIFocusGuide()
        view.addLayoutGuide(guide1)
        NSLayoutConstraint.activate([
            guide1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            guide1.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            guide1.topAnchor.constraint(equalTo: view.topAnchor),
            guide1.bottomAnchor.constraint(equalTo: view.centerYAnchor)
        ])
        guide1.preferredFocusEnvironments = [childNode(withName: "Focusable Node 2")!]
        
        let guide2 = UIFocusGuide()
        view.addLayoutGuide(guide2)
        NSLayoutConstraint.activate([
            guide2.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            guide2.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            guide2.topAnchor.constraint(equalTo: view.centerYAnchor),
            guide2.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        guide2.preferredFocusEnvironments = [childNode(withName: "Focusable Node 1")!]
    }
    
    override var preferredFocusEnvironments: [UIFocusEnvironment] {
        return children.compactMap { $0 as? FocusableNode }
    }
    
    override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
        return true
    }
    
    override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        super.pressesBegan(presses, with: event)
        
            // Handle focusable node presses
        if presses.first?.type == .select {
            if let windowScene = view?.window?.windowScene {
                if let focusedNode = windowScene.focusSystem?.focusedItem as? FocusableNode {
                    print("\(focusedNode.name ?? "Unknown Node") selected")
                    focusedNode.performAction()
                }
            }
        }
    }
}

class FocusableNode: SKSpriteNode {
    private let defaultColor = UIColor.white
    private let focusedColor = UIColor.blue
    
    init() {
        let size = CGSize(width: 200, height: 100)
        super.init(texture: nil, color: defaultColor, size: size)
        isUserInteractionEnabled = true // Enables focus and interaction
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override var canBecomeFocused: Bool {
        return true
    }
    
    override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        if context.nextFocusedItem === self {
            color = focusedColor
        } else {
            color = defaultColor
        }
    }
    
    func performAction() {
            // Perform an action when selected
        print("Action performed by \(name ?? "Unknown Node")")
    }
}

@main
struct MyTVOSApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

swiftui sprite-kit focus tvos
1个回答
0
投票

我无法找到集成 SwiftUI 和 SpriteKit 焦点系统的解决方案。 我找到了一种解决方法,使用 SwiftUI 进行焦点管理,并将该信息传达给场景,场景会更新以匹配状态。 这不是我正在寻找的答案,因为它相当复杂,但它有效。 我不会将此答案标记为解决方案 - 我将其附在此处作为解决相同问题的任何人的想法。

import Foundation
import SwiftUI
import SpriteKit

struct ContentView: View {

    @FocusState private var focusButton0 : Bool
    @FocusState private var focusButton1 : Bool

    @State private var focusedIndex      : Int = -1
    @State private var tappedIndex       : Int = -1
    
    var body: some View {
        ZStack {
            // The SpriteKit scene
            SpriteView(scene: GameScene(focusedIndex: $focusedIndex, tappedIndex: $tappedIndex))
                .frame(width: 600, height: 400)
                .background(Color.black)
            
            // Overlaying SwiftUI buttons that correspond to FocusableNodes
            VStack(spacing: 0) {
                
                Rectangle()
                    .fill(Color.clear)
                    .frame(width: 200, height: 100)
//                    .background(focusedIndex == 0 ? Color.orange.opacity(0.5) : Color.clear)
                    .focusable(true)
                    .focused($focusButton0)
                    .onChange(of: focusButton0) { _,v in
                        if v {
                            focusedIndex = 0
                        }
                    }
                    .onTapGesture {
                        tappedIndex = 0
                    }

                Rectangle()
                    .fill(Color.clear)
                    .frame(width: 200, height: 100)
//                    .background(focusedIndex == 1 ? Color.orange.opacity(0.5) : Color.clear)
                    .focusable(true)
                    .focused($focusButton1)
                    .onChange(of: focusButton1) { _,v in
                        if v {
                            focusedIndex = 1
                        }
                    }
                    .onTapGesture {
                        tappedIndex = 1
                    }

            }
        }
    }
}

class GameScene: SKScene {
    @Binding var focusedIndex: Int
    @Binding var tappedIndex: Int
    private var focusableNodes: [FocusableNode] = []

    init(focusedIndex: Binding<Int>, tappedIndex: Binding<Int>) {
        self._focusedIndex = focusedIndex
        self._tappedIndex = tappedIndex
        super.init(size: CGSize(width: 600, height: 400))
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func didMove(to view: SKView) {
        backgroundColor = .black

        // Define focusable nodes
        let button1 = FocusableNode(name: "Button 1", position: 0)
        button1.position = CGPoint(x: size.width / 2, y: size.height / 2 + 50)
        addChild(button1)

        let button2 = FocusableNode(name: "Button 2", position: 1)
        button2.position = CGPoint(x: size.width / 2, y: size.height / 2 - 50)
        addChild(button2)

        focusableNodes = [button1, button2]
    }

    override func update(_ currentTime: TimeInterval) {
        for node in focusableNodes {
            node.update(isFocused: node.positionIndex == focusedIndex, isTapped: node.positionIndex == tappedIndex)
        }
    }
}

class FocusableNode: SKSpriteNode {
    var positionIndex: Int

    init(name: String, position: Int) {
        self.positionIndex = position
        let size = CGSize(width: 200, height: 100)
        super.init(texture: nil, color: .white, size: size)
//        isUserInteractionEnabled = true
        self.name = name
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func update(isFocused: Bool, isTapped: Bool) {
        if isFocused {
            color = .blue
        } else if isTapped {
            color = .red
        } else {
            color = .gray
        }
    }
}

@main
struct MyTVOSApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.