SpriteKit - 获取缩放场景可见区域的实际 size/frame .AspectFill

SpriteKit - Getting the actual size/frame of the visible area on scaled scenes .AspectFill

我一直在用 SpriteKit 制作游戏,但在支持所有屏幕尺寸时遇到了问题。我试图通过将所有场景缩放模式设置为 .AspectFill 并为宽度为 480 和高度为 800 的所有屏幕设置固定大小来解决该问题,如下所示:

let scene = GameScene(size:CGSize(width: 480, height: 800))

我去添加一个与游戏场景边缘对齐的边框(一个 SKShapeNode),我发现 self.frame.width 和 self.frame.height 没有给出尺寸设备的可见区域。经过一番研究后,我发现了很多使用 UIScreen.mainScreen().bounds.height 和 UIScreen.mainScreen().bounds.width 的建议。我实现了这种获取可见屏幕尺寸的方式,但它仍然给我提供了与播放器可见场景的实际尺寸不匹配的尺寸。

我不确定这是否与以下事实有关:无论设备的分辨率如何,我的所有场景都具有固定的宽度和高度,并且使用 .AspectFill 进行缩放。一段时间以来,我一直被支持所有屏幕尺寸的问题所困扰,我不确定如何去做。如果有帮助,我正在使用矢量图形文件而不是 PNG。 所以我的问题是: 是否有支持所有屏幕尺寸的快速方法,或者我是否必须为每个设备对场景中每个节点的坐标进行硬编码?我不明白为什么这会是解决方案,因为它看起来太乏味而且代码看起来很乱。

我也在问如何获得一个 rectangle/frame 来给出场景缩放后设备上场景可见区域的尺寸?

如果我的任何一个问题之前被问过,我很抱歉,但我找到的每个答案都没有具体解决我的问题,或者看起来太乱了,无法编写。我想我已经提供了有关该问题的足够信息,但如果我还没有提供所需的信息,请向我询问。

非常感谢,感谢您的帮助

不知道为什么:

struct Constants {
    static let vcHeight = UIScreen.mainScreen().bounds.height
    static let vcWidth = UIScreen.mainScreen().bounds.width
}

没有给出正确的界限。 但我不知道您为什么要使用 .aspectFill 来获得正确的尺寸。
当我制作我的游戏时,我使用了这个

let position.x = vcHeight/3   //to putting him on a third of the screen for all iPhone's 

这也可用于获得合适的尺寸。 我对矢量图形一无所知,所以我无法为您提供进一步的帮助。

纵横比填充的工作原理如下:

场景的尺寸将始终保持不变,因此如果您说场景是 480 点 x 800 点,那么无论设备如何,它都将始终保持不变。

然后发生的是它将从 SKView 的中心开始缩放场景,直到它填满最远的墙壁。

这将依次裁剪您的场景。

因此,在针对不同宽高比设计游戏时,您需要将游戏设计为处理裁剪。

现在在您的特定情况下,您需要弄清楚设备的纵横比是多少,并从场景方面解决差异。

你的场景是 3x5,设备是 9x16,这意味着你的设备将增长到 9.6x16 以填满你的 9x16 设备的大小。我们得到这个是因为我们的高度比宽度更远,所以我们需要做 3/5 = a/16, 3*16 = a * 5, (3*16)/5 = a 所以 a = 9.6。

这意味着您的场景在宽度上有 .6 的裁剪,在两个边缘上有 .3 的裁剪。

现在您要问了,.3 与各种屏幕尺寸有何关系。

好吧,我们需要弄清楚 .3 在 9.6 中的百分比是多少。我们用除法来做,.3/9.6 = .03125 或 3.125%;这意味着我们需要将边界推到场景宽度的 3.125%,即 15 点。这意味着 .03125 * 480 = 15。您的边框应该从 15 宽度和 465 开始。

伪代码:

let newSceneWidth = (scene.width * view.height) / (scene.height)
let sceneDifference = (newSceneWidth - view.Width)/2
let diffPercentage = sceneDifference / (newSceneWidth)

let leftEdge = diffPercentage * scene.width;
let rightEdge = scene.width - (diffPercentage * scene.width);

现在,如果高度的缩放距离大于宽度的缩放距离,则必须交换宽度和高度变量。

感谢您的回答,特别感谢 KnightOfDragon 帮助我解决了我的问题。我所做的是创建一个生成 CGRect 的函数,该函数考虑了设备的尺寸及其预定的场景纵横比。这可以用作边界,并使场景中节点的相对定位更容易,以便它们对所有设备可见。再次特别感谢 KnightOfDragon,他根据解决方案的代码回答了问题,没有他们,这将很困难,我将无法分享这个整体解决方案。这是 Swift 中的最终代码:

