How to pinch/zoom on a cocos2d (Kobold2D) CCLayer - cocos2d-iphone

I have been trying to implement a standard pinch/zoom on a CCLayer in cocos2d (using the Kobold2D gesture recognisers) but with only partial success.
Below is my code which does make pinch/zoom work, so long as the pinch point doesn't move. However if I zoom in over one point on the layer and then lift off and move my fingers to zoom in further over another point, there is an instantaneous jump of the layer. It jumps to where the layer would have been if I'd been zooming in over the second point from the start, instead of simply zooming smoothly from where it was.
Can you see what I'm doing wrong or have I missed an existing simple pinch/zoom algorithm that does this job for CCLayers?
NB: I've left the default (YES) value for ignoreAnchorInPosition. Also, at the start self.scalePrePinch = 1.0f
-(void) update:(ccTime)delta
{
KKInput* input = [KKInput sharedInput];
if (input.gesturePinchBegan) {
CGSize scr = [[CCDirector sharedDirector] screenSize];
CGPoint pinchLocation = [self convertToNodeSpace:input.gesturePinchLocation];
CGPoint anchor = ccp(pinchLocation.x/scr.width, pinchLocation.y/scr.height);
CGFloat newScale = input.gesturePinchScale * self.scalePrePinch;
self.anchorPoint = ccp(self.anchorPoint.x + self.scale / newScale * (anchor.x - self.anchorPoint.x),
self.anchorPoint.y + self.scale / newScale * (anchor.y - self.anchorPoint.y));
self.scale = newScale;
}
else
self.scalePrePinch = self.scale;
}

Related

cocos2d flappy bird demo

