【问题标题】:Simulate zero gravity style player movement模拟零重力风格的玩家运动
【发布时间】:2016-06-15 03:52:45
【问题描述】:

我在为正确移动玩家节点的方式建模时遇到了一点麻烦 我想要的。

这是我第一次涉足 Spritekit,我已经建立并运行了基础知识(我有一个静态背景,添加了播放器节点并有一个可播放的带边界检查的边界矩形)

我添加玩家移动的方式是跟踪开始的触摸位置并 将其存储在场景类作用域变量(称为 beginTouchPosition)中,并存储当前触摸位置(称为 currentTouchPosition)。 我还跟踪玩家精灵节点位置(currentPlayerPosition)

我所做的是 onTouchesBegan 我更新“beginningTouchPosition”,然后在 onTouchesMoved 中更新“currentTouchPosition”,这样我可以通过获取相对于“beginningTouchPosition”的方向来了解用户希望他的船移动的方向,因为他/她移动他们的手指。 'currentTouchPosition' 与 'beginningTouchPosition' 的距离也决定了飞船的移动速度。

我通过使用上述要点创建一个 CGVector 并将其与 SKAction.MoveBy 调用一起使用,从而在更新中移动玩家。

我这样做是因为我希望用户能够触摸屏幕上的任何位置以控制移动。

我希望玩家如何移动。我宁愿让船通过在某个方向上应用某个设定速度和设定加速度来移动。这样当手指移动时,玩家将在 1/2 秒的空间内从零加速到 1,并继续朝该方向移动,直到手指再次移动或抬起。

如果手指被抬起,那么船应该继续沿最后一个方向移动,但会开始减速,直到速度回到零。

我基本上是在尝试模拟物体在零重力下的移动方式,具有明显的不现实的减速特征。

我找到了一些教程,展示了如何将对象移向手指触摸,但这不是我想要的,因为我正在尝试制作一款侧面滚动空间射击游戏,玩家可以在可玩区域内的任何地方移动,而不是简单的上下。类似于老式复古游戏“复仇女神”,请参见下面的屏幕:

我附上了我的播放器类代码和场景代码,以便更好地可视化我当前的操作方式。

任何有关如何在指定方向上应用加速度的文献的指针都会有所帮助:)

场景文件 - Level_1.swift

import SpriteKit

// Global

/*
 Level_1 set up and control
 */
class Level_1: SKScene {
    // Instance variables
    var lastUpdateTime:NSTimeInterval = 0
    var dt:NSTimeInterval = 0
    var player = Player() // Sub classed SKSpriteNode for all player related stuff

    var currentTouchPosition: CGPoint!
    var beginningTouchPosition:CGPoint!
    var currentPlayerPosition: CGPoint!

    let playableRectArea:CGRect

    override init(size: CGSize) {
        // Constant - Max aspect ratio supported
        let maxAspectRatio:CGFloat = 16.0/9.0

        // Calculate playable height
        let playableHeight = size.width / maxAspectRatio

        // Determine margin on top and bottom by subtracting playable height 
        // from scene height and then divide by 2
        let playableMargin = (size.height-playableHeight)/2.0

        // Calculate the actual playable area rectangle
        playableRectArea = CGRect(x: 0, y: playableMargin,
                              width: size.width,
                              height: playableHeight)
        super.init(size: size)
    }

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

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */

        currentTouchPosition = CGPointZero
        beginningTouchPosition = CGPointZero

        let background = SKSpriteNode(imageNamed: "background1")
        background.position = CGPoint(x: size.width/2, y: size.height/2)
        background.zPosition = -1

        self.addChild(background)

        currentPlayerPosition = CGPoint(x: 100, y: size.height/2)

        player.position = currentPlayerPosition

        self.addChild(player)

    }

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

        let dxVectorValue = (-1) * (beginningTouchPosition.x - currentTouchPosition.x)
        let dyVectorValue = (-1) * (beginningTouchPosition.y - currentTouchPosition.y)

        player.movePlayerBy(dxVectorValue, dyVectorValue: dyVectorValue, duration: dt)
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        player.removeAllActions()
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
       /* Called when a touch begins */

        for touch: AnyObject in touches {
            beginningTouchPosition = touch.locationInNode(self)
            currentTouchPosition = beginningTouchPosition
        }

    }

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
        currentPlayerPosition = player.position

        if lastUpdateTime > 0 {
            dt = currentTime - lastUpdateTime
        }else{
            dt = 0
        }
        lastUpdateTime = currentTime

        player.boundsCheckPlayer(playableRectArea)
    }
}

