Cocos2d - Moving the Layer/view based on ball movement - cocos2d-iphone

My game world space is a rectangle of 4 screen sizes (2 rows x 2 columns )
I am moving the camera based on the movement of a ball. The ball position is set according to the physics engine.
I notice that, sometimes the ball changes direction slightly as the camera is moving to follow the ball.
Initially I thought it was sort of an illusion based on the movement of the camera, but when I draw a path, the line is of crooked at times.
I used some of the code behind the CCFollow implementation code (not CCFollow itself, but the code behind it), for following the ball
(I had issues with CCfollow)
Below is my Tick() function:
————————————————–
- (void)tick:(ccTime) dt {
bool ballMov = false;
CGPoint pos;
CCSprite *ballData;
dt = 1.0 / 60.0f;
world->Step(dt,8, 3 );
for(b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *tempData = (CCSprite *)b->GetUserData();
if (tempData == ball){
ballData = tempData;
ballMov = true;
ballData.position = ccp(b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
}
}
}
//
// Move the layer so we can keep looking at the travelling ball
// Code is similar to CCFollow code with some adjustments
pos = ballPosition;
if(ballMov){
#define CLAMP(x,y,z) MIN(MAX(x,y),z)
pos = ccp(CLAMP(pos.x,leftBoundary,rightBoundary), CLAMP(pos.y,bottomBoundary,topBoundary));
#undef CLAMP
pos = ccpSub(pos, halfScreenSize );
pos.x = - pos.x ;
pos.y = - pos.y ;
//calculate delta move towards the new position
if(gunShot){
CGPoint moveVect;
CGPoint oldPos = [self position];
double dist = ccpDistance(pos, oldPos);
if (dist > 1){
moveVect = ccpMult(ccpSub(pos,oldPos),0.4); //0.05 is the smooth constant.
oldPos = ccpAdd(oldPos, moveVect);
pos = oldPos;
}
}
}
//move towards the ball
[self setPosition:(pos)];
lastPosition = pos;
ballPosition = CGPointMake([ball body]->GetPosition().x * PTM_RATIO, [ball body]->GetPosition().y * PTM_RATIO );
//if ball is out of gameworld space then reset
if (!CGRectContainsPoint ( outerBoundaryRect, ballPosition)){
ball.visible = false ;
[self recreateBody]; //Ball is out of Gameworld, recreate it at the default position
}
}

Related

Cocos2d : Shooting a ball in the direction of the mouse click/Touch

