Cocos2D prevent sprite from going off-screen? - ios

Is it possible to prevent my CCSprite from going off-screen? I already allow it to go offscreen on the left and right so that is fine but I just want to stop it from going off screen on the top and bottom.
So far what I have done is just cause the sprite to just get stuck on either the top or bottom. I don't want this to affect the movement of the sprite, all I want to happen is the CCSprite will just stop when it hits the top or bottom.
Can anyone show me how to do this?
Thanks!
Edit:
CGSize size = [[CCDirector sharedDirector] winSize];
if ((sprite.y <= size.height) && (sprite.y >= 0) ) {
// Set new position
} else {
// sprite is colliding with top/bottom limits, do whatever you like, for example change direction
}

To limit the sprite within a boundary, don't check the current position but check the new position instead. But, rather than using (possibly multiple) if conditions, you can use clamping method:
Technique 1 - using MIN and MAX combo:
CGPoint newPosition = ... (assign new position here using touch location or something)
sprite.position = ccp(newPosition.x, MAX(0, MIN(size.height, newPosition.y)));
Technique 2 - using clampf:
CGPoint newPosition = ... (assign new position here using touch location or something)
sprite.position = ccp(newPosition.x, clampf(newPosition.y, 0, size.height));

CGSize winSize = [[CCDirector sharedDirector] winSize];
CCSprite* sprite = [CCSprite node];
CGSize spriteSize = sprite.boundingBox.size;
if ((sprite.position.y + spriteSize.height/2 < 0 )||(sprite.position.y + spriteSize.height/2 > winSize.height) ) {
//Sprite is out of screen
}
not tested, but since you have the anchorpoint at 0.5, 0.5 as standard this should work for you

Related

Why is scaling/zooming-in an SKNode forcing the view into the left of the screen?

I'm following Ray Wenderlich's 'iOS Games by Tutorials' & I got everything in my world setup & working: The entire game is in Landscape mode & there's one SKNode called _worldNode & everything, except _uiNode (in-game UI), is added to it. Player character walks to a touched location & _worldNode moves under him like a treadmill. However, like all functionality (or as they call it: "juice") addicts I wanted to add zoom in/out functionality through UIPinchGestureRecognizer by scaling _worldNode, which I did. But now every time I zoom in, the "camera" moves to the bottom left. Zooming out moves the view to the top right of the screen. It's a mess. I need the view to stay centered on the player character & I've tried everything I could come up with & find online. The closest thing I came to was using the technique from SKNode scale from the touched point but I still get the bloody bottom left/top right mess. I realized this mess happens only when I update the camera/view (it's really _worldNode.position). Therefore, 'didSimulatePhysics' or 'didFinishUpdate' methods don't help. In fact, even a one time button that slightly moves/updates the camera view (_worldNode.position) still gives me the bottom left/top right problem. Here is my code. I hope someone can take a look & tell me what to modify to get things working.
#interface GameScene () <SKPhysicsContactDelegate, UIGestureRecognizerDelegate>
{
UIPinchGestureRecognizer *pinchGestureRecognizer;
}
//Properties of my GameScene.
#property SKNode *worldNode;
#property etc. etc.
//Called by -(id)initWithSize:(CGSize)size method & creates the in-game world.
-(void)createWorld
{
[_worldNode addChild:_backgroundLayer];
[self addChild:_worldNode];
self.anchorPoint = CGPointMake(0.5, 0.5); //RW tutorial did it this way.
_worldNode.position = CGPointMake(-_backgroundLayer.layerSize.width/2, -_backgroundLayer.layerSize.height/2); //Center.
//Then I add every node to _worldNode from this point on.
}
//Neccessary for gesture recognizers.
-(void)didMoveToView:(SKView *)view
{
pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handleZoomFrom:)];
[view addGestureRecognizer:pinchGestureRecognizer];
}
//"Camera" follows player character. 'didSimulatePhysics' doesn't help either.
-(void)didFinishUpdate
{
//IF '_pinchingScreen' == YES then screen pinching is in progress. Thus, camera position update will seize for the duration of pinching.
if (!_pinchingScreen)
{
_worldNode.position = [self pointToCenterViewOn:_player.position];
}
}
//Method that is called by my UIPinchGestureRecognizer. Answer from: https://stackoverflow.com/questions/21900614/sknode-scale-from-the-touched-point?lq=1
-(void)handleZoomFrom:(UIPinchGestureRecognizer*)recognizer
{
CGPoint anchorPoint = [recognizer locationInView:recognizer.view];
anchorPoint = [self convertPointFromView:anchorPoint];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
// No code needed for zooming...
_player.movementMode = 2; //Stop character from moving from touches.
_pinchingScreen = YES; //Notifies 'didFinishUpdate' method that pinching began & camera position update should stop for now.
}
else if (recognizer.state == UIGestureRecognizerStateChanged)
{
//Technique from the above Stack Overflow link - Commented out.
// CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
//
// [_worldNode setScale:(_worldNode.xScale * recognizer.scale)];
//
// CGPoint mySkNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
// CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, mySkNodeAnchorPointInScene);
//
// _worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//
// recognizer.scale = 1.0;
//Modified scale: 2.0
if(recognizer.scale > _previousWorldScale)
{
_previousWorldScale = recognizer.scale;
CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
[_worldNode setScale:2.0];
CGPoint worldNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, worldNodeAnchorPointInScene);
_worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//[_worldNode runAction:[SKAction scaleTo:2.0 duration:0]]; //This works too.
}
//Original scale: 1.0
if(recognizer.scale < _previousWorldScale)
{
_previousWorldScale = recognizer.scale;
CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
[_worldNode setScale:1.0];
CGPoint worldNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, worldNodeAnchorPointInScene);
_worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//[_worldNode runAction:[SKAction scaleTo:1.0 duration:0]]; //This works too.
}
}
else if (recognizer.state == UIGestureRecognizerStateEnded)
{
// No code needed here for zooming...
_pinchingScreen = NO; //Notifies 'didFinishUpdate' method that pinching has stopped & camera position update should resume.
_player.movementMode = 0; //Resume character movement.
}
}
So could anyone please tell me, by looking at the above code, why the camera/view shifts to the bottom left upon zooming in? I've sat several days on this problem & I still can't figure it out.
Thanks to JKallio, who wrote detailed code in his answer to Zoom and Scroll SKNode in SpriteKit, I've been able to find a piece of code that solves the problem. There's a method called 'centerOnNode' that is small, elegant & solves my problem perfectly. Here it is for anyone that just needs that:
-(void) centerOnNode:(SKNode*)node
{
CGPoint posInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(node.parent.position.x - posInScene.x, node.parent.position.y - posInScene.y);
}
Then you call that method inside your 'didSimulatePhysics' or inside 'didFinishUpdate' like so:
//"Camera" follows player character.
-(void)didFinishUpdate
{
//IF '_pinchingScreen' == YES then screen pinching is in progress. Thus, camera position update will seize for the duration of pinching.
if (!_pinchingScreen)
{
if (_previousWorldScale > 1.0) //If _worldNode scale is greater than 1.0
{
[self centerOnNode:_player]; //THIS IS THE METHOD THAT SOLVES THE PROBLEM!
}
else if (_previousWorldScale == 1.0) //Standard _worldNode scale: 1.0
{
_worldNode.position = [self pointToCenterViewOn:_player.position];
}
}
}
P.S. Just remember the question wasn't HOW to zoom in. But how to fix the issue with the camera once the world is ALREADY zoomed in.