I am working with the flappy bird demo trying different things just to get to "know each other".
Going through the demo, I've managed to change the direction of the game to vertical scroll moving upwards.
Having reversed the CGFloat to negative values makes my obstacles move upward but once they are out of bounds they do not re-spawn.
If I change the values for a downward scroll they re-spawn as per the update method.
Can someone explain to me what I'm doing wrong with the x to y conversion? Why is the bottom recognized and the top of my screen not?
Thanks in advance
#import "MainScene.h"
static const CGFloat scrollSpeed = -280.f; //upwards
static const CGFloat firstObstaclePosition = -568.f;
static const CGFloat distanceBetweenObstacles = 80;
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
NSMutableArray *_obstacles;
}
- (void)spawnNewObstacle {
CCNode *previousObstacle = [_obstacles lastObject];
CGFloat previousObstacleYPosition = previousObstacle.position.y;
if (!previousObstacle) {
// this is the first obstacle
previousObstacleYPosition = firstObstaclePosition;
}
CCNode *obstacle = [CCBReader load:#"Obstacle"];
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles);
[_physicsNode addChild:obstacle];
[_obstacles addObject:obstacle];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y + delta * scrollSpeed);//move on Y axis
_physicsNode.position = ccp(_physicsNode.position.x, _physicsNode.position.y - (scrollSpeed *delta));//scroll in Y axis
//spawn more
NSMutableArray *offScreenObstacles = nil;
for (CCNode *obstacle in _obstacles) {
CGPoint obstacleWorldPosition = [_physicsNode convertToWorldSpace:obstacle.position];
CGPoint obstacleScreenPosition = [self convertToNodeSpace:obstacleWorldPosition];
if (obstacleScreenPosition.y < -obstacle.contentSize.height) {
if (!offScreenObstacles) {
offScreenObstacles = [NSMutableArray array];
}
[offScreenObstacles addObject:obstacle];
}
}
for (CCNode *obstacleToRemove in offScreenObstacles) {
[obstacleToRemove removeFromParent];
[_obstacles removeObject:obstacleToRemove];
// for each removed obstacle, add a new one
[self spawnNewObstacle];
}
}
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_obstacles = [NSMutableArray array];
[self spawnNewObstacle];
[self spawnNewObstacle];
[self spawnNewObstacle];
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
}
#end
I've attached the _physicsNode screenshot from SB.
It looks like your obstacles will be spawning fine if they are a short, constant height, and the distance between them value is large enough. It may be better to incorporate the height of the obstacles to get a more meaningful value of the distance variable. Just a thought.
The line -
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles);
Could be -
obstacle.position = ccp(0, previousObstacleYPosition + distanceBetweenObstacles + previousObstacle.contentSize.height);
As for the problem of the vertical scrolling working downwards and not upwards I believe it is due to this line:
if (obstacleScreenPosition.y < -obstacle.contentSize.height) {
Since this line is responsible for determining when an obstacle is off the screen it has an effect on the spawning of the next obstacle. It makes sense why this line works for downwards scrolling but needs to be changed for upwards scrolling.
Try:
if (obstacleScreenPosition.y > (_physicsNode.contentSize.height + obstacle.contentSize.height)) {
You may or may not need the size of the obstacle depending on where it is anchored.
I hope this works, Good luck.

CGRectIntersectsRect not working how I would like

Thanks for taking the time to look at my noob question and any help will be greatly appreciated.
Firstly I am trying to make an object 'bounce' from one side of the screen to the other using bezier curves (don't want to use box2d).
The object has 4 states representing the 4 movements, when the object hits (reacts to) the floor it moves from the previous state to a new state, please see picture below...
**My problem is that I can not get it to transition from state 1 to state 4 using CGRect correctly. I can currently get the object to move states but then one of two things happen..
1) The below code will allow the object to change from state 1 to state 2 correctly but it will then not react with the floor (it just moves through it) at the end of state 2 so does not change to state 3.
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
CGRect characterBox = [character adjustedBoundingBox];
if (CGRectIntersectsRect(floorBoudingBox, ballBoundingBox)) {
if (characterState == kStateTravelling) {
[self changeState:kStateTravellingPoint2];
}
if (characterState == kStateTravellingPoint2) {
[self changeState:kStateTravellingPoint3];
}
}
2) The below code will stop the object when it first hits the floor but if I remove the floor for a second while the program is running the object will then move to state 3 and once again stop when it hits the floor, if I remove the floor again it will once again move to state 4, this is without adding the other states (as you will see below) so it is just repeating state 2 each time. This I am guessing is because it returns TRUE as soon as it hits the floor and while it is TRUE it will not action.
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
isfloorWithinBoundingBox =
CGRectIntersectsRect(floorBoudingBox, ballBoundingBox) ?
YES : NO;
if (isfloorWithinBoundingBox) {
[self changeState:kStateTravellingPoint2];
}
What I want it to do is move between the 4 states if the floor is active in a 'bounce' movement, so as soon as state 1 is complete it will hit the floor and move into state 2 until it completes the curve movement and hits the floor again and then moves into state 3 etc.
Right I have got the above to work but do not completely understand how I have done it!
I tried using screenSize to determine where the object was and base my CGRect from that but it did not work at first. I then kept the code but added 'CGSize screenSize = [CCDirector sharedDirector].winSize;' to the method and then to object performed just how I wanted.
I now however have the following errors when I use screenSize..
'Local declaration of 'screenSize' hide instance variable'
What I would like to know is why its not working using screenSize until I declare it in the method (as its already been declared in another class that is imported into this one).
Thanks again to anyone that has taken the time to read this.
my code is below...
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
CGPoint floorPosition = [floorCharacter position];
*Below is where I have added screenSize*
CGSize screenSize = [CCDirector sharedDirector].winSize;
isfloorWithinBoundingBox =
CGRectIntersectsRect(floorBoudingBox, ballBoundingBox) ?
YES : NO;
if ((characterState == kStateTravelling) && (isfloorWithinBoundingBox == YES)) {{
[self changeState:kStateTravellingPoint2];
}
return;
}
if ((characterState == kStateTravellingPoint2)&&(isfloorWithinBoundingBox == YES) && (floorPosition.x > screenSize.width*0.32f) && (floorPosition.x < screenSize.width*0.45f)) {{
[self changeState:kStateTravellingPoint3];
}
return;
}
if ((characterState == kStateTravellingPoint3)&&(isfloorWithinBoundingBox == YES) && (floorPosition.x > screenSize.width*0.45f) && (floorPosition.x < screenSize.width*0.55f)) {{
[self changeState:kStateTravellingPoint4];
}
return;
}

Camera following the touched body

I need to have the Cocos2d camera follow a sprite (attached to a Box2D body) that the user is touching on the screen. As the user is dragging the player around, I need it to be able to go to other parts of the world. This has to be through touch, and not automatic scrolling.
I tried several approaches based on tutorials but nothing seem to address this issue. For example the solution offered here Move CCCamera with the ccTouchesMoved method? (cocos2d,iphone) by #Michael Fredrickson has the entire layer move, but when it moves, the sprites / bodies on the screen have unmatched coordinations and when I test to see if they're touched, the if(fixture->TestPoint(locationWorld)) fails.
I also looked at the tutorials here http://www.learn-cocos2d.com/2012/12/ways-scrolling-cocos2d-explained/ but this also isn't what I'm looking for.
Any help would be greatly appreciated.
EDIT:
I'm accepting Liolik's answer below because it put me on the right track. The last piece of the puzzle, though, is to make the value received from the getPoint method an instance variable, and deduce it from locationWorld which I'm doing the TestPoint against. Like this:
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
b2Vec2 diff = b2Vec2(difference.x, difference.y);
for (b2Body* b = _world->GetBodyList(); b; b = b->GetNext()) {
b2Fixture* f = b->GetFixtureList();
while(f != NULL) {
if(f->TestPoint(locationWorld-diff)) {
b2MouseJointDef def;
def.bodyA = _groundBody;
def.bodyB = b;
def.target = locationWorld-diff;
def.maxForce = 9999999.0f * b->GetMass();
_mouseJoint = (b2MouseJoint*)_world->CreateJoint(&def);
b->SetAwake(true);
}
f = f->GetNext();
}
}
in update function :
CGPoint direction = [self getPoint:myBody->GetPosition()];
[self setPosition:direction];
- (CGPoint)getPoint:(b2Vec2)vec
{
CGSize screen = [[CCDirector sharedDirector] winSize];
float x = vec.x * PTM_RATIO;
float y = vec.y * PTM_RATIO;
x = MAX(x, screen.width/2);
y = MAX(y, screen.height/2);
float _x = area.width - (screen.width/2);
float _y = area.height - (screen.height/2);
x = MIN(x, _x);
y = MIN(y, _y);
CGPoint goodPoint = ccp(x,y);
CGPoint centerOfScreen = ccp(screen.width/2, screen.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
return difference;
}
So if i understand correctly, when the sprite is inside of the middle of the screen, the background is stationary and the sprite follows your finger, but when you scroll toward the edge, the camera starts to pan?
I had something roughly similar in my game Star Digger where there's a ship in the middle of the screen on its own layer that has to fly around the world, and had the same problem when the ship fired bullets into the main world layer.
heres what I did:
float thresholdMinX = winSize*1/3;
float thresholdMaxX = winSize*2/3;
if(touch.x > thresholdMaxX) //scrolling right
{
self.x += touch.x - thresholdMaxX;
}
else if(touchX < thresholdMinX)
{
self.x += thresholdMinX - touchX;
}
else
{
sprite.position = touch;
}
CGPoint spritePointInWorld = ccp(sprite.x - self.x, sprite.y - self.y);
then every time you calculate collisions, you need to recompute the sprites "actual" position in the world, which is its screen position minus the worlds offset, instead of the sprites screen position.

Cocos2D Make Camera Follow Sprite Not Working Properly

I am using the following code to make the camera follow my character as he falls down the screen:
[self runAction:[CCFollow actionWithTarget:charSprite worldBoundary:[levelLoader gameWorldSize]/*CGRectMake(0,0,320,3000)*/]];
What happens is this: the code runs as you would expect, and follows the character as he falls from the top of the screen. However: when he reaches the middle of the screen the camera stops following him.
I have tried both the code above as well as replacing the [levelLoader gameWorldSize] with the commented out CGRectMake(0,0,320,3000) - both yield the same results. As a last resort I tried to use some code I found on www.raywenderlich.com that has worked for me before (see below) (it worked on the x-axis in another game I wrote), but the same problem occurred.
-(void)setViewpointCenter:(CGPoint) position
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
CGRect worldRect = [levelLoader gameWorldSize];
int x = MAX(position.x, worldRect.origin.x + winSize.width / 2);
int y = MAX(position.y, worldRect.origin.y + winSize.height / 2);
x = MIN(x, (worldRect.origin.x + worldRect.size.width) - winSize.width / 2);
y = MIN(y, (worldRect.origin.y + worldRect.size.height) - winSize.height/2);
CGPoint actualPosition = ccp(x, y);
CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
self.position = viewPoint;
}
My world is created in Portrait mode using LevelHelper.
Any help would be greatly appreciated!
CCFollow follows the node within the world boundary, minus half the screen width/height. If your sprite starts falling at y = 320 then CCFollow will follow it until y = 160.
You can either position the sprite to your world boundary's top (y = 3000) or make the world boundary negative (y = -3000). Either way, when the sprite reaches the world boundary, scrolling will stop half a screen width/height before the world boundary.

side scrollling boundaries in cocos2d with levelhelper

I am making a side scrolling game with levelhelper and sneakyinput.
i have couple questions.
i have sneakyinput on a different layer and i am facing a problem on scrolling with the parallax at levelhelper.
i cant manage to apply boundaries and move the layer properly.
how i will fix the scrolling? to be inside the boundaries and the character centered?
i have those 2 methods inside the update method
-(void) update:(ccTime)deltaTime{
[self applyJoystick:_leftJoystick forTimeDelta:deltaTime];
[self setViewpointCenter:hero.position];}
-(void)applyJoystick:(SneakyJoystick *)aJoystick forTimeDelta:(float)delta{
CGRect worldRect = [loader gameWorldSize];
CGPoint scaledVelocity=ccpMult(aJoystick.velocity, 90.0f);
CGPoint newPosition =ccp(hero.position.x+scaledVelocity.x*delta, hero.position.y +scaledVelocity.y *delta);
float posX = MIN(worldRect.origin.x + worldRect.size.width - hero.centerToSides, MAX(hero.centerToSides, newPosition.x));
float posY = MIN(worldRect.origin.y + worldRect.size.height - hero.centerToBottom, MAX(hero.centerToBottom, newPosition.y));
[hero setPosition:cpp(posX,posY)];}
-(void)setViewpointCenter:(CGPoint) position {
CGSize winSize = [[CCDirector sharedDirector] winSize];
CGRect worldRect = [loader gameWorldSize];
CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
int x = MAX(position.x, worldRect.origin.x + winSize.width / 2);
int y = MAX(position.y, worldRect.origin.y + winSize.height / 2);
x = MIN(x, (worldRect.origin.x + worldRect.size.width) - winSize.width / 2);
y = MIN(y, (worldRect.origin.y + worldRect.size.height) - winSize.height/2);
CGPoint actualPosition = ccp(x, y);
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
self.position = viewPoint;}
also i try to flip the character (LHSprite) i use
if (newPosition.x< hero.position.x)
hero.flipX=YES;
else
hero.flipX = YES;
but isnt working but i tried to use also
hero.scaleX=-1
to flip it instead of flipX,flips but goes to the other side of the screen fliped.
solved partially.
it seemed that
myParallax = [loader parallaxNodeWithUniqueName:#"Parallax_1"];
was messing it up so i disable it,
i change the first method to
-(void)applyJoystick:(SneakyJoystick *)aJoystick forTimeDelta:(float)delta{
CGRect worldRect = [loader gameWorldSize];
CGSize winSize = [[CCDirector sharedDirector] winSize];
CGPoint scaledVelocity=ccpMult(aJoystick.velocity, 200.0f);
CGPoint newPosition =ccp(hero.position.x+scaledVelocity.x*delta, hero.position.y +scaledVelocity.y *delta);
float posX = MIN(worldRect.size.width - hero.centerToSides, MAX(hero.centerToSides, newPosition.x));
float posY = MIN(winSize.height -winSize.height/2+ hero.centerToBottom, MAX(hero.centerToBottom, newPosition.y));
if (scaledVelocity.x >= 0)
hero.scaleX = 1.0;
else
hero.scaleX = -1.0;
[hero setPosition:ccp(posX, posY)];
}
it seems when the scrolling isnt correct and goes to white area out of the world and everything is correct you need to find an equation about parallax ratio and levelsize and velocity.