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?
Related
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;
}
}
I have recently discovered that the CCProgressTimer class is replaced with CCProgressNode in the latest version of cocos2d,
however, when i tried to implement the following code, there is nothing happen to the progressNode
I have read all kinds of documentation, it seems like I have used all the latest methods.
This all happens in gamePlay Class
This is how I define the node.
CCProgressNode *_healthBar;
float _life;
This is the setUp method
- (void)initializeHealthBar {
self->_healthBar = [[CCProgressNode alloc] init]; // This wasn't there before, but thought it might be the memory allocation problem,
// _health is the code connection between sprite builder and Xcode.
self->_healthBar = [CCProgressNode progressWithSprite:_health];
[_healthBar setType:CCProgressNodeTypeBar];
[_healthBar setPercentage:_life];
[_healthBar setBarChangeRate:ccp(0.1,0.1)];
_healthBar.position = _health.position;
// So the _healthBar turns out positioned correctly, because _health is already positioned in sprite builder
[_contentNode addChild:_healthBar];
}
This is how i Involk the change on health bar... (It works, the healthBar is depleting... )
-(void) hit {
if(_healthBar.percentage > 0)
{
self->_life -= 34;
self->_healthBar.percentage -= 34;
}
if (self->_healthBar.percentage <= 0 && ![_myHero isGameOver]) {
self->_healthBar.percentage = 0;
[_myHero isGameOver: TRUE];
[self gameOver];
}
}
I do not completely understand your problem, I have just written a small working example for a Left -> Right Bar Type Progress Bar
- (void) onEnter
{
[super onEnter];
CCSprite *sprite = [CCSprite spriteWithImageNamed:#"emma.png"];
_progressNode = [CCProgressNode progressWithSprite:sprite];
_progressNode.type = CCProgressNodeTypeBar;
_progressNode.midpoint = ccp(0.0f, 0.0f);
_progressNode.barChangeRate = ccp(1.0f, 0.0f);
_progressNode.percentage = 0.0f;
_progressNode.positionType = CCPositionTypeNormalized;
_progressNode.position = ccp(0.5f, 0.5f);
[self addChild:_progressNode];
self.userInteractionEnabled = YES;
}
- (void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
_progressNode.percentage += 10.0f;
}
Notice that the CCSprite is not added to the scene, you can't use SpriteBuilder for that one I'm afraid. (Unless you want to remove it from the parent but that gets a little messy)
Also, do all the setup before you call the percentage setter.
And the percentage is actually a double. Always check to make sure that there are no casting problems happening.
I am trying to create a game where a character runs forever to the right (the game is landscape). On the ground there are spikes that the character can jump over. Currently, I am creating a new (and somewhat random) set of spikes in almost a checkpoint-like style where once the character reaches a certain distance, the next set of randomly organized spikes are created and the checkpoint distance gets pushed back and so on. Along with the spikes, I have a separate but very similar checkpoint-like system that is used to create the tiles that make up the ground.
This is my code for that portion, 'endlessX' and 'endlessGroundX' are the checkpoint value:
- (void) didSimulatePhysics {
if (player.position.x > endlessX) {
int random = player.position.x + self.frame.size.width;
[self createSpike:random];
endlessX += self.frame.size.width/2.2 + arc4random_uniform(30);
}
if (player.position.x + self.frame.size.width > endlessGroundX) {
[self createGround:endlessGroundX];
endlessGroundX += tile1.frame.size.width;
}
[self centerOnNode: player];
}
The parameter of the createSpike and createGround method is just the 'x' value for the SKSpriteNodes.
I am currently having it as the character itself is the one moving and the spikes and tiles are stationary. This is how I am creating the character:
-(void) createPlayer {
player = [SKSpriteNode spriteNodeWithImageNamed:#"base"];
player.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
player.name = #"player";
player.zPosition = 60;
player.xScale = 0.8;
player.yScale = 0.8;
player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player.frame.size.height/2];
player.physicsBody.mass = 1;
player.physicsBody.linearDamping = 0.0;
player.physicsBody.angularDamping = 0.0;
player.physicsBody.friction = 0.0;
player.physicsBody.restitution = 0.0;
player.physicsBody.allowsRotation = NO;
player.physicsBody.dynamic = YES;
player.physicsBody.velocity = CGVectorMake(400, 0);
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = wallCategory;
player.physicsBody.contactTestBitMask = wallCategory | spikeCategory;
[myWorld addChild:player];
}
With that, the character will never lose any of its kinetic energy to friction or any other force like that. Then, I am using the 'center on node' method that apple used in their adventure game so that the character will always remain in the same x-position on the screen:
- (void) centerOnNode: (SKSpriteNode *) node {
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(self.frame.size.width/5 + node.parent.position.x - cameraPositionInScene.x, node.parent.position.y);
}
I am calling this method in 'didSimulatePhysics.'
When I run this for some time, the programs gets slower and slower. I am guessing that that is due to the fact that I am never removing these nodes and they are always being added. However, to fix this problem, I tried doing something like this:
-(void)update:(CFTimeInterval)currentTime {
[self enumerateChildNodesWithName:#"*" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x + 50 < player.position.x) {
[node removeFromParent];
}
}];
}
(the +50 would be just to make sure that the node is off the screen before removing it)
However, when I did this, instead of removing the specific node that satisfies the 'if' statement, the program removes all of the sprite nodes. Is there a different method or something that I am missing to fix this? Or are there any other simple ways to remove the specific nodes?
Lacking quite a few details, like how you are animating the spikes for instance, makes it a bit hard to be too specific. Nevertheless, from what you are sharing I guess you might be looking for something a little like this:
SKAction *moveSpikeAction = [SKAction moveToX:-50 duration:5];
SKAction *removeSpikeAction = [SKAction removeFromParent];
SKAction *spikeSequence = [SKAction sequence:#[moveSpikeAction, removeSpikeAction]];
[yourSpikeSpriteNode runAction:spikeSequence];
The idea simply being that when the spike has animated to the off screen position you use the removeFromParent action to clear it.
In my spritekit game I am creating Stone randomly through update method. Here is my code for random creation of stone
//Create random island
-(void)createRandomStone:(NSMutableArray *)imageArray
{
int getRandomNumberCoordinate = [self getRandomNumberBetween:(int)0 to:(int)768];
int getRandomStoneImage = [self getRandomNumberBetween:0 to:(int)([imageArray count] - 1)];
NSLog(#"Stone Image name = %d", getRandomStoneImage);
SKSpriteNode *createStone = [SKSpriteNode spriteNodeWithTexture:[imageArray objectAtIndex:getRandomStoneImage]];
if((getRandomNumberCoordinate + createStone.size.height / 2) > 768 )
createIsland.position = CGPointMake(_myScreenSize.width + createStone.size.width, 768 - createStone.size.height / 2);
else if((getRandomNumberCoordinate - createStone.size.height / 2) < 0 )
createStone.position = CGPointMake(_myScreenSize.width + createStone.size.width, 0 + createIsland.size.height / 2);
else
createStone.position = CGPointMake(_myScreenSize.width + createStone.size.width, getRandomNumberCoordinate);
createStone.name = #"Stone";
createStone.zPosition = 3;
[self addChild:createStone];
//Apply physics on the Stone
createStone.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: CGSizeMake(createStone.size.width - createStone.size.width / 4, createStone.size.height - createStone.size.height / 6)];
createStone.physicsBody.categoryBitMask = CollisionTypeStone;
createStone.physicsBody.contactTestBitMask = CollisionTypeMan;
createStone.physicsBody.usesPreciseCollisionDetection = YES;
createStone.physicsBody.collisionBitMask = 0;
}
Stones are moving from the coordinate 1024 to 0 and if any stone cross the 0 coordinate the the stone will remove by using the code
-(void)updateStonePosition:(NSString *)whichDirection andMoveAmount:(float)speed
{
for (SKNode* node in self.children)
{
if([node.name isEqualToString:#"Stone"])
{
node.position = CGPointMake(node.position.x - speed, node.position.y);
if(node.position.x < -node.frame.size.width / 2)
[node removeFromParent];
}
}
}
Both of the above two methods are calling from the update method. And if the man hit by any 5 stone then the game will again reload. The reloading code is:
[self.view presentScene:[[MyScene alloc] initWithSize:self.size] transition:[SKTransition doorsCloseHorizontalWithDuration:0.5f]];
the game is reloaded but after few second an error message shows EXC_BAD_ACCESS(code=2, address = 0x0) on the line
SKSpriteNode *createStone = [SKSpriteNode spriteNodeWithTexture:[imageArray objectAtIndex:getRandomStoneImage]];
Please help. Thanks in advance.
To me, and with the code/information you are providing, it seems that when your scene is reloaded the array you are passing to "createRandomStone:" is nil.
Try checking for nil before calling the method:
if (imageArray != nil) {
// call createRandomStone
} else {
NSLog(#"imageArray is nil")
}
see if that helps identifying the root cause of the error.
I was also trying to find out some information how to reset/restart the SpriteKit game but I wasn't successful. I didn't spend enough time to find out why this is happening in my case, probably should try to do some cleaning before the transition happens like removing all actions and maybe all nodes...but I found a workaround for myself..
So it seems the crashing is happening when you call/do a transition to the same scene where you are at the moment so what I did is that:
1) I make a transition to my ResetScene which is just an empty scene
- with some parameters so I could identify a previous scene
2) In ResetScene in DidMoveToView I make transition back to my previous scene
This is how I fixed this problem but I don't think it is an ideal solution. Note, you can't really spot a difference that there are 2 transitions instead of one.
Let's see if somebody would join this thread with more wisdom :)
If your imageArray is not nil, and you've got more than one item, and it's still crashing, then you could be experiencing one of SpriteKit's bugs with the physics bodies (or skshapenodes elsewhere).
See this thread..: https://stackoverflow.com/a/25007468/557362
I've had this sort of problem myself, and I've got code in my common scene base class to clean up scenes, but I don't use physics bodies - which could be your problem (see link).
-(void)cleanUp
{
[self cleanUpChildrenAndRemove:self];
}
- (void)cleanUpChildrenAndRemove:(SKNode*)node {
for (SKNode *child in node.children) {
[self cleanUpChildrenAndRemove:child];
}
[node removeFromParent];
}
I also found that crashes were happening on transitions - and that if I delayed cleanup for a second after transition (and kept the scene alive just long enough), I had no crashes.
E.g.
SKTransition* doors = [SKTransition fadeWithDuration:0.75];
[[self view] presentScene:newscene transition:doors];
SKNode* dummynode = [SKNode node];
// wait for the transition to complete before we give up the reference to oldscene
[dummynode runAction:[SKAction waitForDuration:2.0] completion:^{
// failing to clean up nodes can result in crashes... nice.
[((MySceneBaseClass*)oldscene) cleanUp];
}];
[newscene addChild:dummynode];
I'm having a problem with side scrolling in Cocos2d. What the situation is, is that i have a sprite that contains multiple other sprites know as actions. The user can swipe back and forth horizontally to scroll through the multiple actions. Whats happening now is that it is very jerky and seems to lag and not a smooth scroll but just very choppy. Not sure what the problem is, I've tried to change the time of the animation but that doesn't seem to work.
- (void)translateInventoryForSwipe:(int)xTranslationValue {
NSArray* tempArray = [NSArray arrayWithArray:self.slotsCenterCoordinates];
[self.slotsCenterCoordinates removeAllObjects];
for (NSNumber* i in tempArray) {
NSNumber* newXCoordinate = [NSNumber numberWithInt:[i intValue] + xTranslationValue];
[self.slotsCenterCoordinates addObject:newXCoordinate];
}
[self updatePositionOfActionsInInventory];
}
this method takes in the delta x of the two touches from the parent view. (current touch minus previous touch) This sets the centre coord of all the actions in the scrolling view.
- (void)updatePositionOfActionsInInventory {
for (int inventoryCounter = 0; inventoryCounter < self.inventorySize; inventoryCounter++) {
FFAction* action = [self.actions objectAtIndex:inventoryCounter];
if (action != self.actionBeingDragged)
[self placeAction:action atIndex:inventoryCounter];
}
self.tempAction = nil;
}
- (void)placeAction:(FFAction*)action atIndex:(int)index {
const float yCenterCoordinate = self.boundingBox.size.height/2;
NSNumber* xCenterCoordinate = [self.slotsCenterCoordinates objectAtIndex:index];
CGPoint centerPointForActionAtIndex = ccp([xCenterCoordinate floatValue], yCenterCoordinate);
CCAction* updatePositionAction = [CCMoveTo actionWithDuration:0.03f position:centerPointForActionAtIndex];
if ([action.view numberOfRunningActions] == 0 || self.tempAction == action) {
[action.view runAction:updatePositionAction];
[action.view released];
}
}
this part is from the parent sprite that handles the touch:
CGPoint currentTouch = [self convertTouchToNodeSpace:touch];
CGPoint previousTouch = [touch previousLocationInView:[touch view]];
int translationPoint = currentTouch.x - previousTouch.x;
[self.inventory translateInventoryForSwipe:translationPoint withPoint:currentTouch];
this then sets the action coordinate mimicking a scrolling effect. I'm not sure where its causing the jerky motion but if anyone has any help on the situation it would be awesome!
Assuming all of the complexity in your code is not required, there are several aspects to consider here, I'll go through them one by one.
First, memory allocation is expensive and a lot of it is done in every call of translateInventoryForSwipe:. A whole new NSArray is created and the self.slotsCenterCoordinates is repopulated. Instead, you should iterate the action sprites and reposition them one by one.
This brings us to the second aspect, which is the use of CCAction to move the sprites. A new CCAction is created for every sprite, again causing delay because of the memory allocation. The CCAction is created, even if it would not be used. Also, the use of actions might be the main cause of the lag as a new action won't be accepted until the previous has finished. A better way to do this would be to directly reposition the sprites by delta instead of assigning actions for repositioning. The action is not required to get smooth movement as the frequency of calls to translateInventoryForSwipe: will be high.
You should also consider using float to send the delta value to the method instead of int. The touch coordinates are floats and especially on retina devices this matters as the distance of two pixels is 0.5f.
Based on these aspects, here is a template of what a fixed method could look like. This is not tested, so there may be errors. Also, I assumed that action.view is the actual sprite, as the actions are assigned there.
- (void)translateInventoryForSwipe:(float)xTranslationValue {
for (FFAction *action in self.actions) {
if (action == self.actionBeingDragged)
continue;
// Position the items manually
float xCoordinate = action.view.position.x + xTranslationValue;
float yCoordinate = self.boundingBox.size.height/2;
action.view.position = ccp(xCoordinate, yCoordinate);
}
}