cocos2dx inverse scaling a layer - c++

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

Related

Zooming towards center of Camera on 2d Plane

Once again, camera zooming on a 2D-Plane. I searched a lot and know that there are similar questions, but I am obviously way too stupid to apply what I was able to find.
Basically I multiply the distance of all elements to the origin by mouseDelta, which is a double between 0.5 and 1. works fine for all elements, but since the anchor of the camera (camX, camY) are the upper left corner of the camera, the objects in the focus of the cam change their position in relation to the focus. I want to scroll "towards" the focus. Here is what I got, but it behaves really weird:
camX and camY, as mentioned, are the coordinates for the upper left of the cam.
mouseDelta is the zoom-level thats stored globally and is changed by each wheel-event.
screenX is the width of the screen/window (fullscreen anyways)
screenY is the height of the screen/window
if (newEvent.type == sf::Event::MouseWheelMoved) //zoom
{
mouseDelta += ((double)newEvent.mouseWheel.delta)/20;
if (mouseDelta > 1) { mouseDelta = 1; }
else if (mouseDelta < 0.5) { mouseDelta = 0.5; }
//resize graphics
for (int i = 0; i < core->universe->world->nodes.size(); i++) {
core->universe->world->nodes.at(i).pic->setSize(mouseDelta);
}
for (int i = 0; i < core->universe->world->links.size(); i++) {
core->universe->world->links.at(i).pic->setSize(mouseDelta);
}
camX = (camX + screenX/2) - (camX + screenX/2)*mouseDelta;
camY = (camY + screenY/2) - (camY + screenY/2)*mouseDelta;
}

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.

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

SDL - drawing 'negative' circles (Fog of War)

