在 SpriteKit 中将相机夹在场景的背景周围

Posted

技术标签:

【中文标题】在 SpriteKit 中将相机夹在场景的背景周围【英文标题】:Clamping camera around the background of a scene in SpriteKit 【发布时间】:2016-02-01 18:19:26 【问题描述】:

所以我有一个基本游戏设置,可以在下面的 bitbucket 链接中找到:

Game link

我目前很难理解如何根据场景布局转换相机节点。

目标是让相机跟随玩家向上,直到到达由场景大小定义的角落边界。在这个特定的测试场景设置中,场景大小为 1000x1000,相机比例为 1。

下面的代码用于在设置新位置跟随角色时修改相机的位置:

    var cameraPosition: CGPoint 

        get 
            return CGPoint(x: camera!.position.x, y: camera!.position.y)
        

        set 

            let cameraScale = CGFloat(1)
            let sceneScale = CGFloat(1)//CGFloat(1 - 0.44  + 0.05 /*possible rounding error adjustment*/)
//            let viewAspectRatio = CGRectGetWidth(view!.frame)/CGRectGetHeight(view!.frame)
            let newPositionValue = double2(x: Double(newValue.x * sceneScale), y: Double(newValue.y * sceneScale))

            let scaledSceneSize = CGSize(width: size.width * sceneScale , height: size.height * sceneScale)
////            scaledSceneSize.height = scaledSceneSize.height / viewAspectRatio

            let cameraSize = view!.bounds.size
            let scaledCameraSize = CGSize(width: cameraSize.width * cameraScale, height: cameraSize.height * cameraScale)


            let minX = 0//-scaledSceneSize.width * anchorPoint.x + scaledCameraSize.width / 2
            let minY = -219//-scaledSceneSize.height * anchorPoint.y + scaledCameraSize.height / 2

            let minValues = double2(x: Double(minX), y: Double(minY))

            let maxX = 0//(scaledSceneSize.width * anchorPoint.x - scaledCameraSize.width / 2) //size.width - cameraSize.width / 2
            let maxY = 219//(scaledSceneSize.height * anchorPoint.y - scaledCameraSize.height / 2) //- cameraSize.height / 2

            let maxValues = double2(x: Double(maxX), y: Double(maxY))

            let clampedPosition = clamp(newPositionValue, min: minValues, max: maxValues)


            camera!.position = CGPoint(x: (clampedPosition.x / Double(sceneScale)), y: (clampedPosition.y / Double(sceneScale)))
        

    

目前存在符合所需场景大小的核心价值,我不确定如何通过比例获得这些结果。默认比例为:

/* Set the scale mode to scale to fit the window */
            scene.scaleMode = .AspectFill

在不知道有比例转换的情况下,默认情况下,我希望边界是 最大场景维度XValue - cameraSize.width/2 maximumSceneDimensionYValue - cameraSize.height/2

作为一个高级示例。有人能帮我翻译一下吗?

整个场景的各个角落应该如下所示:

VS 在相机中出现黑色背景溢出:

【问题讨论】:

【参考方案1】:

像这样的应用正是SKConstraint 的用途。

您可以在 WWDC15 会议 Deeper into GameplayKit with DemoBots.* 中看到这个确切功能的演示 - 约束相机使其跟随玩家,但不会在关卡边缘显示太多空白空间。*(那里的链接应该跳到大约 7:27 开始讨论此功能的谈话中。)

视频中的要点,以及来自 DemoBots 示例代码的一些 sn-ps:

    使用 距离 约束来保持相机以玩家为中心(自动,无需在每个 update() 上直接设置 camera.position)。

    // Constrain the camera to stay a constant distance of 0 points from the player node.
    let zeroRange = SKRange(constantValue: 0.0)
    let playerBotLocationConstraint = SKConstraint.distance(zeroRange, toNode: playerNode)
    

    使用位置约束将相机保持在关卡边缘的一定范围内。通过获取关卡的框架并将该矩形插入相机应与关卡边缘保持的距离来计算该范围。

    // get the scene size as scaled by `scaleMode = .AspectFill`
    let scaledSize = CGSize(width: size.width * camera.xScale, height: size.height * camera.yScale)
    
    // get the frame of the entire level contents
    let boardNode = childNodeWithName(WorldLayer.Board.nodePath)!
    let boardContentRect = boardNode.calculateAccumulatedFrame()
    
    // inset that frame from the edges of the level
    // inset by `scaledSize / 2 - 100` to show 100 pt of black around the level
    // (no need for `- 100` if you want zero padding)
    // use min() to make sure we don't inset too far if the level is small
    let xInset = min((scaledSize.width / 2) - 100.0, boardContentRect.width / 2)
    let yInset = min((scaledSize.height / 2) - 100.0, boardContentRect.height / 2)
    let insetContentRect = boardContentRect.insetBy(dx: xInset, dy: yInset)
    
    // use the corners of the inset as the X and Y range of a position constraint
    let xRange = SKRange(lowerLimit: insetContentRect.minX, upperLimit: insetContentRect.maxX)
    let yRange = SKRange(lowerLimit: insetContentRect.minY, upperLimit: insetContentRect.maxY)
    let levelEdgeConstraint = SKConstraint.positionX(xRange, y: yRange)
    levelEdgeConstraint.referenceNode = boardNode
    

    将这两个约束应用于您的 SKCameraNode

    camera.constraints = [playerBotLocationConstraint, levelEdgeConstraint]
    

