You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SKScene尺寸偏移求助:添加背景宽高比后场景右移

SKScene添加背景.fill后向右偏移的问题

当给背景图片设置.aspectRatio(contentMode: .fill)后,SKScene会向右偏移几个像素(已关闭场景透明度确认尺寸问题),想知道这是SwiftUI的bug还是代码问题。

相关代码

import SwiftUI
import SpriteKit

struct MenuScreenView: View {
    
    @Bindable var player: Player
    private let screenWidth = UIScreen.main.bounds.width
    private let screenHeight = UIScreen.main.bounds.height
    
    var scene: SKScene {
        let scene = MenuScene()
        scene.scaleMode = .resizeFill
        scene.size = CGSize(width: screenWidth, height: screenHeight)
        return scene
    }
    
    var body: some View {
        ZStack {
            Image("background2")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .ignoresSafeArea()
            SpriteView(scene: scene)
                .frame(width: screenWidth, height: screenHeight, alignment: .center)
                .edgesIgnoringSafeArea(.all)
        }
    }
}

#Preview {
    MenuScreenView(player: Player(name: "Name", score: 100, ship: .frigate, ammo: [.missile: 100]))
}
import SpriteKit

class MenuScene: SKScene {
    
    override init() {
        super.init(size: UIScreen.main.bounds.size)
        self.backgroundColor = .clear
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func didMove(to view: SKView) {
        super.didMove(to: view)
        setupUI()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        let nodesAtPoint = nodes(at: location)
        
        for node in nodesAtPoint {
            if let nodeName = node.name {
                print("\(nodeName) button tapped!")
            }
        }
    }
    
    private func setupUI() {
        if isSavedGameExist() {
            createMenuButtonWith(continueTitle, at: CGPoint(x: frame.midX, y: frame.midY + 60))
        }
        createMenuButtonWith(newGameTitle, at: CGPoint(x: frame.midX, y: frame.midY + 20))
        createMenuButtonWith(scoreTitle, at: CGPoint(x: frame.midX, y: frame.midY - 20))
        createMenuButtonWith(settingsTitle, at: CGPoint(x: frame.midX, y: frame.midY - 60))
    }
    
    private func createMenuButtonWith(_ text: String, at position: CGPoint) {
        let button = MenuButton(buttonText: text)
        button.position = position
        addChild(button)
    }
    
    private func isSavedGameExist() -> Bool {
        // TODO: Add logic of check for existing game
        return true
    }
    
    // MARK: - Localization helpers
    
    private var newGameTitle: String {
        NSLocalizedString(Localizable.menuNewGameButtonTitle, comment: "")
    }
    private var settingsTitle: String {
        NSLocalizedString(Localizable.menuSettingsButtonTitle, comment: "")
    }
    private var scoreTitle: String {
        NSLocalizedString(Localizable.menuScoreButtonTitle, comment: "")
    }
    private var continueTitle: String {
        NSLocalizedString(Localizable.menuContinueButtonTitle, comment: "")
    }
}

效果对比

  • 偏移问题截图:偏移问题截图
  • 无.fill时正常效果:正常显示截图

问题原因及解决办法

原因

.aspectRatio(contentMode: .fill)会让图片按比例填充父容器,超出部分裁剪,这会导致ZStack内布局计算出现微小偏差;加上直接用UIScreen.main.bounds固定SpriteView尺寸,和SwiftUI的自适应布局系统不兼容,最终出现偏移。

修复方案

改用GeometryReader获取实际布局尺寸,让SpriteView和背景图片的尺寸完全匹配,调整后的代码如下:

修改后的MenuScreenView.swift

import SwiftUI
import SpriteKit

struct MenuScreenView: View {
    
    @Bindable var player: Player
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                Image("background2")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .ignoresSafeArea()
                
                SpriteView(scene: MenuScene(size: geometry.size))
                    .ignoresSafeArea()
            }
        }
    }
}

#Preview {
    MenuScreenView(player: Player(name: "Name", score: 100, ship: .frigate, ammo: [.missile: 100]))
}

修改后的MenuScene.swift

import SpriteKit

class MenuScene: SKScene {
    
    init(size: CGSize) {
        super.init(size: size)
        self.backgroundColor = .clear
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func didMove(to view: SKView) {
        super.didMove(to: view)
        setupUI()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        let nodesAtPoint = nodes(at: location)
        
        for node in nodesAtPoint {
            if let nodeName = node.name {
                print("\(nodeName) button tapped!")
            }
        }
    }
    
    private func setupUI() {
        if isSavedGameExist() {
            createMenuButtonWith(continueTitle, at: CGPoint(x: frame.midX, y: frame.midY + 60))
        }
        createMenuButtonWith(newGameTitle, at: CGPoint(x: frame.midX, y: frame.midY + 20))
        createMenuButtonWith(scoreTitle, at: CGPoint(x: frame.midX, y: frame.midY - 20))
        createMenuButtonWith(settingsTitle, at: CGPoint(x: frame.midX, y: frame.midY - 60))
    }
    
    private func createMenuButtonWith(_ text: String, at position: CGPoint) {
        let button = MenuButton(buttonText: text)
        button.position = position
        addChild(button)
    }
    
    private func isSavedGameExist() -> Bool {
        // TODO: Add logic of check for existing game
        return true
    }
    
    // MARK: - Localization helpers
    
    private var newGameTitle: String {
        NSLocalizedString(Localizable.menuNewGameButtonTitle, comment: "")
    }
    private var settingsTitle: String {
        NSLocalizedString(Localizable.menuSettingsButtonTitle, comment: "")
    }
    private var scoreTitle: String {
        NSLocalizedString(Localizable.menuScoreButtonTitle, comment: "")
    }
    private var continueTitle: String {
        NSLocalizedString(Localizable.menuContinueButtonTitle, comment: "")
    }
}

修改说明

  1. 用GeometryReader获取父容器的实际可用尺寸,替代UIScreen.main.bounds,避免和SwiftUI布局系统冲突。
  2. 背景图片明确设置frame为GeometryReader的尺寸,确保填充范围准确。
  3. SpriteView不再设置固定frame,直接跟随ZStack尺寸;初始化MenuScene时传入正确尺寸,保证SKScene坐标系统和SwiftUI布局完全匹配。

内容的提问来源于stack exchange,提问作者Anton_Kuznetsov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:54:50