Prevent player from falling through the ground - Sprite Kit

匿名 (未验证) 提交于 2019-12-03 08:59:04

问题:

I've been trying to work on a simple Sprite Kit game that involves dodging red balls. I'm using the built-in gravity mechanism, but I'm having trouble preventing the player from falling through the ground. I've looked up a solution (set ground.physicsBody.dynamic = NO), but the player still falls through. What exactly do I need to do?

Edit: The green and brown texture is the ground. Right now the player is set to not being dynamic, so it is 'flying'

Here is my code in the MyScene.m file:

// //  MyScene.m //  DodgeMan // //  Created by Cormac Chester on 3/8/14. //  Copyright (c) 2014 Testman Industries. All rights reserved. //  #import "MyScene.h" #import "EndGameScene.h"  static const uint32_t redBallCategory =  0x1 << 0; static const uint32_t playerCategory =  0x1 << 1;  @implementation MyScene  -(id)initWithSize:(CGSize)size {     if (self = [super initWithSize:size])     {         /* Setup your scene here */          //Sets player location         playerLocX = 50;         playerLocY = 100;          //Sets player score         score = 0;          //Set Background         self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];          //Set Ground         SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:@"ground"];         ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);         ground.xScale = 0.5;         ground.yScale = 0.5;         ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];         ground.physicsBody.dynamic = NO;          //Player         self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:@"character"];         self.playerSprite.position = CGPointMake(playerLocX, playerLocY);          //Set Player Physics         self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];         self.playerSprite.physicsBody.dynamic = YES;         self.playerSprite.physicsBody.categoryBitMask = playerCategory;         self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;         self.playerSprite.physicsBody.collisionBitMask = 0;         self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;          //Score Label         self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial-BoldMT"];         self.scoreLabel.text = @"0";         self.scoreLabel.fontSize = 40;         self.scoreLabel.fontColor = [SKColor blackColor];         self.scoreLabel.position = CGPointMake(50, 260);          //Pause Button         self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:@"pauseButton"];         self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);         self.pauseButton.name = @"pauseButton";          //Add nodes         [self addChild:ground];         [self addChild:self.playerSprite];         [self addChild:self.scoreLabel];         //[self addChild:self.pauseButton];          //Sets gravity         self.physicsWorld.gravity = CGVectorMake(0,-2);         self.physicsWorld.contactDelegate = self;      }     return self; }  -(void)addBall {     SKSpriteNode *redBall = [SKSpriteNode spriteNodeWithImageNamed:@"locationIndicator"];     int minY = redBall.size.height / 2;     int maxY = self.frame.size.height - redBall.size.height / 2;     int rangeY = maxY - minY;     int actualY = (arc4random() % rangeY) + minY;     NSLog(@"Actual Y: %i", actualY);      //Initiates red ball offscreen     if (actualY >= 75)     {         //Prevents balls from spawning in the ground         redBall.position = CGPointMake(self.frame.size.width + redBall.size.width/2, actualY);         [self addChild:redBall];     }     redBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redBall.size.width/2];     redBall.physicsBody.dynamic = YES;     redBall.physicsBody.categoryBitMask = redBallCategory;     redBall.physicsBody.contactTestBitMask = playerCategory;     redBall.physicsBody.collisionBitMask = 0;     redBall.physicsBody.affectedByGravity = NO;     redBall.physicsBody.usesPreciseCollisionDetection = YES;      //Determine speed of red ball     int minDuration = 3.0;     int maxDuration = 5.0;     int rangeDuration = maxDuration - minDuration;     int actualDuration = (arc4random() % rangeDuration) + minDuration;      // Create the actions     SKAction *actionMove = [SKAction moveTo:CGPointMake(-redBall.size.width/2, actualY) duration:actualDuration];     SKAction *actionMoveDone = [SKAction removeFromParent];     SKAction *ballCross = [SKAction runBlock:^{         score++;         self.scoreString = [NSString stringWithFormat:@"%i", score];         self.scoreLabel.text = self.scoreString;         NSLog(@"Score was incremented. Score is now %d", score);     }];     [redBall runAction:[SKAction sequence:@[actionMove, ballCross, actionMoveDone]]]; }  - (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {     self.lastSpawnTimeInterval += timeSinceLast;     if (self.lastSpawnTimeInterval > 0.5) {         self.lastSpawnTimeInterval = 0;         [self addBall];     } }  -(void)update:(CFTimeInterval)currentTime {     /* Called before each frame is rendered */     // Handle time delta.     //Prevents bad stuff happening     CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;     self.lastUpdateTimeInterval = currentTime;     if (timeSinceLast > 1) { // more than a second since last update         timeSinceLast = 1.0 / 120.0;         self.lastUpdateTimeInterval = currentTime;     }      [self updateWithTimeSinceLastUpdate:timeSinceLast]; }  NSDate *startTime;  -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {     /* Called when a touch begins */     [super touchesBegan:touches withEvent:event];      //Starts Timer     startTime = [NSDate date];      UITouch *touch = [touches anyObject];     CGPoint location = [touch locationInNode:self];     SKNode *node = [self nodeAtPoint:location];      //Pauses Scene     if ([node.name isEqualToString:@"pauseButton"])     {         NSLog(@"Pause button pressed");     }  }  -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {     /* Called when a touch ends */     [super touchesEnded:touches withEvent:event];      NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow];     NSString *elapsedTimeString = [NSString stringWithFormat:@"Elapsed time: %f", elapsedTime];     NSLog(@"%@", elapsedTimeString);       for (UITouch *touch in touches)     {         //Gets location of touch         CGPoint location = [touch locationInNode:self];         NSLog(@"Touch Location X: %f \n Touch Location Y: %f", location.x, location.y);          //Prevents destination from being in the ground         if (location.y < 88)         {             location.y = 87.5;         }          //Moves and animates player         //int velocity = elapsedTime * -3000;         int velocity = 800.0/1.0;         NSLog(@"Velocity: %i", velocity);         float realMoveDuration = self.size.width / velocity;         SKAction *actionMove = [SKAction moveTo:location duration:realMoveDuration];         [self.playerSprite runAction:[SKAction sequence:@[actionMove]]];     }      NSLog(@"Touch ended"); }  //Collision between ball and player - (void)redBall:(SKSpriteNode *)redBall didCollideWithPlayer:(SKSpriteNode *)playerSprite {     NSLog(@"Player died");     [redBall removeFromParent];     [playerSprite removeFromParent];      SKTransition *reveal = [SKTransition crossFadeWithDuration:0.5];     SKScene *endGameScene = [[EndGameScene alloc] initWithSize:self.size gameEnded:YES];     [self.view presentScene:endGameScene transition: reveal]; }  - (void)didBeginContact:(SKPhysicsContact *)contact {     SKPhysicsBody *firstBody, *secondBody;      if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)     {         firstBody = contact.bodyA;         secondBody = contact.bodyB;     }     else     {         firstBody = contact.bodyB;         secondBody = contact.bodyA;     }      //Red ball collides with the player     if ((firstBody.categoryBitMask & redBallCategory) != 0 && (secondBody.categoryBitMask & playerCategory) != 0)     {         [self redBall:(SKSpriteNode *) firstBody.node didCollideWithPlayer:(SKSpriteNode *) secondBody.node];     } }  @end 

