I have been trying all my resources and thanks to my dear friend Sangon, a member here and the IOS Games by tutorial book from Ray Wenderlich and what I am trying to do is to first of all set categories for each node and then if my player's bullet gets collided with the enemy, I want the enemy to disappear. Now I got some suggestions where to start from but this thing is driving me nuts I have tried and tried and the enemy does not disappear. Please help me on this. thanks.
static const uint32_t EnemyCategory = 1;
static const uint32_t bulletCategory = 2;
- (void) fire:(float)targetAngle {
bullet = [SKSpriteNode spriteNodeWithImageNamed:#"cannonbullet"];
bullet.position = _Player.position;
bullet.physicsBody = [SKPhysicsBody
bodyWithCircleOfRadius:bullet.frame.size.width/2];
bullet.physicsBody.categoryBitMask = bulletCategory;
bullet.physicsBody.collisionBitMask = bulletCategory | EnemyCategory;
bullet.physicsBody.contactTestBitMask = bulletCategory | EnemyCategory;
[self addChild:bullet];
int x = _Player.position.x + 1000 * cos(targetAngle);
int y = _Player.position.y + 1000 * sin(targetAngle);
[bullet runAction:[SKAction moveTo:CGPointMake(x, y) duration:2]];
}
- (void) didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *temp;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
temp = contact.bodyB;
} else {
temp = contact.bodyA;
}
if (temp.categoryBitMask == EnemyCategory) {
[temp.node removeFromParent];
}
}
- (void) Enemy:(CGSize)size {
Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy1#2x"];
Enemy.position = CGPointMake(self.size.width/2, self.size.height/2);
Enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:Enemy.frame.size];
Enemy.physicsBody.dynamic = NO;
Enemy.physicsBody.categoryBitMask = EnemyCategory;
[self addChild:Enemy];
}
The only problem that I can see is that you didn't set the SKPhysicsContactDelegate delegate properly
You will have to do something like this:
#interface MyScene : SKScene<SKPhysicsContactDelegate>
Once you do that you will also have to set the scene as the contactDelegate. It would look something like this:
self.physicsWorld.contactDelegate = self;
And after this is done your scene will receive the didBeginContact: and didEndContact: messages automatically. Those methods are not something that you call manually.
Here you can find a tutorial that uses collision detection (and removes the objects after collision detection)
From the documentation ’SKPhysicsContactDelegate’
An object that implements the SKPhysicsContactDelegate protocol can respond when two physics bodies are in contact with each other in a physics world. To receive contact messages, you set the contactDelegate property of a SKPhysicsWorld object. The delegate is called when a contact starts or ends.
Hope this helps
Related
I'm attempting to make a game with objects that bounce between the left and right walls of the screen until they reach the bottom.
I create my objects inside these methods which stores the objects in an NSMutable array and is called when the game begins.
-(void)createContent
{
self.backgroundColor = [SKColor colorWithRed:0.54 green:0.7853 blue:1.0 alpha:1.0];
world = [SKNode node];
[self addChild:world];
crabs = [NSMutableArray new];
[self performSelector:#selector(createCrabs) withObject:nil afterDelay:1];
}
-(void)createCrabs {
crab = [HHCrab crab];
[crabs addObject:crab];
[world addChild:crab];
crab.position = CGPointMake(world.scene.frame.size.width/12 , world.scene.frame.size.height/3.2);
[crab moveLeft];
//Next Spawn
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:10],
[SKAction performSelector:#selector(createCrabs) onTarget:self],
]]];
}
This will endlessly create new objects, however a problem begins with collision detection. Originally I had my collision detection set up like this:
-(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==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
NSLog(#"Crab Hit Left Wall");
[crab stop];
[crab moveRight];
} else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
NSLog(#"Crab Hit Right Wall");
[crab stop];
[crab moveLeft];
}
}
But, after more than one object is on the map, when the original object collides with a wall it begins to glitch and stop moving. This results in a pile up which bugs the new objects being spawned.
I've also tried to use the update CurrentTime method to see if the collision detection would improve, however as predicted, only one object will move at a time while the rest will stay still.
-(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==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
NSLog(#"Crab Hit Left Wall");
self.crabTouchLeftWall = YES;
} else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
NSLog(#"Crab Hit Right Wall");
self.crabTouchRightWall = YES;
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
for (HHCrab *crabNode in crabs){
if (self.crabTouchLeftWall){
[crabNode stop];
[crabNode moveRight];
self.crabTouchLeftWall = NO;
}
if (self.crabTouchRightWall){
[crabNode stop];
[crabNode moveLeft];
self.crabTouchRightWall = NO;
}
}
}
How can I fix this so when one object collides with the wall, it does not effect the movement of the other objects, only itself?
There are a couple of suggestions I have.
As you are creating multiple instances of crab and adding them into the crabs array, I suggest you give each crab a unique name property. Your can do this by having a running int counter. For example:
#implementation GameScene {
int nextObjectID;
}
Then when you create a crab instance:
nextObjectID++;
[crab setName:[NSString stringWithFormat:#"crab-%i",nextObjectID]];
I personally prefer coding my didBeginContact like this:
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (crabCategory | leftWallCategory)) {
// figure out which crab in the array made the contact...
for(HHCrab *object in crabs) {
if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
NSLog("I am %#",object.name);
}
}
}
}
Your code self.crabTouchLeftWall = YES; is not the right way to go. You should create a crab class property and set that to YES. Reason being that each crab instance needs to have these properties. The way you currently have it, all crab instances all share the same BOOLs self.crabTouchLeftWall and self.crabTouchRightWall.
You are checking for the BOOL values contacts in the update method and then running [crabNode stop];. You could do this directly when the contact is registered in the didBeginContact method.
The reason I earlier suggested you use unique names for each crab instance is because when it comes time to remove them from your array, you need to be able to point to which specific crab instance needs removing. Having a unique name just makes things easier.
Be aware that moving nodes manually like that can produce odd results. When using physics engine in Spritekit you have to let it to do calculation by itself, meaning you have to move objects by using forces. This not applies for contact detection, and you can move nodes around if you only need contact detection. But if you need more than just contact detection eg. to simulate collisions and restrict nodes from penetrating each other, then I suggest you to use forces appropriately (applyImpulse:, applyForce: are suitable methods).
You could probably solve your issue by moving the code from your update: method to didSimulatePhysics method, but that is not a solution...Because even if you move everything manually after simulation is done, you can make a mistake and for example manually initiate collision by positioning your nodes to overlap each other. So, the advice is, if you are using phyisics use it all, or don't use it at all and move nodes manually or using actions.In the case you decide not to use physics engine, you can handle collisions by using methods like CGRectIntersectsRect and similar.
And to point once again , which is not related to your question, but it can be useful: You can use physics engine for contact detection even if you move your nodes, for example by SKAction methods. Read more here.
What am I missing here? Log says "Ball hits player" every time it collides with frame border or even floor objects if added.
(I´m very new to Sprite Kit and it is my first time working with collisions. This is making me crazy :D)
#import "MyScene.h"
static const int starHitCategory = 1;
static const int playerHitCategory = 2;
static const int ballHitCategory = 3;
#interface MyScene ()
#end
#implementation MyScene{
SKSpriteNode *player;
SKSpriteNode *redball;
SKSpriteNode *star;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0f, -3.0f);
SKPhysicsBody* borderBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody = borderBody;
self.physicsBody.friction = 0.0f;
//initalizing player node
player = [SKSpriteNode spriteNodeWithImageNamed:#"player.png"];
player.physicsBody.categoryBitMask = playerHitCategory;
player.physicsBody.contactTestBitMask = playerHitCategory;
player.physicsBody.collisionBitMask = playerHitCategory;
player.position = CGPointMake(75,101);
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.size];
player.physicsBody.friction = 0.0f;
player.physicsBody.linearDamping = 0.5f;
player.physicsBody.mass = 10;
player.physicsBody.dynamic = YES;
player.physicsBody.usesPreciseCollisionDetection = YES;
player.physicsBody.allowsRotation = NO;
player.name = #"player";
[self addChild:player];
//initalizing Redball node
redball = [SKSpriteNode spriteNodeWithImageNamed:#"redbdall.png"];
redball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redball.frame.size.width/2];
redball.position = CGPointMake(290,200);
redball.physicsBody.usesPreciseCollisionDetection = YES;
redball.physicsBody.categoryBitMask = ballHitCategory;
redball.physicsBody.contactTestBitMask = ballHitCategory;
redball.physicsBody.collisionBitMask = ballHitCategory;
redball.physicsBody.friction = 2.0f;
redball.physicsBody.restitution = 1.0f;
redball.physicsBody.linearDamping = 0.0f;
redball.physicsBody.allowsRotation = NO;
[self addChild:redball];
//initalizing Star node
star = [SKSpriteNode spriteNodeWithImageNamed:#"star.png"];
star.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:star.size];
star.physicsBody.categoryBitMask = starHitCategory;
star.physicsBody.contactTestBitMask = starHitCategory;
star.physicsBody.collisionBitMask = starHitCategory;
star.position = CGPointMake(205,125);
[self addChild:star];
}
return self;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
if(firstBody.categoryBitMask == playerHitCategory || secondBody.categoryBitMask == ballHitCategory)
{
NSLog(#"ball hits player");
}
}
#end
Thanks in advance
Your if statement will be satisfied if the player/ball object will collide with whatever object it can collide with since you are not also checking that the other object is of the required category.
You should change you if statement to be more strict since you also do not know which object of the two is the ball or the player or the floor:
if((firstBody.categoryBitMask == playerHitCategory && secondBody.categoryBitMask == ballHitCategory) ||
(firstBody.categoryBitMask == ballHitCategory && secondBody.categoryBitMask == playerHitCategory))
{
NSLog(#"ball hits player");
}
This way you can seperate the collision between the objects. Do the same for other object you desire to detect collision with.
Note that you are also setting the bit masks wrong for each physics body object. Read here
look for the section : "Collision and Contact Example: Rockets in Space"
It is very important to understand what each mask property is used for collisions to be detected correctly. (otherwise you indeed can go nuts about it;))
I have a contact listener to detect collisions between a bullet and an asteroid. Inside the beginContact function i check for the sprite types and fire a block from within this function.
The block is set by the scene class when it is allocated, before the contact listener is assigned to the box2dworld. The bullets and asteroids both get box2d bodies as well as sprites.
The problem is that i can access the sprites and do whatever i want with them (run actions, stop actions etc.).
However, as soon as i call the removeChild function on the sprite, I get an EXC_BAD_ACCESS in the b2Contact::Update on listener->BeginContact(this).
Can anyone point me in the right direction?
My tick function:
//Physics simulation
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *)b->GetUserData();
b2Vec2 b2Position = b2Vec2(sprite.position.x/PTM_RATIO,
sprite.position.y/PTM_RATIO);
float32 b2Angle = -1 * CC_DEGREES_TO_RADIANS(sprite.rotation);
b->SetTransform(b2Position, b2Angle);
}
}
for(CCSprite *sprite in spritesToDelete) {
[self removeSprite:sprite];
[spritesToDelete removeObject:sprite];
sprite = nil;
}
My contact listener callback block:
contactListener->contactBlock = ^(CCSprite *A, CCSprite *B) {
NSLog(#"A: %#", [A class]);
NSLog(#"B: %#", [B class]);
CCSprite *bullet = nil , *asteroid = nil;
if ([(NSString *)A.userData isEqualToString:#"asteroid"] && [(NSString *)B.userData isEqualToString:#"bullet"])
{
asteroid = A;
bullet = B;
} else if ([(NSString *)B.userData isEqualToString:#"asteroid"] && [(NSString *)A.userData isEqualToString:#"bullet"])
{
asteroid = B;
bullet = A;
}
if (asteroid != nil && bullet != nil) {
NSLog(#"Asteroid Hit!");
[asteroid stopAllActions];
[bullet stopAllActions];
//If I keep the line below uncommented, I get the error. Adding actions and stuff does not make an issue, only removal of sprite is when I get the error.
[spritesToDelete addObject:bullet];
}
};
I guess you put a pointer to your sprite in b2UserData of b2Body or b2Fixure in order to retrieve when collision happens. When you delete the sprite, this pointer will point to a deleted instance and thus you receive EXC_BAD_ACCESS.
You should nullify the pointer to eliminate the problem
I want a snow particle effect to follow my sprite and I tried some methods but all that ends up happening is the snow will just stay still instead of following. I did this one tutorial (will post as soon as I find it) thats shows how it do it with fire but didn't work out at all. Any tutorials or suggestions will be appreciated. I believe i have to add some kind of code to the snippet part where it says create enemy off screen.
[self schedule:#selector(gameLogicboss:) interval:180 ];
[self schedule:#selector(updateboss:)];
-(void)addTarget1 {
Boss *target1 = nil;
if ((arc4random() % 2) == 0) {{
target1 = [WeakAndFastBoss boss];
}} else {
target1 = [WeakAndFastBoss boss];
}
// Determine where to spawn the target along the Y axis
CGSize winSize = [[CCDirector sharedDirector] winSize];
int minY = target1.contentSize.height/2;
int maxY = winSize.height - target1.contentSize.height/2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
// Create the target slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
target1.position = ccp(winSize.width + (target1.contentSize.width/2), actualY);
[self addChild:target1 ];
// Determine speed of the target
int minDuration = target1.minMoveDuration;
int maxDuration = target1.maxMoveDuration;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
id actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp(-target1.contentSize.width/2, actualY)];
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteMoveFinished:)];
[target1 runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
target1.tag = 1;
[_targets addObject:target1];
}
-(void)gameLogicboss:(ccTime)dt {
[self addTarget1];
iterations_++;
}
- (void)updateboss:(ccTime)dt {
CGRect projectileRect = CGRectMake(projectile.position.x - (projectile.contentSize.width/2), projectile.position.y - (projectile.contentSize.height/2), projectile.contentSize.width, projectile.contentSize.height);
BOOL bossHit = FALSE;
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target1 in _targets) {
CGRect target1Rect = CGRectMake(target1.position.x - (target1.contentSize.width/2), target1.position.y - (target1.contentSize.height/2), target1.contentSize.width, target1.contentSize.height);
if (CGRectIntersectsRect(projectileRect, target1Rect)) {
//[targetsToDelete addObject:target];
bossHit = TRUE;
Boss *boss = (Boss *)target1;
boss.hp--;
if (boss.hp <= 0) {
_score ++;
[targetsToDelete addObject:target1];
}
break;
}
}
for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
_projectilesDestroyed++;
if (_projectilesDestroyed > 2) {
}
}
if (bossHit) {
//[projectilesToDelete addObject:projectile];
[[SimpleAudioEngine sharedEngine] playEffect:#"explosion.caf"];
}
[targetsToDelete release];
}
-(void)spriteMoveFinishedboss:(id)sender {
CCSprite *sprite = (CCSprite *)sender;
[self removeChild:sprite cleanup:YES];
GameOverScene *gameOverScene = [GameOverScene node];
[gameOverScene.layer.label setString:#"You Lose"];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
if (sprite.tag == 1) { // target
[_targets removeObject:sprite];
} else if (sprite.tag == 2) { // projectile
[_projectiles removeObject:sprite];
}
}
I Have found this, at CCParticleSystem.h
/** #typedef tCCPositionType
possible types of particle positions
/
typedef enum {
/* Living particles are attached to the world and are unaffected by emitter repositioning. */
kCCPositionTypeFree,
/** Living particles are attached to the world but will follow the emitter repositioning.
Use case: Attach an emitter to an sprite, and you want that the emitter follows the sprite.
*/
kCCPositionTypeRelative,
/** Living particles are attached to the emitter and are translated along with it. */
kCCPositionTypeGrouped,
you should set it like
myparticleSystem.positionType=kCCPositionTypeGrouped;
Hope it helps.
You don't need to update the particle emitter's position with the sprite.
You can add a particle system to the sprite as a child.
The particle system does need to be typed as such:
CCParticleSystem * booster = [CCParticleSystem particleWithFile:#"boosterParticles.plist"];
//customize your particles' options
//assuming you have a sprite defined as _motherShip
[_motherShip addChild:booster];
/*
* now that the particles are the _motherShip's child, you must remember
* to set the position relative to the mothership's origin...
*/
particles.position = ccp(15,0);
...So now whenever _motherShip.position changes, the booster will follow. It will even rotate with the ship.
Very simple logic without getting into code:
I spawn a sprite and give it a location (x, y).
For each sprite, I also spawn a CCParticleSystem, give it the required particle type, spawn rates etc.
The CCParticleSystem location is now set to be the same (x,y) location as the sprite, and it should get updated as the sprite's (x,y) location is update.
As the sprite and the CCParticleSystem move around, this location (x, y) is getting updates constantly at random in your schedule method per the interval step time.
Hope that makes sense.
I do this
vehicleParticleSystem = [CCParticleSystemQuad particleWithFile:#"vehicleParticle.plist"];
vehicleParticleSystem.position = ccp(_ship.position.x - _ship.contentSize.width/3, _ship.position.y - _ship.contentSize.height/3);
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
vehicleParticleSystem.scale = 0.5;
}
[self addChild:vehicleParticleSystem z:-1];
and update its position with this
- (void) updateParticleSystem:(ccTime)dt {
vehicleParticleSystem.position = ccp(_ship.position.x - _ship.contentSize.width/3, _ship.position.y - _ship.contentSize.height/3);
}
which is called in the -(void) update:(ccTime)dt method.
The ship is moved by the user via a joypad.
hope this helps. The positioning for the particle is slightly behind the vehicle for an engine effect.
Did you tried to update your particle position taking advantage of the update method?
I'm doing something like the following lines in one of my games and it works as I think you expect
-(void) update:(CCTime)delta{
_sprite.position = CGPointMake(newXPosition, newYPosition);
_fire.position = CGPointMake(_sprite.position.x, _sprite.position.y);
}
I hope it helps!
I have a gamepad with a direction pad and 2 buttons. All of them are digital (not analog).
I have a procedure for handling their events:
-(void)gamepadTick:(float)delta
{
...
if ([gamepad.B active]) {
[ship shoot];
}
...
}
And I call it via a command:
[self schedule:#selector(gamepadTick:) interval:1.0 / 60];
[ship shoot] calls shoot function from Weapon class:
-(void)shoot
{
Bullet *bullet = [Bullet bulletWithTexture:bulletTexture];
Ship *ship = [Ship sharedShip];
bullet.position = [ship getWeaponPosition];
[[[CCDirector sharedDirector] runningScene] addChild:bullet];
CGSize winSize = [[CCDirector sharedDirector] winSize];
int realX = ship.position.x;
int realY = winSize.height + bullet.contentSize.height / 2;
CGPoint realDest = ccp(realX, realY);
int offRealX = realX - bullet.position.x;
int offRealY = realY - bullet.position.y;
float length = sqrtf((offRealX*offRealX) + (offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length / velocity;
[bullet runAction:[CCSequence actions:[CCMoveTo actionWithDuration:realMoveDuration position:realDest],
[CCCallFuncN actionWithTarget:self selector:#selector(bulletMoveFinished:)], nil]];
}
-(void)bulletMoveFinished:(id)sender
{
CCSprite *sprite = (CCSprite *)sender;
[[[CCDirector sharedDirector] runningScene] removeChild:sprite cleanup:YES];
}
The problem is the weapon shoots too much of bullets. Is it possible to reduce their amount without writing using of timers and functions which are separate for each button and direction pad?
Use Delta to keep track of the time between shoots, and only shoot if delta has incremented by a certain amount. This is a common way of doing things that you don't want every frame.
You will need to keep an iVar counter of time elapsed, increment it by Delta on each shoot, then test the amount of time elapsed to see if it meets your desired interval threshold; if it does, then shoot and reset the time elapsed to 0.
-(void) update:(ccTime)delta {
totalTime += delta;
if (fireButton.active && totalTime > nextShotTime) {
nextShotTime = totalTime + 0.5f;
GameScene* game = [GameScene sharedGameScene];
[game shootBulletFromShip:[game defaultShip]];
}
// Allow faster shooting by quickly tapping the fire button if (fireButton.active == NO)
{
nextShotTime = 0;
}
}
it is an original code from "learn cocos2d game development" book by Steffen Itterheim.
But I can improve this code a little.
Updated
It is harder to understand my code than the original one but it has a correct structure. It means the following:
- global timer belongs to a scene;
- ship can shoot via weapon;
- weapon can shoot with bullets and delay between bullets belong to this weapon (not the scene as in the original example)
Scene class contains totalTime variable, a ship object and the following method to handle timer updating:
-(void)update:(ccTime)delta
{
totalTime += delta;
if (!fireButton.active) {
ship.weapon.nextShotTime = 0;
} else if (totalTime > ship.weapon.nextShotTime) {
[ship.weapon updateNextShotTime:totalTime];
[ship shoot];
}
}
Ship class contains a weapon object and a shoot method (this method is not actual for this question).
Weapon class contains nextShotTime variable and the following method:
-(void)updateNextShotTime:(ccTime)currentTime
{
nextShotTime = currentTime + 0.05f;
}