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);
Related
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));
}
I'm working on a game and have a question about edge boundaries. For making a game that is contained within the window if the iPhone I can do this:
- (void) createSceneContents
{
self.backgroundColor = [SKColor blackColor];
self.scaleMode = SKSceneScaleModeAspectFit;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}
The only other method that looked appealing is
bodyWithEdgeFromPoint:ToPoint
I was able to set a leftmost boundary with this method and set a floor and walk left and right with my character. However, the character will fall off the world once it reaches the edge. What I'm going for is an extended background essentially that will be off the screen but ultimately has a far right edge upon which a player can't proceed further. The camera will follow the player as they go either to the left or right edge. The background will most likely consist of different images that will be loaded in once the user reaches the edge of a background. If anyone has tips or advice I'd really appreciate it.
I've been mucking around with sprite-kit but found out the only way to affect a sprites coordinate is with sprite.position, the issue here is I only want to affect one sprite coordinate, let's say y. How would I do this?
You should probably read up on SKActions:
SKAction *moveSprite = [SKAction moveToY:200 duration:0]; // or wherever...
[yourSpriteInstance runAction:moveSprite];
There is also a moveToX:duration:, moveTo:duration:, moveBy:duration: to mention a few of the options..
Like #sangony writes in the comments you can of course access the node's position directly through its position property:
sprite.position = CGPointMake(sprite.position.x, sprite.position.y + 200);
This will work just fine.
That being said, SKActions is the way to go about this in most real-life scenarios: E.g. if you want to have the sprite change its position after 3 seconds, then wait another 2 seconds before it begins rotating. It really is one of the cornerstones of the SpriteKit framework...
Working on an iOS game using SpriteKit. My background is made up of map tiles (essentially an infinite map, procedurally generated).
Our system is designed to manage "chunks" of the map, and we only load chunks near the player. Since SpriteKit requires we add SKSpriteNodes, we no longer have clean control over "unloading" sprites for chunks/tiles that are no longer near the player.
I realize SpriteKit won't actually render things off-screen, but it's going to kill performance if we can't remove sprites no longer needed, or check if a chunk/tile is already added.
Since SKNodes doesn't respond to isEqual:, I only see two ways to do this:
Give each sprite a name with their chunk/tile coordinate, and check this name each update
Maintain a separate array of loaded tiles and check that instead
Is there any easier way of checking/removing if a sprite has been added already? Maybe a partial string match on sprite name?
I'm not sure that using SpriteKit is the best solution (Xcode simulator seems to drag at 30fps, have yet to test on a real device). We originally built this game in Java and we're rendering our own textures - hence only what was loaded and could be fed into opengl manually.
-(void) renderToScene:(SKScene *)scene {
for( Chunk *chunk in loadedChunks ){
for( Tile *tile in [chunk getTiles] ){
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:tileTexture];
sprite.name = #"Tile";
sprite.position = CGPointMake(realX,realY);
[scene addChild:sprite];
}
}
}
What will in fact kill your framerate is frequently creating and removing nodes - you should have a pool of sprites that you re-use rather than recreate.
Just update the sprite's texture, position and other attributes when reusing one of those you no longer need. A common use case is to have enough tiles to span the entire screen plus one row and one column, so that when an entire row or column has moved outside the screen you can reposition it at the other side with new textures according to the map data.
If i'm understanding what you're asking correctly. You want to properly remove a node/sprite from the scene if its no longer within view.
You should just be able to call the [self removeFromParent] method to remove in whenever its outside the bounds of the screen. Remember you can call this method on any object as long as its a child.
For instance if i had character,
SKSpriteNode *character;
SKSpriteNode *sword;
//all the rest of the properties are up to you.
if my character picked up a sword in the game and after a certain time period the sword is no longer accessible. I would do:
[character addChild:sword];
which would add it to the character.
[sword removeFromParent];
when i no longer need the sword to be apart of its parent.
Code --> http://pastebin.com/W3DMYjXa
I am tinkering with SpriteKit and I cannot seem to get an emitter node to rotate with my spaceship the way I would like.
I watched the Apple Tech Talks Session where they described making a spaceship and using an emitter for the thrust exhaust, but they lock the ship to only face one direction.
In my experiment I am trying to allow the player sprite (spaceship) to travel in any direction, I have rotation and scrolling working for the player, but the particle emitter doesn't seem to rotate with the player.
my hierarchy looks something like this
Scene
-->World Node
-->Player Node
---->Player Sprite
---->Emitter Node
My theory is that If I rotate (Player Node) it should rotate both of its children, and it does rotate them, but the emitter continues to emit in the same direction.
I can change the emission angle manually, but it seems needlessly complicated.
here is what I am using to rotate
-(void)rotatePlayerToDirection:(TTDirection)direction {
CGFloat radDir;
CGFloat emiDir;
scrollDirection = direction;
switch (direction) {
case TTUp:
radDir = 0;
emiDir = 3.142;
break;
case TTRight:
radDir = 4.712;
emiDir = 1.571;
break;
case TTDown:
radDir = 3.142;
emiDir = 0;
break;
case TTLeft:
radDir = 1.571;
emiDir = 4.712;
break;
default:
break;
}
SKAction *rotatePlayer = [SKAction rotateToAngle:radDir duration:0.1 shortestUnitArc:YES];
[playerNode runAction:rotatePlayer];
}
Am I missing something here?
Here is a video of this in action
http://youtu.be/NGZdlB9-X_o
I'm fairly certain there is a bug in spritekit to do with the targetNode and rotation. The other answer seems to suggest this is expected behavior, but that ignores that the docs explicitly give this situation as a motivation for the existence of targetNode.
Working with Other Node Types
What you really want is for the particles to be spawned, but thereafter be
independent of the emitter node. When the emitter node is
rotated, new particles get the new orientation, and old
particles maintain their old orientation.
It then gives an example using targetEmitter on how to achieve this. However, the example code does not work.
It seems that setting targetNode breaks particle rotation.
UPDATE: I found a workaround.
Spritekit is failing to adjust the SKEmitterNode.emissionAngle property when SKEmitterNode.targetNode is set. You can work around this by setting it manually after actions have been processed.
Assuming that your SKEmitterNode has only one parent, you can do something like this in your scene.
- (void)didEvaluateActions
{
// A spaceship with a thrust emitter as child node.
SKEmitterNode *thrust = (SKEmitterNode *)[self.spaceship childNodeWithName:#"thrust"];
thrust.emissionAngle = self.spaceship.zRotation + STARTING_ANGLE;
}
Note that if there are multiple ancestor nodes which may be rotated, you will need to loop through them and add all of their zRotations together.
I know this is an old question, and the OP has probably found a solution or workaround, but I thought I would add by two pence worth, in case it helps anybody else.
I am just starting out with SpriteKit and Swift, and am developing a game where nodes fly around and collide with each other, changing direction of travel frequently.
I add an emitter to each node, and initially found that as the node rotated and changed direction, the emission 'trail' remained fixed. I then read that the emitter should have its target node set not as the node it is a child of, but the scene they both exist in.
That at least made the particle trail twist and turn realistically rather than just spurt out in one direction.
But the nodes can spin and changes direction, and I wanted the particles to trail away from the node in the opposite direction to its travel - just like a smoke trail from a plane or rocket.
As the node may rotate, setting the emitters angle of emission to the z-rotation of the node does not work. So...
I track all onscreen nodes in a dictionary, removing them from the scene as they travel off the screen.
So in didSimulatePhysics I use the same dictionary to get the node and calculate the angle of travel from the velocity vector, and then set the emitters emission angle accordingly :
import Darwin
// in your scene class...
override func didSimulatePhysics() {
removeOffScreenParticles()
for (key,particle) in particles {
let v = particle.physicsBody!.velocity
particle.trail!.emissionAngle = atan2(v.dy, v.dx) - CGFloat(M_PI)
}
}
I am aware that this calculation and adjustment is being performed for each on screen node, in every frame, so will probably use my frame counter to only do this check every n'th frame if performance becomes an issue.
I hope this helps someone!
You are setting the emitter's targetNode property to the background. Note that this forces the emitter's particles to be rendered by the background node, and they will be treated as if the background was their parent. So, the particles aren't changing the angle, because the rotation of the background does not change.
Remove [emitter setTargetNode:backgroundNode] and the emitter's emission angle should be rotating correctly along with the player.
If you're looking for a 'middle ground', where the angle rotates along with the player, while the already-rendered particles aren't 'stuck' to the emitter, try this:
First, do not set the targetNode on the emitter (it defaults to nil).
Second, just as you are about to run the rotation action, temporarily set the targetNode to another node, and reset it back to nil when it completes (so that the particles can rotate along):
emitter.targetNode = backgroundNode;
[playerNode runAction:rotatePlayer completion:^{
emitter.targetNode = nil;
}];
Note that the results are also 'middle ground' - the emitter 'puffs' as the target node changes (the previous particles are removed, and new ones are rendered by the new target node). Hope that helps.
Other than that, there's no really straightforward way of achieving this - setting the emission angle manually might be the simplest way, after all. Here's one way to do it, right before running the rotation (the emitter target node is still nil, also, there's no need to hardcode emission angles):
float originalAngle = emitter.emissionAngle;
emitter.emissionAngle = emitter.emissionAngle - radDir;
[playerNode runAction:rotatePlayer completion:^{
emitter.emissionAngle = originalAngle;
}];