Playing around with a ball(stationary) that's on the top left hand corner of the layout. When I use a mouse click I want the ball to be hit by a force and pass through the mouse click. The mouse click can be anywhere on the layout other than on the ball. I used to have this working, using #1 from the code below. But its broken at the moment. When I click the ball at say 90 degrees, it shoots a little bit away from the mouse click like say 90 + x. the angle is off by a 10 degrees or so.
I created the basic level using levelhelper2 toolset to layout the sprites.
/*--------------- touchEnded ------------------------------------*/
-(void)touchEnded:(CCTouch *)touch withEvent:(CCTouchEvent *)event
{
ball = (LHSprite*)[self childNodeWithName:#"Ri"];
ball.anchorPoint = ccp(0.5f,0.5f);
body = [ball box2dBody];
pos = body->GetPosition();
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
CGPoint shootVector = ccpSub( location, ball.position );
/* #1 Tried this first.
b2Vec2 force = b2Vec2(shootVector.x ,shootVector.y);
force.Normalize();
force *= 1.5f;
*/
/* #2 : Try this version */
CGFloat shootAngle = ccpToAngle(shootVector);
float power = 10;
float x1 = -1 * CC_RADIANS_TO_DEGREES(cos(shootAngle));
float y1 = -1 * CC_RADIANS_TO_DEGREES(sin(shootAngle));
b2Vec2 force = b2Vec2(x1* power,y1* power);
body->ApplyLinearImpulse(force, pos, 1);
}
Do you want the Ball flying constantly or accelerated?
For constant speed I would try to use something like this:
CCPoint location = touch->getLocation();
CCPoint ballLoc = ball->getPosition();
CCSize winSize = CCDirector::sharedDirector()->getVisibleSize();
float dX = location.x - ballLoc.x; //delta between touch and ball
float dX = location.y - ballLoc.y;
float c = sqrtf((dX * dX) + (dY*dY)); //distance with pythagoras
float cmax = sqrtf((winSize.width * winSize.width) + (winSize.height*winSize.height)); //max Distance when clicking in the bottom right corner the distance is max
float r = cmax / c; //value to multiply distance
float destX = ballLoc.x + r*dX; //destination is ball location + r*delta
float destY = ballLoc.y + r*dY;
projectile->runAction(CCMoveTo::create(2.0, ccp(realX, realY));
Optional you can add a duration depending on the distance.
I hope this helps.

cocos2dx inverse scaling a layer

I have a CCLayer which holds all my game objects and i have implemented scaling and scrolling. To make sure that you can't scroll out of bounds i am calculating a rect that represents the screen and use it to check if the rect is within bounds. The problem is that my calculations are wrong. The layer is scaled by a scaleFactor like so:
world->setScale(scaleFactor);
and then i calculate the scroll rect:
float scrollWidth = winSize.width * ( 1 / scaleFactor); // winSize is the size of the screen (1280x
float scrollHeight = winSize.height * ( 1 / scaleFactor);
if(scrollRect.l < 0) scrollRect.l = 0;
if(scrollRect.l + scrollWidth > levelWidth) scrollRect.l -= (scrollRect.l + scrollWidth - levelWidth);
scrollRect.r = scrollRect.l + scrollWidth;
world->setPosition(-scrollRect.l, -scrollRect.b);
(the scale factor value is between 1.0 and 0.5)
This sort of works but only when the layer is zoomed out to the max or zoomed in to the minimum but when scaleFactor isn't MAX/MIN it is wrong (there is some left space).
What am i doing wrong? I've also tried changing the anchor points (they are currently set to 0,0) but without any success.
You can do this whatever your scale factor...
here _tileMap is your world
//Code for getting difference b/w two position
CCTouch *fingerOne = (CCTouch*)touchArray->objectAtIndex(0);
CCPoint newTouchLocation = fingerOne->getLocationInView();
newTouchLocation = CCDirector::sharedDirector()->convertToGL(newTouchLocation);
newTouchLocation=_tileMap->convertToNodeSpace(newTouchLocation);
CCPoint oldTouchLocation = fingerOne->getPreviousLocationInView();
oldTouchLocation = CCDirector::sharedDirector()->convertToGL(oldTouchLocation);
oldTouchLocation = _tileMap->convertToNodeSpace(oldTouchLocation);
//get the difference in the finger touches when the player was dragging
CCPoint difference = ccpSub(newTouchLocation, oldTouchLocation);
CCPoint ASD=ccpAdd(_tileMap->getPosition(), ccpMult(difference, _tileMap->getScale()));
CCPoint bottomLeft =ASD;
// Bounding Box....
if (bottomLeft.x >0) {
bottomLeft.x = 0;
}
if (bottomLeft.y>0) {
bottomLeft.y = 0;
}
if (bottomLeft.x < -(mapWidth*_tileMap->getScale() - _screenSize.width)) {
bottomLeft.x = -(mapWidth*_tileMap->getScale()- _screenSize.width);
}
if (bottomLeft.y <-(mapHieght*_tileMap->getScale() - _screenSize.height)) {
bottomLeft.y = - (mapHieght*_tileMap->getScale() - _screenSize.height);
}
_tileMap->setPosition(bottomLeft);
I hope this may help you..

Cocos2D - adding support for normalized position

I want cocos2d to use the nodes' position as a normalized factor when coordinates are less than 1, and otherwise when coordinates are greater than 1, I want cocos2d to treat coordinates as it would normaly do
For instace position (1,1) would locate the node at the top-right corner of the screen, while (.5,.5) would locate the node on the center of the screen.
I was thinking on rewriting the code just where cocos2d pass on the coordinates to the vertex buffer like the following pseudo code
if (Position.x <= 1)
BufferPosition.x = Screen.Width * Position.x;
else
BufferPosition.x = Position.x;
if (Position.y <= 1)
BufferPosition.y = Screen.Height * Position.y;
else
BufferPosition.y = Position.y;
So my question is where would you put this code so that cocos2d works that way with no problem, or what approach would you take istead if it's different from the shown above
Update
I changed every single call/assignment of positionInPixels_ in the entire cocos2d library for positionInPixels
This means that each time, the corresponding getter and setter is caled instead of the CCNode's positionInPixels_ member variable.
Then I changed setPosition, and setPositionInPixels in CCNode.m like this
setPosition
-(void) setPosition: (CGPoint)newPosition
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
position_ = newPosition;
if (position_.x <= 1.0) {
positionInPixels_.x = position_.x * winSize.width;
}
else
{
if( CC_CONTENT_SCALE_FACTOR() == 1 )
positionInPixels_.x = position_.x;
else
positionInPixels_.x = newPosition.x * CC_CONTENT_SCALE_FACTOR();
}
if (position_.y <= 1.0) {
positionInPixels_.y = position_.y * winSize.height;
}
else
{
if( CC_CONTENT_SCALE_FACTOR() == 1 )
positionInPixels_.y = position_.y;
else
positionInPixels_.y = newPosition.y * CC_CONTENT_SCALE_FACTOR();
}
isTransformDirty_ = isInverseDirty_ = YES;
#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX
isTransformGLDirty_ = YES;
#endif
}
setPositionInPixels
-(void) setPositionInPixels:(CGPoint)newPosition
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
positionInPixels_ = newPosition;
if (position_.x <= 1.0) {
position_.x = positionInPixels_.x / winSize.width;
}
else
{
if( CC_CONTENT_SCALE_FACTOR() == 1 )
position_.x = positionInPixels_.x;
else
position_.x = newPosition.x * 1/CC_CONTENT_SCALE_FACTOR() / winSize.width;
}
if (position_.y <= 1.0) {
position_.y = positionInPixels_.y / winSize.height;
}
else
{
if( CC_CONTENT_SCALE_FACTOR() == 1 )
position_.y = positionInPixels_.y;
else
position_.y = newPosition.y * 1/CC_CONTENT_SCALE_FACTOR() / winSize.height;
}
isTransformDirty_ = isInverseDirty_ = YES;
#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX
isTransformGLDirty_ = YES;
#endif
}
This is working so far, even when using normalized coordinates with CCActionInterval
So you really think that this variant is the right one? To change the whole framework instead of just adding one method to the CCNode's extension? I think you will see a lot of problem with positioning very soon.
Also it is not good, that (1.f, 1.f) is always have coordinates (screenWidth, screenHeight). You will have a lot of troubles with small nodes. IMHO if you need such positioning, it is much more appropriate to connect this "factor" coordinates to the node's parent size.
Just do restore cocos2d framework and add your own extension to your project. For example,
#interface CCNode(FactorPositioning)
- (void) setPositionFactor:(CGFloat) factor;
#end
#implementation CCNode(FactorPositioning)
- (void) setPositionFactor:(CGFloat) factor
{
assert(self.parent != nil);
// do your calculations relatieve to the parent's content size
}
#end

