I have a number of individual nodes (tiles) that make up the background of my game. They're each 32x32p, and are positioned next to each other to form floor/ roof/ obstacles. I load a map from a JSON-file, and position the nodes on a background-layer/node based on the contents of the JSON-file when the scene is initialized.
This is working fine, however, I'm experiencing some bugs when moving the background layer/ the physics engine is doing its thing. Some of the nodes move 1 point/pixel away from each other, creating a gap between them. And since the background is a different color, this looks really bad.
The problem mostly occurs further out on the map (not right away), at the same time as I'm either applying impulses to the player or the physics engine is bouncing the player (or similar).
Here's a picture that illustrate my problem:
(Click to open in separate tab)
As you can see, right between the o and d in nodes, there is a gap (more easily visible in the full sized image in the link). There are many gaps like these occurring now and then while playing the game, and they only appear for a split second or two. Sometimes they stay when the player stops moving.
I move the player in the didSimulatePhysics by increasing/ decreasing its x-value. Then lastly in the didSimulatePhysics I call this method to center the worldNode on the player:
- (void)centerViewOn:(CGPoint)centerOn {
CGFloat x = Clamp(centerOn.x, self.size.width / 2, _bgLayer.layerSize.width - self.size.width / 2);
_worldNode.position = CGPointMake(-x, _worldNode.position.y);
}
The worldNode contains both the bgLayer which contains all the individual background nodes, and the player itself.
What is the best way to solve this? One plausible solution could be to make every tile 33x32p, so they're overlapping. In that case you won't see the gaps occurring now and then. But I think it would be better to "kill" the problem rather than hide it.
Anyone have experience laying out their map like this? Or have anyone experienced the same problems before? And if so, how did you solve it?
Thanks in advance!
This is the casting to Int solution based on #LearnCocos2D comment:
- (void)centerViewOn:(CGPoint)centerOn {
CGFloat x = Clamp(centerOn.x, self.size.width / 2, _bgLayer.layerSize.width - self.size.width / 2);
_worldNode.position = CGPointMake(Int(-x), Int(_worldNode.position.y));
}
Related
Note: I have tried this answer: Gap between SKSpriteNodes in SpriteKit collision detection
I am getting gaps in between my SKSpriteNodes, after 5 minutes of letting my game run. Here is my code to make the node:
let tileNode = SKSpriteNode(imageNamed: "world1Tile\(tileNumber).png")
tileNode.position.x = x
tileNode.position.y = y
tileNode.size.width = 128
tileNode.size.height = 128
tileNode.zPosition = 10
tileNode.physicsBody = SKPhysicsBody(rectangleOf: tileNode.size)
tileNode.physicsBody!.isDynamic = false
tileNode.physicsBody!.restitution = 0
tileNode.physicsBody!.allowsRotation = false
tileNode.name = "Tile"
row.append(tileNode)
When I remove the physics body, it is running fine. Here is are some images to show you what I mean:
This image has a physics body, and was taken after immediately after running the app.
This image was taken 5 minutes after running the app.
Why is this happening? I assume it has something to do with the physics body, because my app looks exactly like the first picture, even an hour after running the app if there is no physics body. What physics body property should I change to stop this from happening? Any help would be appreciated.
I had a similar issue not too long ago, where gaps were appearing between nodes that were tiled (although I didn't use physics). Based on this answer, I found that if you want perfect alignment between nodes, it is best to ensure that the positions of nodes as well as the nodes' width and height are whole numbers.
I would suggest to round-off the x and y values of the position of tileNode and see if it will make any difference .
I'm guessing there is no gap. you probably have 'showPhysics' to true in your gameviewcontroller, and the line appears as a gap to me.
compare position with and without the pb to verify.
I had similar problem where gaps between sprites started appearing after around 5 minutes of scrolling with constant speed (game with infinite scroll). I did not use physics and I even had all positions, widths, heights rounded to integer value. I was scrolling the camera and adding new sprites one right after another and everything was working fine except after around 5 minutes of that infinite scrolling gaps begin to appear just as in your case. I spent some time in looking for a solution and it turned out that the problem was that when positions of my objects were becoming big that is in my case X position in the scene was around 150000 then those gaps started to appear and also I noticed that this problem occurred only on devices which had to scale the scene. I was using aspect fill with default scene size for iPhone 6 resolution and those gaps only appeared on iPhone 5 but on iPhone 6 I did not notice them. I was able to fix that issue by subtracting some constant value from X position of all objects (including camera position) from time to time so that everything on the scene relatively did not change position to the camera and look the same but actually absolute positions were changed to keep them low. I guess that larger position values like 150000 and scene scaling cause some floating point rounding issue in SpriteKit and that is why gaps are then becoming visible.
So based on my experience if you have similar gaps I recommend using integer values for all positions, widths, heights and additionally keep values of objects positions of all objects low.
For future reference in case someone is still searching for this, here are my experiences:
If tiles have PhysicsBodies, they are prone to making gaps. A solution for me was making a blank SKNode as a child of the tile, and assigning the PhysicsBody to that.
If possible, make sure bit masks are set in a way that tiles can't collide with each other.
As stated in a previous answer, make sure all measurements are integers and rounded in a way that doesn't leave a one unit gap between them.
A related problem is also SpriteKit's PhysicsBody drifting. There are some threads about this (e.g. https://forums.developer.apple.com/thread/27057 ), and it seems to be abug. In my case, the problem was a combination of PhysicsBodies causing random small gaps, and the drifting making some of them larger. The steps above removed the small gaps. Unfortunately the only workaround for the drifting problem in my case was to only generate PhysicsBodies for nodes that are within a certain distance from the player and destroying them after they are left behind.
For future reference for anyone who needs, I found an different answer specific to my problem. As JohnV said, I may need to round of values when setting the position, but when running the code, I found out that I also need to do that when running SKActions.
My game is based on two screens (A & B) side by side, but the device screen can only display one of them at a time.
A is for example at position (0, 0) and B is at (320, 0)
I tried two solutions to switch from A to B:
First, I place the whole scene into one node, the MainNode. To switch
from A to B, simply set MainNode position to (0, -320).
Other solution, more elegant IMHO (but not for LearnCoco2D who uses the Coco2D library), is to move the scene anchorPoint to (0, -1)
Now, if I want to go from A to B with an animation, these two solutions must be adapted:
By using a SKAction
[Main runAction:[SKAction moveToY:-320 duration:0.1]];
By animating anchorPoint in the update method
if(anchorY > -1) anchorY -= 0.1;
These two solutions works (despite a linear SKAction::timingMode does not render a linear translation properly), but I wonder which is the best in term of optimization, and elegance. Documentation is welcomed ;)
EDIT:
Apparently, my question is not clear (maybe due to my english level).
In few words, my question is: What exactly are the best practices for scrolling a scene?
I am surprised (0, -1) doesn't throw a huge exception. According to the docs you get a range from 0 to 1. This could cause issues in the future. See the docs here
Changing your position sounds like a more elegant way of handling it. however changing it to negative -320 in a span of 1/10th of a second is rather quick and could explain why it looks funny. Also if you aren't making sure you are only calling that once it will look really odd. I would make sure that it is only getting called once and maybe using a bool to toggle if it should be moved instead of checking a position.
If you are going back and forth a lot from one screen to another this might be an ok solution. However if you are looking to scale this to a much larger map where you are going to transition several times to new screens I would recommend a different approach all together. Like creating a new node off screen when you need it and transition a parent node then pop the old node off.
I hope that helps.
Edit
Now that the question is "what exactly are the best practices for scrolling a scene".
I would recommend what LearnCoco2D mentioned in the comment and what I eluded to in my original answer.
Add a sub node to your scene that will handle positioning (lets call it mapNode)
Add any sprites that represent the scene to the mapNode
Move just the MapNode position on update
In the past I have built my Scenes in a similar fashion and have handled the scene position based on the player position in the update loop. I was able to keep the player in the center of the screen as he walked around the map. Might be getting off subject, but that is what I found the best practice for handling scrolling a scene from my experience. The project I am working on can be viewed here
I hope that answers your question.
I'm trying to get into game dev with SpriteKit for iOS, and I'm following a book called iOS Games By Tutorials by Ray Wenderleich.
Right now I'm working on a platform game, but I'm getting some weird results when trying to do something not in the book.
Basically I have a functioning platform game that loads the level from a JSON-file, and lays out bricks/ nodes in a background layer. I want to have multiple floors, so when a user jumps through a portal in the game, a new floor/ level is loaded and placed on screen in the exact same position (all floors are the same size).
I didn't think I would have any troubles with this, as being notified when the user jumps through the portal is really easy. And I'm already loading a level to begin with, so loading another one at this time and replacing the original one shouldn't be hard.
However, it's proving to be very difficult. When the game loads I call this method:
- (void)createWorld {
_bgLayer = [self createScenery:[NSNumber numberWithInteger:1]];
_worldNode = [SKNode node];
[_worldNode addChild:_bgLayer];
[self addChild:_worldNode];
self.anchorPoint = CGPointMake(0.5, 0.5);
_worldNode.position = CGPointMake(-_bgLayer.layerSize.width / 2, -_bgLayer.layerSize.height / 2);
}
This initializes a level (floor 1) from a JSON file, which basically is just a number of 32x32 nodes positioned next to each other forming a map. It then creates a worldNode, adds the bgLayer to the worldNode, and then adds it to the screen. Later it centers the worldNode on the screen. The worldNode is the node that moves when the player moves, the bgLayer is always stationary.
When the user jumps through a portal, I call this method:
- (void)goingUp {
if (_isChangingFloor) return;
_isChangingFloor = YES;
[_bgLayer removeFromParent];
_bgLayer = nil;
[_worldNode removeFromParent];
_worldNode = nil;
_bgLayer = [self createScenery:[NSNumber numberWithInteger:0]];
_worldNode = [SKNode node];
[_worldNode addChild:_bgLayer];
[self addChild:_worldNode];
_worldNode.position = CGPointMake(-_bgLayer.layerSize.width / 2, -_bgLayer.layerSize.height / 2);
}
This method wasn't always this "full", as I thought I could just replace bgLayer with the new one. That didn't work, so now I'm trying to reset everything, to replicate what happens in the createWorld method at start. However, this doesn't work either...
This is the result I get after jumping through the portal:
As you can see, there's 1 node on-screen, however the debug-info says there are 446. I believe this is the total number of nodes on the entire floor, but only around 90-100 should be visible at a time. I've tried adding/ removing nodes from the level, which increases/ decreases this number.
So as you all probably can understand, I'm really confused. Why doesn't it simply replace the old background with a new one when I jump through a portal? There's nothing wrong with the JSON-file itself, as I've tried loading floor0 in at start, and that works fine. Why isn't the behavior the same when jumping through the portal as when I initially load the game? What is different here? Why does it say 446 nodes on-screen when there's clearly only 1. Why is there only 1 node on the screen?
I've been stuck on this for several days now, and I would really appreciate any help that would get me closer to a solution. Thanks in advance!
i think you should check the zposition of _bgLayer it wld be higher index every time you remove and add it may be this would reslove your problem
i see that your scene has anchor point in the middle of screen
should nod your world node position be CGPointZero to make the world be in center of scene?
_worldNode.position = CGPointZero;
instead of
_worldNode.position = CGPointMake(-_bgLayer.layerSize.width / 2, -_bgLayer.layerSize.height / 2);
I am making a game using cocos2d and SpriteBuilder. There is a hero sprite and coins, that should be collected by him. That's a horizontal scrolling game, so every time hero touches a coin, this coin changes it's x-coordinate 2000px to the right and new y-coordinate is generated at random. Using update method I move it to the visible area as a "new" coin. But when hero flyies by and doesn't collect it ,coin must change coordinates only when it's already off the screen, so I tried this solution:
-(void)update:(CCTime)delta{
_coin.position=ccp(_coin.position.x - delta * scrollSpeed, _coinY);
if (CGRectIntersectsRect(_hero.boundingBox,_coin.boundingBox)) {
_coinY=arc4random() % 801 + 100;
_coin.position=ccp(_coin.position.x + 2000.f,_coinY);
}
else if(_hero.position.x >= _coin.position.x + 150){
_coinY=arc4random() % 801 + 100;
_coin.position=ccp(_coin.position.x + 2000.f,_coinY);
}
It works,but after that I found a small bug(I am not sure, whether it's related to this code) : sometimes, when hero touches coin, hero is like pushed away to the left. I have no idea why.
How to fix it?
Is that way, that I am using for coin ,right?
I see 2 issues with your approach:
1. Hero bounces of the coin.
To fix this you need to make your coin a sensor. This way you will still be notified about collisions, but the hero will simply pass through the coin without actually hitting it.
I'm not sure if you can set this in SpriteBuilder, but probably you can enumerate all coins after loading the scene and set sensor property to YES:
coin.physicsBody.sensor = YES;
Things like this is one of the reasons I believe you first need to learn pure Cocos2D and only then use tools making your life easier such as SpriteBuilder. Which is a great tool, but in some cases you still need know what happens behind the scenes.
2. You're mixing physics with things like CGRectIntersectsRect
If you're using physics engine you need to detect collisions via collision delegate and not by checking CGRectIntersectsRect in update:.
It is hard to explain how to do this in a few sentences, so here is a nice tutorial that shows how to detect collisions in Cocos2D v3 and of course there is a chapter about this in my book.
By the way you shouldn't use update:at all when manipulating physics nodes, use fixedUpdate: instead.
I hope this will help you to solve your issue.
I have a scene where my gameplay happens. I'm looking for a way to slowly 'zoom-out' so more and more space becomes visible as the time passes. But I want the HUD and some other elements to stay the same size. It's like the mouse wheel functionality in top-down games.
I tried to do it with sticking everything into display groups and transitioning the xScale, yScale. It works visually of course but game functionality screws up. For example I have a spawner that spawns some objects, its graphics gets scaled but spawner still spawns the objects from its original position, not the new scaled position..
Do I need to keep track of every X, Y position I'm using during gameplay and transition them too? (Which is probably possible but too hard for me since I use a lot of touch events for aiming and path creating etc.) Or is there an easier way to achieve this? Please please say yes :P
I'm looking forward for your answers, thanks in advance! :)
The problem is you are scaling your image, but not your position coordinates.
You need to convert from 'original coordinates' to 'scaled coordinates'
If you scale your map size to half, you should scale the move amounts by half too.
You need to 'scale' your coordinates in the same amount your image is scaled.
For example lets assume your scale factor is 0.5, you have an image:
local scaleFactor = 0.5
image.xScale = scaleFactor
image.yScale = scaleFactor
Then you would need to scale your move amounts by this factor
thingThatIsMoved.x = thingThatIsMoved.x + (moveAmount * scaleFactor)
thingThatIsMoved.y = thingThatIsMoved.y + (moveAmount * scaleFactor)
I hope this helps, I am assuming you have a moveAmount variable and updating the position on the enterFrame() event.