So I'm at the point where I'm starting to reduce my spaghetti code down.
So right now, I have 11 different dinosaur images that I've put into an NSMutableArray using a "for" loop. I've also declared CCSprite instance variables in which I was hoping I can set each dinosaur image to so that I can check bounding boxes, set positions, etc. So I pointed each object from the array to an instance variable So far, I have this code:
.h file:
CCSprite *dinosaur1_c;
CCSprite *dinosaur2_c;
CCSprite *dinosaur3_c;
CCSprite *dinosaur4_c;
CCSprite *dinosaur5_c;
CCSprite *dinosaur6_c;
CCSprite *dinosaur7_c;
CCSprite *dinosaur8_c;
CCSprite *dinosaur9_c;
CCSprite *dinosaur10_c;
CCSprite *dinosaur11_c;
.m file
NSMutableArray *dinoSprites = [[NSMutableArray alloc] init];
for( int i = 1, j = 0; i <= 11 && j <= 10; i++, j++ )
{
id dino = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"dinosaur%d-c.png", i]];
[dinoSprites addObject:dino];
[sceneSpriteBatchNode addChild:dino];
}
dinosaur1_c = (CCSprite *)[dinoSprites objectAtIndex:0];
dinosaur2_c = (CCSprite *)[dinoSprites objectAtIndex:1];
dinosaur3_c = (CCSprite *)[dinoSprites objectAtIndex:2];
dinosaur4_c = (CCSprite *)[dinoSprites objectAtIndex:3];
dinosaur5_c = (CCSprite *)[dinoSprites objectAtIndex:4];
dinosaur6_c = (CCSprite *)[dinoSprites objectAtIndex:5];
dinosaur7_c = (CCSprite *)[dinoSprites objectAtIndex:6];
dinosaur8_c = (CCSprite *)[dinoSprites objectAtIndex:7];
dinosaur9_c = (CCSprite *)[dinoSprites objectAtIndex:8];
dinosaur10_c = (CCSprite *)[dinoSprites objectAtIndex:9];
dinosaur11_c = (CCSprite *)[dinoSprites objectAtIndex:10];
This bit of code does work, but I'm sure it can be reduced. How would I be able to set each of these instance variables using the "for" loop?
I'm using these instance variables in other methods to set positions, check collisions/intersects, fade ins, etc.
I put an equivalent-code to better explain what I'm trying to do:
NSMutableArray *dinoSprites = [[NSMutableArray alloc] init];
for( int i = 1, j = 0; i <= 11 && j <= 10; i++, j++ )
{
id dino = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"dinosaur%d-c.png", i]];
[dinoSprites addObject:dino];
[sceneSpriteBatchNode addChild:dino];
// Set instance variables
dinosaur%i_c = (CCSprite *)[dinoSprites objectAtIndex:j];
}
Is there a way to achieve what I am asking? After 2.5 hours of searching, I still have come up with nothing. Just finding solutions for animation frames.
Am I missing something small or should I have a different way to point to each image in the array to set their positions, fade ins, check bounding boxes, etc?
Any ideas / inputs are greatly appreciated!! Thanks for taking the time to read this! :D
Instead of having eleven CCSprite instance variables, you should just have one NSArray.
So in your .h:
#property (strong) NSArray *dinoSprites;
In your .m, do a
#synthesize dinoSprites;
and then replace the code from your question with:
NSMutableArray *newDinoSprites = [[NSMutableArray alloc] init];
for( int i = 1, j = 0; i <= 11 && j <= 10; i++, j++ )
{
id dino = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"dinosaur%d-c.png", i]];
[newDinoSprites addObject:dino];
[sceneSpriteBatchNode addChild:dino];
}
self.dinoSprites = [newDinoSprites copy];
And then, whenever you need to refer to what you used to call, say, dinosaur8_c you would instead just use (CCSprite *)[dinoSprites objectAtIndex:7].
Is there a way to achieve what I am asking? After 2.5 hours of
searching, I still have come up with nothing. Just finding solutions
for animation frames.
You seem to want a variable whose value is another variable, so that you can iterate over a set of ivars. I can think of two ways to do something like what you're asking.
The first way is to use the address of each variable, i.e. a pointer to each variable. Since pointers themselves are scalar values, not objects, you'd need to use a C-style array:
CCSprite *ivars[] = {&dinosaur1_c, &dinosaur2_c, &dinosaur3_c, &dinosaur4_c};
for (int i = 0; i < 4; i++) {
*(ivars[i]) = [dinoSprites objectAtIndex:i];
}
The second way is to use key value coding. Construct the name of each ivar as a string, and then use that as the key in a call to -setValue:forKey: like this:
NSString *name;
for (int i = 0; i < [dinoSprites count]; i++) {
name = [NSString stringWithFormat:#"dinosaur%d_c", i];
[self setValue:[dinoSprites objectAtIndex:i] forKey:name];
}
All that said, I'd strongly encourage you to not take either of the preceding two approaches to your problem. It's very likely that there are much better solutions to your problem that don't involve having a separate ivar for each dinosaur. You've already got the dinosaurs in an array, so there's no need to create a separate ivar for each. As a general rule, if you ever find yourself creating numbered variables like this, you should probably step back and rethink what you're doing -- numbering your variables is a strong signal that you should be using an array instead.
So, in your case, I don't see any reason that you couldn't use the array you already have everywhere in your code. Instead of dinosaur6_c, you can obviously use [dinoSprites objectAtIndex:6]. But you'll really clean up your code if you think of ways to treat all the dinosaurs with the same code, so that you're never hard-coding specific indicies into your array. For example, if you need to set each dinosaur to a different position, there are at least two good ways to go about it. One is to compute the position, if possible, based on the index. You can do this if you're laying out the dinosaurs in a regular way, like on a 3 x n grid:
for (int i = 0; i < [dinoSprites count]; i++) {
int row = i / DINOS_PER_ROW;
int column = i % DINOS_PER_ROW;
CGPoint position = CGPointMake(row * rowHeight, column * columnWidth);
[[dinoSprites objectAtIndex:i] setPosition:position];
}
If the position of each dinosaur isn't computable from its index in the array, then you're probably currently hard-coding the positions in your code. You can clean that up by at least moving the positions to an array, and perhaps by reading that array in from a data file. That will make your code much shorter, easier to understand, and easier to maintain:
NSArray *positions = [NSArray arrayWithContentsOfURL:someURL]; // read positions from a property list
NSAssert([positions count] >= [dinoSprites count], #"Not enough positions!");
for (int i = 0; i < [dinoSprites count]; i++) {
CGPoint position = [[positions objectAtIndex:i] pointValue];
[[dinoSprites objectAtIndex:i] setPosition: position;
}
Of course, I'm just using the position of the sprite here as an example. You can do exactly the same thing with any attribute that you'd want to apply to a dinosaur, and you can even do them all at once. Instead of a positions array that you load from a property list, you could load an array of dictionaries, where each dictionary contains a number of attributes (color, favorite food, speed, hunger level, etc.).
Related
Problem
I've never made a card game before, and I'm having quite some difficulty at the moment.
However, I've managed to create the deck and such.
-(NSMutableArray *)arrayWithDeck:(id)sender
{
[sender removeAllObjects];
NSArray *faces = [[NSArray alloc] initWithObjects:#"A",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K", nil];
NSArray *suits = [[NSArray alloc] initWithObjects:#"h",#"d",#"c",#"s", nil];
for (int i = 0; i < 52; i++) {
NSString *cardToAdd = [NSString stringWithFormat:#"%#%#", faces[i % 13], suits[i / 13]];
[sender addObject:cardToAdd];
}
return sender;
}
Then deal the cards to the players ( just to one player at the moment )
-(void) dealPlayersWithPlayers: (int) players withDealtCards: (int) dealt {
if (players == 2){
__block float add = 0;
for (int i = 0; i < dealt; i++) {
add = add + ((self.frame.size.width / 100) * 10);
NSString *imageName = [NSString stringWithFormat:#"%#", deck[i]];
NSString *bundle = [[NSBundle mainBundle] pathForResource:imageName ofType:#"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:bundle];
SKTexture *texture = [SKTexture textureWithImage:image];
cardDisplay = [SKSpriteNode spriteNodeWithTexture:texture];
cardDisplay.size = CGSizeMake(104, 144);
cardDisplay.anchorPoint = CGPointMake(0.5, 0.5);
cardDisplay.position = CGPointMake(-self.frame.size.width/2.5 + add, -218);
cardDisplay.zPosition = 1;
cardDisplay.userInteractionEnabled = NO;
cardDisplay.name = [NSString stringWithFormat:#"card"];
[self addChild:cardDisplay];
}
}
}
Then when touchesBegan, on a card it animates to the centre of the screen, i.e a pre-phase for a "lay cards" button. However, I'm really struggling to figure a way to keep track of the card pressed. I.e, is the card Js, Ah, 8c or whatever it could be so it can obviously be used, However the SKSpriteNode.name is already taken for the detection on touchesBegan.
Another issue I'm having is when the cards are layed. They are messing up the z-index. However, an easy fix for this could be to just keep incrementing the z-index but is it the best way of doing it? My example here shows what I'm talking about.
I would say the fact that you have them rendering and moving up you are off to a great start.
My recommendation though would be to look at a MVC (Model View Controller) approach to the problem. Keep the info for cards played and what the player has for cards in a separate object from your view. That way when you touch your card you can have your controller work with your model to identify it and decide what happens next. It will be very hard to manage the game if you are relaying only on the SKSpriteNode and looking at its name with no pointer to it to compare to anything.
As far as sorting your z index your model would know which card was added first in your hand and your controller can then inform the view the appropriate position and z index.
At the very least I would consider subclassing SKSpriteNode and make a CardSpriteNode at least then you don't have to look at the sprite name for touch and could just check to see if it is a CardSpriteNode.
if ([node isKindOfClass:[CardSpriteNode class]])
//handle touch logic if a card
MVC is a simple concept and I would look at some write ups on that. Everyone has a slightly different approach to what can see what, but all agree on keeping the information separate.
Hope that helps.
I have an array called '_orderOfCardPlacement' which gets objects put in it after every turn of my game. These objects are UIButtons which have a number of details such as setImage, setBounds, setCenter and most importantly to this question setTag. There are two possible outcomes for setTag either '1' or '2', They are initially set with one or the other and this can be changed via another method which is called every turn. What i need to do is look through this array and see how many times '1' has come up and how many times '2' has come up and then compare that result. So if '1' comes up nine times and '2' comes up 7 times then '1' wins in this case. So when i NSLog my array (say after one turn) i get this:
"<OBShapedButton: 0x7ff6f968e6a0; baseClass = UIButton; frame = (103 387.5; 150 135); opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; tag = 1; layer = <CALayer: 0x7ff6f968e8a0>>"
and subsequent turns will add more buttons to the array. But I need to know once this array is full and the game is complete how to access the 'tag = ' part of the objects to compare. Thanks for any advice, ill add more code if necessary!
Here's the solution in full, if there's a more elegant way to do what i'm doing here, definitely interested to know!
//set the amount of cards each player has
_howManyCardsForComputer = 0;
_howManyCardsForPlayer = 0;
for (int i = 0; i < _orderOfCardPlacement.count; i++) {
UIButton *auxButton = (UIButton *) [_orderOfCardPlacement objectAtIndex:i];
NSInteger playerScore = auxButton.tag;
NSMutableArray *totalScoreArray = [[NSMutableArray alloc] init];
[totalScoreArray addObject:[NSNumber numberWithInteger:playerScore]];
for (int j = 0; j < totalScoreArray.count; j++) {
if ([[totalScoreArray objectAtIndex:j] isEqualToNumber:[NSNumber numberWithInteger:1]]) {
_howManyCardsForPlayer++;
}
if ([[totalScoreArray objectAtIndex:j] isEqualToNumber:[NSNumber numberWithInteger:2]]) {
_howManyCardsForComputer++;
}
}
}
_playerScore.text = [NSString stringWithFormat:#"Players cards %i", _howManyCardsForPlayer];
_playerScore.hidden = NO;
_computerScore.text = [NSString stringWithFormat:#"Computers cards %i", _howManyCardsForComputer];
_computerScore.hidden = NO;
I am not sure if I understood clearly what you want to achieve. If your buttons are stored in a NSMutableArray, you can check their tags by geting the i-th element (inside a for-loop) and apply a casting.
NSMutableArray *arrayWithButtons = [[NSMutableArray alloc] init];
//... Fill the array
//Check tags
for(int i=0; i<numButtons; i++){
UIButton *auxButton = (UIButton*) [arrayWithButton objectAtIndex:i];
int auxTag = auxButton.tag;
//... perform the proper operations
}
I hope I understand it well, and this can help you.
I have picked up programming as a hobby, so please bear with any 'old school' or completely wrong practices!
I am trying to move three images across the view using a timer. They then need to be dodged by another UIImageView. The obstacles are in an array:
objArray = [NSMutableArray arrayWithObjects:[UIImage imageNamed:#"img-1.png"],
[UIImage imageNamed:#"img-2.png"],
[UIImage imageNamed:#"img-3.png"], nil];
I then have a for loop to create these views, and it's here that I think the problems start.
count = [objArray count];
for(int i=0; i < count; i++)
{
NSLog (#"Element %i = %#", i, [objArray objectAtIndex: i]);
randX = arc4random_uniform(450);
randX = randx + 50;
randY = arc4random_uniform(236);
randY = randy + 45;
imgObj = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[objArray objectAtIndex:i]]];
imgObj.center = CGPointMake(randX, randY);
[self.view addSubview:imgObj];
[self startAnimation];
All three images display ok. The problem is getting these to move.
I understand the need for the timer, which is initiated in the startAnimation method. I have tried setting a tag in the for loop. However this always results in the last object "img-3.png", which then moves ok.
My question is: how do I differentiate the three views I create in the loop so I can call them elsewhere?
Assigning tags to you UIImageViews should work:
imgObj.tag = i+1; //skipping 0 here since other subviews may have this tag
Then you should be able to access the views via
[self.view viewWithTag:tag];
Or you could create another NSMutableArray and store the UIImageViews in it.
in the project i'm working on I have multiple characters you can play as (in this case I will refer to them as drones). In my gamescene I have all the physics setup through _drone. A .CCB file created with spritebuilder. Previously I had this file animated through spritebuilder, but now with the addition of multiple drones to choose from I need to set it to display and cycle through the appropriate frames. I've been looking all day for things related to this and most answers I see are for cocos2d v2.0 or when I get something that has no errors showing in Xcode, it doesn't apply it to my _drone class. What I'm looking to do is something like this: [_drone setSpriteFrame:[CCSpriteFrame frameWithImageNamed:#"DefectiveDroneSpriteSheet/Drone1.png"]]; As this sets the frame for _drone to what I want. However I don't know how to make this cycle between the frames.
I found an answer on here recently that showed how to do it:
NSMutableArray *animationFrames = [NSMutableArray array];
for(int i = 1; i <= FRAMES; ++i)
{
CCSpriteFrame *spriteFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"animationFrame%d.png", i]]; //
}
//Create an animation from the set of frames you created earlier
CCAnimation *animation = [CCAnimation animationWithSpriteFrames: animationFrames delay:delay];
//Create an action with the animation that can then be assigned to a sprite
CCActionAnimate *animationAction = [CCActionAnimate actionWithAnimation:animation];
CCActionRepeatForever *repeatingAnimation = [CCActionRepeatForever actionWithAction:animationAction];
[self runAction:repeatingAnimation];
Unfortunately this didn't work for me, but maybe I did something wrong. I seemed to have the most issue with the for loop/ a warning saying variable spriteFrame is never used, or something like that. The problem with this code is that after hours of messing with it and trying to find updated documentation I couldn't figure out how to do what my first example did, apply it directly to _drone. So with all that said... what should I do about solving this? Is there another easier way that i'm overlooking?
Thanks for your time, much appreciated!
edit for Guru:
Hello, thanks for responding. This is what my for loop looks like currently that is throwing me 4 warnings.
for(int i = 1; i <= 2; ++i)
{
CCSpriteFrame *frame1 = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"DefectiveDroneSpriteSheet/Drone1.png", i]]; //
CCSpriteFrame *frame2 = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"DefectiveDroneSpriteSheet/Drone1.png", i]]; //
}
I get a warning "unused variable for frame 1 and frame 2" and "data argument not used by format string" for the 'i'. Also doing this, how do I make this animation apply to my _drone object? As the object is already placed in the scene through Spritebuilder?
Second Edit:
- (void)didLoadFromCCB {
NSMutableArray *animationFrames = [NSMutableArray array];
for(int i = 1; i <= 2; ++i)
{
CCSpriteFrame *spriteFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"DroneSpriteSheets%d.png", i]]; //
[animationFrames addObject: spriteFrame];
}
//Create an animation from the set of frames you created earlier
CCAnimation *animation = [CCAnimation animationWithSpriteFrames: animationFrames delay:1.0f];
//Create an action with the animation that can then be assigned to a sprite
CCActionAnimate *animationAction = [CCActionAnimate actionWithAnimation:animation];
CCActionRepeatForever *repeatingAnimation = [CCActionRepeatForever actionWithAction:animationAction];
[_drone runAction:repeatingAnimation];
//-----------------------------------------------------------
Thats the relevant part of my code, i get this error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
You are not adding frames in array:
for(int i = 1; i <= FRAMES; ++i)
{
CCSpriteFrame *spriteFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"animationFrame%d.png", i]]; //
[animationFrames addObject: spriteFrame];
}
I want to implement functionality so that i can add/remove vertices to/from a vertex array during runtime.
Is there a common way of doing this?
The recommended format for vertex data seems to be C arrays of structs,
so i've tried the following. Keep a pointer to an array of Vertex structs as property:
#property Vertex *vertices;
and then make a new array and copy the data over
- (void) addVertex:(Vertex)newVertex
{
int numberOfVertices = sizeof(vertices) / sizeof(Vertex);
Vertex newArray[numberOfVertices + 1];
for (int i = 0; i < numberOfVertices; i++)
newArray[i] = vertices[i];
newArray[numberOfVertices] = newVertex;
self.vertices = newArray;
}
but no luck. I'm not exactly confident in C so probably this is really trivial..
This is how I just did it:
//verts is an NSMutableArray and I want to have an CGPoint c array to use with
// glVertexPointer(2, GL_FLOAT, 0, vertices);... so:
CGPoint vertices[[verts count]];
for(int i=0; i<[verts count]; i++)
{
vertices[i] = [[verts objectAtIndex:i] CGPointValue];
}
here's how i do it now:
// re-allocate the array dynamically.
// realloc() will act like malloc() if vertices == NULL
Vertex newVertex = {{x,y},{r,g,b,a}};
numberOfVertices++;
vertices = (Vertex *) realloc(vertices, sizeof(Vertex) * numberOfVertices);
if(vertices == NULL) NSLog(#"FAIL allocating memory for vertex array");
else vertices[numberOfVertices - 1] = newVertex;
// clean up memory once i don't need the array anymore
if(vertices != NULL) free(vertices);
i suppose icnivad's method above is more flexible since you can do more stuff with a NSMutableArray, but using plain C arrays with malloc/realloc should be (much?) faster .