Sprite doesn't move upon collision - ios

When I detect collision with a sprite, I want it (fuelSprite) to move to a new randomly generated position.
I generate the sprite like this
//setup fuel
SKSpriteNode *fuel = [SKSpriteNode spriteNodeWithImageNamed:#"fuel.png"];
fuel.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
[fuel setScale:0.6];
fuel.zPosition = 1;
fuel.shadowCastBitMask = 1;
fuel.name = #"fuelNode";
fuel.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fuel.frame.size];
fuel.physicsBody.dynamic = FALSE;
fuel.physicsBody.affectedByGravity = false;
fuel.physicsBody.usesPreciseCollisionDetection = YES;
fuel.physicsBody.categoryBitMask = fuelCategory;
fuel.physicsBody.collisionBitMask = fuelCategory | fireCategory;
fuel.physicsBody.contactTestBitMask = fireCategory;
[self addChild:fuel];
[_items addObject:#"fuelNode"];
Then when collision is detected I want to grab the sprite (only thing in the array) and move it
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"contact detected");
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
NSLog(#"Hit");
score ++;
SKSpriteNode *object = [_items firstObject];
object.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
}
This is in my header file for the fuelSprite and items array
#property (nonatomic, strong) SKSpriteNode *fuelSprite;
#property (nonatomic) NSMutableArray *items;
For some reason I can't get the sprite to move to a new random location :(
The hit detection is working flawless and the NSlog shows that

