Extending Right Edge Boundary Sprite Kit - ios

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.

Related

How to reduce the impact of gravity on a single SKSpriteNode

I am building an endless runner game where platforms appear at random intervals and then scroll right to left across the screen.
I have a sprite that jumps up vertically using
- (void)makeCharacterJump {
[self.spriteCaveman.physicsBody applyImpulse:CGVectorMake(0.0f, 80.0f)];
}
The problem is that the effect of gravity on the sprite means that it falls quite quickly and cant make the gap between the platforms.
What I would like to do is slightly slow down the effect of gravity on the falling sprite so it creates the impression of slightly floating down.
Any ideas?
If the character is the only node affected by gravity then you can change the scene’s gravity with:
self.physicsWorld.gravity = CGVectorMake(0, desiredGravity);
If it is not then you’ll have to play with the character’s physics body properties: friction, linearDamping or angularDamping values.
Hope that helps.

Replacing (background) node gives weird result / nodes not visible on screen

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);

Spritekit - Bottom of screen coordinates

What are the coordinates for the bottom of the screen... or how can I create a "floor" at the bottom of the screen in spritekit?
Sorry, but I don't understand screen coordinates that well in spritekit.
You need to understand the Sprite Kit coordinate system as explained in Apple's Documentation here.
Here's how you create a floor at the bottom of the screen in SpriteKit:
SKNode *floor = [SKNode node];
node.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(CGRectGetMidX(self.frame),1.0 , CGRectGetWidth(self.frame), 1)];
[self addChild: floor];
You need some universal approach to get coordinates of corners on the screen.
Using code from that answer you can get CGRect with necessary information.
Example:
let screenRect = getVisibleScreen(
sceneRect: self.scene!.frame,
viewRect: self.view!.frame )
And then you can use it:
screenRect.minX
screenRect.maxX
screenRect.minY
screenRect.maxY
screenRect.width
screenRect.height
This is more then enough to calculate coordinates of "floor" or any other relative positions.
The location of the bottom of the screen will depend on what coordinate system you are using for your scene.
Out of the box, the bottom of the screen will be at y coordinate zero, but there are a few things that can happen that will affect that.
For instance, if you are using the scene editor in xCode, and your scene's anchorPoint property is something other than y=0, then the "origin" of your scene will not be at the bottom of the screen. In the recent xCode beta, they changed the default behavior to have the scene's origin at the center of the scene instead of the lower left corner, so that would explain why you might be seeing things in the center of the screen when you expect them to be at the bottom.
Also, the "bottom of the screen" will be relative to whatever parenting structure you have in your scene. For instance, if you place a background sprite in your scene, and want to attach a floor sprite to that which is at the bottom of the screen, you'll have to do some computing to figure out where to place it because you are going to inherit the translation and rotation of the floor's parent node (and any parents that node has).
To keep things simple, you can just place everything directly on the stage and manage their z-order manually. This will let you, basically, use the same coordinate system for everything. This is often fine; as long as you're not trying to do anything complex with your sprites, you don't need a complicated "tree" of nodes.
But even with this approach, the metrics of your scene are going to have to be handled dynamically. The width and height of your scene are going to depend on how you approach displaying your scene on different devices with different sizes. For instance, the top right of an iPhone 4 is going to be in a different place than the top right of an iPad Pro. A full discussion of how to deal with that is beyond the scope of your question, but generally, you'll probably want to use a "reference width" or a "reference height" for your scene, use .AspectFit or .AspectFill for the scaleMode, and set your scene's size accordingly. (I.e., inspect the view's frame to get the actual aspect ratio of your scene and set your scene size to match your reference metric on one axis and scale the other axis to match the device's aspect ratio.) This will let you use the same metrics for all devices (although one of your two axes will be fluid).

Adjacent SKPhysicsBody not working properly

