This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Generating Random Numbers in Objective-C
Hi I am creating an app where when you press a pimple it travels to a random place in the view. THe pimple is a UIImageView. Here is the code that I put in:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *myTouch = [touches anyObject];
CGPoint point = [myTouch locationInView:pimple];
if ( CGRectContainsPoint(pimple.bounds, point) ) {
[self checkcollision];
}
}
-(void)checkcollision
{
pimple.center = CGPointMake(random(), random());
sad.hidden = NO;
NSURL* popURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"pop" ofType:#"mp3"]];
AudioServicesCreateSystemSoundID((CFURLRef) popURL, &popID);
AudioServicesPlaySystemSound(popID);
}
This code: pimple.center = CGPointMake(random(), random()); does not work. When I press the pimple, the pimple dissapears. Since I have not stated any .hidden for the pimple it must mean it IS moving. But you cant see it, (its outside the view.) Is there something wrong with my code? Am i missing something? Please help.
afaik random() ist just the same as rand() in c which
Returns a pseudo-random integral number in the range 0 to RAND_MAX. [...] RAND_MAX is a constant defined in cstdlib. Its default value may vary between implementations but it is granted to be at least 32767.
As a result you move our pimple to a random position inside a rect that is much bigger than your window. Try this: (I assume that self is a UIView)
pimple.center = CGPointMake(
random() % (unsigned int)self.bounds.size.width,
random() % (unsigned int)self.bounds.size.height);
The %-Operator is the Modulo-Operator.
It seems that you dont seed your pseudo-random-generator which leeds to always the same trajectory on each startup. There is another Thread on stackoverflow which tells us to use arc4random instead of random()/rand()
Related
I'm pretty new to iOS and cocos2d and I'm having a problem trying to create what I want the code to do. Let me give you the rundown first then i'll show what I've got.
What I got so far is a giant sprite in the middle and when that is touched, I want to have say 2000 of a different sprite generate from the center position and like a particle system, shoot off in all directions.
First off, I tried coding implementing the velocity code (written in Objective-c) over to Cocos2d and that didn't work. -code-
-(void)ccTouchBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(CGRectContainsPoint([[self getChildByTag:1] boundingBox], location))
{
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[self addChild:ballGuySprite z:7];
ballGuySprite.position = ccp(((s.width + i *10) /2), (s.height + i *10) /2);
}
}
}
What that does is when I touch the first sprite, 100 of the other sprites are on top of each other leading to the top right corner.
The velocity code that I used when as followed and when I try to apply it to the sprite nothing happens. - Velocity code -
-(void) checkCollisionWithScreenEdges
{
if(ballGuysRect.origin.x <= 0)
{
ballVelocity.x = abs(ballVelocity.x);
}
if(ballGuysRect.origin.x >= VIEW_WIDTH - GUY_SIZE)
{
ballVelocity.x = -1 * abs(ballVelocity.x);
}
if(ballGuysRect.origin.y <= 0)
{
ballVelocity.y = abs(ballVelocity.y);
}
if(ballGuysRect.origin.y >= VIEW_HEIGHT - GUY_SIZE)
{
ballVelocity.y = -1 * abs(ballVelocity.y);
}
}
-(void) updateModelWithTime:(CFTimeInterval)timestamp
{
if(lastTime == 0.0)
{
lastTime = timestamp;
}
else
{
timeDelta = timestamp - lastTime;
lastTime = timestamp;
ballGuysRect.origin.x += ballVelocity.x * timeDelta;
ballGuysRect.origin.y += ballVelocity.y * timeDelta;
[self checkCollisionWithScreenEdges];
}
}
When I attach that code to the sprite, nothing happen.
I also tried adding a CCParticleExplosion which did do what I wanted but I still want to add a touch function to each individual sprite that's generated and they tend to just fade away.
So again, I'm still fairly new to this and if anyone could give any advice that would be great.
Thanks for your patients and time to read this.
Your code looks good to me, but you never seem to update the position of your sprites. Somewhere in updateModelWithTime I would expect you to set ballGuySprite.position = ballGuysRect.origin plus half of its height or width, respectively.
Also, I don't see how updateModelWithTime can control 100 different sprites. I see only one instance of ballGuysRect here. You will need a ballGuysRect for each sprite, e.g. an array.
Finally, I'd say that you don't really need ballGuysRect, ballVelocity, and the sprite. Ball could be a subclass of CCSprite, including a velocity vector. Then all you need to do is keep an array of Balls and manage those.
I am not sure what version of cocos2d you are using but a few things look a bit odd.
Your first problem appears to be that you are just using the same sprite over and over again.
Since you want so many different sprites shooting away, I would recommend that you use a CCSpriteBatchNode, as this should simplify things and speed things up.
The following code should help you get that set up and move them offscreen with CCMoveTo:
//in your header file:
CCSpriteBatchNode *batch;
//in your init method
batch = [CCSpriteBatchNode batchNodeWithFile:#"ball.png"];
//Then in your ccTouches method
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[batch addChild:ballGuySprite z:7 tag:0];
ballGuySprite.position = ccp(where-ever the center image is located);
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(random off screen location)];
[ballGuySprite runAction:actionMove];
}
Also usually your update method looks something like the following:
-(void)update:(ccTime)delta{
//check for sprites that have moved off screen and disable them.
}
Hope this helps.
I am setting the value of a key in an NSDisctionary using this:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touchesEnded currentcolor: %#", currentColor);
if (currentTool == #"paint") {
NSLog(#"End Paint");
CGPoint point = [[touches anyObject] locationInView:self.view];
MaskPath * myNewPath = [mapView.drawthis containsPoint:point];
[myNewPath.attributes setValue:currentColor forKey:#"fill"];
[mapView setNeedsDisplay];
} else {
NSLog(#"End Pattern");
currentColor = #"1.png";
CGPoint point = [[touches anyObject] locationInView:self.view];
MaskPath * myNewPath = [mapView.drawthis containsPoint:point];
[myNewPath.attributes setValue:currentColor forKey:#"fill"];
[mapView setNeedsDisplay];
}
}
If I try to log the value of currentColor the app crashes with exc bad access (line 3)
If I take the Log out and go with the hardcoded value everything works fine. It also works fine with the first part of the if statement. I've checked the function that assigns currentColor and it is delivering the correct values. If I hard code the value of currentColor at that point it works fine. I've run the analyzer and I've got no memory leaks or issues. How else can I track this issue down?
One glaring issue is that you are working with an autorelease variable as evidenced by your example. Most likely you are doing the same thing outside of that method.
I recommend you make currentColor a property:
#property (nonatomic, retain) NSString *currentColor;
Then, to set it, you would do something like this:
self.currentColor = #"SomeColor";
Since you declared the currentColor property with a retain, you will not get a bad access when you pass it to your NSLog:
NSLog(#"This is my color: %#", self.currentColor);
If you already DO have a currentColor property and you are simply assigning the ivar, then you should be doing:
currentColor = [NSString alloc] initWithString:#"Foo"];
To sum it up:
1. self.currentColor = #"Foo"; //<-This is automagically retained
2. currentColor = [NSString alloc] initWithString:#"Foo"]; //<-Accessing the ivar directly, so you must ownify the string :D
a big noob needs help understanding things.
I have three UIViews stored inside a NSMutableArray
lanes = [[NSMutableArray arrayWithCapacity:3] retain];
- (void)registerLane:(Lane*)lane {
NSLog (#"registering lane:%i",lane);
[lanes addObject:lane];
}
in the NSLog I see: registering lane:89183264
The value displayed in the NSLog (89183264) is what I am after.
I'd like to be able to save that number in a variable to be able to reuse it elsewhere in the code.
The closest I could come up with was this:
NSString *lane0 = [lanes objectAtIndex:0];
NSString *description0 = [lane0 description];
NSLog (#"description0:%#",description0);
The problem is that description0 gets the whole UIView object, not just the single number (dec 89183264 is hex 0x550d420)
description0's content:
description0:<Lane: 0x550d420; frame = (127 0; 66 460); alpha = 0.5; opaque = NO; autoresize = RM+BM; tag = 2; layer = <CALayer: 0x550d350>>
what I don't get is why I get the correct decimal value with with NSLog so easily, but seem to be unable to get it out of the NSMutableArray any other way. I am sure I am missing some "basic knowledge" here, and I would appreciate if someone could take the time and explain what's going on here so I can finally move on. it's been a long day studying.
why can't I save the 89183264 number easily with something like:
NSInteger * mylane = lane.id;
or
NSInteger * mylane = lane;
thank you all
I'm really confused as to why you want to save the memory location of the view? Because that's what your '89183264' number is. It's the location of the pointer. When you are calling:
NSLog (#"registering lane:%i",lane);
...do you get what's actually being printed out there? What the number that's being printed means?
It seems like a really bad idea, especially when if you're subclassing UIView you've already got a lovely .tag property which you can assign an int of your choosing.
You're making life infinitely more complex than it needs to be. Just use a pointer. Say I have an array containing lots of UIViews:
UIView *viewToCompare = [myArray objectAtIndex:3];
for (id object in myArray) {
if (object == viewToCompare) {
NSLog(#"Found it!");
}
}
That does what you're trying to do - it compares two pointers - and doesn't need any faffing around with ints, etc.
I've created one of my first apps using Objective-C. Being a noob, there's a lot of stuff I want to do, but don't know how to apply it in Objective-C. Please take a look at the method below (which I created from scratch) and tell me what you would do to make it better. Obviously I've duplicated code across 2 UILabels, but I'd like to simplify that (I hate duplicating code) but I'm unaware what the best way to do it is. I just need suggestions which will help me better understand the right way to do stuff in Objective-C
timeText and dateText are of type UILabel
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (isRearranging)
{
NSLog(#"touchesMoved");
NSLog(#"touches=%#,event=%#",touches,event);
//TOUCH INFO
UITouch *touch = [[touches allObjects] objectAtIndex:0];
CGPoint currentLocation = [touch locationInView:touch.view];
CGPoint previousLocation = [touch previousLocationInView:touch.view];
//FRAME INFO
float timeHalfWidth = timeText.frame.size.width / 2;
float timeHalfHeight = timeText.frame.size.height / 2;
CGRect timeTextRect = CGRectMake(timeText.center.x - (timeHalfWidth), timeText.cener.y - (timeHalfHeight), timeText.frame.size.width, timeText.frame.size.height);
float dateHalfWidth = dateText.frame.size.width / 2;
float dateHalfHeight = dateText.frame.size.height / 2;
CGRect dateTextRect = CGRectMake(dateText.center.x - (dateHalfWidth), dateText.center.y - (dateHalfHeight), dateText.frame.size.width, dateText.frame.size.height);
//IF TIME TEXT
if(CGRectContainsPoint(timeTextRect,previousLocation))
{
CGPoint item = timeText.center;
CGPoint diff;
diff.x = previousLocation.x - item.x;
diff.y = previousLocation.y - item.y;
CGPoint newLoc;
newLoc.x = currentLocation.x - diff.x;
newLoc.y = currentLocation.y - diff.y;
if (newLoc.x<timeHalfWidth)
newLoc.x = timeHalfWidth;
if (newLoc.y<timeHalfHeight)
newLoc.y = timeHalfHeight;
[timeText setCenter:(newLoc)];
}
//IF DATE TEXT
if(CGRectContainsPoint(dateTextRect,previousLocation))
{
CGPoint item = dateText.center;
CGPoint diff;
diff.x = previousLocation.x - item.x;
diff.y = previousLocation.y - item.y;
CGPoint newLoc;
newLoc.x = currentLocation.x - diff.x;
newLoc.y = currentLocation.y - diff.y;
if (newLoc.x<dateHalfWidth)
newLoc.x = dateHalfWidth;
if (newLoc.y<dateHalfHeight)
newLoc.y = dateHalfHeight;
[dateText setCenter:(newLoc)];
}
}
touchMoved = YES;
}
Thanks so much for your help!
A first step, independent from the language you are working in, would be to follow DRY - most of your code is the same for both labels.
Then there is already functionality for hit-testing in the SDK, e.g. -hitTest:withEvent: or -pointInside:withEvent::
NSArray *labels = [NSArray arrayWithObjects:timeText, dateText, nil];
for (UILabel *label in labels) {
if ([label pointInside:previousLocation withEvent:nil]) {
[self relocateLabel:label];
break;
}
}
I will answer an additional question posed by asker in a comment. Quote:
What if I wanted to use mixed types in that array? i.e. a couple UILabel and a couple UIImageView. Is there a way to compare types or use generics? This is a poor guess/example for (NSObject *obj in objects) { if (label.type==UILabel) [self relocateLabel:obj]; else if (label.type==UIImageView) [self relocateImage:obj]; }
As Georg Fritzsche answered there, Objective-C messaging is dynamic. Object will be "queried" if it supports that message at runtime, and if so, it will execute the method associated with the message. Method/message name is called a "selector".
If you explicitly want to figure out object's class, you can do that as well.
if([view isKindOfClass:[UILabel class]])
{
// your code here
}
If you just want to figure out if the target object responds to a selector (that is, implements a method):
if([view respondsToSelector:#selector(relocateView:)])
{
// your code here
}
Selectors are derived from method names by omitting arguments themselves, leaving colons intact and appending everything closely. For example, if you had a message send (that is, method call): [thing moveTowardsObject:door movementType:XYZ_CRAWL], its selector would be: moveTowardsObject:movementType: and you'd get it using #selector(moveTowardsObject:movementType:).
In a loop such as what Georg posted, you typically want to just check if the target object responds to a selector, since otherwise an exception would be thrown, and Objective-C code rarely catches exceptions as part of a normal code flow (as opposed to what Python developers do).
My problem is, I did coding for a sprite. It should change it should change it's image from( 1, 2, 3). It should look like count down time to start a game. 1, 2, 3 are 3 png images. But the images are not displayed in equal intervals of time. I mean time between (1 - 2), (2 - 3) is not same. It is random. Please help me with my problem. Help me if there is better solution than what I am doing.(My animation should be like, before any game starts we see count down 1 then 2 then 3 then GO).
-(id)init
{
if((self = [super init]))
{
[[CCDirector sharedDirector] setAnimationInterval:60.0/60];
[[CCDirector sharedDirector] setDisplayFPS:NO];
CCAnimation* numberAnimation = [CCAnimation animationWithName:#"countDown" delay: 60.0/60];
for( int i=1;i<4;i++)
[numberAnimation addFrameWithFilename: [NSString stringWithFormat:#"number_%02d.png", i]];
id numberAction = [CCAnimate actionWithAnimation: numberAnimation restoreOriginalFrame:NO];
id action2 = [CCFadeOut actionWithDuration:0.5f];
CCSprite *number;
number = [CCSprite spriteWithFile:#"number.png"];
....
}
}
You'll have to update some of the classes used in this article a developer on my team wrote since it was written for Cocos2D 0.8.2 but I think this should do the trick for you...
http://getsetgames.com/2009/08/05/improving-the-performance-of-animating-sprites-in-cocos2d/
From a quick looks like you need to make the following changes...
IntervalAction change to CCIntervalAction
CocosAnimation change to CCAnimation (I think?)
CocosNodeFrames change to CCNodeFrames (I think?)
You'll have to double check the Cocos2D 0.99 release notes. It details all the class names you'll need.