PDA

View Full Version : Multiple shapes/objects




roboticapps1
Oct 4, 2010, 06:02 PM
Hi, I'm fairly new to programming with cocoa and I wanted to try programming simple things for the iOS.

I had a question about filling the screen with multiple shapes/objects. I've created a shape, specifically a star, and I have it moving around the screen the way I want it to. Now I want to populate the screen with multiple stars and add stars as time passes and delete or get rid of old stars that have been used or destroyed (I haven't thought about all the ins and outs of the game). In any case, I've been thinking about how to continually add stars and I wanted some feedback. Thought I might get some good advice before wasting hours of researching/coding.

I'm sorry I don't have code to post but the code I have is only for the star object and I didn't think that would help.

Thanx



Jman012
Oct 4, 2010, 08:00 PM
What part of it do you not know what to do? If it's actually adding separate stars, then it's actually quite easy. You'll have to add a new class, and derive it from UIView. Make the variables you want, and draw the star in the .m, then all you have todo is alloc the star in your ViewController.


//Star.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class Star;


@interface Star : UIView {

}

- (void)destroyStar;

@end


//Star.m
#import "Star.h"

@implementation Star

//Functions on the load to draw star, e.g. awakeFromNib, init, etc

- (void)destroyStar {
[self removeFromSuperView];
[self release];
}

@end

And now in your ViewController, setup a simple timer, I'm pretty sure it's NSTimer. and every so often just add your Star like a UIView
Star *tempStar = [[Star alloc] initWithFrame:CGRectMake(50, 50, 64, 64)];

It now SHOULD work, I may be a little rusty on the release thing. If not, just post again. But this code is basically like a ViewController, or any other modified view. You have the simple methods such as dealloc, awakeFromNib, init, and your own methods too. Hope this helped!

roboticapps1
Oct 4, 2010, 08:21 PM
thanx for the reply and thanx for the help. I actually have this part down. I might not be understanind objective-c completely so please correct if I'm wrong. The part that I'm actually stuck on is how to make an unknown number of stars. for example, it seems irrational to declare 50+ tempStars...


Star *tempStar1...
Star *tempStar2...
Star *tempStar3...


And this way, I would have to set a fixed number of 'tempStars' but I would like to keep making stars as long as the game doesn't end.

And since you brought up the topic of NSTimer, I've trying to set a time delay but it doesn't work the way I would have thought. For example...


For (int i=0; i<5; i++) {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(addObject) userInfo:nil repeats:NO];

...
}


this actually executes the timer 5 times immediately and then there is a 2 second delay on the iPhone, which is not that I was expecting. Would appreciate any input.

Thanx

I should mention that I also tried


[self performSelector:@selector(addObject) withObject:nil afterDelay:2.0];


with the same results

Jman012
Oct 4, 2010, 09:02 PM
Ok, sorry I didn't make myself clear in my other post. And I quickly used an NSTimer example form here http://stackoverflow.com/questions/1449035/how-do-i-use-nstimer

Anyways, this is how you would go about calling it:


- (void)viewDidLoad {
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(createStar:)
userInfo:nil
repeats:YES];
}

- (void)createStar:(NSTimer *)timer {
Star *tempStar = [[Star alloc] initWithFrame:CGRectMake(50,50,64,64)];
[tempStar makeAndAnimStar];
[self.view addSubview:tempStar];
[tempStar release];
}


1. makeAndAnimStar will be a method that you make in Star.m, which will consist of draw the star, then animating it however you need it to. You can then, inside the Star.m, set up another timer and when it ends, destroy your Star with [self removeFromSuperView]; and [self release];
2. I'm not entirely sure if the release will/not work the right way. You can always play around with it, and be sure to release it again in the dealloc method.
3. What this is doing is just creating different instances of your Star class. The Star class itself can draw and animate itself, so the ViewController only needs to activate it and nothing else.

I don't know if you're using CG to draw the star of if it's just an image, but if it is an image, the way you'd add it is: [self addSubview:imageView];, because self is that instance of Star class.

Feel free to ask any other questions, and anyone else can correct me if I'm wrong on anything.

chown33
Oct 4, 2010, 09:20 PM
thanx for the reply and thanx for the help. I actually have this part down. I might not be understanind objective-c completely so please correct if I'm wrong. The part that I'm actually stuck on is how to make an unknown number of stars. for example, it seems irrational to declare 50+ tempStars...


