Sprite Kit customFieldWithEvaluationBlock - block not called - ios

I created a new SpriteKit iOS project and added this code to GameScene.m. The block to calculate the force field is never called, so node n1 never moves. Anyone know why? What am I missing?
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
// Create a red circle in the middle of scene
SKShapeNode *n1 = [SKShapeNode shapeNodeWithCircleOfRadius:20];
n1.fillColor = [UIColor redColor];
n1.position = CGPointMake(self.size.width / 2, self.size.height / 2);
n1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
n1.physicsBody.affectedByGravity = NO;
n1.physicsBody.dynamic = YES;
[self addChild:n1];
// Try to add a custom force field to the scene
SKFieldNode *f1 = [SKFieldNode customFieldWithEvaluationBlock:^vector_float3(vector_float3 position, vector_float3 velocity, float mass, float charge, NSTimeInterval deltaTime) {
printf("block called\n");
vector_float3 res = { 1.0, 0.0, 0.0 };
return res;
}];
f1.enabled = YES;
[self addChild:f1];
}

Related

How to add physicsBody to tiles generated by JSTileMap and change them into one physicsBody

I use JSTileMap to draw a level. I changed it a little bit to add SKPhysicsBody to every tile but sometimes when I apply some impulse to main character and he hits the wall/ground/ceiling he is acting weird. He reflects from surfaces with the way that is contrary to the principles of physics. I think that it happens because the player hits the point where two physics bodies (for example two physics bodies of the ground) connects.
SKPhysicsBody class provides a method to create one physics body from different physics bodies + (SKPhysicsBody *)bodyWithBodies:(NSArray *)bodies; Can I use this method to create one physics body from ale the tiles physics bodies?
Here's a method from JSTileMap where I add physics bodies:
+(id) layerWithTilesetInfo:(NSArray*)tilesets layerInfo:(TMXLayerInfo*)layerInfo mapInfo:(JSTileMap*)mapInfo
{
TMXLayer* layer = [TMXLayer node];
layer.map = mapInfo;
layer.tilesByColumnRow = [NSMutableDictionary dictionary];
// basic properties from layerInfo
layer.layerInfo = layerInfo;
layer.layerInfo.layer = layer;
layer.mapTileSize = mapInfo.tileSize;
layer.alpha = layerInfo.opacity;
layer.position = layerInfo.offset;
// recalc the offset if we are isometriic
if (mapInfo.orientation == OrientationStyle_Isometric)
{
layer.position = CGPointMake((layer.mapTileSize.width / 2.0) * (layer.position.x - layer.position.y),
(layer.mapTileSize.height / 2.0) * (-layer.position.x - layer.position.y));
}
NSMutableDictionary* layerNodes = [NSMutableDictionary dictionaryWithCapacity:tilesets.count];
//MY CODE
NSMutableArray *arrayOfSprites = [[NSMutableArray alloc] init];
SKNode *theSprite;
//----||----
// loop through the tiles
for (NSInteger col = 0; col < layerInfo.layerGridSize.width; col++)
{
for (NSInteger row = 0; row < layerInfo.layerGridSize.height; row++)
{
// get the gID
NSInteger gID = layerInfo.tiles[col + (NSInteger)(row * layerInfo.layerGridSize.width)];
// mask off the flip bits and remember their result.
bool flipX = (gID & kTileHorizontalFlag) != 0;
bool flipY = (gID & kTileVerticalFlag) != 0;
bool flipDiag = (gID & kTileDiagonalFlag) != 0;
gID = gID & kFlippedMask;
// skip 0 GIDs
if (!gID)
continue;
// get the tileset for the passed gID. This will allow us to support multiple tilesets!
TMXTilesetInfo* tilesetInfo = [mapInfo tilesetInfoForGid:gID];
[layer.tileInfo addObject:tilesetInfo];
if (tilesetInfo) // should never be nil?
{
SKTexture* texture = [tilesetInfo textureForGid:gID];
SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithTexture:texture];
sprite.name = [NSString stringWithFormat:#"%ld",(long)(col + row * layerInfo.layerGridSize.width)];
// make sure it's in the right position.
if (mapInfo.orientation == OrientationStyle_Isometric)
{
sprite.position = CGPointMake((layer.mapTileSize.width / 2.0) * (layerInfo.layerGridSize.width + col - row - 1),
(layer.mapTileSize.height / 2.0) * ((layerInfo.layerGridSize.height * 2 - col - row) - 2) );
}
else
{
sprite.position = CGPointMake(col * layer.mapTileSize.width + layer.mapTileSize.width/2.0,
(mapInfo.mapSize.height * (tilesetInfo.tileSize.height)) - ((row + 1) * layer.mapTileSize.height) + layer.mapTileSize.height/2.0);
}
// flip sprites if necessary
if(flipDiag)
{
if(flipX)
sprite.zRotation = -M_PI_2;
else if(flipY)
sprite.zRotation = M_PI_2;
}
else
{
if(flipY)
sprite.yScale *= -1;
if(flipX)
sprite.xScale *= -1;
}
// add sprite to correct node for this tileset
SKNode* layerNode = layerNodes[tilesetInfo.name];
if (!layerNode) {
layerNode = [[SKNode alloc] init];
layerNodes[tilesetInfo.name] = layerNode;
}
//adding physicsbody to every tile
//sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(sprite.frame.size.width, sprite.frame.size.height)];
//sprite.physicsBody.dynamic = NO;
//sprite.physicsBody.categoryBitMask = mapCategory;
//[arrayOfSprites addObject:sprite.physicsBody];
[layerNode addChild:sprite];
NSUInteger indexes[] = {col, row};
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2];
[layer.tilesByColumnRow setObject:sprite forKey:indexPath];
//MY CODE
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size];
sprite.physicsBody.dynamic = NO;
[arrayOfSprites addObject:sprite.physicsBody];
//-----||------
#ifdef DEBUG
// CGRect textRect = [texture textureRect];
// NSLog(#"atlasNum %2d (%2d,%2d), gid (%d,%d), rect (%f, %f, %f, %f) sprite.pos (%3.2f,%3.2f) flipx%2d flipy%2d flipDiag%2d", gID+1, row, col, [tilesetInfo rowFromGid:gID], [tilesetInfo colFromGid:gID], textRect.origin.x, textRect.origin.y, textRect.size.width, textRect.size.height, sprite.position.x, sprite.position.y, flipX, flipY, flipDiag);
#endif
}
}
}
//MY CODE
NSArray *array = [arrayOfSprites copy];
theSprite = [SKNode node];
theSprite.physicsBody = [SKPhysicsBody bodyWithBodies:array];
theSprite.physicsBody.dynamic = NO;
theSprite.position = CGPointMake(layer.position.x+16, layer.position.y+16);
[layer addChild:theSprite];
//-----||------
// add nodes for any tilesets that were used in this layer
for (SKNode* layerNode in layerNodes.allValues) {
if (layerNode.children.count > 0) {
[layer addChild:layerNode];
}
}
[layer calculateAccumulatedFrame];
return layer;
}
After adding physics bodies to every tile and adding those physics bodies to NSMutableArray I assign a copy of this NSMutableArray to the NSArray and try to create one physics body out of it like this:
//MY CODE
NSArray *array = [arrayOfSprites copy];
theSprite = [SKNode node];
theSprite.physicsBody = [SKPhysicsBody bodyWithBodies:array];
theSprite.physicsBody.dynamic = NO;
theSprite.position = CGPointMake(layer.position.x+16, layer.position.y+16);
[layer addChild:theSprite];
//-----||------
In result, one physics body with the height and width of one tile is added.
If you want to use [SKPhysicsBody bodyWithBodies:array], then you need to make sure that all the bodies in the array are relative to the parent node.
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size]; means that your physics body position is relative to the sprite node. You need the body relative to the parent node.
The only way I know how to do this is with center:
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size center:sprite.position];
This should place the SKPhysicsBody at the location of the sprite when it is added to the parent node.