Reading nodes position after physics simulation

So I'm trying to make an app where you pick up objects with your finger, it moves with your finger and then you flick it away to throw it. I found some threads for similar games but not with this particular problem.
Since I couldn't really keep the object along with my finger by applying force or impulse I disable the physics on it while I'm "holding it", measure velocity so when I release it I apply that vector as an impulse.
This works excellent visually but when I try to read the position of the object it's not synced up with "reality". I've made a condition so if velocity isn't strong enough when you release the object it is supposed to return to its starting position but it doesn't move at all (except if you manage to throw it, that works fine) so when I tried to output the .position to the console it didn't correlate to what I was seeing on the screen.
Any ideas? I know you shouldn't set position and simulate physics at the same time but I disable the physics for when I'm dragging the object. Why won't it keep track of .position just because it is moving it with physics?
Here I try to throw a potion:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) { //Picked up
potion.physicsBody.dynamic = NO;
potion.physicsBody.velocity = CGVectorMake(0.0, 0.0);
potion.physicsBody.angularVelocity = 0.0;
potion.zRotation = 0.0;
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation];
} else if (recognizer.state == UIGestureRecognizerStateChanged) { //Moved around
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
[self panForTranslation:translation];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
velocity = CGVectorMake(translation.x, translation.y);
} else if (recognizer.state == UIGestureRecognizerStateEnded) { //Dropped
_selectedNode = nil;
potion.physicsBody.dynamic = YES;
if (potion.position.y < 250 && velocity.dy < 20) { //Returned to starting position
NSLog(#"Pos:%f,%f",potion.position.x,potion.position.y);
potion.physicsBody.velocity = CGVectorMake(0.0, 0.0);
potion.physicsBody.angularVelocity = 0.0;
potion.zRotation = 0.0;
potion.position = CGPointMake(gameWidth / 2, 150);
NSLog(#"Returning potion");
NSLog(#"Pos:%f,%f",potion.position.x,potion.position.y);
} else { //Flung away
[potion.physicsBody applyImpulse:velocity];
NSLog(#"Add impulse: %f, %f", velocity.dx, velocity.dy);
NSLog(#"Pos:%f,%f",potion.position.x,potion.position.y);
}
}
}
EDIT:After some more testing I've observed that if it has never been thrown (dynamic with impulse) it always shows (160, 150) as its position no matter where it is. Sometimes it shows something like 160.00031 and I don't know why. After it has been thrown it shows the correct position in the NSLog but changing the position still doesn't work
EDIT2: Just saw that it doesn't show the correct position after a throw. Just different but they seem offset. I throw objects in the +y direction (up) and when I put it down in the left corner the position.x is correct but position.y is offset by about 300. So where y should be 0 it's 300.

Problems with offsets in portrait mode Cocos2d

I am working on the basis of Ray Wenderlich's tutorial on rotating turrets in Cocos 2d (see here: http://www.raywenderlich.com/25791/rotating-turrets-how-to-make-a-simple-iphone-game-with-cocos2d-2-x-part-2). I need my game to be in portrait mode so I have managed to get the position of the turret correctly:
The turret manages to shoot right, but not left. Here is my code:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (_nextProjectile != nil) return;
// Choose one of the touches to work with
UITouch *touch = [touches anyObject];
CGPoint location = [self convertTouchToNodeSpace:touch];
// Set up initial location of projectile
CGSize winSize = [[CCDirector sharedDirector] winSize];
_nextProjectile = [[CCSprite spriteWithFile:#"projectile2.png"] retain];
_nextProjectile.position = ccp(160, 20);
// Determine offset of location to projectile
CGPoint offset = ccpSub(location, _nextProjectile.position);
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
// Determine where you wish to shoot the projectile to
int realX = winSize.width + (_nextProjectile.contentSize.width/2);
float ratio = (float) offset.y / (float) offset.x;
int realY = (realX * ratio) + _nextProjectile.position.y;
CGPoint realDest = ccp(realX, realY);
// Determine the length of how far you're shooting
int offRealX = realX - _nextProjectile.position.x;
int offRealY = realY - _nextProjectile.position.y;
float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;
// Determine angle to face
float angleRadians = atanf((float)offRealY / (float)offRealX);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
float rotateDegreesPerSecond = 180 / 0.5; // Would take 0.5 seconds to rotate 180 degrees, or half a circle
float degreesDiff = _player.rotation - cocosAngle;
float rotateDuration = fabs(degreesDiff / rotateDegreesPerSecond);
[_player runAction:
[CCSequence actions:
[CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
[CCCallBlock actionWithBlock:^{
// OK to add now - rotation is finished!
[self addChild:_nextProjectile];
[_projectiles addObject:_nextProjectile];
// Release
[_nextProjectile release];
_nextProjectile = nil;
}],
nil]];
// Move projectile to actual endpoint
[_nextProjectile runAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:realMoveDuration position:realDest],
[CCCallBlockN actionWithBlock:^(CCNode *node) {
[_projectiles removeObject:node];
[node removeFromParentAndCleanup:YES];
}],
nil]];
_nextProjectile.tag = 2;
}
Thanks for the help!
You are checking x axis instead of Y
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return
;
Did you actually set the application to run in portrait mode or have you just rotated the simulator and repositioned the turret?
If you didn't explicitly set the app to run in portrait your x and y coordinates will be swapped (x will run from the ios button to the top of the phone, not accross as you would expect).
If it is converted properly I have answered this question before :)
This issue here is that you've copy-pasted the math instead of editing it properly for your purposes. There are some assumptions made in Ray's code that rely on you shooting always to the right of the turret instead of up, down, or left.
Here's the math code you should be looking at:
// Determine offset of location to projectile
CGPoint offset = ccpSub(location, _nextProjectile.position);
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
Note here that you will have an offset.x less than 0 if the tap location is to the left of the turret, so this is an assumption you took from Ray but did not revise. As gheesse said, for your purposes this should be set to offset.y as you don't want them shooting south of the projectile's original location. But this is only part of the problem here.
// Determine where you wish to shoot the projectile to
int realX = winSize.width + (_nextProjectile.contentSize.width/2);
Here's your other big issue. You did not revise Ray's math for determining where the projectile should go. In Ray's code, his projectile will always end up on a location that is off the screen to the right, so he uses the width of the screen and projectile's size to determine the real location he wants the projectile to go. This is causing your issue since you don't have the assumption that your projectile will always head right - yours will always go up (hint, code similar to this should be used for your realY)
float ratio = (float) offset.y / (float) offset.x;
int realY = (realX * ratio) + _nextProjectile.position.y;
Again, Ray makes assumptions in his math for his game and you haven't corrected it in this realY. Your code has the turret turning in ways that will effect the realX coordinate instead of the realY, which is the coordinate that Ray's always shoot right turret needed to effect.
You need to sit down and re-do the math.

Acceleration, moving items

I'm now developing a game that uses acceleration to play. I found out how to make my item move, but not to change its 'origin', or more precisely, the origin for acceleration calculation:
In fact, my image moves, and its center is defined like this:
imageView.center = CGPointMake(230, 240);
As you can see, I use landscape mode. But, I want that my image moves "progressively". What i mean by progressively is like in the game Lane Splitter:
You can see that the bike moves, and for example, when he's completely on the left side, the man can orient his iPad horizontally, but the bike doesn't go back in the middle of the screen. I don't know how to do that, because when I try a solution, my image moves, but gets back to the center as soon as my iPhone is horizontal. I understand why, but I don't know how to correct this.
This is my current code:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
int i = 0;
float current;
if (i == 0)
{
imageView.center = CGPointMake(230, 240);
current = 240;
i++;
}
//try to modify the origin of acceleration
imageView.center = CGPointMake(230, current - (acceleration.y*200));
current = imageView.center.y;
}
The problem is that i is a local variable. Your code is equivalent to
imageView.center = CGPointMake(230, 240);
float current = 240;
imageView.center = CGPointMake(230, current - (acceleration.y*200));
[imageView center];
Instead, try something like this (assuming your image view is at the right location on startup):
CGPoint current = imageView.center;
current.y -= acceleration.y*200;
imageView.center = current;
Also bear in mind that acceleration.y is in the device coordinate space; you'll need to compensate for interface rotation if your UI supports multiple orientations.

How to smoothly move a UIView with the users finger on iOS

in my app I have a UIImageView representing a chalk. The user can pick that up and drag it over the screen to draw with it.
I implemented that using touchesBegan, touchesMoved and touchesEnded. In touchesMoved I move the center of my UIImageView to the current touch location and draw a line with core graphics from the last to the current location.
This works well but the image view movement isn't very smooth and it also lags behind the touch. I already tried to use a UIPanGestureRecognizer (didn't recognize the gesture) and a UIScrollView in which I placed the chalk (didn't really figure out how to configure it so that it could be moved far enough in all directions).
Can you give me some hints how to improve the quality of my chalk movement?
Thanks!
The following tapGesture method method works smooth for me:
- (void)handlePan:(UIPanGestureRecognizer*)gesture{
View* view = (View*)gesture.view;
if (gesture.state == UIGestureRecognizerStateEnded){
view.dragStartingPoint = CGPointZero;
return;
}
CGPoint point = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan){
view.dragStartingPoint = point;
view.dragStartingFrame = view.frame;
return;
}
if (CGPointEqualToPoint(view.dragStartingPoint, CGPointZero))
return;
CGFloat x = view.dragVerticallyOnly? view.frame.origin.x: view.dragStartingFrame.origin.x + point.x - view.dragStartingPoint.x;
CGFloat y = view.dragHorizontallyOnly? view.frame.origin.y: view.dragStartingFrame.origin.y + point.y - view.dragStartingPoint.y;
if (self.dragVerticallyOnly == NO){
if (x < view.dragMinPoint.x){
x = view.dragMinPoint.x;
}
else if (x > self.dragMaxPoint.x){
x = view.dragMaxPoint.x;
}
}
if (self.dragHorizontallyOnly == NO){
if (y < view.dragMinPoint.y){
y = view.dragMinPoint.y;
}
else if (y > self.dragMinPoint.y){
y = view.dragMinPoint.y;
}
}
CGFloat deltaX = x - view.frame.origin.x;
CGFloat deltaY = y - view.frame.origin.y;
if ((fabs(deltaX) <= 1.0 && fabs(deltaY) <= 1.0))
return; // Ignore very small movements
[UIView animateWithDuration:0.1 animations:^{
view.frame = CGRectMake(x, y, view.frame.size.width, view.frame.size.height);
}];
}
The most important points are:
Make the movement with the animated option; (one tenth of a second seems to do the job well done).
Avoid making movements when not necessary. Making some calculations will not make it slower, but making unnecessary movements might. However you cannot avoid to many movements otherwise it will not follow the user pan gesture.
Depending on how you want your view to behave, there can be more optimisations that you may want to make.
Remark: My method also takes into account boundaries and movement directions limits that you may define in case you need it. For instance, in my case I added a speed limit. If the user goes beyond a certain pan speed I just ignore further pan gestures and move the view (animated) where the user was pointing to, and at that speed. It may or not make sense in same cases. In mine, it did!
I hope I have been of some help!
look at this component : spuserresizableview
http://cocoacontrols.com/platforms/ios/controls/spuserresizableview
The source code is very simple and can help you to understand the view handling.
Try reducing the size of the Chalk image u are using....
Edited: By size i meant the image file size.
It helped me when i faced similar issue.
I rewrote how the movement is calculated and it is a little smoother now. Not perfect, but it's enough.

Resources