Star *tempStar1...
Star *tempStar2...
Star *tempStar3...


And this way, I would have to set a fixed number of 'tempStars' but I would like to keep making stars as long as the game doesn't end.

This is one of the things that arrays are good for: working with a collection of objects. You should read up on NSArray and NSMutableArray, and the general idea of collections of objects.

Any decent book that teaches Objective-C and Cocoa will cover arrays and dictionaries. So if you're not using a book, you probably should be, simply so you can learn the basics in a well-guided fashion, instead of guessing at it in order to make a game from an idea. There's plenty of time to write a game later, after you know how to use the building-blocks. You have to write and debug a lot of smaller simpler programs first. You learn to write and debug well by doing it, so you might as well practice on simple things, instead of having to learn it when your complex program fails for some unexplained reason.

Jman012
Oct 4, 2010, 09:21 PM
This is one of the things that arrays are good for: working with a collection of objects. You should read up on NSArray and NSMutableArray, and the general idea of collections of objects.

But although that would work, it's cleaner and more efficient to have instances, as I was taught.

dejo
Oct 4, 2010, 09:39 PM
But although that would work, it's cleaner and more efficient to have instances, as I was taught.
An array is simply a collection of instances. And I would venture it's the one that makes for cleaner and more efficient code.

chown33
Oct 5, 2010, 12:54 AM
But although that would work, it's cleaner and more efficient to have instances, as I was taught.

Please explain how it would be cleaner and more efficient.

And how would arrays prevent you from using instances? An NSArray or NSMutableArray holds instances. An instance is simply an object created by a class. It doesn't mean a separate named variable.

roboticapps1
Oct 5, 2010, 01:04 PM
I thought that a NSMutableArray would be the best case but I wanted to hear someone else's opinion before I got started on it. I was playing with some code last night and I couldn't get things to work. Here's what I have.


//Sprite.h
#import <Foundation/Foundation.h>

@interface Sprite : NSObject {
CGFloat x;
CGFloat y;
CGFloat width;
CGFloat height;
CGFloat speed;
CGRect box;
}

@property (assign) CGFloat x, y, width, height, speed;
@property (assign) CGRect box;

- (void)draw:(CGContextRef)context;

@end

//Sprite.m
#import "Sprite.h"
@implementation Sprite

@synthesize x, y, width, height, box, speed;

- (id)init
{
self = [super init];
if (self) {
x = 0.0;
y = 0.0;
width = 10.0;
height = 10.0;
speed = 1.0;
box = CGRectMake(0, 0, 0, 0);
}
return self;
}

- (void) outlinePath: (CGContextRef) context
{
//this function is not used any longer
//it is has been overloaded with VectorSprites functions
CGFloat w2 = box.size.width*0.5;
CGFloat h2 = box.size.height*0.5;

CGContextBeginPath(context);
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGContextSetRGBFillColor(context, 0, 1, 0, 1);
CGContextMoveToPoint(context, -w2, h2);
CGContextAddLineToPoint(context, w2, h2);
CGContextAddLineToPoint(context, w2, -h2);
CGContextAddLineToPoint(context, -w2, -h2);
CGContextAddLineToPoint(context, -w2, h2);
CGContextClosePath(context);
}

- (void) drawBody: (CGContextRef) context
{
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
[self outlinePath:context]; //book has (context)
CGContextDrawPath(context, kCGPathStroke);

//CGContextSetRGBFillColor(context, 1, 0, 0, 1);
//CGContextDrawPath(context, kCGPathFill);
}

- (void)draw:(CGContextRef)context
{
CGContextSaveGState(context);

CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, x, y);
CGContextConcatCTM(context, t);

[self drawBody: context];

CGContextRestoreGState(context);
}

@end



Then I have a VectorSprite


//VectorSprite.h
#import <Foundation/Foundation.h>
#import "Sprite.h"

@interface VectorSprite : Sprite {
CGFloat *points;
int count;
CGFloat vectorScale;
}

@property (assign) int count;
@property (assign) CGFloat *points;
@property (assign) CGFloat vectorScale; //change size of object

