Im making a game in sprite kit and I'm fairly new to iOS programming and i have been working on getting it so when 2 images collide that one is deleted or made invisible. I have been very unsuccessful with this and was wondering if anyone knew how to do it?
Below is the ship (which always stays) and one of the objects to be deleted.
-(void)addShip
{
//initalizing spaceship node
ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
[ship setScale:0.5];
ship.zRotation = - M_PI / 2;
//Adding SpriteKit physicsBody for collision detection
ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.size];
ship.physicsBody.categoryBitMask = shipCategory;
ship.physicsBody.dynamic = YES;
ship.physicsBody.contactTestBitMask = DonutCategory | PizzaCategory | ChocolateCategory | SoftCategory | AppleCategory | GrapeCategory | OrangeCategory | BananaCategory;
ship.physicsBody.collisionBitMask = 0;
ship.physicsBody.usesPreciseCollisionDetection = YES;
ship.name = #"ship";
ship.position = CGPointMake(260,30);
actionMoveRight = [SKAction moveByX:-30 y:0 duration:.2];
actionMoveLeft = [SKAction moveByX:30 y:0 duration:.2];
[self addChild:ship];
}
- (void)shoot1 //donut
{
// Sprite Kit knows that we are working with images so we don't need to pass the image’s extension
Donut = [SKSpriteNode spriteNodeWithImageNamed:#"1"];
[Donut setScale:0.15];
// Position the Donut outside the top
int r = arc4random() % 300;
Donut.position = CGPointMake(20 + r, self.size.height + Donut.size.height/2);
Donut.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:Donut.size];
Donut.physicsBody.categoryBitMask = DonutCategory;
Donut.physicsBody.dynamic = YES;
Donut.physicsBody.contactTestBitMask = shipCategory;
Donut.physicsBody.collisionBitMask = 0;
Donut.physicsBody.usesPreciseCollisionDetection = YES;
// Add the Dount to the scene
[self addChild:Donut];
// Here is the Magic
// Run a sequence
[Donut runAction:[SKAction sequence:#[
// Move the Dount and Specify the animation time
[SKAction moveByX:0 y:-(self.size.height + Donut.size.height) duration:5],
// When the Dount is outside the bottom
// The Dount will disappear
[SKAction removeFromParent]]]];
}
You have to set the delegate of your physics world:
self.physicsWorld.contactDelegate = self;
Then, you have a delegate that is called when two objects contact :
- (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 & projectileCategory) != 0 &&
(secondBody.categoryBitMask & monsterCategory) != 0)
{
//remove the donut and the target
SKSpriteNode *firstNode = (SKSpriteNode *) firstBody.node;
SKSpriteNode *secondNode = (SKSpriteNode *) secondBody.node;
[firstNode removeFromParent];
[secondNode removeFromParent];
}
}
For more information you can jump to the collision part in this tutorial.
Related
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.
I am having trouble having my game begin paused so that the player has to press a button to start the game. I also have collision detection working, so when the two objects collide they just fall off of the screen. Here is my current code:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
[self buildBackground];
[self startScrolling];
_firstPosition = CGPointMake(self.frame.size.width * 0.817f, self.frame.size.height * .40f);
_squirrelSprite = [SKSpriteNode spriteNodeWithImageNamed:#"squirrel"];
_squirrelSprite.position = _firstPosition;
_atFirstPosition = YES;
_squirrelSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
_squirrelSprite.physicsBody.categoryBitMask = squirrelHitCategory;
_squirrelSprite.physicsBody.contactTestBitMask = nutHitCategory;
_squirrelSprite.physicsBody.collisionBitMask = nutHitCategory;
_squirrelSprite.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild:_squirrelSprite];
// Declare SKAction that waits 2 seconds
SKAction *wait = [SKAction waitForDuration:3.0];
// Declare SKAction block to generate the sprites
SKAction *createSpriteBlock = [SKAction runBlock:^{
const BOOL isHeads = arc4random_uniform(100) < 50;
NSString* spriteName = isHeads ? #"lightnut.png" : #"darknut.png";
SKSpriteNode *nut = [SKSpriteNode spriteNodeWithImageNamed:spriteName];
BOOL heads = arc4random_uniform(100) < 50;
nut.position = (heads)? CGPointMake(257,600) : CGPointMake(50,600);
nut.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(200,160)];
nut.physicsBody.categoryBitMask = nutHitCategory;
nut.physicsBody.contactTestBitMask = squirrelHitCategory;
nut.physicsBody.collisionBitMask = squirrelHitCategory;
nut.physicsBody.affectedByGravity = NO;
self.physicsWorld.contactDelegate = self;
[self addChild: nut];
SKAction *moveNodeUp = [SKAction moveByX:0.0 y:-700.0 duration:1.3];
[nut runAction: moveNodeUp];
}];
// Combine the actions
SKAction *waitThenRunBlock = [SKAction sequence:#[wait,createSpriteBlock]];
// Lather, rinse, repeat
[self runAction:[SKAction repeatActionForever:waitThenRunBlock]];
}
return self;
}
My touchesBegan:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (_atFirstPosition)
{
SKAction *moveNodeLeft = [SKAction moveByX:-207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeLeft withKey:#"moveleft"];
} else {
SKAction *moveNodeRight = [SKAction moveByX:207.8 y:0.0 duration:0.85];
[_squirrelSprite runAction: moveNodeRight withKey:#"moveright"];
}
_atFirstPosition = !_atFirstPosition;
_squirrelSprite.xScale *= -1.0;
}
Finally, my didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
if(firstBody.categoryBitMask == squirrelHitCategory || secondBody.categoryBitMask == nutHitCategory)
{
}
}
I've been trying to figure this out for the last 2 weeks and I have read many tutorials and code to start the game with a simple start button and end screen, but nothing has worked. Any help is greatly appreciated!
I would use a navigation controller and have your start button on the root view and have that button push a viewcontroller that presents your game.
I have a object in the scene which is determine by:
SKSpriteNode* object = [SKSpriteNode spriteNodeWithTexture:_objectTexture];
[object setScale:2];
object.position = CGPointMake(0, y);
object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object.size];
object.physicsBody.dynamic = NO;
then if my character hit it, it will stay there. Now, I want whenever the character hit the object, the object also move its upper half. How it moves depends on the index.
if index value in range 1, object change texture
if index value in range 2, object and character reflex by normal physical contact law
if index value in range 3, object broken into half
I tried to put
object.physicsBody.dynamic = YES;
then I saw it pulled down and disappear by gravity.
How do I set object dynamic and condition to satisfy the above need?
Thanks a lot for your help !
Added (as requested): My delegate method:
#interface MyScene () <SKPhysicsContactDelegate> {
SKSpriteNode* _character;
SKTexture* _object1;
SKTexture* _object2;
SKAction* _moveObjects;
SKNode* _moving;
SKNode* _objects;
BOOL _canRestart;
NSInteger _index;
SKTexture* characterTexture;
SKSpriteNode* _characterSprite;
}
later part (not sure if it is relevant)
-(id)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
/*Setup Scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -6.0);
self.physicsWorld.contactDelegate = self;
My objects are moved on scence by:
CGFloat distanceToMove = self.frame.size.width + 2 * _object1.size.width;
SKAction* moveObjects = [SKAction moveByX:-distanceToMove y:0 duration:0.01* distanceToMove];
SKAction* removeObjects = [SKAction removeFromParent];
_moveObjectsAndRemove = [SKAction sequence:#[moveObjects,removeObjects]];
Ps: add didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (_moving.speed > 0) {
//Score node
if ((contact.bodyA.categoryBitMask & scoreCategory) == scoreCategory || (contact.bodyB.categoryBitMask & scoreCategory) == scoreCategory)
{
_score++;
// Add a little visual
[_scoreLabelNode runAction:[SKAction sequence:#[[SKAction scaleTo:1.5 duration:0.1], [SKAction scaleTo:1.0 duration:0.1]]]];
}
else
//Collided with world
{
_moving.speed = 0;
_character.physicsBody.collisionBitMask = worldCategory;
[_character runAction:[SKAction rotateByAngle:M_PI * _character.position.y*0.01 duration:_character.position.y * 0.03] completion:^{_character.speed = 0; }];
....
}
}
You need to set
object.physicsBody.affectedByGravity = NO;
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
I have looked, and have found answers for single collisions, but I am looking for a way to detect more than one type of collision. I am making a game where there are 3 collisions I would like. The user plane colliding with enemy bullets, the user's bullet colliding with the enemy plane (which i have working already), and the enemy bullet and user bullet colliding. I have all the categoryBitMask and contactTestBitMask set up and correct. Here is my delegate method.
- (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 user plane hits enemy bullet
if ((firstBody.categoryBitMask == playerShipCategory) &&
(secondBody.categoryBitMask == enemyBulletCategory)) {
[self takeDamageByAmount:POINT_INCREMENTER];
[_enemyBullet removeFromParent];
SKAction *bounce = [SKAction sequence:#[
[SKAction fadeAlphaTo:.5 duration:.2],
[SKAction fadeAlphaTo:1.0 duration:.2],
[SKAction fadeAlphaTo:.5 duration:.2],
[SKAction fadeAlphaTo:1.0 duration:.2]
]];
[_playerPlane runAction:bounce];
}
// if the user bullet hits the enemy bullet
else if ((firstBody.categoryBitMask == bulletCategory) &&
(secondBody.categoryBitMask == enemyBulletCategory)) {
[_enemyBullet removeFromParent];
[_bullet removeFromParent];
}
// if bullet hits enemy ship - THIS ONE WORKS, but none of the others work for some reason
else if ((firstBody.categoryBitMask == bulletCategory) &&
(secondBody.categoryBitMask == enemyShipCategory)) {
[self gainPointsByAmoint:POINT_INCREMENTER];
[self projectile:(SKSpriteNode *)firstBody.node didCollideWithMonster:(SKSpriteNode *)secondBody.node];
}
}
This should work it's already tested and working
//Define the collider Category
typedef NS_ENUM(uint32_t, CollisionType) {
enemyShipCategory = 0x1 << 0,
enemyBulletCategory = 0x1 << 1,
playerShipCategory = 0x1 << 2,
bulletCategory = 0x1 << 3,
};
// Set the category that this physics body belongs to
// and specify which categories of bodies cause intersection
// notifications with this physics body
ship.physicsBody.categoryBitMask = playerShipCategory;
ship.physicsBody.contactTestBitMask = enemyBulletCategory;
shipBullet.physicsBody.categoryBitMask = bulletCategory;
shipBullet.physicsBody.contactTestBitMask = enemyShipCategory | enemyBulletCategory;
enemy.physicsBody.categoryBitMask = enemyShipCategory;
enemy.physicsBody.contactTestBitMask = bulletCategory;
enemyBullet.PhysicsBody.categoryBitMask = enemyBulletCategory;
enemyBullet.physicsBody.contactTestBitMask = bulletCategory;
// And handle Collisions
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (playerShipCategory | enemyBulletCategory)) {
SKNode *Ship, *bullet;
if (contact.bodyA.categoryBitMask == playerShipCategory) {
Ship = contact.bodyA.node;
bullet = contact.bodyB.node;
} else {
Ship = contact.bodyB.node;
bullet = contact.bodyA.node;
}
[self takeDamageByAmount:POINT_INCREMENTER];
[bullet removeFromParent];
SKAction *bounce = [SKAction sequence:#[
[SKAction fadeAlphaTo:.5 duration:.2],
[SKAction fadeAlphaTo:1.0 duration:.2],
[SKAction fadeAlphaTo:.5 duration:.2],
[SKAction fadeAlphaTo:1.0 duration:.2]
]];
[Ship runAction:bounce];
}
else if (collision == (bulletCategory | enemyBulletCategory)) {
[contact.bodyA.node removeFromParent];
[contact.bodyB.node removeFromParent];
}
else if (collision == (bulletCategory | enemyShipCategory)) {
SKNode *shipBullet, *enemyShip;
if (contact.bodyA.categoryBitMask == shipBullet) {
shipBullet = contact.bodyA.node;
enemyShip = contact.bodyB.node;
} else {
shipBullet = contact.bodyB.node;
enemyShip = contact.bodyA.node;
}
[self gainPointsByAmoint:POINT_INCREMENTER];
[self projectile:shipBullet didCollideWithMonster:enemyShip];
}
}
Good Luck!!