Sprite Kit中的单向平台碰撞

7

我正在使用Swift制作一个类似于“Doodle Jump”的克隆游戏,问题是当玩家跳跃时,它的头撞到了平台底部并没有穿过去。我应该如何使玩家能够越过平台并跳跃通过呢?这是我的代码:

import SpriteKit

class GameScene: SKScene, SKPhysicsContactDelegate {

    var hero = SKSpriteNode(imageNamed: "hero");
    var stepSizeTest = SKSpriteNode(imageNamed: "step");
    var start = false;
    var jumpSpeed = CGFloat(0);
    var gravity = CGFloat(0);
    var stepPositionDivision:CGFloat = 0 //allows the step to spawn on specific places on y axes
    var setpPositionHeightIncrease:CGFloat = 0;

    var positionX:CGFloat = 0;
    var timeInterval:NSTimeInterval = 0;
    var CurTime:NSTimeInterval = 0;
    var TimeWhenThePreviousStepSpawned:NSTimeInterval = 0;
    var standart:Bool = false;
    var move:Bool = false;
    var oneJumpOnly:Bool = false;
    var cracked:Bool = false;
    var jumped:Bool = false;

    struct PhysicsCategory{
        static var None: UInt32 = 0;
        static var step: UInt32 = 0b1;
        static var hero: UInt32 = 0b10;
        static var All: UInt32 = UInt32.max;
    }

    func didBeginContact(contact: SKPhysicsContact) {
        var contactBody1: SKPhysicsBody;
        var contactBody2: SKPhysicsBody;
        if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask){
            contactBody1 = contact.bodyA;
            contactBody2 = contact.bodyB;
        }
        else{
            contactBody1 = contact.bodyB;
            contactBody2 = contact.bodyA;
        }

        if (contactBody1.categoryBitMask == PhysicsCategory.step && contactBody2.categoryBitMask == PhysicsCategory.hero){
            jumpSpeed = CGFloat(self.frame.size.height*0.01320422535);

        }
//        if (contactBody1.categoryBitMask == PhysicsCategory.ground && contactBody2.categoryBitMask == PhysicsCategory.hero){
//
//        }
    }

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        stepPositionDivision = self.frame.size.height/23;

        jumpSpeed = CGFloat(self.frame.size.height*0.01320422535);
        gravity = CGFloat(self.frame.size.height*(-0.0003521126761));

        self.physicsWorld.contactDelegate = self;

        self.physicsWorld.gravity = CGVectorMake(0,0);

        let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame);
        sceneBody.friction = 0;
        self.physicsBody = sceneBody;


        self.hero.anchorPoint = CGPoint(x: 0.5, y: 0.5);
        self.hero.position = CGPoint(x:self.frame.size.width/2, y:self.hero.size.height/2);
        self.hero.physicsBody = SKPhysicsBody(rectangleOfSize: self.hero.size)
        self.hero.physicsBody?.restitution = 1;
        self.hero.physicsBody?.affectedByGravity = false;
        self.hero.physicsBody?.friction = 0;
        self.hero.physicsBody?.categoryBitMask = PhysicsCategory.hero;
        self.hero.physicsBody?.collisionBitMask = PhysicsCategory.step;
        self.hero.physicsBody?.contactTestBitMask = PhysicsCategory.step;

        self.addChild(self.hero);
    }

    func ranPositionX() -> CGFloat{
        let stepSise = UInt32(self.stepSizeTest.size.width)
        let posX = arc4random_uniform(UInt32(self.frame.size.width) - stepSise) + stepSise
        return CGFloat(posX)
    }

    func stepSpawn(){
        if (setpPositionHeightIncrease < self.frame.size.height){
            let step = SKSpriteNode(imageNamed: "step");
            let posX = ranPositionX()

            step.anchorPoint = CGPoint(x: 0.5, y: 0.5);
            step.position = CGPoint(x:posX, y:setpPositionHeightIncrease);
            step.physicsBody = SKPhysicsBody(rectangleOfSize: step.size);
            step.physicsBody?.affectedByGravity = false;
            step.physicsBody?.dynamic = true;
            step.physicsBody?.friction = 0;
            step.physicsBody?.categoryBitMask = PhysicsCategory.step;
            step.physicsBody?.collisionBitMask = PhysicsCategory.None;
            step.physicsBody?.contactTestBitMask = PhysicsCategory.hero;

            self.addChild(step);
            setpPositionHeightIncrease += stepPositionDivision
        }
    }

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

        }
    }

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */

        self.hero.position.y += jumpSpeed
        jumpSpeed += gravity

        if (start == false){
            //self.hero.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 40))
            start = true;
        }
        stepSpawn()
    }
}

1
我认为你需要枚举场景中的所有节点,如果它们在玩家上方,则切换它们的collisionBitMask以使它们不发生碰撞;如果它们在玩家下方,则切换它们的collisionBitMask以使它们发生碰撞。 - Steve Ives
2个回答

6