回答1:

you definitely can't set its dynamic to no brother. you need gravity effect that player. (he is not effected by physic world so he is flying right now. we need him to fall down to ground aren't we? :)

So here is the simple solution. idea is that you create a "invisible rectangle block" on the ground surface that has physic body. and you need to set its dynamic to no in order to prevent it falling down

so this block is a node obviously, and its size : as high as the ground , and as wide as the screen. and you need to adjust the position a little bit to put its upper bound right on the ground surface.

good luck

i actually drew a picture but i can't post it here because of my reputation :(



回答2:

Your problem is with physicsBody's categoryBitMask and collisionTestBitMask. Your bitwise declarations :

static const uint32_t redBallCategory =  0x1 << 0; static const uint32_t playerCategory =  0x1 << 1;  

This has actually set the following bit patterns (i've shortened to 8 bits for the example) : redBallCategory - 00000001 and playerCategory - 00000010

However in the following code, you tell the player to only collide with collision bit mask - 00000000; self.playerSprite.physicsBody.collisionBitMask = 0;

So your first problem is here. The player will not collide with any category that you have defined.

Your second problem is you have not given the ground a categoryBitMask, or collisionBitMask. By default this means all bits are set, IE the ground's collisionBitMask is equal to 11111111;

There will be no collision between these two physics bodies.

Try this - i have simply added a third physics category, and edited your code slightly to set the ground categoryBitMask / collisionBitMask, and your player collisionBitMask.

static const uint32_t redBallCategory =  0x1 << 0; static const uint32_t playerCategory =  0x1 << 1; static const uint32_t groundCategory = 0x1 << 2;  @implementation MyScene  -(id)initWithSize:(CGSize)size { if (self = [super initWithSize:size]) {     /* Setup your scene here */      //Sets player location     playerLocX = 50;     playerLocY = 100;      //Sets player score     score = 0;      //Set Background     self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];      //Set Ground     SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:@"ground"];     ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);     ground.xScale = 0.5;     ground.yScale = 0.5;     ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];     ground.physicsBody.categoryBitMask=groundCategory;     ground.physicsBody.collisionBitMask=playerCategory|redBallCategory;     ground.physicsBody.dynamic = NO;      //Player     self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:@"character"];     self.playerSprite.position = CGPointMake(playerLocX, playerLocY);      //Set Player Physics     self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];     self.playerSprite.physicsBody.dynamic = YES;     self.playerSprite.physicsBody.categoryBitMask = playerCategory;     self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;     self.playerSprite.physicsBody.collisionBitMask = groundCategory|redBallCategory;     self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;      //Score Label     self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial-BoldMT"];     self.scoreLabel.text = @"0";     self.scoreLabel.fontSize = 40;     self.scoreLabel.fontColor = [SKColor blackColor];     self.scoreLabel.position = CGPointMake(50, 260);      //Pause Button     self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:@"pauseButton"];     self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);     self.pauseButton.name = @"pauseButton";      //Add nodes     [self addChild:ground];     [self addChild:self.playerSprite];     [self addChild:self.scoreLabel];     //[self addChild:self.pauseButton];      //Sets gravity     self.physicsWorld.gravity = CGVectorMake(0,-2);     self.physicsWorld.contactDelegate = self;  } return self; 

}



回答3:

Just:

self.playerSprite.physicsBody.dynamic = NO; 

should work.



回答4:

Your problem occurs due to scaling. For some reason in sprite kit scaling an image doesn't change it's size when used in following code. Judging by your pictures, your physics body rectangle for your ground is actually twice as big as you think and already engulfing the player, which is why there would by no collision detection. This is from recent experience with a very similar style game.



回答5:

Do you have an edge loop physcis body around the scene? Collision flags and category flags set correctly so the player collides with the ground?



回答6:

i had same problem and i soved it simply...

On the update method i've putted an if statement:

if(player.position.y<your_closest_value_near_ground){  player.position.y == your_Closest_value_near_ground  } 

The comparition differs by the anchor point you have.. hope it helps someone



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!