草庐IT

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

coder 2024-01-15 原文

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


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

private var JumpThroughPlatformObject = SKSpriteNode()

这是在 DidMove 中:

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

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

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

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

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

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

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

下面的函数给我带来了 3 个主要问题:

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

  2. 如果我掉落,平台的左右边缘仍然会与玩家完全碰撞

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

    设零: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

    }
    }
}

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

最佳答案

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

基本上,这一切都与

有关
  1. 检测平台碰撞
  2. 然后判断你的播放器是否在平台下
  3. 让您的玩家通过平台(然后登陆)

--

SK Physics 让这有点复杂:

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

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

--

因此,为了让一切正常运行,必须发挥一点创造力和独创性!


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 需要我们向玩家类添加另一个状态。我们需要临时存储联系的平台,以便我们可以检查玩家是否已成功通过平台(所以我们可以重置碰撞 mask )

然后我们只检查 didFinishUpdate 玩家的框架是否在该平台之上,如果是,我们重置 mask 。

这里是所有的文件,还有一个指向 github 的链接:
https://github.com/fluidityt/JumpUnderPlatform



播放器.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()
  }
}

希望对您有所帮助!

关于ios - 试图制作我可以从下面跳过去但落在上面的平台。无法微调逻辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44913398/

有关ios - 试图制作我可以从下面跳过去但落在上面的平台。无法微调逻辑的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  4. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  6. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  7. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  8. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  9. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