播放器节点 - Player.swift

import Foundation
import SpriteKit

struct PhysicsCategory {
    static let None         : UInt32 = 0
    static let All          : UInt32 = UInt32.max
    static let Player       : UInt32 = 0b1       // 1
    static let Enemy        : UInt32 = 0b10      // 2
}

class Player: SKSpriteNode{

    init(){

        // Initialize the player object
        let texture = SKTexture(imageNamed: "ship1")

        super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())

        self.xScale = 2
        self.yScale = 2
        self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        self.zPosition = 1

        // Player physics
        self.physicsBody?.allowsRotation = false
        self.physicsBody?.dynamic = false
        self.physicsBody?.categoryBitMask = PhysicsCategory.Player
    }

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

    // Check if the player sprite is within the playable area bounds
    func boundsCheckPlayer(playableArea: CGRect){
        let bottomLeft = CGPoint(x: 0, y: CGRectGetMinY(playableArea))
        let topRight = CGPoint(x: playableArea.size.width, y: CGRectGetMaxY(playableArea))

        if(self.position.x <= bottomLeft.x){
            self.position.x = bottomLeft.x
            // velocity.x = -velocity.x
        }

        if(self.position.x >= topRight.x){
            self.position.x = topRight.x
            // velocity.x = -velocity.x
        }

        if(self.position.y <= bottomLeft.y){
            self.position.y = bottomLeft.y
            // velocity.y = -velocity.y
        }

        if(self.position.y >= topRight.y){
            self.position.y = topRight.y
            // velocity.y = -velocity.y
        }
    }

    /*
        Move the player in a certain direction by a specific amount
    */
    func movePlayerBy(dxVectorValue: CGFloat, dyVectorValue: CGFloat, duration: NSTimeInterval)->(){
        let moveActionVector = CGVectorMake(dxVectorValue, dyVectorValue)
        let movePlayerAction = SKAction.moveBy(moveActionVector, duration: 1/duration)
        self.runAction(movePlayerAction)
    }

}