I have this 800x600square I want to draw to the screen. I want to 'cut' circles in it (where alpha would be 0). Basically I'm drawing this whole rectangle over a map so in these 'circles' I drew, you can see the map, otherwise you see the grey square
So, I assume you're trying to add fog of war to one of you game?
I had a small demo I made for a local University a few weeks ago to show A* pathfinding, so I thought I could add fog of war to it for you. Here's the results:
Initial map
First, you start with a complete map, totally visible
Fog
Then, I added a surface to cover the entire screen (take note that my map is smaller than the screen, so for this case I just added fog of war on the screen, but if you have scrolling, make sure it covers each map pixel 1:1)
mFogOfWar = SDL_CreateRGBSurface(SDL_HWSURFACE, in_Width, in_Height, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
SDL_Rect screenRect = {0, 0, in_Width, in_Height};
SDL_FillRect(mFogOfWar, &screenRect, 0xFF202020);
Then, you need to draw it... I added this call after drawing the game objects and before drawing the UI
DrawSurface(mFogOfWar, 0, 0);
Where
void RenderingManager::DrawSurface(SDL_Surface* in_Surface, int in_X, int in_Y)
{
SDL_Rect Dest = { in_X, in_Y, 0, 0 };
SDL_BlitSurface(in_Surface, NULL, mScreen, &Dest);
}
Which should give you the following result:
"Punch Surface"
I then created a 32 bits .png that looks like this (checkerboard shows alpha)
When rendering my main character, I added this call:
gRenderingManager.RemoveFogOfWar(int(mX) + SPRITE_X_OFFSET, int(mY) + SPRITE_Y_OFFSET);
The offset is only there to center the punch with the sprite, basically, what I'm passing to RemoveFogOfWar is the center of my sprite.
Remove Fog Of War
Now the meat of the fog of war. I did two versions, one where Fog of War is removed permanently and one where the fog of war is reset. My fog of war reset relies on my punch surface to have a contour where the alpha is reset to 0 and the fact that my character moves of less pixels than the contour contains per frame, otherwise I would keep the Rect where my punch was applied and I would refill it before drawing again the new punch.
Since I couldn't find a "multiply" blend with SDL, I decided to write a simple function that iterates on the punch surface and updates the alpha on the fog of war surface. The most important part is to make sure you stay within the bounds of your surfaces, so it takes up most of the code... there might be some crop functions but I didn't bother checking:
void RenderingManager::RemoveFogOfWar(int in_X, int in_Y)
{
const int halfWidth = mFogOfWarPunch->w / 2;
const int halfHeight = mFogOfWarPunch->h / 2;
SDL_Rect sourceRect = { 0, 0, mFogOfWarPunch->w, mFogOfWarPunch->h };
SDL_Rect destRect = { in_X - halfWidth, in_Y - halfHeight, mFogOfWarPunch->w, mFogOfWarPunch->h };
// Make sure our rects stays within bounds
if(destRect.x < 0)
{
sourceRect.x -= destRect.x; // remove the pixels outside of the surface
sourceRect.w -= sourceRect.x; // shrink to the surface, not to offset fog
destRect.x = 0;
destRect.w -= sourceRect.x; // shrink the width to stay within bounds
}
if(destRect.y < 0)
{
sourceRect.y -= destRect.y; // remove the pixels outside
sourceRect.h -= sourceRect.y; // shrink to the surface, not to offset fog
destRect.y = 0;
destRect.h -= sourceRect.y; // shrink the height to stay within bounds
}
int xDistanceFromEdge = (destRect.x + destRect.w) - mFogOfWar->w;
if(xDistanceFromEdge > 0) // we're busting
{
sourceRect.w -= xDistanceFromEdge;
destRect.w -= xDistanceFromEdge;
}
int yDistanceFromEdge = (destRect.y + destRect.h) - mFogOfWar->h;
if(yDistanceFromEdge > 0) // we're busting
{
sourceRect.h -= yDistanceFromEdge;
destRect.h -= yDistanceFromEdge;
}
SDL_LockSurface(mFogOfWar);
Uint32* destPixels = (Uint32*)mFogOfWar->pixels;
Uint32* srcPixels = (Uint32*)mFogOfWarPunch->pixels;
static bool keepFogRemoved = false;
for(int x = 0; x < destRect.w; ++x)
{
for(int y = 0; y < destRect.h; ++y)
{
Uint32* destPixel = destPixels + (y + destRect.y) * mFogOfWar->w + destRect.x + x;
Uint32* srcPixel = srcPixels + (y + sourceRect.y) * mFogOfWarPunch->w + sourceRect.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3; // fetch alpha channel
unsigned char* srcAlpha = (unsigned char*)srcPixel + 3; // fetch alpha channel
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
*destAlpha = *srcAlpha;
}
}
SDL_UnlockSurface(mFogOfWar);
}
Which then gave me this with keepFogRemoved = false even after the character had moved around
And this with keepFogRemoved = true
Validation
The important part is really to make sure you don't write outside of your pixel buffer, so watch out with negative offsets or offsets that would bring you out of the width or height. To validate my code, I added a simple call to RemoveFogOfWar when the mouse is clicked and tried corners and edges to make sure I didn't have a "off by one" problem
case SDL_MOUSEBUTTONDOWN:
{
if(Event.button.button == SDL_BUTTON_LEFT)
{
gRenderingManager.RemoveFogOfWar(Event.button.x, Event.button.y);
}
break;
}
Notes
Obviously, you don't need a 32 bits texture for the "punch", but it was the clearest way I could think of to show you how to do it. It could be done using as little as 1 bit per pixel (on / off). You can also add some gradient, and change the
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
To something like
if(*srcAlpha > *destAlpha)
{
continue;
}
To keep a smooth blend like this:
3 State Fog of War
I thought I should add this... I added a way to create a 3 state fog of war: visible, seen and fogged.
To do this, I simply keep the SDL_Rect of where I last "punched" the fog of war, and if the alpha is lower than a certain value, I clamp it at that value.
So, by simply adding
for(int x = 0; x < mLastFogOfWarPunchPosition.w; ++x)
{
for(int y = 0; y < mLastFogOfWarPunchPosition.h; ++y)
{
Uint32* destPixel = destPixels + (y + mLastFogOfWarPunchPosition.y) * mFogOfWar->w + mLastFogOfWarPunchPosition.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3;
if(*destAlpha < 0x60)
{
*destAlpha = 0x60;
}
}
}
mLastFogOfWarPunchPosition = destRect;
right before the loop where the fog of war is "punched", I get a fog of war similar to what you could have in games like StarCraft:
Now, since the "seen" fog of war is semi transparent, you will need to tweak your rendering method to properly clip "enemies" that would be in the fog, so you don't see them but you still see the terrain.
Hope this helps!

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!