一种方法是在英雄下落或跳跃时切换相应的碰撞位(bit)开/关状态。如果英雄的速度dy属性小于零,则英雄正在下落,如果dy大于零,则英雄正在跳跃(或弹起)。以下是如何实现的示例:

// Add this to the update method
if let body = hero.physicsBody {
    let dy = body.velocity.dy
    if dy > 0 {
        // Prevent collisions if the hero is jumping
        body.collisionBitMask &= ~PhysicsCategory.step
    }
    else {
        // Allow collisions if the hero is falling
        body.collisionBitMask |= PhysicsCategory.step
    }
}

4
这是一个不错的方法,我自己在几个项目中也用过。但有一个问题:如果英雄在下落时向侧面移动,他的侧面可能会与平台的侧面碰撞。为了解决这个问题,我将上述方法与最小高度的物理体相结合:只有平台的顶部边缘需要一个物理体,而不是它整个可见形状,英雄精灵只需底部边缘。(如果英雄的其他部分需要与敌人或其他东西碰撞,则使用不与地板碰撞的单独物理体。) - rickster
2
在高层次上,这些语句设置和取消特定的位。在这种情况下,它们在英雄下落时打开对应于“步骤”节点的类别位,并在跳跃时关闭。x &= ~b010 简单地是 x = x & ~b010 的简写形式,而 x |= b010x = x | b010 的简写形式,其中 &、~ 和 | 分别是 and_、_notor 位运算符。请参阅 位运算符 以了解这些运算符的描述。 - 0x141E
1
不要使用 update 方法,将其放置在 didBeginContactdidEndContact 方法中,节省那些宝贵的循环。 - Knight0fDragon
1
此外,我不认为距离在接触检测中被使用,它只是一个简单的重叠检查。使用usesPreciseCollisionDetection可能会这样做,但我怀疑。我会假设它进行线检查并查看两个对象相交的位置,并在一个对象撞击相交点所需的时间内评估另一个对象是否也在那里。 - Knight0fDragon
1
这种方法不会导致游戏卡顿。此外,您可以在跳跃时使用额外的CPU周期向下滚动世界并计算未来障碍物的位置(例如Doodle Jump游戏)。 - 0x141E
显示剩余5条评论

2
最好的方法是使用2个类别,称之为CollisionPlatformNoCollisionPlatform
将您的SKSpriteNode称为Hero
将所有平台设置为NoCollisionPlatform
您的平台SKPhysicsBody应该是一条线,而不是一个盒子。创建这些物体时最好使用边缘循环,这样可以减少对物理世界的压力。 Hero只应在collisionBitMask中具有CollisionPlatform,但在contactTestBitMask中同时具有CollisionPlatformNoCollisionPlatformHero还应知道其先前的位置。
在您的didBeginContact方法中,当您与NoCollisionPlatform相撞时,请检查Hero的底部是否等于或低于平台,Hero的先前位置是否在平台上方。 如果成立,则在您的平台上,将平台的categoryBitMask更改为CollisionPlatform。
在您的didEndContact method中,将平台的categoryBitMask设置回NoCollisionPlatform 现在要小心如何做到这一点,因为如果您移动到位于同一x轴上的下一个平台,则可能由于舍入误差而掉落。如果您没有处理1/2像素位置,则建议四舍五入或转换为Int 要实现强制向下跳跃,只需记住Hero最后接触的平台,并在命令上将categoryBitMask设置回NoCollisionPlatform
此时,我建议子类化您的Hero,并在这个子类中保留指向他最后接触的平台以及他的先前位置的指针。确保在他不再接触它时将其清空为nil。
还要注意,以这种方式进行操作意味着您不需要对Hero施加任何物理力,除了重力外,因此SKAction将与此相辅相成。如果您不施加重力,则我不知道当您撞击平台时会发生什么,因为您的Hero的速度将为0,谁知道会发生什么样的推动反应(如果有)。但是,对于向左和向右(甚至向上),这种情况会发生,因此您可以将一小部分速度应用于您的精灵,以便系统知道您移动的方向,但足够弱,不会移动一个像素。
做到所有这些也应该处理侧面检测,因为你只需要关注你的平台上的1个像素,你的Hero在上一帧可能在它上方,在下一帧可能在它下方,以至于你期望你的Hero不会落在平台上的机会非常小(他需要在1帧内以大约1度的角度在好几个像素上下降才能实现这一点),我认为在这种情况下让那个人落地。

如果你想更进一步,可以在掩码中保留一点空间来考虑这个问题,并在需要时进行设置,这样你就可以有多个类别,只需保留一个来执行碰撞测试,而不必创建多个条目。我喜欢保留第30位来处理这种情况,第31位我保留用来检查精灵是活着还是死了。

请注意,这仅适用于1个Hero

如果您想要多个Hero,则必须另找其他方法。

一种方法是在各自的HerocollisionBitMask上进行交换。这种方法的问题是,如果您创建了一个楼梯状的平台,您可能会撞到第二个平台的侧面。

另一件事情是,使用我的高级方法,并为每个Hero保留categoryBitMask的某些位。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接