How can i make a node collide with the sides of moving scene?

Hi i wish to know how can my nodes can collide with a moving scene. I have a main node that cans move along the x axis and cans collide with other nodes. I want to make the non main nodes collide with the sides of the moving scene. How can i make that ? ( the camera only follow the main node).
I have an obstacle class.
in my MLObstacle.m
-(id)init
{
if(self = [super init])
{
int aux = arc4random()%3;
switch (aux) {
case 0:
self = [MLObstaculo spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake((aux+1)*10,(aux+1)*13 )];
break;
case 1:
self = [MLObstaculo spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake((aux+1)*10,(aux+1)*15 )];
break;
case 2:
self = [MLObstaculo spriteNodeWithColor:[UIColor redColor] size:CGSizeMake((aux+1)*10,(aux+1)*14 )];
break;
}
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
self.physicsBody.affectedByGravity = NO;
self.physicsBody.categoryBitMask = obstacleCategory;
self.physicsBody.contactTestBitMask = visibleSidesCategory;
self.physicsBody.collisionBitMask = visibleSidesCategory;
[self setDx:0.0];
[self setDy:2.4];
}
return self;
}
So i add Obstacles to my WorldClass
In my WorlGen.m
-(void)GenerateWorld
{
SKSpriteNode *Ground = [SKSpriteNode spriteNodeWithColor:[UIColor greenColor] size:CGSizeMake(self.scene.frame.size.width,100)];
Ground.position = CGPointMake(0, -self.scene.frame.size.height/2 + Ground.size.height/2);
Ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:Ground.size];
Ground.physicsBody.dynamic = NO;
[self addChild:Ground];
for(int i = 0; i < 3; i++)
{
MLObstaculo * auxobs= [[MLObstaculo alloc]initObstaculo];
auxobs.position = CGPointMake((i+1)*50, Ground.position.y + Ground.size.height/2 +auxobs.size.height/2 );
[self.Obstaculos addObject:auxobs];
[self addChild:auxobs];
auxobs = NULL;
}
}
and then i add the world to my scene
Scene.m
-(id)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size]
{
WorldGen * world = [[WorldGen alloc] init];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsWorld.contactDelegate = self;
self.physicsBody.categoryBitMask = VisibleSidesCategory
self.physicsBody.collisionBitMask = ObstacleCategory;
self.anchorPoint = CGPointMake(0.5,0.5);
[self addChild:world];
[world GenerateWorld];
}
}
Update...
-(void)didSimulatePhysics
{
[self centerOnNode:hero];
}
-(void)centerOnNode:(SKNode *)node
{
CGPoint pointinScene = [self convertPoint:node.position fromNode:node.parent];
world.position = CGPointMake(world.position.x -pointinScene.x , world.position.y);
}
I initalized all the category variables with this format ( static const uint32_t ObstacleCategory = 0x1 <<1; )
First edit by defining your collisionBitMask
-(id)init
{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
self.physicsBody.categoryBitMask = ObstacleCategory;
self.physicsBody.contactTestBitMask = VisibleSidesCategory;
self.physicsBods.collisionBitMask = VisibleSidesCategory;
}
Second make this steps in Xcode -> File -> New -> File -> C and C++ -> Header File
Give a name of Physics.h and add those lines in it:
typedef enum : uint8_t {
ObstacleCategory = 1,
VisibleSidesCategory = 2,
} ColliderType;
and then import Physics.h into all your classes (You don't need to alloc or init for Physics.h, just use ObstacleCategory and VisibleSidesCategory)
From this and another question's problem, I have finally determined there is an error in the API. Specifically, a node's anchorPoint is considered (0,0) during initialization if using a WithRect: or FromRect: based initializer(but not a RectOfSize: or center: based one), so you get unexpected behavior.
Thus, even when the anchorPoint of the Scene and other objects are set to (0.5,0.5), a node places its lower left corner relative to the parent node's midpoint, rather than placing the child's midpoint relative to the parent node's midpoint.
Specifically, this can be solved in any one of these three ways:
Use an anchorPoint of (0,0).
Create your nodes without using Rect based initializers. You can use shapeNodeWithRectOfSize: instead of shapeNodeWithRect: and bodyWithEdgeLoopFromPath: instead of bodyWithEdgeLoopFromRect:
Set the Rect's initial position as lower and to the left, like so:
node.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(-node.size.width/2, -node.size.height/2, node.size.width, node.size.height)];

