I am making a Doodle Jump clone game in Swift and the issue is that when the player jumps, it hits its head on the bottom of the platform and doesn't pass through. How can I make the player pass over the platform and jump through them? I have my code here:
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()
}
}
One way to do this is to toggle the appropriate collision bit(s) on/off, if the hero is falling or jumping. The hero is falling if its velocity's dy
property is less than zero and jumping (or bouncing up) if dy
is greater than zero. Here's an example of how to do this:
// 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
}
}
The best way to go about this is to use 2 categories, let's call it CollisionPlatform
, and NoCollisionPlatform
.
Let's call your SKSpriteNode Hero
Set all platforms to NoCollisionPlatform
Your platform SKPhysicsBody
should be a line, not a box. It is better to use edge loops when creating these bodies, will put less strain on the physics world.
The Hero
should only have CollisionPlatform
in the collisionBitMask
, but both CollisionPlatform
and NoCollisionPlatform
in the contactTestBitMask
.
Your Hero
should also know its previous position.
On your didBeginContact
method, when you collide with NoCollisionPlatform
, you check if the bottom of the Hero
is equal to or below the platform and the previous position of the Hero
is above the platform. If this is true, then on your platform, change the categoryBitMask
of the platform to CollisionPlatform
On your didEndContact method
set the categoryBitMask
of the platform back to NoCollisionPlatform
Now, be careful with how you do it, because if you move to the next platform that lies on the same x axis, you may fall through due to rounding errors. If you are not dealing with 1/2 pixel positions, I recommend rounding or casting to Int
To achieve a forced down jump, simply remember the last platform that the Hero
was touching, and on the command, set the categoryBitMask
back to NoCollisionPlatform
.
At this point I would recommend sub classing your Hero
, and keeping a pointer to the platform he last touched inside this sub class along with his previous position. Be sure to clear it to nil when he no longer touches it.
Also note, that doing it this way means you do not need to apply any physics forces to your Hero
other than gravity, so SKAction's will work hand in hand with this. If you do not apply gravity, then I do not know what happens when you hit the platform, since the velocity of your Hero
will be 0, who knows what kind of push reaction (if any) will happen. With going left and right (even up), this will happen though, so what you can do is apply a small amount of velocity to your sprite so that the system knows the direction you are moving, but is weak enough that it won't move a pixel.
Doing all of this should also handle the side detection, since you only have 1 pixel to worry about for your platform, the chances of your Hero
being above it the previous frame, and below it the next frame in such a way that you expect your Hero
to not land on the platform is really slim (He would need to drop at an angle of like 1 degree over a good length of pixels in 1 frame to achieve this, I say let the guy land in this case.
If you want to get even more advanced, reserve a bit on the mask to account for this, and just set when needed, this way you can have multiple categories, with only 1 reserved to do the collision test, instead of having to make multiple entries. I like reserving bit 30 to handle this case, bit 31 I reserve to check if the sprite is alive or dead.
Do note that this will only work with 1 Hero
If you want to have multiple Heroes
, then you have to work another way.
One is you do the swapping on the individual Hero
's collisionBitMask
instead. The problem with this approach is if you create a staircase of platforms, you may end up colliding into the side of the 2nd platform.
The other thing you can do, is use my advance method, and reserve certain bits of the categoryBitMask
for each Hero
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With