Gaps in cocos2d infinite looping game - ios

I am using this code to implement infinite looping, but I'v got gaps for 1-2 seconds every time the offscreen image coordinates are changed. Why do they appear? How to fix it? I am also using SpriteBuilder.
#import "MainScene.h"
static const CGFloat scrollSpeed =100.f;
#implementation MainScene{
CCPhysicsNode *_world;
CCNode *_oneb;
CCNode *_twob;
NSArray *_bb;
}
- (void)didLoadFromCCB {
_bb = #[_oneb, _twob];
}
-(void)update:(CCTime)delta{
_world.position=ccp(_world.position.x - (scrollSpeed * delta), _world.position.y ); // moving world
for (CCNode *ground in _bb) {
// get the world position of the ground
CGPoint groundWorldPosition = [_world convertToWorldSpace:ground.position];
// get the screen position of the ground
CGPoint groundScreenPosition = [self convertToNodeSpace:groundWorldPosition];
// if the left corner is one complete width off the screen, move it to the right
if (groundScreenPosition.x <= (-1 * ground.contentSize.width)) {
ground.position = ccp(ground.position.x + 2 * ground.contentSize.width, ground.position.y);
}
}
}
#end
EDIT: I changed -1 to -0.5. Works fine!

Seems like you are using small image for iPhone 3.5-inch on iPhone 4-inch simulator. What resolution of your background image?
EDIT: In my game I have an infinite loop, too. Maybe my code may help you? First background sprite should be 1137x640, second 1136x640. And you will never have gaps again! Hope it helps.
init method:
backgroundSprite = [CCSprite spriteWithFile:#"background.png"];
backgroundSprite.anchorPoint = ccp(0,0);
backgroundSprite.position = ccp(0,0);
[self addChild:backgroundSprite z:0];
backgroundSprite2 = [CCSprite spriteWithFile:#"background2.png"];
backgroundSprite2.anchorPoint = ccp(0,0);
backgroundSprite2.position = ccp([backgroundSprite boundingBox].size.width,0);
[self addChild:backgroundSprite2 z:0];
tick method:
backgroundSprite.position = ccp(backgroundSprite.position.x-1,backgroundSprite.position.y);
backgroundSprite2.position = ccp(backgroundSprite2.position.x-1,backgroundSprite2.position.y);
if (backgroundSprite.position.x<-[backgroundSprite boundingBox].size.width) {
backgroundSprite.position = ccp(backgroundSprite2.position.x+[backgroundSprite2 boundingBox].size.width,backgroundSprite.position.y);
}
if (backgroundSprite2.position.x<-[backgroundSprite2 boundingBox].size.width) {
backgroundSprite2.position = ccp(backgroundSprite.position.x+[backgroundSprite boundingBox].size.width,backgroundSprite2.position.y);
}

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.

Change Physical body frame in spritekit

I develop an iOS game using SpriteKit (such a helpful framework to quickly make a game). I add texture and configure a physical body for a main character as image
The green rectangle is the frame of the physical body. I'm using the following code to create it
#interface MainCharacter : SKSpriteNode
#end
#implementation MainCharacter
+ (instancetype)mainCharacterAtPosition:(CGPoint)pos {
MainCharacter* mainChar = [[MainCharacter alloc] initWithTexture:[SKTexture textureWithImageNamed:#"stand_up"]];
mainChar.position = pos;
mainChar.xScale = 0.5f;
mainChar.yScale = 0.5f;
return mainChar;
}
- (instancetype)initWithTexture:(SKTexture *)texture {
if (self = [super initWithTexture:texture]) {
self.name = kCharacterName;
self.anchorPoint = CGPointMake(0.5f, 0.0f);
[self standup];
CGSize spriteSize = self.size;
CGPoint center = CGPointMake(spriteSize.width*(self.anchorPoint.x-0.5f), spriteSize.height*(0.5f-self.anchorPoint.y));
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize center:center];
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = kCharacterCategory;
self.physicsBody.contactTestBitMask = 0x0;
self.physicsBody.collisionBitMask = 0x0;
}
return self;
}
- (void)standup {
SKAction* standupAction = [SKAction setTexture:self.standupTexture resize:YES];
[self runAction:standupAction];
}
- (void)standdown {
SKAction* standownAction = [SKAction setTexture:self.standdownTexture resize:YES];
[self runAction:standownAction completion:^{
}];
[self performSelector:#selector(standup) withObject:nil afterDelay:1.0f];
}
MainCharacter is a class that inherits from SKSPriteNode, just an convienient class to manage a main character. Stand Up is a first state of the character. I have another state, temporarily called stand down (demonstrate as following image)
I add a swipe down gesture to make character stand down.
The green rectangle also the physical body but it's too large for the character. I want to make a physical body frame as the red rectangle.
Can anyone help me how to make the physical body smaller when my character stand down and enlarge the physical body after it stands up
You can destroy the current physics body self.physicsBody = nil; and then simply create a new one with the new size requirements.
I solve this problem by using 2 nodes for 2 states (as a suggestion): stand up state and stand down state. I named it
standupNode and standdownNode
First, add the standupNode to the game scene. If swipe donw gesture recognize, I remove the standupNode from game scene and add the standdownNode instead. On contrary, removing the standdownNode from the game scene then add the standupNode if character stands up

Tilemap floor NOT display before background picture

I am having a little trouble doing something that is supposed to be very simple.
I cannot get the floor of my tiles to display above a background picture. However I can get all my other game objects to show from my control pad, to my HUD to even coins and monsters set up in the same tile map. Basically everything appears in front the background like I expect the floor of my tilemap so it looks like im walking on air. I have tried many things like changing which layer i add the background picture or the tilemap floor too, or even tried setting it the same way i set my characters but same results. Tilemap floor is always at the back.
Adding my Set up code, Hope it is helpful too solving the problem.
I created this BG sprite since, I wanted my tilemap to scroll vertically or horzi. automatically. So the easiest way I found to do that was to make the tilemap the child of the "bg" and scroll the "bg" hence scrolling the tile map. However, I have tried setting the background as the child of the bg and setting the Z for both of them but that didnt seem to help.
Thanks in advance for any help in solving this
#implementation GameLevelScene
{
SKNode *_worldNode;
SKSpriteNode *bg;
SKSpriteNode *bkg;
}
Init
-(id)initWithSize:(CGSize)size level:(int)level {
if (self = [super initWithSize:size]) {
// [self showBackground];
NSDictionary *levelData = config[#"levels"][level];
//[show background];
if (levelData[#"tmxFile"]) {
[self showBackground];
_tileMap = [ JSTileMap mapNamed:levelData[#"tmxFile"]];
}
//self.backgroundColor = [SKColor colorWithRed:.4 green:.4 blue:.95 alpha:1.0];
// UIImage *bkgb =[UIImage imageNamed:#"land.jpg"];
// self.position=CGPointZero;
// self.anchorPoint=CGPointZero;
// self.backgroundColor=[UIColor colorWithPatternImage:bkgb];
//Above code shows no picture but it changes the color
[self setUpWorld];
[self createChar];
[self controlPadNode];
//[show background];
}
return self;
}
setUpWorld
- (void)setUpWorld
{
bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg3"];
bg.userInteractionEnabled=NO;
bg.anchorPoint = CGPointZero;
bg.zPosition=0;
bg.position = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
_worldNode = [SKNode node];
if (_tileMap) {
[bg addChild:_tileMap];
}
[bg addChild:_worldNode];
self.physicsWorld.contactDelegate = self;
}
create char
- (void)createChar
{
_Layer = [[TmxTileMapLayer alloc]
initWithTmxObjectGroup:[_tileMap
groupNamed:#"LevelOneObjects"]
tileSize:_tileMap.tileSize
gridSize:_bgLayer.gridSize];
[self addChild:_Layer];
}
Create Control
- (SKSpriteNode *)controlPadNode
//-(void)controlPad
{
SKSpriteNode *controlPadNode = [SKSpriteNode spriteNodeWithImageNamed:#"controller.png"];
controlPadNode.position = CGPointMake(100,50);
controlPadNode.name = #"controlPadNode";
controlPadNode.zPosition = 1.0;
[self addChild:controlPadNode];
}
background
-(void)showBackground
{
bkg = [SKSpriteNode spriteNodeWithImageNamed:#"desert_land.jpg"];
bkg.userInteractionEnabled=NO;
bkg.anchorPoint = CGPointZero;
bkg.position = CGPointZero;
bkg.zPosition=-1;
bkg.name = #"bkg";
// [self addChild:bkg];
//[_tileMap addChild:bkg];
// [_worldNode addChild:bkg];
[bg addChild:bkg];
}
Set your bkg.zposition to 1 and set your bg.zPosition to 2.
Also, the whole point of having a tile map is NOT to use a gigantic background picture but to instead use tiles.
** Update **
I just tested your code and ran a sample project myself. I assume you are using the Tiled app for your tiles. I ran variations on parents (self, worldNode, etc...) and zPositions. The bottom line is you cannot do it. Tiled does not have an alpha channel for its background color options. So either the background image is covered by the tile map or, in your case, the background image covers the tile map.
Your possible solutions are to either not use Tiled and place your tiles manually or to look for another tile app which has an alpha channel.
I noticed that where you define the wall properties of the tile if you say
SKSpriteNode* wall =
[SKSpriteNode spriteNodeWithColor:[SKColor greenColor]
size:CGSizeMake(w, h)];
the green color will be uptop of the background. Bit of a dirty fix than an answer

CCProgressNode for Health Bar not working

I have recently discovered that the CCProgressTimer class is replaced with CCProgressNode in the latest version of cocos2d,
however, when i tried to implement the following code, there is nothing happen to the progressNode
I have read all kinds of documentation, it seems like I have used all the latest methods.
This all happens in gamePlay Class
This is how I define the node.
CCProgressNode *_healthBar;
float _life;
This is the setUp method
- (void)initializeHealthBar {
self->_healthBar = [[CCProgressNode alloc] init]; // This wasn't there before, but thought it might be the memory allocation problem,
// _health is the code connection between sprite builder and Xcode.
self->_healthBar = [CCProgressNode progressWithSprite:_health];
[_healthBar setType:CCProgressNodeTypeBar];
[_healthBar setPercentage:_life];
[_healthBar setBarChangeRate:ccp(0.1,0.1)];
_healthBar.position = _health.position;
// So the _healthBar turns out positioned correctly, because _health is already positioned in sprite builder
[_contentNode addChild:_healthBar];
}
This is how i Involk the change on health bar... (It works, the healthBar is depleting... )
-(void) hit {
if(_healthBar.percentage > 0)
{
self->_life -= 34;
self->_healthBar.percentage -= 34;
}
if (self->_healthBar.percentage <= 0 && ![_myHero isGameOver]) {
self->_healthBar.percentage = 0;
[_myHero isGameOver: TRUE];
[self gameOver];
}
}
I do not completely understand your problem, I have just written a small working example for a Left -> Right Bar Type Progress Bar
- (void) onEnter
{
[super onEnter];
CCSprite *sprite = [CCSprite spriteWithImageNamed:#"emma.png"];
_progressNode = [CCProgressNode progressWithSprite:sprite];
_progressNode.type = CCProgressNodeTypeBar;
_progressNode.midpoint = ccp(0.0f, 0.0f);
_progressNode.barChangeRate = ccp(1.0f, 0.0f);
_progressNode.percentage = 0.0f;
_progressNode.positionType = CCPositionTypeNormalized;
_progressNode.position = ccp(0.5f, 0.5f);
[self addChild:_progressNode];
self.userInteractionEnabled = YES;
}
- (void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
_progressNode.percentage += 10.0f;
}
Notice that the CCSprite is not added to the scene, you can't use SpriteBuilder for that one I'm afraid. (Unless you want to remove it from the parent but that gets a little messy)
Also, do all the setup before you call the percentage setter.
And the percentage is actually a double. Always check to make sure that there are no casting problems happening.

CCLayer scaling and touch implementation?

I've made a CCLayer that holds CCSprite and two CCLabelBMFont's. My goal is to create a customized "button" which will scale down when pressed. I've ran into problems with touch and scaling of this layer.
First is the touch, I can't touch the layer bounding box accurately even if I convert the touch like this:
CGPoint currentTouchLocation = [self convertTouchToNodeSpace:touch];
Touch is handled like this:
// Touching shop item?
if(CGRectContainsPoint([self boundingBox], currentTouchLocation)) {
NSLog(#"Pressing item");
mShopItemPushed = true;
return true;
}
return false;
Seems like there is no realistic size boundingBox for a CCLayer with it's contents by default so I figure I need to overwrite one based on the CCLayer contents? Any ideas how I can do this correctly?
Second problem is the scaling of this CCLayer based "button". If I get a touch handling to work somehow, scaling the layer down by half causes the scaled layer to move off tens of pixels from the original position. There are no anchors set but still moves the layer quite a bit to the side and up when scaling. How can I prevent this behavior?
Here is some code of the CCLayer based button:
+(id) shopItem:(NSString*)fileName : (CGPoint)position : (NSString*)itemName : (int)itemPrice
{
return [[[self alloc] initWithShopItemData:fileName:position:itemName:itemPrice] autorelease];
}
-(id) initWithShopItemData:(NSString*)fileName : (CGPoint)position : (NSString*)itemName : (int)itemPrice
{
self = [super init];
[self setPosition:position];
mShopItemPushed = false;
mPicture = [CCSprite spriteWithSpriteFrameName:fileName];
[mPicture setPosition:CGPointMake(position.x - (3.0f * [DeviceSpecific cellSize]), position.y)];
[self addChild:mPicture z:1];
// Make price string
NSString* price = [NSString stringWithFormat:#"%d", itemPrice];
mItemPrice = [CCLabelBMFont labelWithString:price fntFile:[DeviceSpecific scoreAndCoinFont]];
[mItemPrice setScale:0.5f];
[mItemPrice setAnchorPoint:CGPointMake(1.0f, 0.5f)];
[mItemPrice setPosition:CGPointMake(position.x + (3.5f * [DeviceSpecific cellSize]), position.y)];
[self addChild:mItemPrice z:1];
mItemName = [CCLabelBMFont labelWithString:itemName fntFile:[DeviceSpecific scoreAndCoinFont]];
[mItemName setScale:0.5f];
[mItemName setAnchorPoint:CGPointMake(0.0f, 0.5f)];
[mItemName setPosition:CGPointMake(mPicture.position.x + [DeviceSpecific cellSize], mPicture.position.y)];
[self addChild:mItemName z:1];
self.isTouchEnabled = YES;
return self;
}
The [DeviceSpecific cellSize] is just a measuring unit to keep the distances correct on different devices.
I solved this by overwriting the boundingBox -function with a rect based on the outer limits of the items in this layer. Scaling problem remained so I just made another indicator for received touches.

Resources