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

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.

Related

Using a random point in two actions

Sorry I'm new to this.
Right now I'm trying to create an endless randomly moving sprite that rotates to its direction. However I can't figure out how to use the random location generated from randomPoint on the rotateAction. Basically the bug randomly rotates instead of using the point it's going to. Is there a way I can use the same random point twice?
-(void)moveRandom:(CCSprite*)roach
{
CGPoint randomPoint = ccp(arc4random()%480, arc4random()%320);
NSLog(#"%#", NSStringFromCGPoint(randomPoint));
int minDuration = 2.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration - minDuration;
int randomDuration = (arc4random() % rangeDuration) + minDuration;
float dY = roach.position.y - randomPoint.y;
float dX = roach.position.x - randomPoint.x;
float offset = dX<0 ? 90.0f : -90.0f;
float angle = CC_RADIANS_TO_DEGREES(atan2f(dY, dX)) + offset;
[roach runAction:
[CCActionSequence actions:
[CCActionRotateTo actionWithDuration:0.001 angle:angle],
[CCActionMoveTo actionWithDuration:randomDuration position: randomPoint],
[CCActionCallBlock actionWithBlock:^{
[self performSelector:#selector(moveRandom:) withObject:roach afterDelay:0.5];
}],
nil]
];
Some things that stand out: It looks like you're trying to get the angle in the wrong direction. The conditional offset is not needed. radians to degrees should be negated .. I'm guessing you want:
float dY = randomPoint.y - roach.position.y;
float dX = randomPoint.x - roach.position.x;
float angle = -CC_RADIANS_TO_DEGREES(atan2f(dY, dX));

How to change the direction of a moving sprite at screen edges?

I am using this code in cocos2d-x for shooting in specific directions but at the edge of screen my sprite(i.e. my shooting bubbles) used to get lost. I need help in making it to change its angle when it collides at screen edges and change direction to go up.
Code in c++ using cocos2dx:-
CCSize winSize = CCDirector::sharedDirector()->getVisibleSize();
CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
// Determinie offset of location to projectile
float offX = flocationX - m_pOwner->getPosition().x;
float offY = flocationY - m_pOwner->getPosition().y;
// Bail out if we are shooting down or backwards
if (offY <= 0) return;
// Ok to add now - we've double checked position
// Determine where we wish to shoot the projectile to
//float realX = origin.x + winSize.width + (m_pOwner->getPosition().x);
float realY = origin.y + winSize.height + (m_pOwner->getPosition().y);
// float ratio = offY / offX;
float ratio = offX / offY;
//float realY = (realX * ratio) + m_pOwner->getPosition().y;
float realX = (realY * ratio) + m_pOwner->getPosition().x;
CCPoint realDest = ccp(realX, realY);
// Determine the length of how far we're shooting
float offRealX = realX - m_pOwner->getPosition().x;
float offRealY = realY - m_pOwner->getPosition().y;
float length = sqrtf((offRealX * offRealX) + (offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;
// Move projectile to actual endpoint
m_pOwner->runAction( CCSequence::create(
CCMoveTo::create(realMoveDuration, realDest),
CCCallFuncN::create(getOwner()->getParent()->getComponent("SceneController"),
callfuncN_selector(SceneController::spriteMoveFinished)),
NULL) );

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 - Moving the Layer/view based on ball movement

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
}
}

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!