Better understanding of CGRect in cocos2d - iphone

I'm trying to expand my knowledge when using cocos2d and I have 2 questions : 1. What is CGRects purpose and 2. If its used to makes sprites collide, how do I do that?

CGRect is a C struct, defined as part of Core Graphics (hence the CG), ie it’s a “data” container, nothing more, nothing less.
It’s actually made of two more C structs, a CGPoint for the origin and a CGSize for the size, as you no doubt realise.
In Core Graphics (Quartz) on the Mac, the coordinate system is bottom left defines the origin, also for OpenGL and hence Cocos2d. On iOS it’s top left. Where that origin is often depends on where you are in, for example, the view hierarchy (look at frame and bounds in the UIView documentation).
However, this makes no difference to CGRect, the struct is not enforcing a coordinate system, only defining a data type that has an origin and a size. It would work equally well for a coordinate system defined as rotated 45 degrees to that used on iOS – the expectation is that you are consistent in your use, of course
And to answer your second question, here is a sample code of collision detection.
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
for (CCSprite *monster in _monsters) {
if (CGRectIntersectsRect(projectile.boundingBox, monster.boundingBox)) {
[monstersToDelete addObject:monster];
}
}
for (CCSprite *monster in monstersToDelete) {
[_monsters removeObject:monster];
[self removeChild:monster cleanup:YES];
}
if (monstersToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[monstersToDelete release];
}
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
And to better understand the whole concept, here is a link to a great tutorial by ray.
http://www.raywenderlich.com/25736/how-to-make-a-simple-iphone-game-with-cocos2d-2-x-tutorial

Related

Adding a Hud Layer to Scene Cocos2d-3

