我小时候在我的玩家类中为我的玩家制作了一个碰撞箱精灵。如何在游戏场景的 didBegin 方法中访问该碰撞箱精灵?

Posted

技术标签:

【中文标题】我小时候在我的玩家类中为我的玩家制作了一个碰撞箱精灵。如何在游戏场景的 didBegin 方法中访问该碰撞箱精灵?【英文标题】:I made a hitbox sprite as a child to my player, in my player Class. how do I access that hitbox sprite in gamescene's didBegin method? 【发布时间】:2017-07-12 23:46:39 【问题描述】:

这个“hitbox”的目的是让玩家只能在地面/平台上行走时跳跃。hitbox 比玩家宽一点,并且在玩家的脚上。这是我的播放器类:

class Player: SKSpriteNode 

  let maxPlayerSpeed:CGFloat = 300
  static var isPlayerOnGround = false



    init() 

        //players texture
        let texture = SKTexture(imageNamed: "playerMove1")

        super.init(texture: texture, color: SKColor.clear, size: texture.size())

        //hitbox that sits underneath the player and follows him
        let jumpHitBox = SKSpriteNode(color: .red, size: CGSize(width: texture.size().width - (texture.size().width / 8), height: texture.size().height / 5))

        jumpHitBox.position.y = (-texture.size().height) + (texture.size().height / 2)
        jumpHitBox.alpha = 0.5
        jumpHitBox.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: jumpHitBox.size.width,
                                                                   height: jumpHitBox.size.height))

        jumpHitBox.zPosition = 3
        jumpHitBox.physicsBody?.pinned = true
        jumpHitBox.physicsBody?.allowsRotation = false
        jumpHitBox.physicsBody?.categoryBitMask = CollisionTypes.playerJump.rawValue
        jumpHitBox.physicsBody?.collisionBitMask = 0
        jumpHitBox.physicsBody?.contactTestBitMask = CollisionTypes.ground.rawValue
        addChild(jumpHitBox)



        physicsBody = SKPhysicsBody(rectangleOf: size)
        physicsBody?.categoryBitMask = CollisionTypes.player.rawValue
        physicsBody?.contactTestBitMask = CollisionTypes.star.rawValue | CollisionTypes.saw.rawValue | CollisionTypes.finish.rawValue
        physicsBody?.collisionBitMask = CollisionTypes.ground.rawValue
        physicsBody?.affectedByGravity = true
        physicsBody?.restitution = 0.2
        physicsBody?.isDynamic = true
        physicsBody?.allowsRotation = false
        setScale(0.6)
        zPosition = 5
        physicsBody?.linearDamping = 0.0 

required init?(coder aDecoder: NSCoder) 
            super.init(coder: aDecoder) //error here
        

我想在这个方法中使用 jumpHitBox 作为 GameScene.swift 中的一个额外的 contact.bodyA/B 节点:

func didBegin(_ contact: SKPhysicsContact) 

        if contact.bodyA.node == player 
           playerCollided(with: contact.bodyB.node!)


         else if contact.bodyB.node == player 
           playerCollided(with: contact.bodyA.node!)

        
    

我不知道如何在 GameScene.swift 的 didBegin 中从我的玩家类中引用 jumpHitBox 子节点。

非常感谢任何建议。

编辑:我在

处遇到错误
required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder) //error here
    

我没有在我的播放器类中放任何东西,但是自从将 jumpHitBox 精灵移动到全局声明后,我在所需的 init 中的 super.init(coder:aDecoder) 行出现错误:Property 'self.jumpHitBox'未在 super.init 调用时初始化

【问题讨论】:

嘿 GG,你为什么不直接使用 velocity.dy 就像你在平台下跳转一样?只有在这种情况下,如果 dy == 0(意味着不跳跃/在地面上),那么您允许玩家跳跃。对我来说,这听起来很多比尝试跟踪平台和命中框(我曾经自己做过!) 我实际上是这样做的,但是如果玩家跳到一块地块旁边并且在他处于跳跃的高峰时以任何方式接触到地块,那么他可以再次跳跃,因为当你的速度在你开始下降之前变为零时,跳跃的峰值出现在一瞬间。我的问题更多是针对大块浮地而不是平台。我只是使用术语“平台”来使我的问题更清楚。感谢您的回复! 嗯,如果你只是让它停止一个框架以确保 dy 为 0... 我该怎么做?我从未听说过“失速”框架?但这听起来确实可行。 后面会举个例子。 【参考方案1】:

虽然我会尝试 cmets 中提到的更简单的velocity.dy 方法,但您的问题是您的命中框是在初始化程序的范围内声明的,因此您只能在那里访问它。如果你给 hitbox 一个更高的范围,比如一个类属性,那么你可以在任何地方访问它:

class Player: SKSpriteNode 

  let maxPlayerSpeed:CGFloat = 300

  // This is probably going to cause you bugs later btw.. it should probably be
  // just a regular property:
  static var isPlayerOnGround = false

  // Now you can just call playerInstance.jumpHitBox :
  private(set) var jumpHitBox = SKSpriteNode()

  init() 

    //players texture
    let texture = SKTexture(imageNamed: "playerMove1")

    super.init(texture: texture, color: SKColor.clear, size: texture.size())
    jumpHitBox = SKSpriteNode(color: .red, size: CGSize(width: texture.size().width - (texture.size().width / 8), height: texture.size().height / 5))
    //hitbox that sits underneath the player and follows him
  


更新:

class Player: SKSpriteNode 

  let maxPlayerSpeed:CGFloat = 300

  // This is probably going to cause you bugs later btw.. it should probably be
  // just a regular property:
  static var isPlayerOnGround = false

  // Now you can just call playerInstance.jumpHitBox :
  var jumpHitBox = SKSpriteNode()

  private func makeHitBox() -> SKSpriteNode 
    let texture = SKTexture(imageNamed: "playerMove1")

    //hitbox that sits underneath the player and follows him
    let localJumpHitBox = SKSpriteNode(color: .red, size: CGSize(width: texture.size().width - (texture.size().width / 8), height: texture.size().height / 5))

    localJumpHitBox.position.y = (-texture.size().height) + (texture.size().height / 2)
    localJumpHitBox.alpha = 0.5
    localJumpHitBox.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: localJumpHitBox.size.width,
                                                                    height: localJumpHitBox.size.height))

    localJumpHitBox.zPosition = 3
    localJumpHitBox.physicsBody?.pinned = true
    localJumpHitBox.physicsBody?.allowsRotation = false
    localJumpHitBox.physicsBody?.categoryBitMask = CollisionTypes.playerJump.rawValue
    localJumpHitBox.physicsBody?.collisionBitMask = 0
    localJumpHitBox.physicsBody?.contactTestBitMask = CollisionTypes.ground.rawValue

    return localJumpHitBox
  

  init() 

    //players texture
    let texture = SKTexture(imageNamed: "playerMove1")

    super.init(texture: texture, color: SKColor.clear, size: texture.size())

    physicsBody = SKPhysicsBody(rectangleOf: size)
    physicsBody?.categoryBitMask = CollisionTypes.player.rawValue
    physicsBody?.contactTestBitMask = CollisionTypes.star.rawValue | CollisionTypes.saw.rawValue | CollisionTypes.finish.rawValue
    physicsBody?.collisionBitMask = CollisionTypes.ground.rawValue
    physicsBody?.affectedByGravity = true
    physicsBody?.restitution = 0.2
    physicsBody?.isDynamic = true
    physicsBody?.allowsRotation = false
    setScale(0.6)
    zPosition = 5
    physicsBody?.linearDamping = 0.0
  

  required init?(coder aDecoder: NSCoder) 
    super.init(coder: aDecoder) //error here
    jumpHitBox = makeHitBox()
    addChild(jumpHitBox)
  

【讨论】:

谢谢,我什么时候使用“let”和“private(set) var” 我按照你的建议做了,我得到了这个错误:属性'self.jumpHitBox'在 super.init 调用时没有初始化。它所指的那一行实际上是在“必需的初始化?”中。在我的播放器类中没有任何内容。我不确定它告诉我要做什么。我在 OP 中做了一个小编辑来解释这个问题,如果你有时间请看一下。我会很感激的。 @genericguy25 好的,您正在使用 SKS 编辑器。让我看看我能在这里掀起什么。 @genericguy25 好的,看看我在更新部分添加的代码是否工作得更好。 @genericguy25 也是,private(set) var 是因为我不想使用惰性属性或使用可选属性(以防止编译器抱怨)。所以我把它做成了 var (本地可变),所以我可以稍后分配它所有的好东西,但是对于你的 GameScene 等中的任何东西,它都将被认为是一个 let (不可变的)。我现在把var 简单化了,以减少混淆,因为我不确定它是否与required init(coder:) 混淆。【参考方案2】:

从类外部引用 jumpHitBox 的一种方法是使其成为 Player 类的属性,如 let jumpHitBox: SKSpriteNode 中一样,就像您将 maxPlayerSpeed 声明为 Player 类的属性一样。

如果您进行此更改,请记住删除此行中的let let jumpHitBox = SKSpriteNode(color: .red ....,因为您现在只需为其分配一个值,而不是声明它。您现在还应该将呼叫移动到 super.init 到此行之后。否则编译器会报错,即在调用super.init之前必须为类的所有属性赋值。

现在应该可以在 didBegin 中使用 player.hitBox 访问 jumpHitBox

希望这会有所帮助!

【讨论】:

谢谢!出于某种原因,当我执行您的建议或@Fluiditiy 的建议时,我收到此错误:Property 'self.jumpHitBox' not initialized at super.init call。它所指的那一行实际上是在“必需的初始化?”中。在我的播放器类中没有任何内容。请参阅 OP 以获得更好地解释问题的小编辑。 @genericguy25 after jumpHitBox = SKSpriteNode(color: .red ... etc.) 是否调用了 super.init?该错误表明 jumpHitBox 未分配值。【参考方案3】:

这是一个次要答案,显示了解决问题的不同方法。请注意,玩家只有在地面上时才会跳跃。这使用了“延迟帧”或“帧跳过”,或者您想使用它,因为跳转命令 (pb.applyImpulse) 仅在 didSimulatePhysicsinside 中调用,这意味着角色不会'实际上,直到下一帧为止。

// NOTE: This is a very simple example. The player bounces a bit on landing,
// which is essentially "landing lag" before you can jump again. In other words,
// the less the player bounces when contact the ground, the faster the player
// can jump again. There are ways to make this bouncing effect minimal / 0, but would
// require more work to implement, and I don't have any brilliantly simple ideas at the moment.
class GameScene : SKScene 

  var player = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
  var initialY = CGFloat(0)

  var flag_wantsToJump = false

  override func didMove(to view: SKView) 
    self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)

    let pb = SKPhysicsBody(rectangleOf: player.size)
    pb.restitution = 0
    player.physicsBody = pb
    addChild(player)
  

  override func mouseDown(with event: NSEvent) 
    // Tells game that we want to jump next frame:
    flag_wantsToJump = true
  

  override func update(_ currentTime: TimeInterval) 
    // Give us new, initial frame data to compare against whatever our position.y will be
    // later in the frame:
    let curY = player.position.y
    initialY = curY
  

  override func didSimulatePhysics() 

    // Determine whether or not we want to jump next frame:
    guard flag_wantsToJump else  return 

    // Determine whether or not we are allowed to jump:
    let curY = player.position.y
    print(curY, initialY)

    if curY == initialY 
      // Even though we are calling .applyImpulse this frame, it won't be processed
      // until next frame--because we are calling this from inside `didSimulatePhysics`!
      player.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 75))
    
  

  override func didFinishUpdate() 
    // Clear our flags for new mouse / touch input for next frame:
    flag_wantsToJump = false
  

【讨论】:

谢谢你!我正在对诸如移动角色和跳跃之类的冲动与 skaction 进行一些研究。有人说不要在 2d 游戏中使用基于物理的运动,其他人说这没什么大不了的。我冲动地移动我的播放器。你认为我应该切换到 SKActions 吗? @genericguy25 skactions 没有区别。人们谈论的是在 2d 平台游戏中使用 physics,而不是调整精灵的 position。例如,任何通过物理方式导致玩家移动的事物都涉及物理体(脉冲、力、速度等)。不使用物理的东西是.position或者使用SKAction.move 如您所见,物理很难用于平台游戏,但另一种选择是制作自己的命中检测系统、重力和跳跃命令。您可以将直线位置运动与物理命中检测相结合,但这几乎总是会导致难以解决的问题/错误 这对我来说很有意义。感谢您的澄清。

以上是关于我小时候在我的玩家类中为我的玩家制作了一个碰撞箱精灵。如何在游戏场景的 didBegin 方法中访问该碰撞箱精灵?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查我的播放器是否与对象发生碰撞?

碰撞时,我的玩家以外的物理实体不会调用 didBeginContact

使用固定的SKPhysicsJoint将平台检测命中框作为子项附加到玩家精灵更改玩家的碰撞和检测位掩码

什么是更有效的碰撞方式?

玩家在与标记对象碰撞时消失

带布尔值的碰撞器