为了更深入地了解,download Apple's DemoBots sample code project,它有很多 cmets 和支持代码,我从上面的 sn-ps 中修剪了这些代码,以防止这篇文章变得过长。相机约束的所有内容都在 func setCameraConstraints()LevelScene.swift 中。

* 尽管有会话名称,但它不仅仅是 GameplayKit……它展示了如何利用 ios 8 / OS X 10.11 / Xcode 7 中引入的许多技术来构建类似于全尺寸的东西游戏:App Thinning、新的 SpriteKit 功能、ReplayKit 等等。

【讨论】:

您错过了问题的重点,是的,这是约束的主要应用,但仍需要计算并知道一般偏移量,这就是上述问题试图得到的答案到。 如果你的关卡足够大并且相机从不缩放或旋转,那么你的插图可以简单地是屏幕宽度/高度的一半。如果你的关卡总是小于设备屏幕,您只需要从关卡框架中插入一个恒定的半屏幕大小的插图。 这些条件不适用于上面的示例应用程序,我需要可以随相机缩放的东西。 我添加了更多关于 DemoBots 如何计算其插图的内容。如果您不使用约束,您仍然可以使用插入数学 - 只需在将相机的潜在位置与插入矩形进行比较后直接设置相机的位置。 (不过,使用约束要简单得多。) 这实际上是我找到的对演示机器人最好的解释。谢谢!【参考方案2】:

我没有使用您的代码。我做了一个示例项目并让它工作。

这是我的代码

import SpriteKit

class GameScene: SKScene 

    let world = SKSpriteNode(imageNamed: "world.jpg")
    let player = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(10, 10))

    var cam: SKCameraNode!

    override init(size: CGSize) 
        super.init(size: size)
        print(world.size)
        addChild(world)
        addChild(player)

        world.zPosition = 1
        player.zPosition = 2

        cam = SKCameraNode()
        self.camera = cam
        addChild(cam)
    

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

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) 
        for touch: AnyObject in touches 
            let location = touch.locationInNode(self)

            player.position = location
        
    

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) 
        for touch: AnyObject in touches 
            let location = touch.locationInNode(self)

            player.position = location
        
    

    func clampCamera()

        func clamp(inout input: CGFloat, num1: CGFloat, num2: CGFloat) 
            if input < num1 
                input = num1
            
            else if input > num2 
                input = num2
            
        

        let lBoundary = -world.size.width/2 + size.width/2
        let rBoundary = world.size.width/2 - size.width/2
        let bBoundary = -world.size.height/2 + size.height/2
        let tBoundary = world.size.height/2 - size.height/2

        clamp(&camera!.position.x, num1: lBoundary, num2: rBoundary)
        clamp(&camera!.position.y, num1: bBoundary, num2: tBoundary)

    

    override func update(currentTime: NSTimeInterval) 
        camera!.position = player.position
        clampCamera()
    

这是我用作“世界”的同一张图片 http://i.imgur.com/XhZbh8q.jpg

【讨论】:

这与我的第一种方法非常相似,在将相机向后缩放和其他条件时不起作用。我附上上面代码的主要原因是参考具体场景。如果您可以在 repo 中发布的代码中使用上述内容,那么它应该是完美的,但我 100% 肯定它不会。相机比例应该是 0.44,但我已经对所有内容进行了规范化,以确定这些值的来源。 注意到场景的宽度与相机的宽度不匹配。 我玩弄了你的代码,它开始成为我自己的项目,哈哈。真的有很多事情要做。如果我是你,我可能会开始一个新的示例项目,添加每个元素一次一个复杂度(cameraScale..等),直到您找到解决问题的更简单方法。 这就是上述内容被标准化的原因。基本上唯一需要注意的是,cameraScale 为 1,玩家位置设置为初始 cameraPosition,场景为 1000x1000,cameraPosition 需要将值锁定在 1000x1000 场景的限制范围内除了这些点之外什么都没有显示(无论相机比例如何)。 没有其他真正的影响,您可以删除与场景配置和播放器组件无关的所有内容。

以上是关于在 SpriteKit 中将相机夹在场景的背景周围的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Swift 在 SpriteKit 程序中初始化/移动相机?

SpriteKit:场景中的静态背景与移动的 SKCameraNode

滚动场景背景 - Unity

Spritekit -- 保持场景以高于特定高度的精灵为中心

如何在 iOS SpriteKit 中添加滚动背景效果。

如何应用SPRITEKIT的CAMERA实现游戏中的ENDLESS无限循环背景