【问题讨论】:

    标签: ios swift sprite-kit


    【解决方案1】:

    基本上,我们需要一个零重力场景和一个触摸会导致力类型物理动作的玩家。这不是 moveBy 类型的数字动作,它简单地通过某某移动屏幕上的角色。

    我继续测试了代码,试图让你得到与你描述的相似的东西。我稍微更改了您的一些代码...以使其与我自己的设置一起使用,因为您没有提供 GameViewController 代码,因此请询问您是否有任何问题。

    我在最后提供的代码带有 cmets,上面写着 // IMPORTANT CODE with a #beside.

    这里详细说明您为什么使用每条“重要代码”

    1. 我们需要物理来完成您所描述的内容,因此首先确保玩家类将具有物理主体。身体将是动态的并受重力影响(零重力),但是为了游戏性,您可能需要稍微调整一下重力。

      let body:SKPhysicsBody = SKPhysicsBody(texture: texture, alphaThreshold: 0, size: texture.size() )
      
      self.physicsBody = body
      self.physicsBody?.allowsRotation = false
      
      self.physicsBody?.dynamic = true
      self.physicsBody?.affectedByGravity = true
      
    2. 既然你想要零重力,我们需要改变我们场景中的物理世界重力

        scene?.physicsWorld.gravity = CGVectorMake(0, 0)
      
    3. 接下来,我们将 movePlayerBy() 更改为使用力而不是简单的数字运动。我们使用 SKAction.applyForce 来做到这一点。

    这为您提供了基于与滑动相关的力的设置。 但是,无论滑动多么用力,您都可能需要恒定的速度。您可以通过规范化向量来做到这一点。请参阅此处以了解谁提出了该问题以及它如何适用于此处 (http://www.scriptscoop2.com/t/adc37b4f2ea8/swift-giving-a-physicsbody-a-constant-force.html)

         func movePlayerBy(dxVectorValue: CGFloat, dyVectorValue: CGFloat, duration: NSTimeInterval)->(){
    
        print("move player")
        let moveActionVector = CGVectorMake(dxVectorValue, dyVectorValue)
        let movePlayerAction = SKAction.applyForce(moveActionVector, duration: 1/duration)
        self.runAction(movePlayerAction)
        }
    
    1. 如果你想让玩家减速,我们必须添加一个函数来将玩家的速度设置为 0。我已经做到了,这会在函数最初调用后 0.5 秒发生。否则“通过重力漂浮”效果并没有真正注意到,因为运动将以 touchesEnded() 结束。

    您可以尝试其他方法来降低加速度,例如在以下序列中的暂停操作之前使用最初使用的负力。

    还有许多其他方法可以让它更像是真正的减速……比如第二个序列,它以设定的时间间隔从速度中减去 -1,直到它达到 0,然后我们将速度硬编码为 0。 但是,从游戏的角度来看,这取决于您。

    所以这应该足以给你一个想法。

        func stopMoving() {
              let delayTime: NSTimeInterval = 0.5  // 0.5 second pause
    
              let stopAction: SKAction = SKAction.runBlock{
                  self.physicsBody?.velocity = CGVectorMake(0, 0)
               }
    
              let pause: SKAction = SKAction.waitForDuration(delayTime)
    
              let stopSequence: SKAction = SKAction.sequence([pause,stopAction])
    
              self.runAction(stopSequence)
    
         }
    
    1. 我们将 touchesEnded() 更改为调用 stopMoving() .. 但是,在没有这个的情况下尝试它以在没有“减速”的情况下看到它。

      override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
            player.removeAllActions()
      
             player.stopMoving()
       }
      

    其他说明。

    目前,边界仅使用我创建的代码捕获左侧和右侧的玩家...我不确定这是否会在您的设置中发生。但是,由于这是另一个需要弄清楚的问题,我没有进一步研究它。

    这是我使用的代码......我提供它是因为我为了测试而做了一些其他的小改动。除了将新的重要代码放在哪里之外,我不会担心其他任何事情。

    GameScene.Swift

    import SpriteKit
    
    // Global
    
    /*
     Level_1 set up and control
     */
    
    
    class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
    
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        /* Called when a touch begins */
    
    
    
    }
    
    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }
    }
    
    
    
    class Level_1: GameScene {
    // Instance variables
    var lastUpdateTime:NSTimeInterval = 0
    var dt:NSTimeInterval = 0
    var player = Player() // Sub classed SKSpriteNode for all player related stuff
    
    var currentTouchPosition: CGPoint  = CGPointZero
    var beginningTouchPosition:CGPoint = CGPointZero
    var currentPlayerPosition: CGPoint = CGPointZero
    
    var playableRectArea:CGRect = CGRectZero
    
    
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        // IMPORTANT CODE 2 //
    
        scene?.physicsWorld.gravity = CGVectorMake(0, 0)
    
        // Constant - Max aspect ratio supported
        let maxAspectRatio:CGFloat = 16.0/9.0
    
        // Calculate playable height
        let playableHeight = size.width / maxAspectRatio
    
        // Determine margin on top and bottom by subtracting playable height
        // from scene height and then divide by 2
        let playableMargin = (size.height-playableHeight)/2.0
    
    
        // Calculate the actual playable area rectangle
        playableRectArea = CGRect(x: 0, y: playableMargin,
                                  width: size.width,
                                  height: playableHeight)
    
        currentTouchPosition = CGPointZero
        beginningTouchPosition = CGPointZero
    
        let background = SKSpriteNode(imageNamed: "Level1_Background")
        background.position = CGPoint(x: size.width/2, y: size.height/2)
        background.zPosition = -1
    
        self.addChild(background)
    
        // CHANGED TO Put my own texture visible on the screen
    
        currentPlayerPosition = CGPoint(x: size.width/2, y: size.height/2)
    
        player.position = currentPlayerPosition
    
        self.addChild(player)
    
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        for touch: AnyObject in touches {
            currentTouchPosition = touch.locationInNode(self)
        }
    
        let dxVectorValue = (-1) * (beginningTouchPosition.x - currentTouchPosition.x)
        let dyVectorValue = (-1) * (beginningTouchPosition.y - currentTouchPosition.y)
    
    
        player.movePlayerBy(dxVectorValue, dyVectorValue: dyVectorValue, duration: dt)
    
    }
    
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        player.removeAllActions()
    
        // IMPORTANT CODE 5 //
        player.stopMoving()
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        /* Called when a touch begins */
        print("touch")
        for touch: AnyObject in touches {
            beginningTouchPosition = touch.locationInNode(self)
            currentTouchPosition = beginningTouchPosition
        }
    
    }
    
    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
        currentPlayerPosition = player.position
    
        if lastUpdateTime > 0 {
            dt = currentTime - lastUpdateTime
        }else{
            dt = 0
        }
        lastUpdateTime = currentTime
    
        player.boundsCheckPlayer(playableRectArea)
    }
    }
    

    GameViewController.swift

    import UIKit
    import SpriteKit
    
    class GameViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        if let scene = GameScene(fileNamed:"GameScene") {
            // Configure the view.
            let skView = self.view as! SKView
            skView.showsFPS = true
            skView.showsNodeCount = true
    
            /* Sprite Kit applies additional optimizations to improve rendering performance */
            skView.ignoresSiblingOrder = true
    
            /* Set the scale mode to scale to fit the window */
            scene.scaleMode = .AspectFill
    
            skView.presentScene(scene)
        }
    }
    
    override func shouldAutorotate() -> Bool {
        return true
    }
    
    override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
            return .AllButUpsideDown
        } else {
            return .All
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
    }
    
    override func prefersStatusBarHidden() -> Bool {
        return true
    }
    }
    

    Player.swift

    import Foundation
    import SpriteKit
    
    struct PhysicsCategory {
        static let None         : UInt32 = 0
        static let All          : UInt32 = UInt32.max
        static let Player       : UInt32 = 0b1       // 1
        static let Enemy        : UInt32 = 0b10      // 2
    }
    
    class Player: SKSpriteNode{
    
    init(){
    
        // Initialize the player object
        let texture = SKTexture(imageNamed: "Player1")
    
        super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
    
        self.xScale = 2
        self.yScale = 2
        self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        self.zPosition = 1
    
        // Player physics
    
        // IMPORTANT CODE 1 //
    
        let body:SKPhysicsBody = SKPhysicsBody(texture: texture, alphaThreshold: 0, size: texture.size() )
    
        self.physicsBody = body
        self.physicsBody?.allowsRotation = false
    
    
    
        self.physicsBody?.dynamic = true
        self.physicsBody?.affectedByGravity = true
    
        self.physicsBody?.categoryBitMask = PhysicsCategory.Player
    }
    
    
    
    required init?(coder aDecoder: NSCoder) {
    
    
        super.init(coder: aDecoder)
    }
    
    
    // Check if the player sprite is within the playable area bounds
    func boundsCheckPlayer(playableArea: CGRect){
        let bottomLeft = CGPoint(x: 0, y: CGRectGetMinY(playableArea))
        let topRight = CGPoint(x: playableArea.size.width, y: CGRectGetMaxY(playableArea))
    
        if(self.position.x <= bottomLeft.x){
            self.position.x = bottomLeft.x
            // velocity.x = -velocity.x
        }
    
        if(self.position.x >= topRight.x){
            self.position.x = topRight.x
            // velocity.x = -velocity.x
        }
    
        if(self.position.y <= bottomLeft.y){
            self.position.y = bottomLeft.y
            // velocity.y = -velocity.y
        }
    
        if(self.position.y >= topRight.y){
            self.position.y = topRight.y
            // velocity.y = -velocity.y
        }
    }
    
    /*
     Move the player in a certain direction by a specific amount
     */
    
    
    // IMPORTANT CODE 3 //
    
    func movePlayerBy(dxVectorValue: CGFloat, dyVectorValue: CGFloat, duration: NSTimeInterval)->(){
    
        print("move player")
        let moveActionVector = CGVectorMake(dxVectorValue, dyVectorValue)
        let movePlayerAction = SKAction.applyForce(moveActionVector, duration: 1/duration)
        self.runAction(movePlayerAction)
    }
    
    // IMPORTANT CODE 4 //
    
    func stopMoving() {
        let delayTime: NSTimeInterval = 0.5  // 0.5 second pause
    
        let stopAction: SKAction = SKAction.runBlock{
            self.physicsBody?.velocity = CGVectorMake(0, 0)
        }
    
        let pause: SKAction = SKAction.waitForDuration(delayTime)
    
        let stopSequence: SKAction = SKAction.sequence([pause,stopAction])
    
        self.runAction(stopSequence)
    
    }
    
    }
    

    【讨论】:

    • 嗨科里。完全没想到您会深入了解如何操作,我非常感谢它,因为它确实向我解释了很多并且第一次尝试。我还没有实现速度部分,但今天晚些时候会解决这个问题。非常感谢,不胜感激。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-26
    • 2019-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多