Do i need to call [convertToGl]? - cocos2d-iphone

i'm using cocos2d and i have subclassed cccnode (i want to draw circles and that works)
.h
#interface CCSpriteCircle : CCSprite {
float radius;
float angle; //in radians
NSUInteger segments;
BOOL drawLineToCenter;
ccColor4B cColor;
}
-(id) initWithRadius: (float)radius_ withAngle: (float)angle_ withSegments: (NSUInteger)segments_ withDrawLineToCenter:(BOOL)drawLineToCenter_;
#property(nonatomic,assign) float radius;
#property(nonatomic,assign) float angle;
#property(nonatomic,assign) NSUInteger segments;
#property(nonatomic,assign) BOOL drawLineToCenter;
#property(nonatomic,assign) ccColor4B cColor;
#end
//my .m file
#implementation CCSpriteCircle
#synthesize radius, angle,segments,drawLineToCenter,cColor;
-(id) initWithRadius: (float)radius_ withAngle: (float)angle_ withSegments: (NSUInteger)segments_ withDrawLineToCenter:(BOOL)drawLineToCenter_
{
if( (self=[super init])) {
self.radius = radius_;
self.angle = angle_;
self.segments = segments_;
self.drawLineToCenter = drawLineToCenter_;
//[self draw];
}
return self;
}
-(void)draw {
glLineWidth(1);
glColor4ub(cColor.r, cColor.g, cColor.b, cColor.a);
ccDrawCircle(ccp(self.position.x,self.position.y), radius, angle, segments, drawLineToCenter);
// restore original values
glLineWidth(1);
glColor4ub(255,255,255,255);
glPointSize(1);
}
#end
All works fine except that if i place the center of my ccspritecircle to 480 (that's the end of the screen) it doesnt appears but if i place it to 200px it's at the end of the screen.
if i change the position code in my helloworld scene like this:
from:
circle_.position = ccp(480, 0);
to:
circle_.position = [[CCDirector sharedDirector] convertToGL: CGPointMake(480,0)];
then i dont see the circle anymore. Am i doing something wrong?

If your only purpose is to draw a custom circle, without any other form of graphics (image files) then you should subclass CCNode, not CCSprite. CCSprite is a special type of CCNode that handles the loading and display of graphics files.
You should also call "[super draw]" from inside your override, to ensure that additional processing is done correctly - this can either be before or after your code, depending on how it affects your end result.
ccDrawCircle is already converting the x/y coordinates to GL space, so you don't need to worry about that either.
Your CCNode's anchorPoint and position could be a factor here though. The default 0.5/0.5 anchorPoint should set the center of your circle at the x/y position of your node so (480,0) with a radius of 32 would put a 64x64 circle at the bottom right of the screen (in portrait) and you would only be able to see the top-left quadrant of the circle.
You say that setting it to 200px places it at the end of the screen, without seeing how the circle was initialized, it's hard to identify why this would occur - but it more than likely has to do with the radius of the circle your using.
Keep in mind that changing the anchorPoint may not affect the rendering of the circle as your "draw" method is not taking into account the current anchorPoint, so it may be placing the circle at the x/y irregardless of anchorPoint settings.

Related

Clip a CCSprite added to CCSpriteBatchNode

I'm clipping my sprite with this code:
//At my CCSprite subclass m.
-(void)visit
{
CGPoint worldOrg = [self convertToWorldSpace:ccp(0, 0)];
CGPoint dest = [self convertToWorldSpace:ccp(self.contentSize.width, self.contentSize.height)];
CGPoint dims = ccpSub(dest, worldOrg);
glEnable(GL_SCISSOR_TEST);
glScissor(worldOrg.x, worldOrg.y, dims.x, dims.y);
#define SHOW_CLIPPED_AREA 1
#if SHOW_CLIPPED_AREA
//Draws a red rectangle showing clipped area
ccDrawSolidRect(ccp(0, 0), ccp(1024, 1024), ccc4f(64, 0, 0, 128));
#endif
[super visit];
glDisable(GL_SCISSOR_TEST);
}
Then just create the sprite as usual, adjust the sprite.contentSize property to whatever I need:
CCSprite aSprite = [CCSprite spriteWith...];
aSprite.contentSize = CGSizeMake(20,20);
//Add it to my layer
[self addChild:aSprite];
And it works as expected!
Problem...
When adding it to a CCSpriteBatchNode, it wont clip the sprite... it shows the sprite but without clipping it.
Can someone please help me out with this, I've googled everywhere with no answer to this.
I've also used the ClippingNode class from Steffen Itterheim, but I'm also having issues adding it to a CCSpriteBatchNode.
Any help will be appreciated.
Clipping or any custom drawing won't work with sprite-batched sprites.
The CCSpriteBatchNode will not call visit (nor draw) methods on their children because the batch node takes over rendering of the children. Therefore any code you write in draw or visit methods of a CCSprite will have no effect when you sprite-batch the sprite.

How can I display the bounding box for a CCNode in cocos2d?

As a follow-on question to my previous question about displaying the anchor point, I subclassed CCSprite and changed its draw method as follows:
[super draw];
ccDrawColor4F(0, 1, 0, 1);
ccDrawCircle(self.anchorPointInPoints, 20, 0, 8, YES);
This works great. For extra credit, I added the following to display its bounding box:
CGRect bb = self.boundingBox;
CGPoint vertices[4] = {
[self convertToNodeSpace:ccp(bb.origin.x, bb.origin.y)],
[self convertToNodeSpace:ccp(bb.origin.x + bb.size.width, bb.origin.y)],
[self convertToNodeSpace:ccp(bb.origin.x + bb.size.width, bb.origin.y + bb.size.height)],
[self convertToNodeSpace:ccp(bb.origin.x, bb.origin.y + bb.size.height)],
};
ccDrawPoly(vertices, 4, YES);
This also works great, until I reparent a sprite:
CGPoint oldPosition = [sprite convertToWorldSpace:sprite.position];
[sprite removeFromParentAndCleanup:NO];
[parentSprite addChild:sprite];
sprite.position = [sprite convertToNodeSpace:oldPosition];
The sprite's now in the proper position and its anchor point draws where it should, but the bounding box draws in the wrong place. What am I doing wrong?
Bounding box of a node is relative to its parent. Drawing done in draw method is in node's local space. convertToNodeSpace: converts coordinates from world space to local space, not from parent's space.
When you reparent node to a parent with different origin while maintaining same "world" position of this node, origin of its bounding box changes.
Your mistake is that you treat your sprite's bounding box as if its coordinates were in world space.
Second, you don't need to do the convert-to-x-space dance to draw bounding box of a sprite. Open ccConfig.h file in cocos2d folder of your project and change
#define CC_SPRITE_DEBUG_DRAW 0
line to
#define CC_SPRITE_DEBUG_DRAW 1
Third, coordinates of sprite.position point are relative to its parent, not to the sprite. When you call [node convertToWorldSpace:aPoint], it will treat aPoint as if it were in node's local space. If you want to get world coordinates of a node's position, you should call convertToWorldSpace: on node's parent: [node.parent convertToWorldSpace:node.position].
I normally draw AABBs with:
Rect aabb = someNode->getBoundingBox();
DrawNode* drawNode = DrawNode::create();
drawNode->drawRect(aabb.origin, aabb.origin + aabb.size, Color4F(1, 0, 0, 1));
parentNode->addChild(drawNode, 100);
Note: If the Rect aabb size is (0,0) then the rectangle will not be drawn.

Moving Box2d Bodies Like CCSprite Objects

In cocos2d, you can ease in CCSprites and move them around in all kinds of ways. Most importantly - they can have easing in/out. For most games this is desirable for smooth movement etc.
id action = [CCMoveTo actionWithDuration:dur position:pos];
move = [CCEaseInOut actionWithAction:action rate:2];
[self runAction: move];
When moving a box2d body, the sprite attached to it is updated after the box2d step(). Moving the sprite and then updating the body is not an option here, as it entirely defeats the purpose of the physics framework.
So the other option, which I have successfully implemented, is to calculate the displacement, velocity and acceleration of a sprite by treating it as a mechanics entity in its own right. Each time I call my update() on the sprite so the character can decide where to move etc, my superclass also stores the previous position and velocity. These are stored as box2d compliant values by dividing by the PTM_RATIO.
In the subclass of CCSprite, called FMSprite:
-(CGPoint) displacement {
return ccpSub(self.position, lastPos);
}
-(b2Vec2) getSpriteVelocity:(ccTime)dt {
return b2Vec2(self.displacement.x / dt / PTM_RATIO,
self.displacement.y / dt / PTM_RATIO);
}
-(b2Vec2) getSpriteAccel:(ccTime)dt {
b2Vec2 currVel = [self getSpriteVelocity:dt];
if (dt == 0) {
return b2Vec2(0,0);
} else {
float accelX = (currVel.x - lastVel.x)/dt;
float accelY = (currVel.y - lastVel.y)/dt;
return b2Vec2(accelX, accelY);
}
}
// This is called each update()
-(void) updateLast:(ccTime)dt {
// MUST store lastVel before lastPos is updated since it uses displacement
lastVel = [self getSpriteVelocity:dt];
lastPos = ccp(self.X, self.Y);
}
// Leave this method untouched in subclasses
-(void) update:(ccTime)dt {
[self updateObject:dt];
// Store previous update values
[self updateLast:dt];
}
// Override this method in subclasses for custom functionality
-(void) updateObject:(ccTime)dt {
}
I have then subclassed "FMSprite" into "FMObject", which stores a b2Body etc.
In order to move the body, I must first move a sprite and track its acceleration, through which I can find the required force (using the mass) needed to follow the sprite's motion. Since I can't move the object's sprite (which is synchronized to the body), I make another sprite called a "beacon", add it as a child to the object, and move it around. All we need to do then is to have a function to synchronize the position of the box2d body with this beacon sprite using the forces I mentioned before.
-(void) followBeaconWithDelta:(ccTime)dt {
float forceX = [beacon getSpriteAccel:dt].x * self.mass;
float forceY = [beacon getSpriteAccel:dt].y * self.mass;
[self addForce:b2Vec2(forceX, forceY)];
}
The result is brilliant, a smooth easing motion of the b2body moving where ever you want it to, without playing around with any of its own forces, but rather copying that of a CCSprite and replicating its motion. Since it's all forces, it won't cause jittering and distortions when colliding with other b2Body objects. If anyone has any other methods to do this, please post an answer. Thanks!
What I do is different from yours, but can also Moving Box2d Bodies Like CCSprite Objects and even use the CCAction.
The most important thing is to create an object that contain ccSprite and b2body.
#interface RigidBody : CCNode {
b2Body *m_Body;
CCSprite *m_Sprite;
}
And then, rewrite the setPosition method.
-(void)setPosition:(CGPoint)position
{
CGPoint currentPosition = position_;
b2Transform transform = self.body->GetTransform();
b2Vec2 p = transform.p;
float32 angle = self.body->GetAngle();
p += [CCMethod toMeter:ccpSub(position, currentPosition)];
self.body->SetTransform(p, angle);
position_ = position;
}
The setPosition method calculate how much the position change,and set it to the b2body.
I hope I have understanding your question and the answer is helpful for you...

