试图制造我可以从下面跳过但可以降落在上面的平台。无法微调逻辑

Posted

技术标签:

【中文标题】试图制造我可以从下面跳过但可以降落在上面的平台。无法微调逻辑【英文标题】:Trying to make platforms that I can jump through from underneath but land on top of. having trouble fine tuning the logic 【发布时间】:2017-07-04 20:07:31 【问题描述】:

我的目标是在 .sks 文件中设置我的所有平台,以便更轻松地设计我的关卡。


这是在游戏 scene.swift 的顶部在 didMove 之前声明的:

private var JumpThroughPlatformObject = SKSpriteNode()

这是在 DidMove 中:

if let JumpThroughPlatformObjectNode = self.childNode(withName: "//jumpThroughPlatform1") as? SKSpriteNode 
        JumpThroughPlatformObject = JumpThroughPlatformObjectNode 

我参考平台以从 .sks 中获取它的高度,因为我的所有平台都将是相同的高度,我只需要从一个中获取它。

下面是我试图在我的更新方法中使用来关闭碰撞,直到我的播放器完全位于平台之上。仅检查我的玩家速度是否大于零的主要问题是:玩家是否处于跳跃的高峰(他的速度减慢到零)。如果发生这种情况并且玩家在一个平台内,他要么立即弹到平台顶部,要么向下发射。

我不希望我的平台必须是 1 像素高的线。我还需要让玩家拥有一个完整的碰撞盒,因为他将与其他类型的环境进行交互。这让我相信我只需要将平台的顶部注册为碰撞盒,而不是整个平台。

我写的这个 if 语句应该采用平台的 y 位置并将其高度的一半添加到它,因为 y 位置基于精灵的中心,我认为这会将平台的碰撞放在它的顶部边界。

我对播放器做了同样的事情,但反过来。将玩家碰撞仅放在其边界的底部。但它不能完美地工作,我现在不知道为什么。

if (JumpThroughPlatformObject.position.y + (JumpThroughPlatformObject.size.height / 2)) > (player.position.y - (player.size.height / 2))

下面的函数给了我三个主要问题:

    我的玩家跳跃总是 dy = 80。如果我要跳到那个 position.y = 90 的平台,玩家跳跃的高峰会在平台中间停止,但他会传送到而不是继续倒在地上。

    如果我摔倒,平台的左右边缘仍然会与玩家完全碰撞

    如果我的播放器在一个平台上,而我正上方有另一个播放器,则播放器无法跳过它。

    设零:CGFloat = 0

        if let body = player.physicsBody 
            let dy = player.physicsBody?.velocity.dy
    
            // when I jump dy is greater than zero else I'm falling
    
           if (dy! >= zero) 
    
               if (JumpThroughPlatformObject.position.y + (JumpThroughPlatformObject.size.height / 2)) > (player.position.y - (player.size.height / 2)) 
    
                    print(" platform y: \(JumpThroughPlatformObject.position.y)")
                    print ("player position: \(player.position.y)")
    
                    // Prevent collisions if the hero is jumping
                    body.collisionBitMask = CollisionTypes.saw.rawValue | CollisionTypes.ground.rawValue
    
                
    
           
            else 
                // Allow collisions if the hero is falling
                body.collisionBitMask = CollisionTypes.platform.rawValue | CollisionTypes.ground.rawValue | CollisionTypes.saw.rawValue
            
         
    

任何建议将不胜感激。这几天我一直在扯头发。

在 didBegin 和 didEnd 中编辑:

   func didBegin(_ contact: SKPhysicsContact) 

    if let body = player.physicsBody 
        let dy = player.physicsBody?.velocity.dy
        let platform = JumpThroughPlatformObject
        let zero:CGFloat = 0


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

            if (dy! > zero || body.node!.intersects(platform)) && ((body.node?.position.y)! - player.size.height / 2 < platform.position.y + platform.size.height / 2) 

                body.collisionBitMask &= ~CollisionTypes.platform.rawValue

            



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

        if (dy! > zero || body.node!.intersects(platform)) && ((body.node?.position.y)! - player.size.height / 2 < platform.position.y + platform.size.height / 2) 

            body.collisionBitMask &= ~CollisionTypes.platform.rawValue
        
    


func didEnd(_ contact: SKPhysicsContact) 

    if let body = player.physicsBody 
//            let dy = player.physicsBody?.velocity.dy
//            let platform = JumpThroughPlatformObject

    if contact.bodyA.node == player 


        body.collisionBitMask |= CollisionTypes.platform.rawValue

    else if contact.bodyB.node == player 

        body.collisionBitMask |= CollisionTypes.platform.rawValue

    
    

