Why does this code not properly center scene when doing UIPanGestureRecognizer? - objective-c

I am a beginner developing a program for the AppStore using Xcode's sprite kit. I made a 'test' program so I can try out new things before adding it to my game. Right now I am fiddling around with swiping a scene - I have an SKNode (called "background") where I am adding several children as SKSpriteNodes. One sprite node is visible on the initial scene (the center, position 160,240), and two more that are not visible: to the left of the scene (position -160,240), and to the right of the scene (position 480,240).
I would like my game to be able to swipe left or right, and when it swipes left or right, the view will auto-center itself (with animation) to one of the three SKSpriteNodes. My code using the UIPanGestureRecognizer to move the background node works properly, and my code for auto-centering the view works MOSTLY (background position set to 0,0 or -320,0 or +320,0), but sometimes it has a strange offset and doesn't completely center itself (for example, the background position will be 7,0 or -34,0 when I pan right or left). What am I doing wrong?
P.S: I am using code from RayWenderlich's "iOS Games" for the SKTMoveEffect. I also want to note that if I make the function f(t)=t, there is no problem (at least in my several tests), but f(t)=t^2 or anything else seems to have an issue; if it helps to see the code for this I can post it too
#implementation LTMyScene
{
SKNode *background;
SKSpriteNode *spaceship1, *spaceship2;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
background=[SKNode node];
[self addChild:background];
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 30;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[background addChild:myLabel];
spaceship1=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship1.position=CGPointMake(-self.size.width/2, self.size.height/2);
spaceship1.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship1];
spaceship2=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship2.position=CGPointMake(self.size.width*3/2, self.size.height/2);
spaceship2.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship2];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
UIPanGestureRecognizer *swipe = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragPlayer:)];
[[self view] addGestureRecognizer:swipe];
}
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
[[[self view] layer] removeAllAnimations];
CGPoint trans = [gesture translationInView:self.view];
SKAction *moveAction = [SKAction moveByX:trans.x y:0 duration:0];
[background runAction:moveAction];
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
if([gesture state] == UIGestureRecognizerStateEnded) {
CGFloat finalX=0;
if (abs(background.position.x)<self.size.width/2) {
finalX=0;
} else if (abs(background.position.x)<self.size.width*3/2) {
finalX=self.size.width*background.position.x/abs(background.position.x);
}
NSLog(#"%f",finalX);
SKTMoveEffect *upEffect =
[SKTMoveEffect effectWithNode:background duration:0.5
startPosition:background.position
endPosition:CGPointMake(finalX, 0)];
upEffect.timingFunction = ^(float t) {
// return powf(2.0f, -3.0f * t) * fabsf(cosf(t * M_PI * 1.0f)) //has bounce
// return (-1.0f*t*t+1) //no bounce ... for parabola this is only solution with (1,0) and (0,1) as intercepts and vertex at (1,0)
return (t*t)
;};
SKAction *upAction = [SKAction actionWithEffect:upEffect];
[background runAction:upAction];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
NSLog(#"%f,%f",background.position.x,background.position.y);
}
#end

You have to remember that you are moving the larger background node with other nodes as its children. Keeping that in mind, the code you need to center on a specific node is:
_worldNode.position = CGPointMake(-(myNode.position.x-(self.size.width/2)), -(myNode.position.y-(self.size.height/2)));
The above assumes that your main background "canvas" is called _worldNode and your target node is a child of _worldNode

Related

SKSpriteNode not showing up when added

I was using Objective-C and SpriteKit. I made a SKSpriteNode of a simple ball, then added it. Unfortunately, it's not showing up.
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
// Set background color
self.backgroundColor = [SKColor whiteColor];
// Creating a new spritenode from an image
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed: #"ball"];
// Add the sprite node to the scene
[self addChild: ball];
Did you forget to set the sprite's position?
ball.position = CGPointMake(CGRectGetMidX(self.scene.frame), CGRectGetMidY(self.scene.frame));

Cocos2D v3 CCParallaxNode scrolling can't keep player in focus

I am very fresher to game development and I need to develop a game like NinjaJump.
I have created a CCParallaxNode to setup scrolling background and added CCPhysicsNode to setup Physics world. I have created player object as shown below.
// Add a sprite
_sprite = [CCSprite spriteWithImageNamed:#"Icon.png"];
_sprite.position = ccp(self.contentSize.width/2,100);
_sprite.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _sprite.contentSize} cornerRadius:0.0];
_sprite.physicsBody.friction = 0.0f;
_sprite.physicsBody.collisionGroup = #"player";
_sprite.physicsBody.collisionType = #"Player";
//_sprite.physicsBody.collisionMask = 0;
//[self addChild:_sprite];
[foreground addChild:_sprite];
foreground is just a node added into CCScene to easily manage player in-focus.
// code for physics world
_physicsWorld = [CCPhysicsNode node];
_physicsWorld.gravity = ccp(0,-100);
_physicsWorld.debugDraw = YES;
//_physicsWorld.collisionDelegate = self;
[self addChild:_physicsWorld];
_foreground = [CCNode node];
//[self addChild: _foreground];
[_physicsWorld addChild: _foreground];
To make player always visible we have implemented update method as
- (void) update:(CFTimeInterval)currentTime {
// Calculate player y offset
if (_player.position.y > 200.0f) {
//_midgroundNode.position = CGPointMake(0.0f, -((_player.position.y - 200.0f)/4));
_foreground.position = CGPointMake(0.0f, -(_player.position.y - 200.0f));
}
}
I can't understand but the player scrolls off screen anyhow. The code is written in Cocos2d v3.
I have also setup a demo project to show what I implemented: https://www.dropbox.com/s/5s55d00kk80wun4/HumptyJump-Example.zip?dl=0
Any kind of help is appreciated. Thanks in advance.
I could not run your sample code but one thing, I can tell you for sure that there is no need of Physics Engine here.
You just keep your player at a particular height and just move your object right to left.
Apply moving parallax background image and objects to give a feel that your character is moving upwards or downwards.
For reference you can see the games like Swing Drop, made on the same approach as above.
I have implemented 2 fixed views changing their positions while physics body goes down ;).
See the same in action
#interface TestScene () {
CCNode *_background;
CCNode *_foreground;
NSArray *grounds; // Keeps track of each of the 2 views
CCPhysicsNode *_physicsWorld;
}
#implementation TestScene
- (id)init {
self = [super init];
if (!self) return(nil);
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
// Physics world setup
_physicsWorld = [CCPhysicsNode node];
_physicsWorld.gravity = ccp(0,-100);
_physicsWorld.debugDraw = YES;
_physicsWorld.collisionDelegate = self;
[self addChild:_physicsWorld];
// Foreground node in which platforms are there and moves downwards as player goes up
_foreground = [CCNode node];
// Adding background images
CCSprite *default1 = [CCSprite spriteWithImageNamed: #"Default.png"];
[default1 setAnchorPoint: ccp(0, 0)];
CCSprite *default2 = [CCSprite spriteWithImageNamed: #"Default.png"];
[default2 setAnchorPoint: ccp(0, 0)];
[default2 setPosition: ccp(0, default1.contentSize.height)];
[_foreground addChild: default1];
[_foreground addChild: default2];
// Adding into array
grounds = #[default1, default2];
// Adding into physics world
[_physicsWorld addChild: _foreground];
// creating player
_player = [CCSprite spriteWithImageNamed: #"Assets.atlas/Player.png"];
[_player setPosition: ccp(160.0f, 160.0f)];
_player.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _player.contentSize} cornerRadius:0]; // 1
_player.physicsBody.collisionGroup = #"playerGroup"; // 2
_player.physicsBody.collisionType = #"player";
//[_physicsWorld addChild:_player];
[_foreground addChild: _player];
// Multiple platforms can be added into body, they are static entities only
PlatformNode *platform = (PlatformNode *)[PlatformNode node];
[platform createPlatformAtPosition:CGPointMake(110, 50) ofType: PLATFORM_NORMAL];
[_foreground addChild: platform];
return self;
}
- (void) update:(CFTimeInterval)currentTime {
// Take background and physics world down, 163 was Y position of the player
_physicsWorld.position = ccp(_physicsWorld.position.x, 163 - _player.position.y);
// loop the ground
for (CCNode *ground in grounds) {
// Get the world position
CGPoint groundWorldPosition = [_physicsWorld convertToWorldSpace: ground.position];
// Get screen position
CGPoint groundScreenPosition = [self convertToNodeSpace: groundWorldPosition];
NSLog(#"Positioning ground ---> world: (%f, %f) & screen: (%f, %f)", groundWorldPosition.x, groundWorldPosition.y, groundScreenPosition.x, groundScreenPosition.y, nil);
if (groundScreenPosition.y <= -ground.contentSize.height) {
ground.position = ccp(ground.position.x, ground.position.y + 2 * ground.contentSize.height);
break;
}
}
}
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// Take the player upside
[_player.physicsBody applyImpulse: ccp(0, 215)];
}
#end
This is how I coded the background :).

