I have a problem with my cocos2d game. I am trying to delete a projectile shot by an enemy every 5 seconds (each projectile is supposed to have a lifetime of 5 seconds), but I cannot figure out how to do it. I get the error of
Assertion failure in -[CCTimer initWithTarget:selector:interval:]
Here is my code:
-(void)projectileShooting:(ccTime)dt
{
[self schedule:#selector(projectileShooting:) interval:2.5];
projcount++;
if([proj count] <= 15 ){
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
for(CCSprite *projectile in proj){
[self schedule:#selector (deleteprojectile:projectile:) interval:5];
}
}
}
}
for(CCSprite *enem2 in enemytwo)
{
if( [proj count] <= 15){
CCSprite * projectilebull = [CCSprite spriteWithFile:#"Projectile.png"];
CGPoint MyVector = ccpSub(enem2.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enem2.contentSize.width/2+10);
MyVector = ccpMult(MyVector,-1);
projectilebull.position = ccpAdd(enem2.position, MyVector);
[self addChild:projectilebull];
[proj addObject:projectilebull];
for(CCSprite *projectile in proj){
}
}
}
}
-(void)deleteprojectile:(CCSprite *)protime:(ccTime)dt{
NSMutableArray *timepro = [[NSMutableArray alloc]init];
[timepro addObject:protime];
for(CCSprite *objecttime in timepro){
[proj removeObject:objecttime];
[self removeChild:objecttime cleanup:YES];
}
}
It's a bit of a hack but this is what I use in my program, until I find a more elegant solution. I have a method in my game layer that I call to remove a node from its parent, like so:
-(void)removeNode:(CCNode*)node {
[node removeFromParentAndCleanup:YES];
}
And when I want to schedule a node for deletion after a delay, I call it like this:
[self performSelector:#selector(removeNode:) withObject:node afterDelay:delay];
Simple, and it works.
Change argument name in your selector to protime instead of projectile. The selector must match the signature defined in you object's class definition.
Your selector was not defined properly and probably the Timer is checking if the object implements the given selector.
I did not have time to test it so thanks to #RamyAlZuhouri for confirming.
Related
I am having some trouble getting the userData values from my object. I know the userData is being set for the objects because else where in my code I am able to access it without any problems.
I created a SKNode *column3 to hold all the buttons that are in that column. which was added to scene like so:
column3 = [SKNode node];
[self addChild:column3];
column3.name = #"column3";
when I create the buttons I assign them to the appropriate column like so:
[column3 addChild:newButton];
Later in my code I need to loop through the column3 node group and get the #"buttonRow" userData from each object in that group. For some reason it only gives me "NULL" for the values. The NSLog's are just what I was using to test, they have no real importance to me.
I need to get this userData in order to shift all my buttons down to take up any empty spaces on screen when any button is deleted/removed from that column. Game will shift buttons down and add new ones to top to fill column back up.
I tried changing column3.children to self.children and it game me all the column nodes IE/column1, column2, column3 etc.. so I am not really sure why it does not work. Been reading and trying to figure out for a while now.
for(SKNode * child in column3.children) { //loop through all children in Column3.
SKSpriteNode* sprite = (SKSpriteNode*)child;
NSString* name = sprite.name;
NSLog(#"child name %#", name);
NSString *childRowTmp = [child.userData objectForKey:#"buttonRow"];
NSLog(#"child row %#", childRowTmp);
int childRowNumber = [childRowTmp intValue];
NSLog(#"child row # %i", childRowNumber);
}
Any help or tips would be great, thank you.
UPDATE:
here is how I create the button using my button class file I created.
//Create Blue Button.
NSString *rand = [self genRandStringLength:10];
newButton = [[ButtonNode alloc] initWithButtonType:1 column:3 row:i uniqueID:rand];
newButton.name = rand;
[column3 addChild:newButton]; //add the button to correct column
[column3Array addObject:newButton];
blueTotal++;
totalButtons++;
column3Total++;
here is the custom class file where the Object is created.
-(id)initWithButtonType:(int)buttonType column:(int)buttonColumn row:(int)buttonRow uniqueID:(NSString *)uniqueID {
self = [super init];
uniqueStr = uniqueID;
rowNumber = buttonRow;
columnNumber = 3; //this is just hard coded for testing
buttonNumber = 1;
[self addButtonBlue];
}
here is the part of the class that creates and adds the button
- (void) addButtonBlue {
SKSpriteNode *button;
//button type 1
button = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:kBoxSize];
button.name = uniqueStr;
button.physicsBody.categoryBitMask = blueCategory;
button.physicsBody.contactTestBitMask = blueCategory;
button.physicsBody.collisionBitMask = blueCategory | redCategory | yellowCategory | greenCategory | orangeCategory;
NSString *tmpColumn = [NSString stringWithFormat:#"%i",columnNumber];
NSString *tmpType = [NSString stringWithFormat:#"%i",buttonNumber];
NSString *tmpRow = [NSString stringWithFormat:#"%i",rowNumber];
button.userData = [NSMutableDictionary dictionary];
[button.userData setValue:uniqueStr forKey:#"buttonID"];
[button.userData setValue:tmpType forKeyPath:#"buttonType"];
[button.userData setValue:tmpColumn forKeyPath:#"buttonColumn"];
[button.userData setValue:tmpRow forKey:#"buttonRow"];
button.position = CGPointMake(xPos , yPos );
[self addChild:button];
}
I am having the same issue, see below the code which generates a green or red sprite. Attempting to give it the colour value within the SKSpriteNode.userData. In my log output the userData is (null)!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
For a workaround I guess we could create a reference dictionary where the SKSpriteNode instances are the keys and a NSDictionary of data are the values.
//----- * UPDATE ** -------
And theres no wonder, after the userData was initialised, another sprite was being initialised in its place, leaving the userData nil!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite;
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
I am using SpriteKit.
The code below basically makes a lattice of dots on the screen. However, I want to call each 'dot' a different name based on its position, so that I can access each dot individually in another method. I'm struggling a little on this, so would really appreciate if someone could point me in the right direction.
#define kRowCount 8
#define kColCount 6
#define kDotGridSpacing CGSizeMake (50,-50)
#import "BBMyScene.h"
#implementation BBMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
// Background
self.backgroundColor = [SKColor colorWithRed:0.957 green:0.957 blue:0.957 alpha:1]; /*#f4f4f4*/
CGPoint baseOrigin = CGPointMake(35, 385);
for (NSUInteger row = 0; row < kRowCount; ++row) {
CGPoint dotPosition = CGPointMake(baseOrigin.x, row * (kDotGridSpacing.height) + baseOrigin.y);
for (NSUInteger col = 0; col < kColCount; ++col) {
SKSpriteNode *dot = [SKSpriteNode spriteNodeWithImageNamed:#"dot"];
dot.position = dotPosition;
[self addChild:dot];
//6
dotPosition.x += kDotGridSpacing.width;
}
}
}
return self;
}
Here is an image of what appears on screen when I run the above code...
http://cl.ly/image/3q2j3E0p1S1h/Image1.jpg
I simply want to be able to call an individual dot to do something when there is some form of user interaction, and I'm not sure how I would do that without each dot having a different name.
If anyone could help I would really appreciate it.
Thanks,
Ben
- (void)update:(NSTimeInterval)currentTime {
for(SKNode *node in self.children){
if ([node.name containsString:#"sampleNodeName"]) {
[node removeFromParent];
}
}
}
Hope this one helps!
You can set the name property of each node inside the loop.
Then you can access them with self.children[index].
If you want to find a specific node in your children, you have to enumerate through the array.
Update:
To clarify how to search for an item by iterating, here is a helper method:
- (SKNode *)findNodeNamed:(NSString *)nodeName
{
SKNode *nodeToFind = nil;
for(SKNode *node in self.children){
if([node.name isEqualToString:nodeName]){
nodeToFind = node;
break;
}
}];
return nodeToFind;
}
I was wondering how to call a method after a CCAction is finished (I would also like to call it every 2.5 seconds after it is first called). I have a sprite that goes to a random position, and I want it to run this method (a shooting bullet one) after it is done moving to its random position. So far the method is called when it is still moving. Can anyone help?
Here is the enemy create method:
(void)enemy1{
gjk= arc4random()%6;
enemy1 = [CCSprite spriteWithFile:#"enemy1.png"];
int d = arc4random()%480+480;
int o = arc4random()%320+320;
x = arc4random()%480;
if( x <= 480 && x>= 460){
x=x-100;
}
if(x <= 100){
x = x+50;
}
y = arc4random()%320;
if(y <=320 && y >= 290){
y = y-100;
}
if(y < 100){
y = y + 100;
}
enemy1.position = ccp(o,d);
[enemy1 runAction:[CCMoveTo actionWithDuration:3 position: ccp(x,y)]];
CCRotateBy *rotation = [CCRotateBy actionWithDuration:20 angle:1080];
CCRepeatForever * repeatforever = [CCRepeatForever actionWithAction:rotation];
[enemy1 runAction:repeatforever];
[self addChild:enemy1];
[enemy addObject :enemy1];
}
And the method for shooting a projectile:
(void)projectileShooting:(ccTime)dt {
projcount++;
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
}
}
The shooting method is called in the init method by the code, so it is called every 2.5 seconds.
[self schedule:#selector(projectileShooting:) interval:2.5];
I know I tried to make the shooting happen by making it so that is shoots when the y position is < 320, but it is still moving when it passes the position of 320.
You can make a sequence of actions and at the end of sequence you can give a callback function which will be executed at the end. Something like this
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(methodToRunAfterAction)],
nil]];
Adding on to Tayyab's answer, you can simply start the actual scheduling of your projectile firing within the startShooting method (or whatever you want to call it) which is fired at the end of your move action:
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(startShooting)],
nil]];
where startShooting is defined as follows:
- (void) startShooting
{
// start scheduling your projectile to fire every 2.5 seconds
[self schedule:#selector(projectileShooting:) interval:2.5];
}
First of all, I'm an Objective-C novice. So I'm not very familiar with OS X or iOS development. My experience is mostly in Java.
I'm creating an agent-based modeling-framework. I'd like to display the simulations and to do that I'm writing a little application. First, a little bit about the framework. The framework has a World class, in which there is a start method, which iterates over all agents and has them perform their tasks. At the end of one "step" of the world (i.e., after all the agents have done their thing), the start method calls the intercept method of an object that implements InterceptorProtocol. This object was previously passed in via the constructor. Using the interceptor, anyone can get a hook into the state of the world. This is useful for logging, or in the scenario that I'm trying to accomplish: displaying the information in a graphical manner. The call to intercept is synchronous.
Now as far as the GUI app is concerned, it is pretty simple. I have a controller that initializes a custom view. This custom view also implements InterceptorProtocol so that it can listen in, to what happens in the world. I create a World object and pass in the view as an interceptor. The view maintains a reference to the world through a private property and so once I have initialized the world, I set the view's world property to the world I have just created (I realize that this creates a cycle, but I need a reference to the world in the drawRect method of the view and the only way I can have it is if I maintain a reference to it from the class).
Since the world's start method is synchronous, I don't start the world up immediately. In the drawRect method I check to see if the world is running. If it is not, I start it up in a background thread. If it is, I examine the world and display all the graphics that I need to.
In the intercept method (which gets called from start running on the background thread), I set setNeedsToDisplay to YES. Since the start method of the world is running in a separate thread, I also have a lock object that I use to synchronize so that I'm not working on the World object while it's being mutated (this part is kind of janky and it's probably not working the way I expect it to - there are more than a few rough spots and I'm simply trying to get a little bit working; I plan to clean up later).
My problem is that the view renders some stuff, and then it pretty much locks up. I can see that the NSLog statements are being called and so the code is running, but nothing is getting updated on the view.
Here's some of the pertinent code:
MasterViewController
#import "MasterViewController.h"
#import "World.h"
#import "InfectableBug.h"
#interface MasterViewController ()
#end
#implementation MasterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_worldView = [[WorldView alloc] init];
World* world = [[World alloc] initWithName: #"Bhumi"
rows: 100
columns: 100
iterations: 2000
snapshotInterval: 1
interceptor: _worldView];
for(int i = 0; i < 999; i++) {
NSMutableString* name = [NSMutableString stringWithString: #"HealthyBug"];
[name appendString: [[NSNumber numberWithInt: i] stringValue]];
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: name
layer: #"FirstLayer"
infected: NO
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
}
NSLog(#"Added all bugs. Going to add infected");
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: #"InfectedBug"
layer: #"FirstLayer"
infected: YES
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
[_worldView setWorld: world];
//[world start];
}
return self;
}
- (NSView*) view {
return self.worldView;
}
#end
WorldView
#import "WorldView.h"
#import "World.h"
#import "InfectableBug.h"
#implementation WorldView
#synthesize world;
- (id) initWithFrame:(NSRect) frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void) drawRect:(NSRect) dirtyRect {
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
CGContextClearRect(myContext, CGRectMake(0, 0, 1024, 768));
NSUInteger rows = [world rows];
NSUInteger columns = [world columns];
NSUInteger cellWidth = 1024 / columns;
NSUInteger cellHeight = 768 / rows;
if([world running]) {
#synchronized (_lock) {
//Ideally we would need layers, but for now let's just get this to display
NSArray* bugs = [world bugs];
NSEnumerator* enumerator = [bugs objectEnumerator];
InfectableBug* bug;
while ((bug = [enumerator nextObject])) {
if([bug infected] == YES) {
CGContextSetRGBFillColor(myContext, 128, 0, 0, 1);
} else {
CGContextSetRGBFillColor(myContext, 0, 0, 128, 1);
}
NSLog(#"Drawing bug %# at %lu, %lu with width %lu and height %lu", [bug name], [bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight);
CGContextFillRect(myContext, CGRectMake([bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight));
}
}
} else {
[world performSelectorInBackground: #selector(start) withObject: nil];
}
}
- (BOOL) isFlipped {
return YES;
}
- (void) intercept: (World *) aWorld {
struct timespec time;
time.tv_sec = 0;
time.tv_nsec = 500000000L;
//nanosleep(&time, NULL);
#synchronized (_lock) {
[self setNeedsDisplay: YES];
}
}
#end
start method in World.m:
- (void) start {
running = YES;
while(currentIteration < iterations) {
#autoreleasepool {
[bugs shuffle];
NSEnumerator* bugEnumerator = [bugs objectEnumerator];
Bug* bug;
while((bug = [bugEnumerator nextObject])) {
NSString* originalLayer = [bug layer];
NSUInteger originalX = [bug x];
NSUInteger originalY = [bug y];
//NSLog(#"Bug %# is going to act and location %i:%i is %#", [bug name], [bug x], [bug y], [self isOccupied: [bug layer] x: [bug x] y: [bug y]] ? #"occupied" : #"not occupied");
[bug act];
//NSLog(#"Bug has acted");
if(![originalLayer isEqualToString: [bug layer]] || originalX != [bug x] || originalY != [bug y]) {
//NSLog(#"Bug has moved");
[self moveBugFrom: originalLayer atX: originalX atY: originalY toLayer: [bug layer] atX: [bug x] atY: [bug y]];
//NSLog(#"Updated bug position");
}
}
if(currentIteration % snapshotInterval == 0) {
[interceptor intercept: self];
}
currentIteration++;
}
}
//NSLog(#"Done.");
}
Please let me know if you'd like to see any other code. I realize that the code is not pretty; I was just trying to get stuff to work and I plan on cleaning it up later. Also, if I'm violating an Objective-C best practices, please let me know!
Stepping out for a bit; sorry if I don't respond immediately!
Whew, quiet a question for probably a simple answer: ;)
UI updates have to be performed on the main thread
If I read your code correctly, you call the start method on a background thread. The start method contains stuff like moveBugFrom:... and also the intercept: method. The intercept method thus calls setNeedsDisplay: on a background thread.
Have all UI related stuff perform on the main thread. Your best bet is to use Grand Central Dispatch, unless you need to support iOS < 4 or OS X < 10.6 (or was it 10.7?), like this:
dispatch_async(dispatch_get_main_queue(), ^{
// perform UI updates
});
-(void)spriteMoveFinishedid)sender //when sprite is outside of screen,it's deleted;
{
CCSprite *sprite = (CCSprite *)sender;
if (sprite.tag == 1) //it's enemy;
{
escapeNum++;
[self removeChild:sprite cleanup:YES];
if (escapeNum == 10)
{
[self stopGameEx]; //Because the speed of every enemy isn't same to other and there are bullets,it maybe happens that two sprites disappear at the same time, and the program stop at this with error --- program received signal:“EXC_BAD_ACCESS”。
}
//...........
}
}
How to resolve it?
Inside if loop, change sprite tag to mark it as expired sprite, and some where else remove.
enum
{
kTagExpiredSprite = 999 //any number not ur sprite tag(1)
};
.....
-(void)spriteMoveFinishedid)sender //when sprite is outside of screen,it's deleted;
{
CCSprite *sprite = (CCSprite *)sender;
if (sprite.tag == 1) //it's enemy;
{
escapeNum++;
sprite.tag = kTagExpiredSprite;
if (escapeNum == 10)
{
[self stopGameEx]; //Because the speed of every enemy isn't same to other and there are bullets,it maybe happens that two sprites disappear at the same time, and the program stop at this with error --- program received signal:“EXC_BAD_ACCESS”。
}
//...........
}
}