+ (VectorSprite *) withPoints:(CGFloat *) rawPoints count:(int) count;
- (void)updateSize;

@end

//VectorSprite.m
#import "VectorSprite.h"

@implementation VectorSprite
@synthesize points, count, vectorScale;
#define RANDOM_SIZE (arc4random() % 5) + 3

+ (VectorSprite *) withPoints:(CGFloat *)rawPoints count:(int)count
{
VectorSprite *object = [[VectorSprite alloc] init];
object.count = count;
object.points = rawPoints;
object.vectorScale = RANDOM_SIZE;
[object updateSize];

return object;
}

- (void) updateSize
{
CGFloat w, h, minX, minY, maxX, maxY;
w = h;
minX = minY = maxX = maxY = 0.0;
for (int i = 0; i < count; i++) {
CGFloat x1 = points[i*2]*vectorScale;
CGFloat y1 = points[i*2+1]*vectorScale;
if (x1 < minX) minX = x1;
if (x1 > maxX) maxX = x1;
if (y1 < minY) minY = y1;
if (y1 > maxY) maxY = y1;
}
width = ceil(maxX - minX);
height = ceil(maxY - minY);
}

- (void) outlinePath:(CGContextRef)context
{
CGContextBeginPath(context);
CGContextSetRGBFillColor(context, 1, 0, 0, 1);
for (int i = 0; i < count; i++) {
CGFloat x1 = points[i*2]*vectorScale;
CGFloat y1 = points[i*2+1]*vectorScale;
if (i == 0) {
CGContextMoveToPoint(context, x1, y1);
}
else {
CGContextAddLineToPoint(context, x1, y1);
}
}
CGContextClosePath(context);
}

@end



Both the Sprite and the VectorSprite has been working well for - at least the way I have been calling them.

Lastly I have a StarView


//StarView.h
#import <UIKit/UIKit.h>
#import "Sprite.h"


@interface StarView : UIView {
Sprite *star;
//NSMutableArray *stars;
}

//@property (nonatomic, retain) NSMutableArray *stars;

@end

//StarView.m
#import "BallsView.h"
#import "VectorSprite.h"
#import "Sprite.h"

@implementation StarView
#define RANDOM_X arc4random() % 320
#define RANDOM_Y arc4random() % 460

//Points to make Star
//right now it is just a rect
#define kVectorArtCount 4
static CGFloat kVectorArt[] = {
-5,5, 5,5, 5,-5, -5,-5,
};

- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
}
return self;
}

- (id)initWithCoder:(NSCoder *) coder {
if(self = [super initWithCoder:coder]) {
star = [VectorSprite withPoints:kVectorArt count:kVectorArtCount];
star.x = RANDOM_X;
star.y = RANDOM_Y;

self.backgroundColor = [UIColor whiteColor];

[NSTimer scheduledTimerWithTimeInterval:(1.0/60.0) target:self selector:@selector(moving) userInfo:nil repeats:YES];
//[NSTimer scheduledTimerWithTimeInterval:(3.5) target:self selector:@selector(addStars) userInfo:nil repeats:YES];
//could not get other stars/squares on the screen
}
return self;
}

/*
//this only moves the star/square to a new location and the moves
//but it does not keep the original star square
-(void)addBalls
{
star = [VectorSprite withPoints:kVectorArt count:kVectorArtCount];
star.x = arc4random() % 320;
star.y = arc4random() % 460;
}
*/

- (void)moving
{
star.x -= star.speed;
star.y -= star.speed;
[self setNeedsDisplay];
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
[star draw:context];
CGContextRestoreGState(context);
}

- (void)dealloc {
[super dealloc];
[star release];
}

@end


my problem right now is how to get multiple stars/objects/squares on the screen at the same time, moving at the same time, delete objects and create objects at any given time, until time runs out or game play is over. I thought through the idea of a NSMutableArray and even tried to implement it but I think the way I have my code right now, I have problems drawing the object and using the array.

Hope that's a better explanation of the problem.

chown33
Oct 5, 2010, 01:57 PM
You haven't shown any code or description of how you thought of using an array, so there's no way to know what you had a problem with, or to suggest how to solve that problem. That means the best answer will be generalized.

The usual procedure when going from a single thing to a collection of multiple things is to apply the same action to each of the collected things that you were previously applying to the single thing.

