i'm pretty new to Objective-C and Cocos2D so go easy, I know this is basic stuff.
I'm using an array to randomly place 4 gold coin sprites on the screen, and i'm using another sprite (a dragon) to fly around and collect the coins. Obviously, I want the coin to disappear and another to randomly appear (like collecting apples in Snake). My problem is that I don't know how to reference the individual sprites. I've added some handling to the update method but a) I don't think its appropriate and b) it doesn't do anything. Please help. I think I need to utilise:
[self removeChild:sprite cleanup:YES];
But I'm not sure how. This is how i'm populating the screen with rings::
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
CCSprite *_goldRing;
NSMutableArray *_goldRings;
}
-(void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_goldRing.physicsBody.collisionType = #"Level";
_goldRing.physicsBody.sensor = TRUE;
_physicsNode.collisionDelegate = self;
_hero.physicsBody.collisionType = #"hero";
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
_meteorites = [NSMutableArray array];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
}
-(void)spawnNewGoldRing {
CGFloat randomX = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat randomY = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat rangeX = 200;
CGFloat rangeY = 300;
CCNode *goldRing = [CCBReader load:#"goldRing"];
goldRing.position = ccp(randomX * rangeX, (randomY * rangeY)+100);
[_physicsNode addChild:goldRing];
[_goldRings addObject:goldRing];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y);
if(_hero.position.x <= _hero.contentSize.width/2 * -1.5) {
_hero.position = ccp(_ground.contentSize.width + _hero.contentSize.width/2, _hero.position.y);
NSMutableArray *collectedGoldRings = nil;
for (CCNode *goldRing in _goldRings) {
CGPoint goldRingWorldPosition = [_physicsNode convertToWorldSpace:goldRing.position];
CGPoint goldRingScreenPosition = [self convertToNodeSpace:goldRingWorldPosition];
if (goldRingScreenPosition.x < -goldRing.contentSize.width) {
if (!collectedGoldRings) {
collectedGoldRings = [NSMutableArray array];
}
[collectedGoldRings addObject:goldRing];
}
}
for (CCNode *goldRingToRemove in collectedGoldRings) {
[goldRingToRemove removeFromParent];
[_goldRings removeObject:goldRingToRemove];
// for each removed goldRing, add a new one
[self spawnNewGoldRing];
}
}
}
Would it be too much to ask how to keep count of these too to display a score?
Thanks so much for any help.
EDIT*
NSLog on the array of gold rings
"<goldRing = 0x9b756b0 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
)
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Gold Ring Loaded
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Array:(
"<goldRing = 0x9b77e50 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
As I see, you are using spritebuilder, so you are using the new version of Cocos2D.
In this version you can track a collision between two types of nodes. In your case, the coins and the hero.
This is as simple as:
1) Set your coin.physicsBody.collisionType = #"coin";
2) Set your hero.physicsBody.collisionType = #"hero";
3) Implement this method:
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair hero:(CCNode *)nodeA coin:(CCNode *)nodeB{
//this method will be automatically called when the hero hits a coin.
//now call your method to remove the coin and call the method to add another coin.
}
To keep a count of the score, just make an int variable, int score;. In your didLoadFromCCB method, initialize it to 0, score=0; and inside this collision method, just do score++; and maybe do a NSLog(#"Your score:%i",score);
Hope this helps.
for things like that, you do not need to use physics. When your hero will touch a coin, just update the user score (it could be an integer) and the coin position.
in your update method do something like
for(CCSprite *gold in _goldRings){
if(CGRectIntersectsRect(_hero.boundingBox,gold.boundingBox)){
//touched a coin
//do whatever u want
//do not remove the coin from the array, just change it's position!
_userPoints += 1;
gold.position=ccp(arc4random()%200 , arc4random()%300);
break;
}
}
Related
Im experiencing a problem with my Hero Character, when he lands on a moving platform, rather then moving along with it, he literally stands on the same X position until the platform moves offscreen. I searched many answers with no avail.
Here is my methods for my hero character and the moving platforms.
-(void)HeroAdd
{
_Hero = [SKSpriteNode spriteNodeWithImageNamed:#"Hero-1"];
_Hero.name = #"Daniel";
_Hero.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_Hero.size];
_Hero.physicsBody.categoryBitMask = fPlayerCategory;
_Hero.physicsBody.contactTestBitMask = fPlatformCategory | fEnemyCategory;
_Hero.physicsBody.usesPreciseCollisionDetection = YES;
_Hero.physicsBody.affectedByGravity = YES;
_Hero.physicsBody.dynamic = YES;
_Hero.physicsBody.friction = .9;
_Hero.physicsBody.restitution = 0;
_Hero.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
_Hero.position = CGPointMake(_Hero.position.x - 252, _Hero.position.y + 50);
if (self.size.width == 480) {
_Hero.position = CGPointMake(_Hero.position.x + 44, _Hero.position.y);
}
[self addChild:_Hero];
}
My moving platform code
-(void)createPlatform {
SKTexture *objectTexture;
switch (arc4random_uniform(2)) {
case (0):
objectTexture = [SKTexture textureWithImageNamed:#"shortPlatform"];
break;
case (1):
objectTexture = [SKTexture textureWithImageNamed:#"highPlatform"];
default:
break;
}
SKSpriteNode *variaPlatform = [SKSpriteNode spriteNodeWithTexture:objectTexture];
variaPlatform.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
variaPlatform.position = CGPointMake(variaPlatform.position.x + 500, variaPlatform.position.y - 140);
variaPlatform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:variaPlatform.size];
variaPlatform.physicsBody.usesPreciseCollisionDetection = YES;
variaPlatform.physicsBody.categoryBitMask = fPlatformCategory;
variaPlatform.physicsBody.contactTestBitMask = fPlatformCategory |fPlayerCategory | fEnemyCategory;
variaPlatform.physicsBody.dynamic = NO;
variaPlatform.physicsBody.affectedByGravity = NO;
SKAction *moveLeft = [SKAction moveTo:CGPointMake(180, variaPlatform.position.y) duration:3];
SKAction *moveDown = [SKAction moveTo:CGPointMake(180, -700) duration:4];
SKAction *removeFromParent = [SKAction removeFromParent];
SKAction *AllThree = [SKAction sequence:#[moveLeft, moveDown, removeFromParent]];
[self addChild:variaPlatform];
[variaPlatform runAction:AllThree];
}
Any type of information would be truly appreciated.
I ran into the same issue when I added conveyer belts into a game. Sprite Kit does not drag objects no matter how much resistance is added. You currently have 2 options.
Option 1 - Add a ledge at each end of your platform. This is the easiest to implement but is less graceful and still allows the player to slide off if he lands on the ledge.
Option 2 -
Step 1: Add code which makes the player move in sync with the horizontal moving platform. You can either use something like self.physicsBody.velocity = CGVectorMake(-50, self.physicsBody.velocity.dy); or use self.position = CGPointMake(self.position.x+10, self.position.y);. You will have to play around with the x values to sync them to the platform's speed.
Step 2: Activate the above code whenever the player makes contact with the platform and deactivate when contact is lost.
Step 3: In case the platform switches directions, set up left and right limits which notify you via contact when the platform switches direction. Depending on the platform's direction you apply either +x or -x movement values to your player.
I know this option sounds complicated but it is not. You just need to go step by step.
* EDIT to provide sample code *
This is the logic I have behind the horizontal moving platforms:
PLATFORMS
If you have more than 1 horizontal moving platform, you will need to store them in an array in the GameScene (my Levels). I have created my own class for them but you do not have to do this.
You will have to set left and right limits (invisible SKNodes with contacts) to set a BOOL property for the platform which tells the player class which way to push as each platform will probably not be the same length. This is why you need to keep a reference to each platform (hence the array).
PLAYER
When the player jumps on the platform, set a Player class property BOOL to TRUE which activates the constant left or right push depending on which way the platform is currently moving. On the flip side, losing the contact cancels the push.
// This code in my "Levels class" which is the default GameScene class.
- (void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryPlatformHorizontal | CategoryPlayer))
{
[_player setPlatformHorizontalContact:true];
for(Platform *platformObject in platformArray)
{
if(([platformObject.name isEqualToString:contact.bodyB.node.name]) || ([platformObject.name isEqualToString:contact.bodyA.node.name]))
{
_player.currentPlatform = platformObject;
}
}
}
}
- (void)didEndContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryPlatformHorizontal | CategoryPlayer))
{
[_player setPlatformHorizontalContact:false];
}
}
// This code is in Player.h
#property (strong) Platform *currentPlatform;
#property BOOL platformHorizontalContact;
// This code is in Player.m
- (void)update:(NSTimeInterval)currentTime
{
if(self.platformHorizontalContact == true)
{
if(self.currentPlatform.movingLeft == true)
{
self.physicsBody.velocity = CGVectorMake(-75, self.physicsBody.velocity.dy); // set your own value depending on your platform speed
} else {
self.physicsBody.velocity = CGVectorMake(75, self.physicsBody.velocity.dy); // set your own value depending on your platform speed
}
}
}
I am using this code to implement infinite looping, but I'v got gaps for 1-2 seconds every time the offscreen image coordinates are changed. Why do they appear? How to fix it? I am also using SpriteBuilder.
#import "MainScene.h"
static const CGFloat scrollSpeed =100.f;
#implementation MainScene{
CCPhysicsNode *_world;
CCNode *_oneb;
CCNode *_twob;
NSArray *_bb;
}
- (void)didLoadFromCCB {
_bb = #[_oneb, _twob];
}
-(void)update:(CCTime)delta{
_world.position=ccp(_world.position.x - (scrollSpeed * delta), _world.position.y ); // moving world
for (CCNode *ground in _bb) {
// get the world position of the ground
CGPoint groundWorldPosition = [_world convertToWorldSpace:ground.position];
// get the screen position of the ground
CGPoint groundScreenPosition = [self convertToNodeSpace:groundWorldPosition];
// if the left corner is one complete width off the screen, move it to the right
if (groundScreenPosition.x <= (-1 * ground.contentSize.width)) {
ground.position = ccp(ground.position.x + 2 * ground.contentSize.width, ground.position.y);
}
}
}
#end
EDIT: I changed -1 to -0.5. Works fine!
Seems like you are using small image for iPhone 3.5-inch on iPhone 4-inch simulator. What resolution of your background image?
EDIT: In my game I have an infinite loop, too. Maybe my code may help you? First background sprite should be 1137x640, second 1136x640. And you will never have gaps again! Hope it helps.
init method:
backgroundSprite = [CCSprite spriteWithFile:#"background.png"];
backgroundSprite.anchorPoint = ccp(0,0);
backgroundSprite.position = ccp(0,0);
[self addChild:backgroundSprite z:0];
backgroundSprite2 = [CCSprite spriteWithFile:#"background2.png"];
backgroundSprite2.anchorPoint = ccp(0,0);
backgroundSprite2.position = ccp([backgroundSprite boundingBox].size.width,0);
[self addChild:backgroundSprite2 z:0];
tick method:
backgroundSprite.position = ccp(backgroundSprite.position.x-1,backgroundSprite.position.y);
backgroundSprite2.position = ccp(backgroundSprite2.position.x-1,backgroundSprite2.position.y);
if (backgroundSprite.position.x<-[backgroundSprite boundingBox].size.width) {
backgroundSprite.position = ccp(backgroundSprite2.position.x+[backgroundSprite2 boundingBox].size.width,backgroundSprite.position.y);
}
if (backgroundSprite2.position.x<-[backgroundSprite2 boundingBox].size.width) {
backgroundSprite2.position = ccp(backgroundSprite.position.x+[backgroundSprite boundingBox].size.width,backgroundSprite2.position.y);
}
I made a little prototype game and so far everything works fine, collision also. Now I want to optimize some stuff. I have creeps with different properties and I want to put them in an array. But I don't know how to check the objects in an array for collision because the command is expecting a SKnode I guess. Here I am defining the creeps and the array:
SKSpriteNokde *creep1 = [SKSpriteNode spriteNodeWithTexture:_creepTexture1];
[creep1 setScale:1];
creep1.position = CGPointMake( 10, y )
creep1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:creep1.frame.size];
creep1.physicsBody.dynamic = NO;
[CreepPair addChild:creep1];
SKSpriteNokde *creep2 = [SKSpriteNode spriteNodeWithTexture:_creepTexture2];
[creep2 setScale:2];
creep2.position = CGPointMake( 50, y + creep1.size.height);
creep2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:creep2.frame.size];
creep2.physicsBody.dynamic = NO;
[CreepPair addChild:creep2];
..
SKSpriteNode *Level1 = [NSArray arrayWithObjects:creep1,creep2,creep3,creep4,nil];
Here I am checking for collision and this works:
if ([creep1 intersectsNode:Player] {
creep1.hidden = YES;
NSLog(#"Lost 1 Life!");
}
But I want to check all creeps like this:
if ([Level1.allobjects intersectsNode:Player] {
creep1.hidden = YES;
NSLog(#"Lost 1 Life!");
}
The last code obviously don't work, but how can I manage this?
This might work
for (SKSpriteNokde *creep in Level1)
{
if ([creep intersectsNode:Player] {
creep.hidden = YES;
NSLog(#"Lost 1 Life!");
}
}
here I assume that Level1 is array.
I am making a CCSprite (which is supposed to represent a bomb) on double taps, like this.
- (void)handleDoubleTap:(UITapGestureRecognizer *)doubletapRecognizer
{
[self placeBomb];
}
-(void)placeBomb
{
NSLog(#"Bomb placed");
_circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
CGPoint circle0position = ccp(_cat.position.x , _cat.position.y);
CGPoint c0TileCoordt = [self tileCoordForPosition:circle0position];
CGPoint c0TileCoord = [self positionForTileCoord:c0TileCoordt];//Supereffective way to get rid of decimals
_circle.position = c0TileCoord;
[self addChild:_circle];
id fade = [CCScaleTo actionWithDuration:3.5 scale:0];
[_circle runAction:fade];
[self performSelector:#selector(explosion) withObject:nil afterDelay:3];
}
Then I run -(void)explosion which - surprise - makes it explode, which then again calls an checkForDamage method which tests for collisions within the explosion area in the lifespan of the explosion.
The problem is that I don't want to have a limit on amount of bombs (sprites), but when I place two bombs now, the explosion for the first one happens in the spot of the second one Which I obviously can't blame, because thats what I ask it to do. The explosion is based of the _circle.position The explosion block is about three hundred lines long, so I won't bother you with posting it here (Heres pastebin link), but as an example I will post the checkForDamage.
-(void)checkForDamage //Gets called with CCTime on explosion
{
bool playerHit = NO;
CGPoint bombPos = [self tileCoordForPosition:_circle.position];
for (int i = 0; i <= explosionLenght; i++)
{
CGPoint playerPosF = [self tileCoordForPosition:_cat.position];
int bombX = (bombPos.x + i);
int bombY = (bombPos.y + i);
int bombNegX = (bombPos.x - i);
int bombNegY = (bombPos.y - i);
CGPoint centre = bombPos;
CGPoint top = ccp(centre.x, bombY);
CGPoint left = ccp(bombNegX, centre.y);
CGPoint bottom = ccp(centre.x, bombNegY);
CGPoint right = ccp(bombX, centre.y);
if (CGPointEqualToPoint(top, playerPosF) ||
CGPointEqualToPoint(left, playerPosF) ||
CGPointEqualToPoint(bottom, playerPosF) ||
CGPointEqualToPoint(right, playerPosF))
{
playerHit = YES;
NSLog(#"Player hit, BOOL:%i",playerHit);
[self unschedule:#selector(checkForDamage)];
break;
}
}
[self performSelector:#selector(stopCheckDamage) withObject:nil afterDelay:2];
}
How can I create multiple instances of the _circlebomb, in a such way that I can have multiple explosions and checks for damage going on at the same time. I tried doing it with a for loop and creating sprites with tags, but I don't know how to globally access and perform stuff on sprites by tag. I thought about making a different sprite for each explosion, like
if (explosion1exists) {explosion with circle1}, if (expl1 + expl2 exists) {expl with circle3}
But as I will have about up to 20-30 bombs placed at most, this is very uneffective and I thought that there must be a better way. Any methods to do this effectivly?
Alright, here we go. I have a cocos2d app, and there are targets that move toward the player. When the player moves, I would like for them to slowly change their destination toward the player again, so they aren't just moving into empty space. Is it possible to change the destination of a sprite mid-runAction?
edit:
This is the code in - (void)changeTargetDest
- (void)changeTargetDest {
NSMutableArray* deleteArray = [[NSMutableArray alloc] init];
for(CCSprite* s in _targets) {
float offX = s.position.x - player.position.x;
float offY = s.position.y - player.position.y;
float adjustX;
float adjustY;
float offDistance = sqrt(powf(offX, 2.0f) + powf(offY, 2.0f));
if(offDistance < 15) {
[deleteArray addObject:s];
deaths++;
[deathLabel setString:[NSString stringWithFormat:#"Deaths: %ld", deaths]];
if(deaths == 0)
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %ld.00", score]];
else
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %.2f", ((float)score / (float)deaths)]];
}
else {
adjustX = offX * .99;
adjustY = offY * .99;
CGPoint point = CGPointMake(player.position.x + adjustX, player.position.y + adjustY);
[s setPosition:point];
}//else
}//for
for (CCSprite *target in deleteArray) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
}
This works well, except for one problem. Because the new position is calculated by just taking .99 of the previous offset, the closer the target gets to the player, the more slowly it moves. How can I make its speed constant?
You can stop the action and run a new action each few frames in a scheduled method.
but the better way is to compute the position of targets according to players position and use setPosition to manualy change their positions each frame in your update method.