//Returns a CGRect that has the dimensions and position for any device with respect to any specified scene. This will result in a boundary that can be utilised for positioning nodes on a scene so that they are always visible
func getVisibleScreen(var sceneWidth: Float, var sceneHeight: Float, viewWidth: Float, viewHeight: Float) -> CGRect {
    var x: Float = 0
    var y: Float = 0

    let deviceAspectRatio = viewWidth/viewHeight
    let sceneAspectRatio = sceneWidth/sceneHeight

    //If the the device's aspect ratio is smaller than the aspect ratio of the preset scene dimensions, then that would mean that the visible width will need to be calculated
    //as the scene's height has been scaled to match the height of the device's screen. To keep the aspect ratio of the scene this will mean that the width of the scene will extend
    //out from what is visible. 
    //The opposite will happen in the device's aspect ratio is larger.
    if deviceAspectRatio < sceneAspectRatio {
        let newSceneWidth: Float = (sceneWidth * viewHeight) / sceneHeight
        let sceneWidthDifference: Float = (newSceneWidth - viewWidth)/2
        let diffPercentageWidth: Float = sceneWidthDifference / (newSceneWidth)

        //Increase the x-offset by what isn't visible from the lrft of the scene
        x = diffPercentageWidth * sceneWidth
        //Multipled by 2 because the diffPercentageHeight is only accounts for one side(e.g right or left) not both
        sceneWidth = sceneWidth - (diffPercentageWidth * 2 * sceneWidth)
    } else {
        let newSceneHeight: Float = (sceneHeight * viewWidth) / sceneWidth
        let sceneHeightDifference: Float = (newSceneHeight - viewHeight)/2
        let diffPercentageHeight: Float = fabs(sceneHeightDifference / (newSceneHeight))

        //Increase the y-offset by what isn't visible from the bottom of the scene
        y = diffPercentageHeight * sceneHeight
        //Multipled by 2 because the diffPercentageHeight is only accounts for one side(e.g top or bottom) not both
        sceneHeight = sceneHeight - (diffPercentageHeight * 2 * sceneHeight)
    }

    let visibleScreenOffset = CGRect(x: CGFloat(x), y: CGFloat(y), width: CGFloat(sceneWidth), height: CGFloat(sceneHeight))
    return visibleScreenOffset
}

实际上有一个非常简单的方法可以做到这一点。 SKScene 上的函数 convertPoint(fromView:) 允许将点从其包含的 SKView 坐标系转换为场景 space 坐标。因此,您可以采用视图的原点和等于其最远范围的点,将它们都转换为场景的坐标 space,然后像这样测量差异:

extension SKScene {
    func viewSizeInLocalCoordinates() -> CGSize {
        let reference = CGPoint(x: view!.bounds.maxX, y: view!.bounds.maxY)
        let bottomLeft = convertPoint(fromView: .zero)
        let topRight = convertPoint(fromView: reference)
        let d = topRight - bottomLeft
        return CGSize(width: d.x, height: d.y)
    }
}

(请注意,我还使用了 CGPoint 的扩展,它允许我直接使用点执行加法和减法。请参见下文。另请注意,只有在场景完成后调用此函数才是安全的已经呈现;我有点懒惰,因为强制展开 view,如果场景尚未显示,这将是 nil。)

extension CGPoint {
    static func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
    static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
    }
}

重要提示: 这假设场景没有分配相机节点,或者相机节点的比例为 1:1。如果您添加自己的相机节点并允许对其进行缩放以创建缩放效果,请改用下面的代码。还要记住,附加到相机节点本身的节点不会随场景的其余部分缩放。我通过提供 ignoreCameraScale 在我的代码中允许这样做。如果测量值与随相机移动和缩放的 HUD 元素相关,则将 true 传递给此参数。

extension SKScene {
    func viewSizeInLocalCoordinates(ignoreCameraScale: Bool = false) -> CGSize {
        let reference = CGPoint(x: view!.bounds.maxX, y: view!.bounds.maxY)
        var bottomLeft = convertPoint(fromView: .zero)
        var topRight = convertPoint(fromView: reference)

        if ignoreCameraScale, let camera = camera {
            bottomLeft = camera.convert(bottomLeft, from: self)
            topRight = camera.convert(topRight, from: self)
        }

        let d = topRight - bottomLeft
        return CGSize(width: d.x, height: -d.y)
    }
}