How to change the particle angle in Cocos2D

Now I met a new question. How to modify every particle's angle to make it toward the center. Just like the images:
Image 1: normal particles effecing:
Image 2: which I need:
Image 2: which I need http://tinypic.com/images/404.gif
How about this code? You need to override CCParticleSystemQuad update: or updateQuadWithParticle:newPosition: method for specify the rotation of the particles. CCParticleSystemPoint can't rotate particles.
#interface MyParticleSystem : CCParticleSystemQuad
#end
#implementation MyParticleSystem
- (void)updateQuadWithParticle:(tCCParticle*)particle newPosition:(CGPoint)pos
{
particle->rotation = ccpToAngle(particle->pos) * 180.0f / M_PI;
[super updateQuadWithParticle:particle newPosition:pos];
}
#end
In order to turn particles towards their direction of movement (in your case: towards the center), you can do the following:
Add the oldPos property to the particle tCCParticle struct in CCParticleSystem.h
Initialize the oldPos property with the initial particle position in initParticle: in CCParticleSystem.m
Update the oldPos property with the current particle position in update: in CCParticleSystem.m before the new position is computed. I do this in line 512 immediately after checking whether the particle is still alive.
Override CCParticleSystemQuad as suggested by Kazuki:
- (void)updateQuadWithParticle:(tCCParticle *)particle
newPosition:(CGPoint)pos
{
CGPoint direction = ccpSub(particle->pos, particle->oldPos);
CGPoint n = ccpNormalize(direction);
CGFloat a = -CC_RADIANS_TO_DEGREES(ccpToAngle(n) - M_PI_2);
particle->rotation = a;
[super updateQuadWithParticle:particle newPosition:pos];
}