Moving a camera in SpriteKit

//UPDATE
The updated code has been added that works as I expected. See didSimulatePhysics method in the updated code below. In my case, I only care about moving a character left or right on the x axis where 0 on the x axis is the absolute left and right on the x axis is a configurable value. The Apple adventure game really helped a lot too.
//ORIGINAL POST BELOW
I'm working with Apple SpriteKit and I'm struggling to implement a camera as I would like it to behave. What I've done in the code is load a sprite character, two buttons, and a red box that is off to the right outside of the view at the start. What I'd like to be able to do is move the character with the buttons, and once the player reaches the middle or end of the screen, the camera will then re-adjust to uncover what couldn't be seen in the view. So moving to the right should eventually show the red box that is off outside of the view initially once the player gets there. However, with the code I'm using below, I'm unable to get the camera to follow and adjust the coordinates to the main character at all. I've looked at Apple's advanced scene processing doc as well as a few other stack overflow posts but can't seem to get it right. If anyone could offer some advice it would be appreciated.
#define cameraEdge 150
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
/* Setup your scene here */
//320 568
self.backgroundColor = [SKColor whiteColor];
myWorld = [[SKNode alloc] init];
[self addChild:myWorld];
mainCharacter = [SKSpriteNode spriteNodeWithImageNamed:#"0"];
mainCharacter.physicsBody.dynamic = YES;
mainCharacter.name = #"player";
mainCharacter.position = CGPointMake(20, 20);
CGRect totalScreenSize = CGRectMake(0, 0, 800, 320);
SKSpriteNode *box = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(60, 60)];
SKSpriteNode *boxTwo = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(60, 60)];
SKSpriteNode *boxThree = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(60, 60)];
boxThree.position = CGPointMake(40, 50);
[myWorld addChild:boxThree];
boxTwo.position = CGPointMake(1100, 50);
box.position = CGPointMake(650, 50);
[myWorld addChild:box];
[myWorld addChild:boxTwo];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:totalScreenSize];
self.physicsWorld.gravity = CGVectorMake(0, -5);
mainCharacter.name = #"mainCharacter";
mainCharacter.physicsBody.linearDamping = 0;
mainCharacter.physicsBody.friction = 0;
mainCharacter.physicsBody.restitution = 0;
mainCharacter.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:mainCharacter.size];
[myWorld addChild:mainCharacter];
[self addChild:[self buildLeftButton]];
[self addChild:[self buildRightButton]];
}
return self;
}
- (void)didSimulatePhysics
{
SKSpriteNode *hero = mainCharacter;
if(hero)
{
CGPoint heroPosition = hero.position;
CGPoint worldPosition = myWorld.position;
NSLog(#"%f", heroPosition.x);
CGFloat xCoordinate = worldPosition.x + heroPosition.x;
if(xCoordinate < cameraEdge && heroPosition.x > 0)
{
worldPosition.x = worldPosition.x - xCoordinate + cameraEdge;
self.worldMovedForUpdate = YES;
}
else if(xCoordinate > (self.frame.size.width - cameraEdge) && heroPosition.x < 2000)
{
worldPosition.x = worldPosition.x + (self.frame.size.width - xCoordinate) - cameraEdge;
self.worldMovedForUpdate = YES;
}
myWorld.position = worldPosition;
}
}
-(SKSpriteNode *)buildLeftButton
{
SKSpriteNode *leftButton = [SKSpriteNode spriteNodeWithImageNamed:#"left"];
leftButton.position = CGPointMake(20, 20);
leftButton.name = #"leftButton";
leftButton.zPosition = 1.0;
return leftButton;
}
-(SKSpriteNode *)buildRightButton
{
SKSpriteNode *leftButton = [SKSpriteNode spriteNodeWithImageNamed:#"right"];
leftButton.position = CGPointMake(60, 20);
leftButton.name = #"rightButton";
leftButton.zPosition = 1.0;
return leftButton;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if([node.name isEqualToString:#"leftButton"])
{
[mainCharacter.physicsBody applyImpulse:CGVectorMake(-120, 0)];
}
else if([node.name isEqualToString:#"rightButton"])
{
[mainCharacter.physicsBody applyImpulse:CGVectorMake(120, 10)];
}
}
If you want the view to always be centered on your player's position, modify your code with these points in mind:
1) Create a SKNode and call it myWorld, worldNode or any other name like that.
2) Add the worldNode [self addChild:worldNode];
3) Add all other nodes to the worldNode, including your player.
4) In the didSimulatePhysics method, add this code:
worldNode.position = CGPointMake(-(player.position.x-(self.size.width/2)), -(player.position.y-(self.size.height/2)));
Your view will now always be centered on your player's position.
Update May 2015:
If you are using a map created with Tiled Map Editor, you can use the free SKAToolKit framework. Features include player camera auto follow, test player, test HUD and sprite buttons.

Really need SKSpriteNode children to shoot correctly

So I have a shipNode (which is initially facing right) with two children called _laserCannon1(and 2) that are configured by the method below:
-(void)initializeLaserCannonLocations
{
CGSize size = CGSizeMake(4.0, 4.0);
_laserCannon1 = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:size];
_laserCannon2 = [SKSpriteNode spriteNodeWithColor:[SKColor yellowColor] size:size];
_laserCannon1.position = CGPointMake(self.size.width/2, self.size.height/2 + 15);
_laserCannon2.position = CGPointMake(self.size.width/2, self.size.height/2 - 15);
[self addChild:_laserCannon1];
[self addChild:_laserCannon2];
}
Then I am later "firing" lasers from them based off of the ship's zRotation as follows
-(void)fireLocalLaser
{
CGVector standardLaserVelocity = CGVectorMake(700*cosf(_myShip.zRotation - _myShip.laserCannon1.zRotation),
700*sinf(_myShip.zRotation - _myShip.laserCannon2.zRotation));
[self spawnLaserFrom:CGPointAdd(_myShip.laserCannon1.position, ship.position); withVelocity:standardLaserVelocity fromShip:_myShip];
[self spawnLaserFrom:CGPointAdd(_myShip.laserCannon2.position, ship.position); withVelocity:standardLaserVelocity fromShip:_myShip];
}
Now everything works fine when the ships is facing directly left or right, but for some odd reason as the ship begins to face more towards the top or bottom the shots are fired increasingly towards the middle of the ship, to the point that once the ship is facing vertically both shots are fired from the middle.position.x of the ship. Does it have to do with something from where I placed my _laserCannons?
I hope my math teacher doesn't see this!
Your problem is with the relative position of your 2 laser cannons as they rotate. I have included a picture so I don't have to go into a long winded description of what is happening.
I have included code for a kinda, sorta, duct tape solution on how you can do this if you really want to. It ain't pretty but it works. The code is good from 0 to 90 degrees so you should be able to work out the rest from there if you have your heart set on having 2 cannons. BUT I would strongly suggest your Mighty Space Empire invest in single cannon ships, as it will make your coding job a whole lot easier!
#implementation MyScene {
SKSpriteNode *ship1;
SKShapeNode *turret1;
SKShapeNode *turret2;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
[self createSpaceships];
}
return self;
}
-(void)createSpaceships {
ship1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(50, 50)];
ship1.position = CGPointMake(300, 150);
ship1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship1.size];
ship1.physicsBody.dynamic = NO;
[self addChild:ship1];
SKSpriteNode *frontOfShip = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(2, 50)];
frontOfShip.position = CGPointMake(25, 0);
[ship1 addChild:frontOfShip];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
// touch the left side of the screen to rotate the ship by +0.0785398 radians
// touch the right hand side of the screen to fire lasers
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
if(touchLocation.x < self.size.width/2) // left side of screen
{
SKAction *block0 = [SKAction runBlock:^{
ship1.zRotation = ship1.zRotation +0.0785398;
}];
[self runAction:block0];
} else // right side of screen
{
float turret1offsetX = 0;
float turret1offsetY = 0;
float turret2offsetX = 0;
float turret2offsetY = 0;
if((ship1.zRotation >0) && (ship1.zRotation <=0.785399))
{
turret1offsetX = (14/0.785398) * ship1.zRotation;
turret1offsetY = (23/0.785398) * ship1.zRotation;
turret2offsetX = (-20/0.785398) * ship1.zRotation;
turret2offsetY = (10/0.785398) * ship1.zRotation;
}
if((ship1.zRotation >0.785399) && (ship1.zRotation <=1.570797))
{
turret1offsetX = (14 - (9/0.785398)) * ship1.zRotation;
turret1offsetY = -4 + (27/0.785398) * ship1.zRotation;
turret2offsetX = (3 - (25/0.785398)) * ship1.zRotation;
turret2offsetY = 20 - (10/0.785398) * ship1.zRotation;
}
int x = ship1.position.x + 1000 * cos(ship1.zRotation);
int y = ship1.position.y + 1000 * sin(ship1.zRotation);
turret1 = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, (ship1.position.x+20)+turret1offsetX, (ship1.position.y-25)+turret1offsetY);
CGPathAddLineToPoint(pathToDraw, NULL, x, y);
turret1.path = pathToDraw;
[turret1 setStrokeColor:[UIColor redColor]];
[self addChild:turret1];
turret2 = [SKShapeNode node];
CGMutablePathRef pathToDraw2 = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw2, NULL, (ship1.position.x+20)+turret2offsetX, (ship1.position.y+25)+turret2offsetY);
CGPathAddLineToPoint(pathToDraw2, NULL, x, y);
turret2.path = pathToDraw2;
[turret2 setStrokeColor:[UIColor redColor]];
[self addChild:turret2];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[turret1 removeFromParent];
[turret2 removeFromParent];
}
#end
Yes as sangony put it, it really just has to do with your relative positioning of the _laserCannons from your shipNode. I would personally just start with a singular laserCannon for now, and get more comfortable with the math involved until you feel ready to move on to adding more.

Prevent player from falling through the ground - Sprite Kit

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
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 :(
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;
}
Just:
self.playerSprite.physicsBody.dynamic = NO;
should work.
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.
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?
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

Resources