Frames border does not move

I'm trying to build a simple game for the exercise sake and cannot figure out why the frame's border is not moving when everything else is.
When I reach the last pillar and move a bit to the right the game ends. This is somehow because the frames border does not move when the goat and pillars does.
I have three layers which is SKNode's. One called _mainLayer, second called _playerLayer and third called _squareLayer. _mainLayer contains _playerLayer and _squareLayer.
When I move the screen this code is being used:
-(void)moveScreen {
_xMoved += 100;
SKAction *move = [SKAction moveByX:-100 y:0 duration:0.5];
[_squareLayer runAction:move];
[_playerLayer runAction:move];
}
I have tried to use [_mainLayer runAction:move] instead of the two other calls but then the gameover screen is not moving along which is added in directly into the scene class (the "self").
_gameOverScreen = [[EndScene alloc] init];
_gameOverScreen.position = CGPointMake(self.size.width / 2, self.size.height / 2);
_gameOverScreen.gameOver.size = CGSizeMake(self.frame.size.width, _gameOverScreen.gameOver.size.height / 2);
[self addChild:_gameOverScreen];
The gameover screen is a SKNode class I made myself.
Does anyone have any suggestions?
I found a solution to my problem.
-(void)didSimulatePhysics {
// If the goat didn't get on top of a platform
[_playerLayer enumerateChildNodesWithName:#"Goat" usingBlock:^(SKNode *node, BOOL *stop) {
if (!CGRectContainsPoint(CGRectMake(_xMoved, 0, self.frame.size.width, self.frame.size.height), node.position)) {
_gameOverScreen.numberScore = _score;
[_gameOverScreen testNewScore:_score];
[_scoreLabel removeFromParent];
_gameOverScreen.hidden = NO;
_gameOver = YES;
}
}];
}
By making a new CGRect that simulates the screen moving I got it to work. But why this (the code below) won't work I don't know.
-(void)didSimulatePhysics {
// If the goat didn't get on top of a platform
[_playerLayer enumerateChildNodesWithName:#"Goat" usingBlock:^(SKNode *node, BOOL *stop) {
if (!CGRectContainsPoint(self.frame, node.position)) {
_gameOverScreen.numberScore = _score;
[_gameOverScreen testNewScore:_score];
[_scoreLabel removeFromParent];
_gameOverScreen.hidden = NO;
_gameOver = YES;
}
}];
}