Cocos2d iPhone - Sprite cliping/mask/frame

how can i clip/crop/mask or just set the frame of a CCSprite in Cocos2D?
Something similar to:
setting the frame for UIView, with clipping subviews = TRUE
My CCSprite Main Sprite have multiple Child Sprite added to it.
I only want Mask part of that Main Sprite Sprite visible.
Is there a way to clip or use a mask for CCSprite?
I could cut the background and layer that on top, leaving only that visible area, but is that the only way?!
here's a sample image demonstrating what I'm trying to achieve:
(source: dnamique.com)
I ended up using GL_SCISSOR.
in MainSprite I impemented:
- (void) visit
{
if (!self.visible) {
return;
}
glEnable(GL_SCISSOR_TEST);
glScissor(x, y, width, height);
[super visit];
glDisable(GL_SCISSOR_TEST);
}
This will clip or mask the specified area.
The only tricky bit is that in Landscape mode Cocos2D has 0,0 at the bottom-left side of the screen, while OpenGL has it at the bottom-right corner as it doesn't consider the orientation of the screen.
In other words, for OpenGL consider you have a rotated portrait Screen.
I wrote a ClippingNode class which does exactly that. You can add other nodes (sprites, labels, etc.) to the ClippingNode and they will only be drawn in the region specified by the ClippingNode. It also takes device rotation into account.
Internally it uses GL_SCISSOR_TEST like in Bach's answer.
http://www.learn-cocos2d.com/2011/01/cocos2d-gem-clippingnode/
I tried using Steffen Itterheim's ClippingNode, but was unable to get to work in a sufficiently robust
enough fashion for my needs.
Believe it or not, the below code works fairly well and should be code complete. It handles device orientation changes, anchorPoint, position, scale (scaleX, scaleY). For cocos2d v2, you may just need to
comment out the glPushMatrix and glPopMatrix calls..
To use, simply set the position and contentSize properties and add the child/children you want clipped to this ClippingNode instance. The contentSize property is used to define the dimensions of the clipping region.
example of usage:
ClippingNode *clipNode = [[ClippingNode alloc] init];
clipNode.anchorPoint = ccp(0.5f, 0);
clipNode.position = ccp(100, 25);
clipNode.contentSize = CGSizeMake(120, 120);
// add clipNode to your node hierarchy.
[parentNode addChild:clipNode];
// add one or more children to your clipNode:
[clipNode addChild:child1];
// ClippingNode.h
// CC0 - (public domain. Use in anyway you see fit.)
// No warranty of any kind is expressed or implied.
//
// by UChin Kim.
//
// the caller can simply set the regular cocos2d
// properties: position and contentSize to define the clipping region implicitly (i.e. the
// position and contentSize of the ClippingNode is the clipping region to be used).
// as an added bonus, position seems to work as expected (relative to parent node, instead of
// requiring absolute positioning).
//
// also, anchorPoint and scale properties seem to work as expected as well..
// no special code is neeed to handle device orientation changes correctly..
//
// To visually see exactly what is being clipped, set the following #define
// #define SHOW_CLIPPED_REGION_IN_LIGHT_RED 1
//
#import "cocos2d.h"
#interface ClippingNode : CCNode
#end
//
// ClippingNode.m
//
#import "ClippingNode.h"
#implementation ClippingNode
-(void) visit
{
CGPoint worldOrg = [self convertToWorldSpace:ccp(0, 0)];
CGPoint dest = [self convertToWorldSpace:ccp(self.contentSize.width, self.contentSize.height)];
CGPoint dims = ccpSub(dest, worldOrg);
glPushMatrix();
glEnable(GL_SCISSOR_TEST);
glScissor(worldOrg.x, worldOrg.y, dims.x, dims.y);
#if SHOW_CLIPPED_REGION_IN_LIGHT_RED
glColor4ub(64, 0, 0, 128);
ccDrawSolidRect(ccp(0, 0), ccp(1024, 1024));
#endif
[super visit];
glDisable(GL_SCISSOR_TEST);
glPopMatrix();
}
#end