Help with moving CGPoint (cocos2d)

I don't know how the best way is to explain what I am trying to do, but I will give it a shot.
So I have a football on the screen and when I touch the screen I can drag my finger across the screen which by using a CCProgressTimer gives me a representation of the power of the throw. In other words it is like having a volume bar on the screen and when I touch the screen and drag away from the volume bar it starts to increase and dragging towards the volume bar decreases it. So my question is I am recording my touch when I touch the screen and that touch is what all the calculations are based off of, but I want to some how be able to no matter where I am on the screen be able to decrease or increase. The way it works right now is, I can't start to decrease the power of the throw until I reach that recorded touch. Is it possible to have it work, where when the power is at %100 no matter where I am on the screen if I drag back towards the football to have it decrease before I reach that original touch on the screen. Here is my code.
footballPath = [CCProgressTimer progressWithFile:#"footballPath.png"];
footballPath.visible = NO;
footballPath.percentage = 0;
footballPath.type = kCCProgressTimerTypeHorizontalBarLR;
footballPath.anchorPoint = ccp(0, 0.5);
[self addChild:footballPath];
-(BOOL)ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event {
cachedTouchPt = [self convertTouchToNodeSpace:touch];
if (!self.currentFootball.footballHasEmbarked) {
footballPath.percentage = 0;
[self updateArrowWithTouch:touch];
arrow.visible = YES;
footballPath.visible = YES;
return YES;
}
else {
return NO;
}
}
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
[self updateArrowWithTouch:touch];
}
-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent *)event {
const float max = 100;
CGPoint touchPt = [self convertTouchToNodeSpace:touch];
if (touchPt.x > self.currentFootball.position.x) {
touchPt = ccp(self.currentFootball.position.x, touchPt.y);
}
arrow.visible = NO;
footballPath.visible = NO;
self.currentFootball.footballHasEmbarked = YES;
self.currentFootball.spiraling = YES;
if (self.currentFootball) {
[smgr morphShapeToActive:self.currentFootball.shape mass:25];
}
diff = ccpSub(touchPt, self.currentFootball.position);
if (diff.x == 0 && diff.y == 0) {
diff = ccp(1, 1);
}
float len = footballPath.percentage;
CGPoint norm = ccpNormalize(diff);
if (len > max){
len = max;
}
[self.currentFootball applyImpulse:ccpMult(norm, (len * 245))];
pos = self.currentFootball.position.y;
[self schedule:#selector(newFootball)];
}
- (void) updateArrowWithTouch: (UITouch*) touch {
const float distFromFb = 100;
CGPoint touchPt = [self convertTouchToNodeSpace:touch];
if (touchPt.x > self.currentFootball.position.x) {
touchPt = ccp(self.currentFootball.position.x, touchPt.y);
}
CGPoint fpt = self.currentFootball.position;
CGPoint vect = ccpSub(touchPt, fpt);
float dist = ccpLength(vect);
CGPoint vect2 = ccpSub(touchPt, cachedTouchPt);
float dist2 = ccpLength(vect2);
float degrees = -CC_RADIANS_TO_DEGREES(ccpToAngle(vect));
float factor = dist2;
CGPoint normalVect = ccpMult(vect, 1/dist);
factor = distFromFb;
CGPoint newPoint = ccpAdd(fpt, ccpMult(normalVect, factor));
if (newPoint.x < self.currentFootball.position.x+1) {
arrow.rotation = degrees;
arrow.position = newPoint;
self.currentFootball.rotation = -CC_RADIANS_TO_DEGREES (ccpToAngle(ccpSub(touchPt, self.currentFootball.position)));
footballPath.rotation = self.currentFootball.rotation;
footballPath.position = fpt;
}
float percentage = dist - ccpLength(ccpSub(cachedTouchPt, self.currentFootball.position));
if (percentage < 0.0f){
percentage = 0.0f;
}
// CCLOG(#"cachedDist = %f", cachedDist);
CCLOG(#"percentage = %f", percentage);
diff = vect2;
footballPath.percentage = percentage;
}
The cachedTouchPt = [self convertTouchToNodeSpace:touch]; is the original point that I am talking about. So when I touch the screen it makes a new point and stores it in cachedTouchPt, and so I can't decrease the the power/CCProgressTimer until I reach the X and Y of the cachedTouchPt. If I didn't make sense of what I was trying to say. I need to be able to decrease the power/CCProgressTimer without needing to be at the original point. Is there a way reset the point so that no matter where I am on the screen I can drag towards the football and have it decrease, same with the increasing.
I'll answer your question by showing you the math but you need to convert it to codes yourself.
First, you need to decide (you probably did) how long (in points) does a touch need to move to increase the power of the throw from 0 to 1. Let's refer to this value as D.
Then, let's refer to the coordinate where the touch began as Tx and Ty.
Then, when the touch moved to new coordinate Ux and Uy, you the distance the touch has moved from (Tx, Ty) using formula:
E = sqrt( pow( Ux - Tx, 2 ) + pow( Uy - Ty, 2 ) )
And you calculate the power using formula:
P = E / D
Up to this point I think your code is already doing all these calculations. But what's coming next is how you would handle if player still moves the touch exceeding the distance D from the touch began point, that is to say that E > D.
First, put an IF block:
if (E > D) { ... }
So, now you want to modify Tx and Ty values (i.e. the touch began point) so that the distance from the current touch position to that coordinate is D. Here's the magic formula:
angle = atan2( Uy - Ty, Ux - Tx )
Ty = Uy - D * sin( angle )
Tx = Ux - D * cos( angle )
At this point you might want to modify the power value to 1:
P = 1
E = D
And that's it! The formula will move the touch began point towards the current touch position to maintain the condition of E <= D. Naturally the case where the player moves the touch towards the touch began point is taken care of.
Good luck!

Joint in Box2d with cocos2d

I'm new in box2d and I tried to create joint between two body.
I wrote a joint like
b2RevoluteJointDef jointDef;
jointDef.bodyA=worm_head;
jointDef.bodyB=worm_body;
jointDef.lowerAngle = -0.25f * b2_pi; // -45 degrees
jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees
jointDef.enableLimit=true;
jointDef.maxMotorTorque = 10.0f;
jointDef.motorSpeed = 10.0f;
jointDef.enableMotor = true;
joint=(b2DistanceJoint*)_world->CreateJoint(&jointDef);
but body is not moving when head is moving.
my tick method is
- (void)tick:(ccTime) dt {
//we update the position of the b2body based on the sprite position
for (b2Body* body = _world->GetBodyList(); body != nil; body = body->GetNext())
{
if (body->GetUserData() != NULL) {
CCSprite *spritedata = (CCSprite *)body->GetUserData();
if(spritedata.tag==1)
{
b2Vec2 b2Position = b2Vec2(SCREEN_TO_WORLD(spritedata.position.x),
SCREEN_TO_WORLD(spritedata.position.y));
float32 b2Angle = -1 * CC_DEGREES_TO_RADIANS(spritedata.rotation);
body->SetTransform(b2Position,b2Angle);
}
else {
spritedata.position = ccp(body->GetPosition().x * PTM_RATIO,
body->GetPosition().y * PTM_RATIO);
spritedata.rotation = -1 * CC_RADIANS_TO_DEGREES(body->GetAngle());
}
}
}
}
Why is not moving ? How should I change my code ?
In b2RevoluteJointDef , one body is static body and another is dynamic body. My problem is using two dynamic problem. Now, it solved.