I am having a problem when trying to put two blocks that have physics bodies on top of one another.
http://s1173.photobucket.com/user/Kyle_Decot/media/example_zps02f027fe.mp4.html
As you can see in the video I am able to place my block on top of the stacked blocks even though they are placed right on top of one another.
Both the player block and the other blocks inherit from a base Block class which looks like this
#import "Block.h"
#implementation Block
+ (void)loadSharedAssets {
}
- (id)initWithColor:(UIColor *)color size:(CGSize)size {
self = [super initWithColor:color size:size];
if (self) {
self.texture = [SKTexture textureWithImageNamed:#"tile"];
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: self.size];
self.physicsBody.usesPreciseCollisionDetection = YES;
self.physicsBody.dynamic = NO;
}
return self;
}
#end
Update
I've added a picture to make the problem a little more clear. Essentially the problem is that even though the blue blocks are right above one another, I am still able to jump up (w/ the red block) and sit on the edge of the bottom blue block (which shouldn't be possible). It seems something is off w/ the physics bodies or something.
You can see that the red block is slightly higher than the adjacent blue block for some reason and "sits" on the edge of the bottom blue block. when I jump up against it.
EDIT: replaced original suggestion with answer
Scrolling to section 4.5 Edge Shapes of the box2d documentation and you will find the cause of this (SpriteKit uses Box2D under the hood for it's physics implementation)
In many cases a game environment is constructed by connect several edge shapes end-to-end. This can give rise to an unexpected artifact when a polygon slides along the chain of edges. In the figure below we see a box colliding with an internal vertex. These ghost collisions are caused when the polygon collides with an internal vertex generating an internal collision normal.
The two edges here (edge1 and edge2) are the left hand side edges of the two blue boxes in your game (so picture it rotated 90 digs counter-clockwise)
Box2D introduced ChainShapes to get around this issue (which you can find referenced in section 5.6 Edge Shapes.
The idea is to replace groups of square physics bodies with a chain of these vertices whenever generating more than one box at the same time.
I believe you can access these within SpriteKit by using bodyWithEdgeChainFromPath and passing in a Core Graphics path consisting of the corner points of the boxes you want to combine to form the collision chain
I could imagine that tiny numerical errors in the simulation run by SpriteKit, in particular regarding collision detection and motion will make the red box indeed sit on the edge of the lower blue box. This position may be stable simply because the blue box accelerates the red box leftwards for the short amount of time it remains on screen.
A solution could be to use specific body types that merge the different configurations of boxes with smooth left vertical surfaces that occur in your game. In the specific case, it would be a box that is twice as high than wide. Before you spend a lot of time on this, you could test first of all, if removing the lower blue box stops the red box from getting stuck.
You don't need to define different Block classes for different box configurations: your initWithColor:(UIColor *)color size:(CGSize)size already appears to accept different sizes, but you might want to provide additional texture tiles and identifiers for those to select from.
EDIT: since you've clarified your question a bit and you seem to want the red one to fall back to the ground, while the blue blocks stay where they were, it seems that the bodies may be too rough, which makes them stick, and the red one doesn't slide off.
There's the friction property - modifying it can turn the body from a rough one (1.0) to one that's as smooth as an ice cube (0). Per the documentation, the default value is 0.2, which may still be too rough for your needs. Try setting it to physicsBody.friction = 0.0 in all involved bodies.
EDIT 2: it seems that it's a deeper bug. You can try the following workarounds to achieve your desired effect, if you want to sacrifice a little accuracy:
set your first blue block physics body to the target rectangle size, then make each subsequent lower block body 1-2 pixels narrower, so that you evade the 'lip'
set your physics bodies to circles rather than rectangles if all else fails - bodyWithCircleOfRadius:self.size.width*0.5. For a fast enough simulation, this might do.

OpenAL 3d Positioning and Panning Center

Using OpenAL, one can set the distance model:
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
And the position of a sound effect:
float globalRefDistance = 125.0f;
float globalMaxDistance = 1250.0f;
ALfloat alPos[] = {pos.x, pos.y, 0.0f};
alSourcefv(soundId, AL_POSITION, alPos);
alSourcef(soundId, AL_REFERENCE_DISTANCE, globalRefDistance);
alSourcef(soundId, AL_MAX_DISTANCE, globalMaxDistance);
This attenuates and pans the sound nicely, except when the listener is close to the source and steps back and forth to the left and right of the sound. In this case, the panning quickly goes from left to right. There is not really a spot where it plays the sound panned in the center.
How can I define a range/window/cone where OpenAL puts a 3d sound right in the center, without panning?
I want to be able to walk up to the sound from the left, hearing it gradually fade in from the left channel. Then be in both channels for awhile. Then gradually fade out in the right channel.
I've tried messing with setting the sound to be directional, but it doesn't seem to do the trick:
alPos[0] = 0.0f; alPos[1] = 0.0f; alPos[2] = 1.0f;
alSourcefv(soundId, AL_DIRECTION, alPos);
alSourcef(soundId, AL_CONE_INNER_ANGLE, 180.0f);
alSourcef(soundId, AL_CONE_OUTER_ANGLE, 240.0f);
Instead of using OpenAL's AL_POSITION, I ended up tracking the listener position and all sound positions by hand, then manually applying volume rolloff and panning each tick.
This allows a certain window/width/cone of space where an effect is panned fully center. It sounds much better.
Also note that I switched from AL_LINEAR_DISTANCE_CLAMPED back to the default inverse clamped mode. For some reason the linear clamped mode was causing any effect that was positioned with a negative X value to pan much too quickly, regardless of reference or maximum distance. This only happened on Mac builds, so I think the OpenAL Mac implementation has a panning bug when using linear clamped.

Resources