You are adding a string to the array not a sprite. Try replacing
[_items addObject:#"fuelNode"];
with
[_items addObject:fuel];
Also, since this statement
SKSpriteNode *object = [_items firstObject];
returns an NSString not an SKSpriteNode, using object as a sprite should have caused an exception. I suspect that object is nil because your array wasn't properly allocated.
You can access the sprite in the scene with
SKSpriteNode *object = [self childNodeWithName:#"fuelNode"];
instead of adding it to an array.

Haven't done SpriteKit for a while but instead of setting the position.
Try replacing:
object.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
with:
CGPoint randomPoint = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
CGFloat duration = 2;
SKAction *move = [SKAction moveTo:randomPoint duration:duration];
[ojbect runAction:run];
Hopefully this works for you. :)

The reason is apple prevents you from messing with a node attached to a physicsBody.
You have two options:
A
SKPhysicsBody* tmp = object.physicsBody;
object.physicsBody = nil;
object.position = CGPointMake(arc4random_uniform(self.frame.size.width), arc4random_uniform(self.frame.size.height));
object.physicsBody = tmp;
Why is this bad? Because this way you detach your physics object from your actual node and they may be in two different places at the same time - you don't want that but in some cases it doesnt matter - then you can use this.
B
Create a new physics body at the position you need it to be and attach it back to your node

Related

Accessing individual objects that all have the same categoryBitMask

I have several game world objects the player needs to interact with individually upon his physicsBody.categoryBitMask contacting them. Instead of using separate categoryBitMasks for each individual object (object count surpasses categoryBitMask's limit, which is 32) I just use 1 categoryBitMask and gave all the objects individual names. Here's how it looks in code:
-(void)createCollisionAreas
{
if (_tileMap)
{
TMXObjectGroup *group = [_tileMap groupNamed:#"ContactZone"]; //Layer's name.
//Province gateway.
NSDictionary *singularObject = [group objectNamed:#"msgDifferentProvince"];
if (singularObject)
{
CGFloat x = [singularObject[#"x"] floatValue];
CGFloat y = [singularObject[#"y"] floatValue];
CGFloat w = [singularObject[#"width"] floatValue];
CGFloat h = [singularObject[#"height"] floatValue];
SKSpriteNode *object = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(w, h)];
object.name = #"provinceGateway";
object.position = CGPointMake(x + w/2, y + h/2);
object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(w, h)];
object.physicsBody.categoryBitMask = terrainCategory;
object.physicsBody.contactTestBitMask = playerCategory;
object.physicsBody.collisionBitMask = 0;
object.physicsBody.dynamic = NO;
object.physicsBody.friction = 0;
object.hidden = YES;
[_backgroundLayer addChild:object];
}
/*More code written below. Too lazy to copy & paste it all*/
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody; //Create 2 placeholder reference's for the contacting objects.
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) //If bodyA has smallest of 2 bits...
{
firstBody = contact.bodyA; //...it is then the firstBody reference [Smallest of two (category) bits.].
secondBody = contact.bodyB; //...and bodyB is then secondBody reference [Largest of two bits.].
}
else //This is the reverse of the above code (just in case so we always know what's what).
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
/**BOUNDARY contacts*/
if ((firstBody.categoryBitMask == noGoCategory) && (secondBody.categoryBitMask == playerCategory))
{
//Boundary contacted by player.
if ([_backgroundLayer childNodeWithName:#"bounds"])
{
NSLog(#"Player contacted map bounds.");
}
if ([_backgroundLayer childNodeWithName:#"nogo"])
{
NSLog(#"Player can't go further.");
}
if ([_backgroundLayer childNodeWithName:#"provinceGateway"])
{
NSLog(#"Another province is ahead. Can't go any further.");
}
if ([_backgroundLayer childNodeWithName:#"slope"])
{
NSLog(#"Player contacted a slope.");
}
}
The problem is, in didBeginContact method, when the player contacts any object all the code gets executed. Even the code from objects the player hasn't contacted yet. This means the IF statements, such as if ([_backgroundLayer childNodeWithName:#"slope"]), are not complete. Can someone tell me how to properly write out the IF statements for individual object contact? The following IF statement, inside didBeginContact, doesn't work either:
if ([_player intersectsNode:[_backgroundLayer childNodeWithName:#"slope"]])
Your if statements are all just checking if the child exists, which it seems they all do. What I think you want to check is if the collision node has the name you are looking for, so change:
if ([_backgroundLayer childNodeWithName:#"bounds"])
to:
if ([firstBody.node.name isEqualToString:#"bounds"])

creating physics body for a SKSpriteNode in order to detect contact

as the title suggest, i have issue with creating contact between my enemies sprite and the hero laser(bullet). i create my enemies trough the following method and am adding them to the view.
-(void)Enemies{
//not always come
int GoOrNot = [self getRandomNumberBetween:0 to:1];
if(GoOrNot == 1){
SKSpriteNode *enemy;
int randomEnemy = [self getRandomNumberBetween:0 to:1];
if(randomEnemy == 0)
enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy_spaceship.png"];
else
enemy = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship15.png"];
enemy.scale = 0.4;
enemy.position = CGPointMake(450, 0);
enemy.hidden = NO;
CGPoint location = CGPointMake(450, 320);
SKAction *moveAction = [SKAction moveTo:location duration:2.5];
SKAction *doneAction = [SKAction runBlock:(dispatch_block_t)^() {
//NSLog(#"Animation Completed");
enemy.hidden = YES;
}];
SKAction *moveAsteroidActionWithDone = [SKAction sequence:#[moveAction,doneAction ]];
[enemy runAction:moveAsteroidActionWithDone withKey:#"asteroidMoving"];
[self addChild:enemy];
_enemyBullets = [[NSMutableArray alloc] initWithCapacity:kNumEnemyBullet];
for (int i = 0; i < kNumEnemyBullet; ++i) {
SKSpriteNode *enemyBullets = [SKSpriteNode spriteNodeWithImageNamed:#"rocket"];
enemyBullets.hidden = YES;
[enemyBullets setXScale:0.5];
[enemyBullets setYScale:0.5];
[_enemyBullets addObject:enemyBullets];
[enemy addChild:enemyBullets];
}
}
}
then i add the bullets through a mutable array and then adding my bullets to the enemies sprites as their child. that part works. i can create contact between hero and the enemy bullet and it gets detected. what i have issue with is that my hero's laser does not create contact with the enemy thus i can't make the enemy take hit from the laser. i tries adding the physics body to the method i am using but throws all the other sprites into hell and they don't respond properly anymore.
the following code is my collision code which i am using in update method:
for (SKSpriteNode *enemyBullets in _enemyBullets) {
if (enemyBullets.hidden) {
continue;
}
if ([_ship intersectsNode:enemyBullets]) {
enemyBullets.hidden = YES;
SKAction *blink = [SKAction sequence:#[[SKAction fadeOutWithDuration:0.1],
[SKAction fadeInWithDuration:0.1]]];
SKAction *blinkForTime = [SKAction repeatAction:blink count:4];
SKAction *shipExplosionSound = [SKAction playSoundFileNamed:#"explosion_large.caf" waitForCompletion:NO];
[_ship runAction:[SKAction sequence:#[shipExplosionSound,blinkForTime]]];
_lives--;
_livesLabel.text = [NSString stringWithFormat:#"%d Lives", _lives];
NSLog(#"your ship has been hit!");
}
}
is there any way that i can use to create a physics body for my enemies so i can create the contact between the hero laser and the enemies sprite with the same structure i have currently ? any help is amazingly appreciated.
You most certainly can create a physics body for your enemies. Right after this code enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy_spaceship.png"]; you can add a physics body like this:
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
enemy.physicsBody.categoryBitMask = CategoryEnemy; // or whatever you need
enemy.physicsBody.collisionBitMask = CategoryRock; // or whatever you need
enemy.physicsBody.contactTestBitMask = CategoryBullet // or whatever you need
enemy.physicsBody.dynamic = NO; // or YES
enemy.physicsBody.allowsRotation = NO; // or YES
enemy.physicsBody.restitution = 0; // or another value between 0 to 1
enemy.physicsBody.friction = 0.0; // or another value between 0 to 1
There are many ways to create a physics body. You can do a rectangle shape, circle shape, outline a texture or draw your own.
You should read the Apple docs on this subject to get a better understanding of what you can do and what properties are available.

Game increases node count drastically and freezes

I have created two nodes, a projectile node and a target node, each with their respective categories and when they both contact each other, both are removed and depending on the projectile, even more projectiles appear.
The projectiles also are removed when they touch the edge of the screen.
Everything works fine until after a "wave" of targets (I created a method to occasionally spawn 5 targets in a set design) appear and scroll across the screen and projectiles are all over the place contacting more and more targets, now I would understand the node count increasing, but the amount is no where near the amount, the game then lags to the point of the FPS being 2 FPS and the game eventually freezing and the node count stuck to 700.
didBeginContact Method
static inline Enemies *nodeFromBody(SKPhysicsBody *body1, SKPhysicsBody *body2, uint32_t category) {
Enemies *node = nil;
if (body1.categoryBitMask & category) {
node = (Enemies *)body1.node;
}
else if (body2.categoryBitMask & category) {
node = (Enemies *)body2.node;
}
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody, *secondBody;
SKSpriteNode *projectile = nil;
SKSpriteNode *offScreen = nil;
Enemies *target = nil;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
projectile = nodeFromBody(firstBody, secondBody, projectileCategory);
offScreen = nodeFromBody(firstBody, secondBody, offScreenCategory);
target = nodeFromBody(firstBody, secondBody, targetsCategory);
if (projectile && target) {
if ([projectile.name isEqualToString:#"firstShot"]) {
NSLog(#"firstShot");
[projectile removeAllActions];
[projectile removeFromParent];
[ProjectilePatterns pattern1:self spawnPoint:target.position];
[target removeAllActions];
[target removeFromParent];
[Enemies wave1:self spawnPoint:CGPointMake(CGRectGetMidX(self.frame) - 525, CGRectGetMidY(self.frame) - 50)];
}
}
enemyClassMethod
// These are the regular targets, the other method that spawns the wave is essentially creating new enemies and returns them positioned in certain positions.
+(Enemies *)wave1:(SKScene *)scene spawnPoint:(CGPoint)location {
SKAction *delay = [SKAction waitForDuration:5];
SKAction *remove = [SKAction removeFromParent];
SKAction *delayAndRemove = [SKAction sequence:#[delay, remove]];
Enemies *enemy1 = [[Enemies alloc] init];
enemy1 = [Enemies spriteNodeWithColor:[SKColor blackColor] size:CGSizeMake(30, 30)];
enemy1.position = location;
enemy1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy1.size];
enemy1.physicsBody.categoryBitMask = targetsCategory;
enemy1.physicsBody.contactTestBitMask = projectileCategory;
enemy1.physicsBody.collisionBitMask = 0;
[scene addChild:enemy1];
[enemy1 runAction:delayAndRemove];
[enemy1.physicsBody applyImpulse:CGVectorMake(12, 0)];
return enemy1;
}
Projectile Pattern
+(ProjectilePatterns *)pattern1:(SKScene *)scene spawnPoint:(CGPoint)location {
SKAction *rotate = [SKAction rotateByAngle:1 duration:0.1];
SKAction *rotateForever = [SKAction repeatActionForever:rotate];
ProjectilePatterns *patternBomb = [[ProjectilePatterns alloc] init];
patternBomb = [ProjectilePatterns spriteNodeWithImageNamed:#"contactBomb"];
patternBomb.position = CGPointMake(location.x + 10, location.y);
patternBomb.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:patternBomb.size];
patternBomb.physicsBody.categoryBitMask = projectileCategory;
patternBomb.physicsBody.contactTestBitMask = targetsCategory | offScreenCategory;
patternBomb.physicsBody.collisionBitMask = 0;
patternBomb.physicsBody.friction = 0;
patternBomb.physicsBody.linearDamping = 0;
patternBomb.name = #"p1";
ProjectilePatterns *patternBomb2 = [[ProjectilePatterns alloc] init];
patternBomb2 = [ProjectilePatterns spriteNodeWithImageNamed:#"contactBomb"];
patternBomb2.position = CGPointMake(location.x - 10, location.y);
patternBomb2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:patternBomb2.size];
patternBomb2.physicsBody.categoryBitMask = projectileCategory;
patternBomb2.physicsBody.contactTestBitMask = targetsCategory | offScreenCategory;
patternBomb2.physicsBody.collisionBitMask = 0;
patternBomb2.physicsBody.friction = 0;
patternBomb2.physicsBody.linearDamping = 0;
patternBomb2.name = #"p1";
[scene addChild:patternBomb];
[patternBomb runAction:rotateForever];
[patternBomb.physicsBody applyImpulse:CGVectorMake(5, 0)];
[scene addChild:patternBomb2];
[patternBomb2 runAction:rotateForever];
[patternBomb2.physicsBody applyImpulse:CGVectorMake(-5, 0)];
return patternBomb;
return patternBomb2;
}
I should also note that the scene's physicsWorld gravity is (0,0). I did this so the impulses movement would be constant.

How to do impulse physics

Okay guys, I have two sprites one the enemy who moves to pick up items around the map as the items appear and it does that using SKActions then also I have the wall which he shouldn't go through. anyway. all I am trying to do is to make him bounce out if he hits the wall. How would I go about that?
Here is the code below:
// for the Enemy wall I have this code:
- (void) EnemyBelongings {
EnemyWall = [SKSpriteNode spriteNodeWithImageNamed:#"EnemyWall#2x"];
EnemyWall.name = #"hisWall";
EnemyWall.position = CGPointMake(512, 260);
EnemyWall.xScale = 0.09;
EnemyWall.yScale = 0.09;
EnemyWall.physicsBody.categoryBitMask = PhysicsCategoryEnemyWall;
EnemyWall.physicsBody.contactTestBitMask = PhysicsCategoryEnemy;
EnemyWall.physicsBody.collisionBitMask = 0;
[self addChild:EnemyWall];
}
// For the enemy character I have this code
- (void) Enemy {
_Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
_Enemy.position = CGPointMake(520, _Enemy.size.height/1.50);
_Enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width];
_Enemy.physicsBody.usesPreciseCollisionDetection = YES;
_Enemy.physicsBody.categoryBitMask = PhysicsCategoryEnemy;
_Enemy.physicsBody.contactTestBitMask = PhysicsCategoryEnemyWall;
_Enemy.physicsBody.collisionBitMask = 0;
[self addChild:_Enemy];
}
// For the enemy movement I have this code
- (void) EnemySpawner {
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
[_Enemy runAction:wait];
SKAction *actionXMove2 = [SKAction moveToX:ForEnemy.position.x duration:0.14];
SKAction *actionYMove2 = [SKAction moveToY:ForEnemy.position.y duration:0.14];
[_Enemy runAction:actionYMove2];
[_Enemy runAction:actionXMove2];
}];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if (contact.bodyA.categoryBitMask == PhysicsCategoryEnemy && contact.bodyB.categoryBitMask
== PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyA.node; // a is the enemy here
// Code here
SKNode *wall = contact.bodyB.node; // b is the enemy's wall here
// Code here
}
else if (contact.bodyB.categoryBitMask == PhysicsCategoryEnemy &&
contact.bodyA.categoryBitMask == PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyB.node;
// Code here
SKNode *wall = contact.bodyA.node;
// Code here
}
}
You need to applyImpulse on the node's physicsBody to make it simulate naturally and interact with other physicsBodies.
Please look at my answer here on how to do the same.
Copy the rwAdd, rwSub, rwMult, rwLength and the rwNormalize methods from this tutorial by Ray Wenderlich.
Then, try using this code:
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
CGPoint offset = rwSub(location, ForEnemy.position);
CGPoint direction = rwNormalize(offset);
float forceValue = 200; //Edit this value to get the desired force.
CGPoint shootAmount = rwMult(direction, forceValue);
CGVector impulseVector = CGVectorMake(shootAmount.x, shootAmount.y);
[_Enemy.physicsBody applyImpulse:impulseVector];
}];
PhysicsWorld should simulate bounces automatically. Just set your nodes' physics properties accordingly. Look for the 'restitution' property. It will determine the bounciness of your objects.
And don't use SKAction to move your enemies. This will just "teleport" your nodes around. They don't get to have any real velocity in the physicsWorld (and hence don't bounce). Instead, apply forces to your bodies (or set velocity vector manually).

sprite kit collision detection with child sprite

I'm trying to detect collisions between two sprites but I'm unable to do this with a child sprite.
self.player = [[Player alloc] initWithImageNamed:#"player"];
self.player.position = CGPointMake(150, 75);
[self addChild:self.player];
_object = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
_object.position = CGPointMake(-40, 27);
[self.player addChild:_object];
then I have collision detetion like this
- (void)checkCollisions {
[self.map enumerateChildNodesWithName:#"enemy"
usingBlock:^(SKNode *node, BOOL *stop){SKSpriteNode *enemy = (SKSpriteNode *)node;
if (CGRectIntersectsRect(enemy.frame, _object.frame)) {
[enemy removeFromParent];
}
}]; }
*This does not work!!! but if I use :
CGRectIntersectsRect(enemy.frame, self.player.frame)
I can detect a collision with the main body. how do I do collision detection for a child of another sprite?
The child node's position and frame property are relative to it's parent, which you need to account for in your code.
SKNode has two methods that can help you with converting the position of a given SKNode to/from the coordinate spaces of another node:
convertPoint:fromNode:
convertPoint:toNode:
You can use those to convert a SKNode's position property to another coordinate space. What you want to do is convert the _object's position to the coordinate space of the enemy or visa versa and then use a temporary CGRect with that new position for your CGRectIntersectsRect check.
Here's an example :
CGPoint globalPosition = [self.player convertPoint:_object.position toNode:self.player.parent];
CGRect tempRect = CGRectMake(globalPosition.x, globalPosition.y, _object.frame.size.width, _object.frame.size.height);
if (CGRectIntersectsRect(enemy.frame, _object.frame))
{
// collision occurred
}
This code is assuming that your enemy and your player are in the same coordinate space. (have the same parent)
This happens because _object is a child node for self.player, and enemy node is a child node for self.map node. Both nodes should have same parent if you want to compare their frames.
I suggest you to use Sprite Kit's built-in collision handling mechanism:
// Object:
_object = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
_object.position = CGPointMake(-40, 27);
_object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_object.frame.size;
_object.physicsBody.categoryBitMask = OBJECT_CATEGORY;
_object.physicsBody.contactTestBitMask = ENEMY_CATEGORY;
_object.physicsBody.collisionBitMask = 0;
[self.player addChild:_object];
// Enemy:
// init enemy: SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.frame.size;
enemy.physicsBody.categoryBitMask = ENEMY_CATEGORY;
enemy.physicsBody.contactTestBitMask = OBJECT_CATEGORY;
enemy.physicsBody.collisionBitMask = 0;
// add enemy to the scene: [self addChild:enemy];
// Define that your SKScene conforms to SKPhysicsContactDelegate protocol:
.....
#interface MyScene : SKScene <SKPhysicsContactDelegate>
....
// SKScene.m:
static uint32_t const OBJECT_CATEGORY = 0x1 << 0;
static uint32_t const ENEMY_CATEGORY = 0x1 << 1;
#implementation MyScene
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
.......
self.physicsWorld.contactDelegate = self;
........
}
return self;
}
.......
- (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;
}
if (((firstBody.categoryBitMask & OBJECT_CATEGORY) != 0) &&
(secondBody.categoryBitMask & ENEMY_CATEGORY) != 0) {
NSLog(#"Collision detected!");
}
#end

Resources