添加我所做的,玩家不能再跳过平台。

【问题讨论】:

我想你只是想删除 dy! >= 零测试。这不是正确的测试,因为当玩家从他们部分位于内部的平台上坠落时,您仍然需要禁用碰撞。您对播放器底部与平台顶部的测试应该足以确保播放器不会跌倒在其顶部的任何物体上。 感谢您的回复!不幸的是,在删除最初的 dy > 0 检查后,我仍然遇到 1 和 3 的问题。 这将是一场噩梦,仅供参考:) 哈哈,看来已经是这样了。有没有办法获取当前与玩家联系的节点的 y 位置? 我有一个示例项目给你,但我需要清理它。 【参考方案1】:

这是我为 macOS 和 ios 目标制作的项目的链接:https://github.com/fluidityt/JumpUnderPlatform

基本上,这一切都与

    检测平台碰撞 然后判断你的播放器是否在平台下 让您的玩家通过平台(然后降落在平台上)

--

SK Physics 使这有点复杂:

    在碰撞检测中,您的玩家的.position.y.velocity.dy 为了满足上面的#2 检查,可能已经更改为“假”状态(意味着#3 永远不会发生)。此外,您的播放器会在第一次接触时从平台反弹。

    没有“自动”方式来确定您的玩家何时完成通过对象(从而允许玩家降落在平台上)

--

因此,为了让一切顺利进行,必须使用一点创造力和独创性!


1:检测平台碰撞:

所以,解决1是最简单的:我们只需要使用内置的didBegin(contact:)

我们将严重依赖 3 大位掩码、联系人、类别和碰撞:

(仅供参考,我不喜欢在物理中使用枚举和位数学,因为我是个叛逆的白痴)

struct BitMasks 
  static let playerCategory = UInt32(2)
  static let jupCategory    = UInt32(4) // JUP = JumpUnderPlatform 


override func didBegin(_ contact: SKPhysicsContact) 

  // Crappy way to do "bit-math":
  let contactedSum = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask

  switch contactedSum 

  case BitMasks.jupCategory + BitMasks.playerCategory:
  // ...
  

--

现在,你说你想使用 SKSEditor,所以我已经适应了你:

// Do all the fancy stuff you want here...
class JumpUnderPlatform: SKSpriteNode 

  var pb: SKPhysicsBody  return self.physicsBody!  // If you see this on a crash, then WHY DOES JUP NOT HAVE A PB??

  // NOTE: I could not properly configure any SKNode properties here..
  // it's like they all get RESET if you put them in here...
  required init?(coder aDecoder: NSCoder)  super.init(coder: aDecoder)    

--

现在给玩家:

class Player: SKSpriteNode 

  // If you see this on a crash, then WHY DOES PLAYER NOT HAVE A PB??
  var pb: SKPhysicsBody  return self.physicsBody! 

  static func makePlayer() -> Player 

    let newPlayer = Player(color: .blue, size: CGSize(width: 50, height: 50))
    let newPB = SKPhysicsBody(rectangleOf: newPlayer.size)

    newPB.categoryBitMask = BitMasks.playerCategory
    newPB.usesPreciseCollisionDetection = true

    newPlayer.physicsBody = newPB
    newPlayer.position.y -= 200 // For demo purposes.

    return newPlayer
  