To keep it simple, what is the easiest way to make the default [ Menu ] in default HelloWorld Scene (for example) as it's own layer. Issue I'm having now is that the scene is completely black, with nothing showing up!
GameLayer node:
- (id)init
{
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
self.theMap = [CCTiledMap tiledMapWithFile:#"AftermathRpg.tmx"];
self.contentSize = theMap.contentSize;
self.metaLayer = [theMap layerNamed:#"Meta"];
metaLayer.visible = NO;
CCTiledMapObjectGroup *objects = [theMap objectGroupNamed:#"mainChar"];
NSMutableDictionary *startPoint = [objects objectNamed:#"startPosition"];
int x = [[startPoint valueForKey:#"x"] intValue];
int y = [[startPoint valueForKey:#"y"] intValue];
self.mainChar = [CCSprite spriteWithImageNamed:#"mainChar.png"];
mainChar.position = ccp(x,y);
[self addChild:mainChar];
[self addChild:theMap z:-1];
[self setCenterOfScreen: mainChar.position];
return self;
}
HudLayer node
-(id)init
{
CGSize winSize = [[CCDirector sharedDirector] viewSize];
CCButton *backButton = [CCButton buttonWithTitle:#"[ Menu ]" fontName:#"Verdana-Bold" fontSize:18.0f];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
[backButton setTarget:self selector:#selector(onBackClicked:)];
[self addChild:backButton];
return self;
}
Scene
+ (GameScene *)scene
{
return [[self alloc] init];
}
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
CGSize winSize = [CCDirector sharedDirector].viewSize;
self.gameLayer = [GameLayer node];
[self addChild:gameLayer z:-1];
//self.contentSize = self.gameLayer.contentSize;
hudLayer = [HudLayer node];
hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);
[self addChild:hudLayer z:1];
return self;
}
From the OP I take that you have two issues with one being that the HUD is not static (i.e. it is moving as your map moves which you don't want) and that it is not positioning at the top of the screen.
Looking at the position issue first, your position is set to normalized. Since the scene's content size has been made to be the size of your map, which I take is larger than your screen, then this is why it is showing up at the top right of the map and not the screen. To fix this don't do normalized positioning. If you want to be able to still express the position in the 0 to 1 range, use (remove the line that sets the position type to normalized also):
CGSize winSize = [[CCDirector sharedDirector] viewSize];
backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
If your map is 10,000 x 10,000 then using the normalized positioning like you are will set the button to (8,500, 9,500) rather than the top of the screen.
Looking at the static issue next, from the looks of it you have the Hello World scene that you are adding everything to right? It also looks like you are moving the Hello World scene with a call to:
[self setCenterOfScreen: player.position];
What you want to do instead is this, you first have a scene:
HelloWorldScene* scene;
And to this scene you are adding two "main" layers with one being your gameplay layers as children of a main gameplay layer and the other being your HUD layer, which for example could look something like:
GameplayLayer* gameLayer;
HudLayer* hudLayer;
[scene addChild:gameLayer];
[scene addChild:hudLayer];
When the player moves (or camera or whatever), what should be moving is the game's layer, not the root Hello World scene. Moving the root scene will move all of its children, which includes the hud. That is not what you want.
When I worked on, for example, the Goldfish Mysteries app (https://itunes.apple.com/us/app/finn-friends-mysteries/id740040227?mt=8) I had essentially layers for:
Story (comprised of multiple sub-layers like bg, characters, etc)
Text (highlighted text that plays along with the narration audio)
HUD (comprised of multiple sub-layers)
Whenever there is movement on the story level it occurs on the story layer. When the HUD appears then the text and story layers are paused recursively (i.e. them and all of their children) along with the narration audio if any is playing but the HUD layer remains untouched. Resuming consists of resuming story, text, and any playing narration. I don't remember off the top if dropping the hud moved down the story and text layers in this specific app since I don't have my iPad in front of me, but i have done apps in the past where dropping the hud shifted all other layers. In that situation and for a simple app it would be fine to move the scene since the scene would only be shifted enough to show the TOC (in this type of app for example). What you look to be doing is moving the entire scene with the player's movement, which is not what you really wanted to do by the looks of it.
Either way you want a clear separation between layers and operations that are meant to only happen on specific layers should be directed only towards those layers.
Hope this helped.
UPDATE (Edited):
Based on your new edited OP, you have a new problem that you've introduced. For the hud you shouldn't have to set the layer's position since inside the hud layer everything is already being laid out relative to the screen anyways. So what you should have instead is:
hudLayer = [HudLayer node];
[self addChild:hudLayer z:1];
The second issue is that you are not properly writing your init methods for the game and hud layer classes. The init should look like:
- (instanceType)init
{
self = [super init];
if (self)
{
// Do your init stuff...
}
return self;
}
You are never calling:
self = [super init];

core graphics, how to draw lines at runtime?

The task is, to draw paths at runtime on custom maps which im using in a Scrollview, and then i will have to draw paths at runtime whenever the location coordinates (lat, long) updates. The problem what im trying to solve here is that i have made a class 'graphics' which is a subclass of UIView, in which i code the drawing in the 'drawrect:' method. So when im adding the graphics as subview of the scrollview over image, the line draws, but i need to keep drawing the line as though it were paths. I need to draw the lines at runtime, need to keep updating the points(x,y) of 'CGContextStrokeLineSegments' method. The code:
ViewController:
- (void)loadView {
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
CGRect fullScreenRect=[[UIScreen mainScreen] applicationFrame];
scrollView=[[UIScrollView alloc] initWithFrame:fullScreenRect];
graph = [[graphics alloc] initWithFrame:fullScreenRect];
scrollView.contentSize=CGSizeMake(320,480);
UIImageView *tempImageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"fortuneCenter.png"]];
self.view=scrollView;
[scrollView addSubview:tempImageView2];
scrollView.userInteractionEnabled = YES;
scrollView.bounces = NO;
[scrollView addSubview:graph];
}
Graphics.m:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint point [2] = { CGPointMake(160, 100), CGPointMake(160,300)};
CGContextSetRGBStrokeColor(context, 255, 0, 255, 1);
CGContextStrokeLineSegments(context, point, 2);
}
So how can i draw the lines at runtime. Im just simulating right now, so im not using the realtime data (coordinates). Just want to simulate by using dummy data (coordinates of x,y). Lets say have a button, whenever i press it it updates the coordinates so path extends.
The easiest way would be to add an instance variable representing the points to the UIView subclass.
Then, every time the path changes, update the ivar appropriately and call -setNeedsDisplay or setNeedsDisplayInRect on the custom UIView (or even on its superview). The runtime will then redraw the new path.
You just need to make CGPoint point[] dynamically resizable, from the looks of it.
You can use malloc, a std::vector, or even NSMutableData to store the points you add. Then you pass that array to CGContextStrokeLineSegments.
If 2 points is all you will need, move CGPoint point[2] to an ivar so you may store the positions, then (as Rich noted) invalidate rects appropriately when these values (or the array) are changed.
This subject comes up every now and then, so I created a longer blog post on the general concepts involved with one potential solution, creating and using your own graphics context, here: http://www.musingpaw.com/2012/04/drawing-in-ios-apps.html

Circle to circle collision reaction - Equal priority

I am creating an app where there will be ~10 circles on screen, that can all be pushed and dragged around the screen via touch movements. These circles (which are simply UIView's with a circle drawn in them) also have collision detection on them, and the idea is that they push other circles out of the way when they collide. There is also no acceration or gravity in this app, it's more like pushing coins around, where they are simply pushed out of the way, and when you stop moving, so do they.
I have touch-to-circle interaction working, and the fundamentals of circle-to-circle working. The code however gives Circle01 all the power. IE, it can be pushed into other circles and they will move out of the way as hoped. But when the other circles are pushed into it, they move around Circle01, instead of pushing it out of the way.
I've read dozens of pages about collision detection, but I'm just stumped as to how I'd modify this code to give all the circles equal power.
Relevant code:
MyCircle.m
- (void)drawRect:(CGRect)rect
{
CGPoint viewCenter = CGPointMake(50,50);
bpath = [UIBezierPath bezierPathWithArcCenter:viewCenter radius:50 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
[[UIColor blueColor] setFill];
[bpath fill];
}
CircleVC.m
- (void)viewDidLoad
{
Circle01 = [[MyCircle alloc] initWithFrame:CGRectMake(200, 200, 100, 100)];
Circle01.backgroundColor = [UIColor redColor];
Circle01.alpha = 0.5;
[self.view addSubview:Circle01];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Testing circle collision detection
for (UIView *aView in self.view.subviews)
{
if (Circle01 == anotherView)
continue;
if (CGRectIntersectsRect(Circle01.frame, aView.frame))
{
float xDistance = aView.center.x - Circle01.center.x;
float yDistance = aView.center.y - Circle01.center.y;
float distance = xDistance * xDistance + yDistance * yDistance;
float angle = atan2(yDistance, xDistance);
CGPoint newCenter;
if (distance < 100 * 100)
{
newCenter.x = Circle01.center.x + (Circle01.frame.size.width * cos(angle));
newCenter.y = Circle01.center.y + (Circle01.frame.size.width * sin(angle));
aView.center = newCenter;
}
}
}
}
Thanks for any help.
It may be easier to offload this to a physics engine such as Box2d. Cocos2d is packaged with Box2d, but you will want the stand alone version since you are only using UIView based animations
_
Option 1
Here is a good, though slightly dated tutorial on using Box2d with UIKit.
http://www.cocoanetics.com/2010/05/physics-101-uikit-app-with-box2d-for-gravity/
Some caveats: if you are using ARC you will need to use the god-awful bridging compiler hints that tell the ARC pre-compiler when your C objects are being put into and out of its control.
For example:
bodyDef.userData = (__bridge void *)physicalView;
UIView *oneView = (__bridge UIView*)b -> GetUserData();
_
Option 2
You can use Chipmunk (which would also require the above "god-awful" bridging to ARC statements). Here is a good tutorial: http://www.alexandre-gomes.com/articles/chipmunk/
_
Option 3 - Easiest (Not Cheapest)
Alternatively, you could use the Objective-Chipmunk library that comes with Chipmunk Pro which does work with ARC. Here is a recent and "to-the-point" tutorial on using Obj-Chipmunk with UIImageViews. This would be the same for any UIView based code. http://www.ayarsanimation.com/frankayars/chipmunk
You can buy Obj-Chipmunk costs $89 when you by an Indie version license for Chipmunk Pro.
http://chipmunk-physics.net/chipmunkPro.php
I am not advocating a paid solution, but it may save you time.
It seems pretty simple, unless I'm missing something. Instead of hard-coding Circle01 in your touchesMoved, do your hit detection based on the view that the user touched. That way, whatever view they are dragging will get priority.

Interactive Image

This community has been tremendous help for me in many respects.
First time question (for me), and it's an easy one. I'm working through the iPhone SDK learning curve, at a good rate... but every once in a while, I come across a problem that, despite it's simplicity, is easier to ask and work on something else, then to spend another hour on reading.
I have a 2D game where a vehicle is moving around on the surface rotating to face the direction of travel. I've determined that Core Animation is my best approach.
The vehicle is an image. It's interactive to user input (touch).
Am I on the right track?
UIView (to act as Responder) containing a CALayer tree that includes the image (from a file).
The current file is a GIF. It made it easy to make the frame transparent, leaving only the vehicle image.
From the UIView subclass, how do I load the gif image into a layer?
Sounds simple, so I thought...
Cheers.
You're on the right track with Core Animation. CAKeyFrameAnimation has a path property which you'll use extensively. The following sample code (untested) uses straight line paths, but it's also possible to use curved paths:
UIImage *carImage = [UIImage imageNamed:#"car.png"];
carView = [[UIImageView alloc] initWithImage:carImage];
[mapView addSubview:carView];
CAKeyframeAnimation *carAnimation = [CAKeyframeAnimation
animationWithKeyPath:#"position"];
carAnimation.duration = 5.0;
// keep the car at a constant velocity
carAnimation.calculationMode = kCAAnimationPaced;
// Rotate car relative to path
carAnimation.rotationMode = kCAAnimationRotateAuto;
// Keep the final animation
carAnimation.fillMode = kCAFillModeForwards;
carAnimation.removedOnCompletion = NO;
CGMutablePathRef carPath = CGPathCreateMutable();
CGPathMoveToPoint(carPath, NULL, 0.0, 0.0);
CGPathAddLineToPoint(carPath, NULL, 100.0, 100.0);
CGPathAddLineToPoint(carPath, NULL, 100.0, 200.0);
CGPathAddLineToPoint(carPath, NULL, 200.0, 100.0);
carAnimation.path = carPath;
CGPathRelease(carPath);
[carView.layer addAnimation:carAnimation forKey:#"carAnimation"];
Is there a reason you can't simply subclass UIImageView to handle your touch methods? It would seem to me that instantiating an image view with your image and having the overridden UIResponder methods handle where the vehicle is moving and whatever else you need would be a lot easier than manually managing your CALayer tree.
You can do this with something like the following:
UIImage *vehicleImage = [UIImage imageNamed:#"vehicle.gif"];
VehicleImageView *vehicleView = [[[VehicleImageView alloc]
initWithImage:vehicleImage] autorelease];
Then have VehicleImageView subclass UIImageView:
#interface VehicleImageView : UIImageView
// Your stuff
#end
#implementation VehicleImageView
// Your stuff
// UIResponder methods
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// Custom implementation
}
// Other touch methods...
#end
You do seem to be on the right track, though, in that you do need a UIResponder somewhere in your vehicle's view/view hierarchy for touch methods.
More info:
UIImageView (specifically initWithImage:)
UIImage (specifically imageNamed:)
UIResponder (specifically touchesEnded:withEvent:)

cocos2d Sprite contentSize Problem

I've defined a sprite using the spriteWithFile method, providing a 120px by 30px .png
Sprite *trampoline = [Sprite spriteWithFile:#"trampoline.png"];
[self addChild:trampoline];
When I add this to my Layer and position it, it is where I expect it to be on the screen.
trampoline = [Trampoline node];
trampoline.position = ccp(160,15);
[self addChild:trampoline z:0 tag:1];
However, it seems to have no contentSize. The following NSLog statement:
NSLog(#"Content Size x:%f, y:%f", trampoline.contentSize.width,trampoline.contentSize.height);
Gives the following read out:
2009-07-10 18:24:06.385 TouchSprite[3251:20b] Content Size x:0.000000, y:0.000000
Am I missing something? Shouldn't that be 120.000000 by 30.000000
Any help would be greatly appreciated.
Regards,
Rich
Are these lines part of the Trampoline class?
Sprite *trampoline = [Sprite spriteWithFile:#"trampoline.png"];
[self addChild:trampoline];
From my limited experience with cocos2d, contentSize of a Sprite seems to only apply to the content that actually belongs to the Sprite, and not all children of that Sprite. As a result, in your example above, asking for the contentSize in your log statement won't work, since there isn't any content added to the Trampoline node. However, if you were to override the contentSize method inside your Trampoline class to return the contentSize of the Sprite that actually loaded the graphic, that should work.
Here's a snippet of a Sprite I'm using in a game I'm currently working on that illustrates what I'm talking about:
- (id) init
{
self = [super init];
if (self != nil)
{
self.textLabel = [Label labelWithString:#"*TEXT*"
fontName:#"Helvetica"
fontSize:18];
[textLabel setRGB:0 :0 :0];
textLabel.transformAnchor = CGPointZero;
textLabel.position = CGPointZero;
self.transformAnchor = CGPointZero;
[self addChild:textLabel];
}
return self;
}
//
- (CGSize) contentSize
{
return textLabel.contentSize;
}
This comes from a class that extends Sprite. Until I added the override for contentSize, asking for it from another class would give me the same results your seeing. Now that I'm telling it return the content size of the textLabel, it's working just like I'd expect it to.
I assume Trampoline inherits from Sprite, which then inherits from Node. You are over-writing trampoline with [Trampoline node] which creates a node ... but is the Trampoline implementation overriding the node method to initialize your sprite file into the Trampoline node?
I think you are just getting an empty Node class back from the line:
trampoline = [Trampoline node];