So wherever you moved a single star, you now tell each star in the collection to move. Or where you drew one star, you now tell each star to draw.

By the way, the basic design isn't very object-oriented. The star itself should encapsulate how to perform each action it can do, such as moving, drawing itself in a context, etc. How it does something is the instance's responsibility. When it does something is the owner's responsibility, i.e. the owner decides when to tell the star to move or draw, but all it does is tell the star to move or draw itself.

It should be designed so the sending of a message to a collected group will then perform the action on each member of the group. That is, you could create a Constellation subclass of Star with the same set of methods, but it holds multiple Stars in an internal group. Then you tell the Constallation object to move, draw, etc. and it automatically tells every member in its group to do that action. You can add methods that add and remove other Stars from the Constellation, so you can change the group's membership. You could then make Constellations that held other Constellations, since a Constellation has the same interface as a Star. Then the view just has a single Constellation it tells what to do, and everything else about working with individual Stars or Constellations is encapsulated in each specific class.

Finally, you're going to need separate X-speed and Y-speed values, or a direction in addition to speed. 2D motion has two separate and perpendicular components. When there's only one speed value, the direction is always diagonal (equal in X and Y).

roboticapps1
Oct 5, 2010, 02:17 PM
Chown33, thanx for the help.

Maybe I'm trying to bite off more than I can chew. I thought I knew what I was doing but now it doesn't look so promising.

I tried the array a few times but upon failure, I commented the array out and tried to display just two objects to see if I could get that working. I couldn't get it working without declare two different star objects and that's not what I wanted to do. I can get hundreds of stars on the screen if I just declares hundreds of stars in the StarView but that seems wrong and I'm sure that's bad OOP. I completely understand the speed and the direction of the star but since I was just focused on getting two objects that move, I just put in an arbitrary speed value to make sure it was moving.

Since there are a lot of things to work on, what should the first thing be? What should I do make my star object more complete? I understand what you are saying about OOP and encapsulation, but I couldn't get the drawRect method to work because the Sprite is a NSObject so that is the reason I put the moving method in the StarView.

or... should I quit and go back to "Hello World" :/

Thanx

chown33
Oct 5, 2010, 02:56 PM
Post what you did with an array, even if it doesn't work. You haven't provided any useful description, other than that you couldn't get it to work. You haven't provided any code with an array, either. You really need to provide something for us to see what you tried.

If you have nothing at all that shows what you were thinking or trying with arrays, then I'd have to guess you don't understand the basics of arrays, in which case you need to go back to the introductory material covering arrays and work up from there.


What should I do make my star object more complete? I understand what you are saying about OOP and encapsulation, but I couldn't get the drawRect method to work because the Sprite is a NSObject so that is the reason I put the moving method in the StarView.

That doesn't make sense. A Sprite is a subclass of NSObject, but it's also a Sprite, meaning it has all the methods defined for Sprite. You obviously defined a draw method for Sprite, but what else does every Sprite do?

If a Sprite has ivars for location and motion, then surely a Sprite should be responsible for its own management of location and motion. Otherwise it doesn't make sense for it to have the ivars. Should it have properties for location and motion? What types should the properties be, individual CGFloats, or composites like CGPoint? Analyze what you're trying to do and decide for yourself.

If there are methods Sprite should have that apply to all Sprites, then those should be defined in Sprite, not in Star or VectorSprite.

If you're going to create a sprite subsystem, then you really need to think the design through more carefully, and figure out which things go where. That part of the overall design is a separate issue from how to work with collections.

I think you need to break the design down into its has-a and is-a elements. StarView is-a View. StarView has-a collection of Sprites. Sprite is-a NSObject. Sprite has-a XY location and XY speed (2D linear motion). Sprite has-a way to draw itself. Sprite has-a way to move itself. VectorSprite is-a Sprite. VectorSprite has-a set of coordinates that determine its drawn shape. And so on for all the component parts, and all the things each component has, is, or does.
http://en.wikipedia.org/wiki/Has-a
http://en.wikipedia.org/wiki/Is-a


You also have an ordering problem in this code:
- (void)dealloc {
[super dealloc];
[star release];
}
You should always and only call [super dealloc] LAST. Reread the docs on the basics of subclassing NSObject.