2。 (并处理#4):确定是否在接触平台下:

有很多方法可以做到这一点,但我选择使用KOD提到的player.pb.velocity.dy方法来跟踪玩家的位置......如果你的dy超过0,那么你正在跳跃(在一个平台下) 如果没有,那么你要么站着不动,要么跌倒(需要与平台接触并坚持下去)。

要实现这一点,我们必须掌握更多技术知识,因为物理系统和 SK 在其循环中的工作方式并不总是 100% 与我们认为它应该工作的方式相吻合.

基本上,我必须为 Player 创建一个 initialDY 属性,该属性会在 update 中的每一帧中不断更新

initialDY 将为我们提供首次接触平台所需的正确数据,让我们可以告诉我们更改碰撞掩码,并将玩家的 CURRENT dy 重置为初始 dy(因此播放器不会反弹)。


3。 (并处理#5):允许玩家通过平台

要通过平台,我们需要使用collisionBitMasks。我选择让玩家的碰撞掩码 = 玩家的类别掩码,这可能不是正确的做法,但它适用于这个演示。

你在didBegin中得到了这样的魔法:

  // Check if jumping; if not, then just land on platform normally.
  guard player.initialDY > 0 else  return 

  // Gives us the ability to pass through the platform!
  player.pb.collisionBitMask = BitMasks.playerCategory

现在,处理 #5 将需要我们向我们的播放器类添加另一个状态。我们需要临时存储接触到的平台,以便我们可以检查播放器是否成功完成了通过平台(所以我们可以重置碰撞掩码)

然后我们只检查didFinishUpdate,如果玩家的框架在那个平台之上,如果是,我们重置掩码。

这里是所有文件,以及 github 的链接:https://github.com/fluidityt/JumpUnderPlatform



Player.swift:

class Player: SKSpriteNode 

  // If you see this on a crash, then WHY DOES PLAYER NOT HAVE A PB??
  var pb: SKPhysicsBody  return self.physicsBody! 

  // This is set when we detect contact with a platform, but are underneath it (jumping up)
  weak var platformToPassThrough: JumpUnderPlatform?

  // For use inside of gamescene's didBeginContact (because current DY is altered by the time we need it)
  var initialDY = CGFloat(0)


// MARK: - Funkys:
extension Player 
  static func makePlayer() -> Player 

    let newPlayer = Player(color: .blue, size: CGSize(width: 50, height: 50))
    let newPB = SKPhysicsBody(rectangleOf: newPlayer.size)

    newPB.categoryBitMask = BitMasks.playerCategory
    newPB.usesPreciseCollisionDetection = true

    newPlayer.physicsBody = newPB
    newPlayer.position.y -= 200 // For demo purposes.

    return newPlayer
  


  func isAbovePlatform() -> Bool 
    guard let platform = platformToPassThrough else  fatalError("wtf is the platform!") 

    if frame.minY > platform.frame.maxY  return true  
    else                                 return false 
  

  func landOnPlatform() 
      print("resetting stuff!")
      platformToPassThrough = nil
      pb.collisionBitMask = BitMasks.jupCategory
  


// MARK: - Player GameLoop:
extension Player 

  func _update() 
    // We have to keep track of this for proper detection of when to pass-through platform
    initialDY = pb.velocity.dy
  

  func _didFinishUpdate() 

    // Check if we need to reset our collision mask (allow us to land on platform again)
    if platformToPassThrough != nil 
      if isAbovePlatform()  landOnPlatform() 
    
  



JumpUnderPlatform 和 BitMasks.swift(分别:)

// Do all the fancy stuff you want here...
class JumpUnderPlatform: SKSpriteNode 

  var pb: SKPhysicsBody  return self.physicsBody!  // If you see this on a crash, then WHY DOES JUP NOT HAVE A PB??

  required init?(coder aDecoder: NSCoder)  super.init(coder: aDecoder) 



struct BitMasks 
  static let playerCategory = UInt32(2)
  static let jupCategory    = UInt32(4)



GameScene.swift:

-

确保您的 SKS 编辑器中有两个节点:

-

// MARK: - Props:
class GameScene: SKScene, SKPhysicsContactDelegate 

  // Because I hate crashes related to spelling errors.
  let names = (jup: "jup", resetLabel: "resetLabel")

  let player = Player.makePlayer()



// MARK: - Physics handling:
extension GameScene 

  private func findJup(contact: SKPhysicsContact) -> JumpUnderPlatform? 
    guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else  fatalError("how did this happne!!??") 

    if      nodeA.name == names.jup  return (nodeA as! JumpUnderPlatform) 
    else if nodeB.name == names.jup  return (nodeB as! JumpUnderPlatform) 
    else                             return nil 
  

  // Player is 2, platform is 4:
  private func doContactPlayer_X_Jup(platform: JumpUnderPlatform) 

    // Check if jumping; if not, then just land on platform normally.
    guard player.initialDY > 0 else  return 

    // Gives us the ability to pass through the platform!
    player.physicsBody!.collisionBitMask = BitMasks.playerCategory

    // Will push the player through the platform (instead of bouncing off) on first hit
    if player.platformToPassThrough == nil  player.pb.velocity.dy = player.initialDY 
    player.platformToPassThrough = platform
  

func _didBegin(_ contact: SKPhysicsContact) 

  // Crappy way to do bit-math:
  let contactedSum = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask

  switch contactedSum 

  case BitMasks.jupCategory + BitMasks.playerCategory:
      guard let platform = findJup(contact: contact) else  fatalError("must be platform!") 
      doContactPlayer_X_Jup(platform: platform)

    // Put your other contact cases here...
    // case BitMasks.xx + BitMasks.yy:

    default: ()
    
  


// MARK: - Game loop:
extension GameScene 

  // Scene setup:
  override func didMove(to view: SKView) 
    physicsWorld.contactDelegate = self
    physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
    addChild(player)
  

  // Touch handling: (convert to touchesBegan for iOS):
  override func mouseDown(with event: NSEvent) 
    // Make player jump:
    player.pb.applyImpulse(CGVector(dx: 0, dy: 50))

    // Reset player on label click (from sks file): 
    if nodes(at: event.location(in: self)).first?.name == names.resetLabel 
      player.position.y = frame.minY + player.size.width/2 + CGFloat(1)
    
  

  override func update(_ currentTime: TimeInterval) 
    player._update()
  

  func didBegin(_ contact: SKPhysicsContact) 
    self._didBegin(contact)
  

  override func didFinishUpdate() 
    player._didFinishUpdate()
  


我希望这会有所帮助!

【讨论】:

我真的很感谢你所做的一切!我经历并尝试将您的工作实施到我自己的代码中。不幸的是它可以编译,但我根本无法跳过平台。我错过了一些东西,我不确定在哪里。明天我将尝试从您的 macOS 示例项目开始,将其转换为 iOS 并向后工作。我可以拥有 x 个名为“jup”的平台,并且每个平台的工作方式都相同吗? @genericguy25 它应该可以工作。我就是这样设计的。就像我之前告诉过你的那样,这对于看起来应该很容易的事情来说有点复杂。您必须在 SK 循环的某些部分中跟踪一堆状态并在正确的时间重置: @genericguy25 只是为了强调它才能正常工作,你真的必须有 4 件事...... 1,更新时播放器的initialDY,2,与@987654353 联系的平台@位置检查(着陆)(重置碰撞掩码),3,您必须在第一次接触时将玩家的 CURRENT dy 设置为 initialDY(否则他会弹开),以及 4,您必须更改碰撞掩码在第一次接触时也是如此 @genericguy25 还有,我将在大约 10 分钟内将 iOS 目标推送到 github 上的这个项目......所以如果你不确定什么,你就不必自己尝试去做去做。 @genericguy25 我添加了 iOS 目标,并在我忘记解释的 #2 部分添加了更多信息(但在代码 cmets 中)。【参考方案2】:

你只需要一个让你知道你是否在一个身体里的条件。我还清理了您的代码以避免意外输入错误的类别

if let body = player.physicsBody,   let dy = body.velocity.dy 
   // when I am jumping or I am in a platform, then do not register
   if (dy > zero || body.node.intersects(platform) && (body.node.position.y - body.node.size.height/2 != platform.position.y + platform.size.height / 2)  
        body.collisionBitMask &= ~CollisionTypes.platform.rawValue

   
    else 
        // Allow collisions if the hero is falling
        body.collisionBitMask |= CollisionTypes.platform.rawValue

【讨论】:

我没有在游乐场测试这个,让我知道你遇到了什么错误 感谢您的回复! if 语句中的“平台”给出了一个“未解决的标识符错误。我用我的 OP 中的 JumpThroughPlatformObject 替换了它。当我跳到平台上时,我立刻摔倒了,我怀疑是因为在我着陆之前我的速度变为零跳跃的高峰,因此它停用了碰撞,我摔倒了。有没有办法让我的 .sks 上的每个平台都可以使用它,而不必像我在 OP 中那样引用游戏场景中的每个平台。swift? 我有一种感觉,你正在通过平台,因为你在相交我改变了我的答案 变化基本上是精灵的底部!=平台的顶部并发生相交 谢谢。现在看来,我的角色在平台顶部边缘着陆了一瞬间,然后碰撞消失了,他掉了下来。【参考方案3】:

嗯,上面的答案很有效,但是已经很完整了。 简单的答案是使用 Platform effector 2D 组件。它应用了各种“平台”行为,例如单向碰撞、消除侧摩擦/反弹等。 查看这个 Unity 的官方 tutorial 了解更多信息。

【讨论】:

以上是关于试图制造我可以从下面跳过但可以降落在上面的平台。无法微调逻辑的主要内容,如果未能解决你的问题,请参考以下文章

我可以跳过 Firebase 中无密码登录方法的第一步吗?

这几本JavaScript书籍?你可能没读过但一定听过!

反引号和单引号有啥区别?我可以在上面的查询中使用 IF 语句吗?

一个类中可以没有构造器

动规-POJ-1661

跳过容器技术,直接采用无服务器架构计算