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];
}
Related
Porting my game from Cocos2d v2 to v3 I don't know when the sprites go out of screen.
In v2 my solution was:
-(void) update:(ccTime) delta
{
// Should use a fixed size step based on the animation interval.
int steps = 2;
CGFloat dt = [[CCDirector sharedDirector] animationInterval]/(CGFloat)steps;
for(int i=0; i<steps; i++){
cpSpaceStep(space_, dt);
}
if (mySprite.getPhysicsBody->p.y > 500)
[mySprite resetPosition];
}
now with Cocos2d v3 mySprite.physicsNode.position doesn't change through the time.
Any idea or link with some example?
Thanks.
physicsNode.position doesn't change with time because it uses its parent sprite coordinate space, not the global coordinate space.
You can get the global position of any node, considering the Anchor Point, using this:
CGPoint worldPos = [node convertToWorldSpaceAR:CGPointZero];
After that you can easily convert it to any other node space if necessary (like your level, maybe) using:
CGPoint position = [_levelNode convertToNodeSpaceAR:worldPos];
But beware that you shouldn't hardcode the screen size on your code, as it varies for each device. You can use instead:
CGSize viewSize = [[CCDirector sharedDirector] viewSize];
Is there a simple way (ie an api) that converts an angle of rotation in world space to local space of a CCNode? I know the angle of rotation a node needs to have on the screen, but the node is nested deeply in node hierarchy and I would like to set its angle so that it matches what I want. If there's no api, what CC api calls should I make? Something like this:
CCNode * myLocalNode;
float myLocalAngle = CCAngleConvertFromWorldToNode(myLocalNode,myWorldAngle);
myLocalNode -> setRotation(myLocalAngle);
I think the best way to do is to get two reference points, one representing the origin and the other representing the rotation vector. Convert these 2 points to node space, measure the vector between them (subtraction) and convert that to an angle. Here are 2 methods that should be a category of CCNode (note: I refer to self), one to world and the other to node.
-(float) convertRotationToWorldSpace:(float)rotation
{
CGPoint rot = ccpForAngle(-CC_DEGREES_TO_RADIANS(rotation));
CGPoint worldPt = [self convertToWorldSpace:rot];
CGPoint worldOriginPt = [self convertToWorldSpace:CGPointZero];
CGPoint worldVec = ccpSub(worldPt, worldOriginPt);
return -CC_RADIANS_TO_DEGREES(ccpToAngle(worldVec));
}
-(float) convertRotationToNodeSpace:(float)rotation
{
CGPoint rot = ccpForAngle(-CC_DEGREES_TO_RADIANS(rotation));
CGPoint nodePt = [self convertToNodeSpace:rot];
CGPoint nodeOriginPt = [self convertToNodeSpace:CGPointZero];
CGPoint nodeVec = ccpSub(nodePt, nodeOriginPt);
return -CC_RADIANS_TO_DEGREES(ccpToAngle(nodeVec));
}
I want to build a platform game with cocos2d/Box2D. I use CCFollow to follow the player sprite but CCFollow constantly puts it in the center of the screen. I want CCFollow to follow more naturally, like a human turning a camcorder with an acceptable lag, a small overshoot ...etc.
Here is a method of mine that didn't work: I attached (via a distance joint) a small physics body to the player that doesn't collide with anything, represented by a transparent sprite. I CCFollow'ed the transparent sprite. I was hoping this ghost body would act like a balloon attached to the player, hence a smooth shift in view. The problem is distance joint breaks with too heavy - too light objects. The balloon moves around randomly, and of course, it pulls the player back a little no matter how light it is.
What is a better way of following a moving sprite smoothly?
Try add this to CCActions in cocos2d libs.
-(void) step:(ccTime) dt
{
#define CLAMP(x,y,z) MIN(MAX(x,y),z)
CGPoint pos;
if(boundarySet)
{
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(boundaryFullyCovered) return;
CGPoint tempPos = ccpSub(halfScreenSize, followedNode_.position);
pos = ccp(CLAMP(tempPos.x,leftBoundary,rightBoundary), CLAMP(tempPos.y,bottomBoundary,topBoundary));
}
else {
// pos = ccpSub( halfScreenSize, followedNode_.position );
CCNode *n = (CCNode*)target_;
float s = n.scale;
pos = ccpSub( halfScreenSize, followedNode_.position );
pos.x *= s;
pos.y *= s;
}
CGPoint moveVect;
CGPoint oldPos = [target_ position];
double dist = ccpDistance(pos, oldPos);
if (dist > 1){
moveVect = ccpMult(ccpSub(pos,oldPos),0.05); //0.05 is the smooth constant.
oldPos = ccpAdd(oldPos, moveVect);
[target_ setPosition:oldPos];
}
#undef CLAMP
}
i get this from cocos2d forums.
Perhaps this http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:actions_ease can help you get an "acceleration" effect with CCFollow.
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...
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.