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.
Related
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.
Here is what I'm trying to do: I want to have an app that displays a full-screen image and have that image animate by sliding a UISlider. A changing slider value will sequence through images of an array creating an animation. This animation is a single character on a turn-table. The character looks as though he is rotating around the turn table as the slider changes value.
This project is a portfolio piece for a 3D artist. The artist gave me a sequence of 180 images of the character rendered at the full screen retina resolution. He also gave be an additional 180 images rendered at the non-retina full screen resolution. The idea is that when somebody is viewing his character from any angle on a retina iPad, they can toggle using a UISwitch between retina and non-retina display. The code posted below works fine on the simulator.
However when running this code on an iPad 4, it works for a bit, then I get a memory warning. Then it crashes. I'm assuming having that many images of that size being displayed as fast as somebody wants to move a slider is too much for the iPad 4 to handle.
I'm curious what limitations I should take into account when working with images like this. Is 180 images way too many? What is a reasonable amount? Is there a more efficient way of producing my desired result? Instead of resorting the guess-and-check method of how many images would not cause it to crash, I figured somebody might have some useful insight on my issue.
#implementation RBViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_imageDisplay.image = [UIImage imageNamed:#"HyperReal_0.png"];
_arrayRetina = [[NSMutableArray alloc] initWithCapacity:180];
_arrayNormal = [[NSMutableArray alloc] initWithCapacity:180];
for(int i = 0; i < 179; i++)
{
NSString *myImageString = [[NSString alloc] initWithFormat:#"HyperReal_%i.png", i];
UIImage *myImageGraphic = [UIImage imageNamed:myImageString];
[_arrayRetina addObject:myImageGraphic];
}
for(int i = 0; i < 179; i++)
{
NSString *myImageString = [[NSString alloc] initWithFormat:#"lameHyperReal_%i.png", i];
UIImage *myImageGraphic = [UIImage imageNamed:myImageString];
[_arrayNormal addObject:myImageGraphic];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)switchButton:(id)sender {
if (_switchOnForRetina.isOn == YES)
{
_imageDisplay.image = [_arrayRetina objectAtIndex:_sliderBar.value];
}
else
_imageDisplay.image = [_arrayNormal objectAtIndex:_sliderBar.value];
}
- (IBAction)changeSlider:(id)sender {
if (_switchOnForRetina.isOn == YES)
{
_imageDisplay.image = [_arrayRetina objectAtIndex:_sliderBar.value];
}
else
_imageDisplay.image = [_arrayNormal objectAtIndex:_sliderBar.value];
}
#end
Instead of loading all 180 images at once into an array, couldn't you instantiate the current image to be displayed during the slider's continuous UIContolEventValueChanged event?
Create the slider
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, 155, 20)];
slider.continuous = YES;
slider.value = 0.0f;
slider.minimumValue = 0.0f;
slider.minimumValue = 179.0f;
[slider addTarget:self action:#selector(handleContinuousSlider:) forControlEvents:UIControlEventValueChanged];
Handle it's continuous change event
- (void)handleContinuousSlider:(UISlider *)slider {
//create a UIImage with file name that makes the current integer value of the slider
NSString *myImageString = [[NSString alloc] initWithFormat:#"HyperReal_%i.png", slider.value];
UIImage *myImageGraphic = [UIImage imageNamed:myImageString];
//I assume this is a UIImageView
[_imageDisplay setImage:myImageGraphic];
[myImageGraphic release];
}
Prebuffering memory cost is roughtly:
((2048*1536*4 + 1024*768*4)*180)/1024/1024/1024 = 2.63671875 giga bytes
Even if there some underground optimisation going on, your iPad dont have enough memory to safely run that program.
As a work around your crashes, dont preload all images and instead just load required one
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[ self changeSlider: nil ];
}
- (IBAction)changeSlider:(id)sender {
if (_switchOnForRetina.isOn == YES)
{
_imageDisplay.image = nil;
NSString* pResourcePath = [ [ NSBundle mainBundle ] pathForResource: [ NSString stringWithFormat: #"HyperReal_%i", ( int )_sliderBar.value ] ofType: #"png" ];
_imageDisplay.image = [ UIImage imageWithContentsOfFile: pResourcePath ];
}
else
{
_imageDisplay.image = nil;
NSString* pResourcePath = [ [ NSBundle mainBundle ] pathForResource: [ NSString stringWithFormat: #"lameHyperReal_%i", ( int )_sliderBar.value ] ofType: #"png" ];
_imageDisplay.image = [ UIImage imageWithContentsOfFile: pResourcePath ];
}
}
A better solution here would be to create 2 h264 movies from your images sequence
one in forward animation order, second in backward animation order
then play right movie depending on slider animation
Of course that's a lot more work.
Cheers
I'm not quite sure why you need to programmatically switch between Retina and non-Retina, as #2x handles that really well, but I'll assume there is a usage case I'm not familiar with and will leave it at that.
Regarding the memory usage, we're working on an app with similar functionality, and our solution was to populate an array with strings that refer to the image names (or file paths) of all the individual frames. In your case, you would store the reference myImageString rather than myImageGraphic.
Then when you drag your slider, you'd use something like this:
- (IBAction)changeSlider:(id)sender
{
if (_switchOnForRetina.isOn == YES)
_imageDisplay.image = [UIImage imageNamed:[_arrayRetina objectAtIndex:_sliderBar.value]];
else
_imageDisplay.image = [UIImage imageNamed:[_arrayNormal objectAtIndex:_sliderBar.value]];
}
One additional point, we use imageWithContentsOfFile due to lower memory usage as per this post, though imageNamed's caching may help in this case.
EDIT:
As mccrager pointed out, the above method only changes on touch up. Here's an example I'm using, though I'm using UIGestureRecognizer on a UIView to send a percent to the method rather than relying on a slider.
- (void)dragPosition:(float)myPercent
{
NSInteger index = (self.images.count - 1) * myPercent;
NSString *imageName = [self.images objectAtIndex:index];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:#"jpg"];
self.image = [UIImage imageWithContentsOfFile:imagePath];
}
Hope this helps!
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.).
I have 10 UIImageViews on the screen. They are all in an array called posArray. I also have another UIImageView that is dragged by user and can hit those other 10. What is the easies way of displaying a simple NSlog message if the one object hits any of the other 10?
Right now i'm using touchesBegin, touchesMoved to move my one object and this array below to test if the one objects hits any of the other ten.
I'm just thinking that there is an easier, less memory spending way, way of doing this for some reason.
for (int i = 0; i < [posArray count]; i++) {
UIImageView *tempPos;
tempPos = [posArray objectAtIndex:i];
if (CGRectIntersectsRect(red1.frame, tempPos.frame)) {
red1.center = CGPointMake(tempPos.center.x, tempPos.center.y);
NSLog(#"position piece touched");
}
}
You can also use fast enumeration to get some more speed. Also you can add a break statement after you found one match (if you just need one match):
for (UIImageView * tempPos in posArray){
if (CGRectIntersectsRect(red1.frame, tempPos.frame)) {
red1.center = CGPointMake(tempPos.center.x, tempPos.center.y);
NSLog(#"position piece touched");
break;
}
}