Moving UIImageViews in ScrollView

In my scrollview im having multiple imageviews where i can able to move each imageviews when user taps LONGPRESS i have done this using,
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveImage:)];
[panGesture setMaximumNumberOfTouches:2];
[panGesture setDelegate:self];
[imageview addGestureRecognizer:panGesture];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] init];
[imageview addGestureRecognizer:longPressRecognizer];
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizerv {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
UIView *piece = gestureRecognizerv.view;
CGPoint locationInView = [gestureRecognizerv locationInView:piece];
CGPoint locationInSuperview = [gestureRecognizerv locationInView:piece.superview];
piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);
piece.center = locationInSuperview;
}
}
- (void)moveImage:(UIPanGestureRecognizer *)gestureRecognizerg
{
NSLog(#"sdfgdsgsdg");
UIView *piece = [gestureRecognizerg view];
[self adjustAnchorPointForGestureRecognizer:gestureRecognizerg];
// need to test if the scrollview is already using the touches. If it is, leave them
if (!galleryView.tracking) {
if ([gestureRecognizerg state] == UIGestureRecognizerStateBegan || [gestureRecognizerg state] == UIGestureRecognizerStateChanged) {
CGPoint translation = [gestureRecognizerg translationInView:[piece superview]];
[piece setCenter:CGPointMake([piece center].x + translation.x, [piece center].y + translation.y)];
[gestureRecognizerg setTranslation:CGPointZero inView:[piece superview]];
}
}
}
Here my problem is the THE IMAGEVIEW is moving every where in scollview i need to rearrange the views i.e like Gallery view.
To get that rearrangement behavior you're going to need to have some logic that sets up a grid and 'snaps' elements to it, moves elements out the way when required, etc. It is reasonably complicated. Your current code simply moves views with your gestures.
However, if you are able to target iOS 6 and above you will almost certainly want to use a UICollectionView instead, which gives you a lot of this for free. Collection views simplify the layout and logic behind re-arranging, and will generally make your life a lot easier.

Autoscroll UIScrollView when subview is out of the visible area

EDIT:
I uploaded my implementation to github. Maybe it is better to understand what I want and what my problem is.
I changed the code in the github project a little bit than the posted code here. I think the implementation in the github project is better, but not perfect.
What I want to do:
Have a UIScrollView with movable UIViews (e.g. Images). The user can pan this subviews and zoom in and out. When the user zooms in and moves a subview over the current visible area the scrollView should automatically scroll. As lang as the subview is over the edge the scrollview should scroll. When the subview isn't over the visible area anymore the scrollview should stop moving.
I try to explain my problem as good as possible.
What I have managed to do:
Zoom the scrollview, move the subviews with the UIPanGestureRecognizer, recognize when the subview is over the visible area and start moving (changing the contentOffset) the scrollview. Here I using a NSTimer to move the scrollview as long as the subview is over the visible area.
My problem:
When the subview is over the visible area a NSTimer is started, to change the contentOffset of the subview and the frame (position) of the subview.
After that I can't pan the subview anymore.
I can't figure out how to implement the pan gesture with changing the subview frame in a correct way.
My implementation:
I am using three views:
UIScrollView
MyImageContainerView (UIView, added as a subview to the scrollview)
MyImageView (UIView, added as a subview to MyImageContainerView)
Currently MyImageContainerView manages the workflow. A MyImageView has a UIPanGestureRecognizer attached. The method for this recognizer is implemented in MyImageContainerView:
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
//UIView which is moved by the user
MyImageView *currentView = gestureRecognizer.view;
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStatePossible:
{
break;
}
case UIGestureRecognizerStateBegan:
{
//save both values in global instance variables
currentFrameOriginX = currentView.frame.origin.x;
currentFrameOriginY = currentView.frame.origin.y;
//global BOOL variable, to check if scrollView movement is performed
scrolling = NO;
break;
}
case UIGestureRecognizerStateChanged:
{
CGRect rect = CGRectMake(currentFrameOriginX + [gestureRecognizer translationInView:currentView.superview].x, currentFrameOriginY + [gestureRecognizer translationInView:currentView.superview].y, currentView.frame.size.width, currentView.frame.size.height);
if (CGRectContainsRect(currentView.superview.frame, rect)) {
/*PROBLEM: Here is a problem. I need this change of the frame here, to move the UIView along the movement from the user. In my autoScroll-method I have to set the frame of currentView, too. But I can't set the frame of currentView here and in the autoScroll. But as long as the NSTimer runs and is calling autoScroll: this if-statement isn't called, so I can't move the UIView with my finger anymore. */
if (!scrolling) {
//currently the NSTimer method for the automatically scrolling isn't performed, so:
//change the frame according to the pan gesture
currentView.frame = rect;
}
UIScrollView *scrollView = self.myScrollView; //reference to the "root" UIScrollView
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
CGRect frame = currentView.frame;
CGFloat scale = 1.0 / scrollView.zoomScale;
visibleRect.origin.x *= scale;
visibleRect.origin.y *= scale;
visibleRect.size.width *= scale;
visibleRect.size.height *= scale;
CGSize scrollZone = CGSizeMake(10.0f, 10.0f);
float scrollStep = 3.0f;
CGPoint scrollAmount = CGPointZero;
//determine the change of x and y
if (frame.origin.x+scrollZone.width < visibleRect.origin.x) {
scrollAmount.x = -scrollStep;
}
else if((frame.origin.x+frame.size.width)-scrollZone.width > visibleRect.origin.x + visibleRect.size.width) {
scrollAmount.x = scrollStep;
}
else if (frame.origin.y+scrollZone.height < visibleRect.origin.y) {
scrollAmount.y = -scrollStep;
}
else if((frame.origin.y+frame.size.height)-scrollZone.height > visibleRect.origin.y + visibleRect.size.height) {
scrollAmount.y = scrollStep;
}
if ((scrollAmount.x != 0) | (scrollAmount.y != 0)) {
if (![scrollTimer isValid]) {
//scrollTimer is a global NSTimer instance variable
[scrollTimer invalidate];
scrollTimer = nil;
NSString *scrollString = NSStringFromCGPoint(scrollAmount);
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:scrollString, #"scrollString", currentView, #"currentView", nil];
scrollTimer = [[NSTimer alloc]initWithFireDate:[NSDate date] interval:0.03f target:self selector:#selector(autoScroll:) userInfo:info repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:scrollTimer forMode:NSRunLoopCommonModes];
}
}
else {
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
}
}
break;
}
case UIGestureRecognizerStateEnded:
{
//quite know the scrolling should stop, maybe it would be better when the scrollView scrolls even if the user does nothing when the subview is over the visible area
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
break;
}
default:
{
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
break;
}
}
}
-(void)autoScroll:(NSTimer*)timer {
scrolling = YES; //the scroll method is executed quite know
NSDictionary *info = [timer userInfo];
UIScrollView *scrollView = self.myScrollView;
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
CGPoint scrollAmount = CGPointFromString([info objectForKey:#"scrollString"]);
MyImageView *currentView = [info objectForKey:#"currentView"];
//stop scrolling when the UIView is at the edge of the containerView (referenced over 'self')
if ((currentView.frame.origin.x <= 0 | currentView.frame.origin.y <= 0) ||
((currentView.frame.origin.x+currentView.frame.size.width) > self.frame.size.width | (currentView.frame.origin.y+currentView.frame.size.height) > self.frame.size.height)
) {
scrolling = NO;
return;
}
//move the UIView
CGFloat scale = 1.0 / scrollView.zoomScale;
if (scrollAmount.x != 0) {
scrollAmount.x *= scale;
}
if (scrollAmount.y != 0) {
scrollAmount.y *= scale;
}
CGRect frame = currentView.frame;
frame.origin.x += scrollAmount.x;
frame.origin.y += scrollAmount.y;
currentView.frame = frame;
currentFrameOriginX = currentView.frame.origin.x;
currentFrameOriginY = currentView.frame.origin.y;
//move the scrollView
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.x += scrollAmount.x;
contentOffset.y += scrollAmount.y;
[scrollView setContentOffset:contentOffset animated:NO];
}