Simulate water/ make sprite "float" on water in spritekit - ios

I'm trying to add water to my game. Except for a different background color, there isn't much to it.
However, I'd like the player-sprite to float on top of it (or halfway in it). If the player just walks into the water from below, I'd like him to float to the top. If he falls down, I'd like him to slowly change direction and float back up.
I tried making the gravity negative when he's in the water, but this gives me some slightly unwanted effects. For example as he (the player) surfaces, the normal gravity will push him back down, the water will push him up, and so on. Ultimately the player will be "bouncing" in the water, being pushed from one end to another. I'd like him to calmly remain on top of the water when he surfaces. How can I achieve this?
Here's the code I have in my update-loop:
SKNode *backgroundNodeAtPoint = [_bgLayer nodeAtPoint:_ball.position];
if ([backgroundNodeAtPoint.name isEqualToString:#"WATER"]) {
self.physicsWorld.gravity = CGVectorMake(self.physicsWorld.gravity.dx, 2);
} else {
if (self.physicsWorld.gravity.dy != -4) {
self.physicsWorld.gravity = CGVectorMake(self.physicsWorld.gravity.dx, -4);
}
}
Basically this changes my gravity to 2 when the player is in the water, and otherwise changes it to -4 unless it's already -4.
Thanks!

There are three possible options I believe you have with regards to simulating water.
1) As mentioned in the comments you could try to use SKFieldNode (iOS 8+). But from personal experience the field node didn't really do it for me because you don't get much control over your simulation with it unless you heavily customize it, in which case you might as well just do your own calculations from scratch and reduce complexity.
2) You could adjust the linear and rotational damping of your sprite when inside the water. In fact, even apple mentions this in the quote from their documentation. However this won't give you buoyancy.
The linearDamping and angularDamping properties are used to calculate
friction on the body as it moves through the world. For example, this
might be used to simulate air or water friction.
3) Perform the calculations yourself. In the update method, check when the body enters you "water" and when it does you can calculate viscosity and/or buoyancy and adjust the velocity of your node accordingly. This in my opinion is the best option but also the more difficult.
Edit: I just wrote a quick example of option 3 in Swift. I think it's what you are looking for. I added factor constants on the top so you can adjust it to get exactly what you want. The motion is applied dynamically so it won't interfere with you current velocity (i.e. you can control your character while in the water). Below is the code for the scene and a gif as well. Keep in mind that the delta time is assumed to be 60 frames a second (1/60) and there is no velocity clamping. These are features you may or may not want depending on your game.
Swift
class GameScene: SKScene {
//MARK: Factors
let VISCOSITY: CGFloat = 6 //Increase to make the water "thicker/stickier," creating more friction.
let BUOYANCY: CGFloat = 0.4 //Slightly increase to make the object "float up faster," more buoyant.
let OFFSET: CGFloat = 70 //Increase to make the object float to the surface higher.
//MARK: -
var object: SKSpriteNode!
var water: SKSpriteNode!
override func didMoveToView(view: SKView) {
object = SKSpriteNode(color: UIColor.whiteColor(), size: CGSize(width: 25, height: 50))
object.physicsBody = SKPhysicsBody(rectangleOfSize: object.size)
object.position = CGPoint(x: self.size.width/2.0, y: self.size.height-50)
self.addChild(object)
water = SKSpriteNode(color: UIColor.cyanColor(), size: CGSize(width: self.size.width, height: 300))
water.position = CGPoint(x: self.size.width/2.0, y: water.size.height/2.0)
water.alpha = 0.5
self.addChild(water)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
override func update(currentTime: CFTimeInterval) {
if water.frame.contains(CGPoint(x:object.position.x, y:object.position.y-object.size.height/2.0)) {
let rate: CGFloat = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
let disp = (((water.position.y+OFFSET)+water.size.height/2.0)-((object.position.y)-object.size.height/2.0)) * BUOYANCY
let targetPos = CGPoint(x: object.position.x, y: object.position.y+disp)
let targetVel = CGPoint(x: (targetPos.x-object.position.x)/(1.0/60.0), y: (targetPos.y-object.position.y)/(1.0/60.0))
let relVel: CGVector = CGVector(dx:targetVel.x-object.physicsBody.velocity.dx*VISCOSITY, dy:targetVel.y-object.physicsBody.velocity.dy*VISCOSITY);
object.physicsBody.velocity=CGVector(dx:object.physicsBody.velocity.dx+relVel.dx*rate, dy:object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {object.position = (touches.anyObject() as UITouch).locationInNode(self);object.physicsBody.velocity = CGVectorMake(0, 0)}
}
Objective-C
#import "GameScene.h"
#define VISCOSITY 6.0 //Increase to make the water "thicker/stickier," creating more friction.
#define BUOYANCY 0.4 //Slightly increase to make the object "float up faster," more buoyant.
#define OFFSET 70.0 //Increase to make the object float to the surface higher.
#interface GameScene ()
#property (nonatomic, strong) SKSpriteNode* object;
#property (nonatomic, strong) SKSpriteNode* water;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
_object = [[SKSpriteNode alloc] initWithColor:[UIColor whiteColor] size:CGSizeMake(25, 50)];
self.object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.object.size];
self.object.position = CGPointMake(self.size.width/2.0, self.size.height-50);
[self addChild:self.object];
_water = [[SKSpriteNode alloc] initWithColor:[UIColor cyanColor] size:CGSizeMake(self.size.width, 300)];
self.water.position = CGPointMake(self.size.width/2.0, self.water.size.height/2.0);
self.water.alpha = 0.5;
[self addChild:self.water];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}
-(void)update:(NSTimeInterval)currentTime {
if (CGRectContainsPoint(self.water.frame, CGPointMake(self.object.position.x,self.object.position.y-self.object.size.height/2.0))) {
const CGFloat rate = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
const CGFloat disp = (((self.water.position.y+OFFSET)+self.water.size.height/2.0)-((self.object.position.y)-self.object.size.height/2.0)) * BUOYANCY;
const CGPoint targetPos = CGPointMake(self.object.position.x, self.object.position.y+disp);
const CGPoint targetVel = CGPointMake((targetPos.x-self.object.position.x)/(1.0/60.0), (targetPos.y-self.object.position.y)/(1.0/60.0));
const CGVector relVel = CGVectorMake(targetVel.x-self.object.physicsBody.velocity.dx*VISCOSITY, targetVel.y-self.object.physicsBody.velocity.dy*VISCOSITY);
self.object.physicsBody.velocity=CGVectorMake(self.object.physicsBody.velocity.dx+relVel.dx*rate, self.object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.object.position = [(UITouch*)[touches anyObject] locationInNode:self];
self.object.physicsBody.velocity = CGVectorMake(0, 0);
}
#end

When your player makes contact with the water, push him up until he is no longer in contact with the water. At this point, modify the player's contact bit mask to collide with the water thus "making him walk on water".
Alternately, you can trigger the walk on water contact bit mask modification by a strategically placed invisible node instead waiting for water contact to occur.
To revert the player's contact bit mask back to normal use another predetermined contact the player will make, such as land or an invisible node, as the trigger.

Related

Swift SKEmitterNode width

Good day, I am trying to use a SKEmitterNode in swift, but I can't seem to be able to change its' width, so the particles only cover half of the screen.
My code:
if let particles = SKEmitterNode(fileNamed: "Snow.sks") {
particles.position = CGPointMake(frame.size.width/2, frame.size.height)
particles.targetNode = self.scene
particles.zPosition = 999
addChild(particles)
}
How can I make the particles to cover the whole screen width?
After looking at the so called "emitter editor", as suggested by #Knight0fDragon, I was able to find the right parameter - particlePositionRange
if let particles = SKEmitterNode(fileNamed: "Snow.sks") {
particles.position = CGPointMake(frame.size.width/2, frame.size.height)
particles.targetNode = self.scene
// frame.size.width to cover the length of the screen.
particles.particlePositionRange = CGVector(dx: frame.size.width, dy: frame.size.height)
particles.zPosition = 999
addChild(particles)
}
Through the position and particlePositionRange you can get your goal
By the documentation:
particlePositionRange
Declaration
var particlePositionRange: CGVector { get set }
Discussion
The default value is (0.0,0.0). If a component is non-zero, the same component of a particle’s position is randomly determined and may vary by plus or minus half of the range value

How can one make only one side of a physics body active in SpriteKit?

I have a hero, ground and a table. I want to make a table-top active for collision and contact with hero. But hero should be able not to jump on the top and just run "through" and jump on it, if player wants it, wherever he wants. For better example of what i'm trying to achieve - think about Mario. When you are running on ground, some sky platforms appearing. You could jump on it in the middle of a platform and stay there. So I need physics body to not stop hero when he is contacting it from the bottom, but hold him if he is on top of it.
By now i'm using body with texture for table:
self.table.physicsBody = SKPhysicsBody(texture:table.texture, size:self.table.size)
self.table.physicsBody?.dynamic = false
self.table.physicsBody?.categoryBitMask = ColliderType.Table.rawValue
self.table.physicsBody?.contactTestBitMask = ColliderType.Hero.rawValue
self.table.physicsBody?.collisionBitMask = ColliderType.Hero.rawValue
It obviously, is not working. How can I implement such a thing?
The answer to this question is actually not too difficult but the implementation into a full fledged game will be much more difficult for you. This is not something for a novice programmer to start out with.
First the same code project (tap/click on screen to jump up):
#import "GameScene.h"
typedef NS_OPTIONS(uint32_t, Level1PhysicsCategory) {
CategoryPlayer = 1 << 0,
CategoryFloor0 = 1 << 1,
CategoryFloor1 = 1 << 2,
};
#implementation GameScene {
int playerFloorLevel;
SKSpriteNode *node0;
SKSpriteNode *node1;
SKSpriteNode *node2;
}
-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor whiteColor];
node0 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(400, 10)];
node0.position = CGPointMake(300, 200);
node0.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node0.size];
node0.physicsBody.dynamic = NO;
node0.physicsBody.categoryBitMask = CategoryFloor0;
node0.physicsBody.collisionBitMask = CategoryPlayer;
[self addChild:node0];
node1 = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(400, 10)];
node1.position = CGPointMake(300, 300);
node1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node1.size];
node1.physicsBody.dynamic = NO;
node1.physicsBody.categoryBitMask = CategoryFloor1;
node1.physicsBody.collisionBitMask = CategoryPlayer;
[self addChild:node1];
node2 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(50, 50)];
node2.position = CGPointMake(300, 250);
node2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:node2.size];
node2.physicsBody.categoryBitMask = CategoryPlayer;
node2.physicsBody.collisionBitMask = CategoryFloor0;
[self addChild:node2];
playerFloorLevel = 0;
}
-(void)update:(CFTimeInterval)currentTime {
if(((node2.position.y-25) > (node1.position.y+10)) && (playerFloorLevel == 0)) {
node2.physicsBody.collisionBitMask = CategoryFloor0 | CategoryFloor1;
playerFloorLevel = 1;
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
//SKNode *node = [self nodeAtPoint:touchLocation];
// change 75 value to 50 to see player jump half way up through floor 1
[node2.physicsBody applyImpulse:CGVectorMake(0, 75)];
}
}
The gist of the code is the player node (node2) has to keep checking its y position (update method) in relation to the other floors. In the example, the player jumps up through floor1. Once the player is higher than floor1, the player node's physics body modifies its collision bit mask to include floor1.
Sounds easy enough. However, in a real game you will have a large number of floors and all floors might not be evenly spaced y distances. You have to keep all that in mind when coding.
I am not sure how your platforms looks like (edge based or volume based bodies) but you can consider some of these:
1. Checking positions
Check if the hero's position.y is beneath/above the platform and ignore/handle the collision.
2. Checking velocity
Or to check if player node if falling, which is indicated by a negative velocity.dy value.
I can't say if any of these can fully help you with your game or is it possible with your setup, but you can get some basic idea on where to start.
Enabling/disabling collisions can be done by changing player's and platform's collision bitmasks. If possible try to avoid tracking states like isInTheAir, isOnPlatform, isFaling, isJumping and similar because it can become messy as number of states grows. For example, instead of adding custom boolean variable called "isFalling" and constantly maintaining its state, you can check if velocity.dy is negative to see if player is falling.
I tried changing the platforms collision bitmask but wasn't working fine. I found a different solution.
Inside the update() function you can check the following
if player.physicsBody?.velocity.dy <= 0 {
player.physicsBody?.collisionBitMask = PhysicsCategory.Platform
} else {
player.physicsBody?.collisionBitMask = PhysicsCategory.None
}
In this way, every time the player is going up, it can pass through rocks, and every time it is falling, it can stand.
Using swift you can create a sprite node subclass like this:
class TableNode: SKSpriteNode {
var isBodyActivated: Bool = false {
didSet {
physicsBody = isBodyActivated ? activatedBody : nil
}
}
private var activatedBody: SKPhysicsBody?
init(texture: SKTexture) {
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
// physics body setup. Assuming anchorPoint = (0.5, 0.5)
let bodyInitialPoint = CGPoint(x: -size.width/2, y: +size.height/2)
let bodyEndPoint = CGPoint(x: +size.width/2, y: +size.height/2)
activatedBody = SKPhysicsBody(edgeFromPoint: bodyInitialPoint, toPoint: bodyEndPoint)
activatedBody!.categoryBitMask = ColliderType.Table.rawValue
activatedBody!.collisionBitMask = ColliderType.Hero.rawValue
physicsBody = isBodyActivated ? activatedBody : nil
name = "tableNode"
}
}
Then, update all tableNodes in the gameScene:
override func didSimulatePhysics() {
self.enumerateChildNodesWithName("tableNode") {
node, stop in
if let tableNode = node as? TableNode {
// Assuming anchorPoint = (0.5, 0.5) for table and hero
let tableY = tableNode.position.y + tableNode.size.height/2
let heroY = hero.position.y - hero.size.height/2
tableNode.isBodyActivated = heroY > tableY
}
}
}

Sprite Kit: Anchor Point is Changing Sprite`s actual Position

please help me sort this confusion out.
From Sprite Kit Programming Guide:
A sprite node’s anchorPoint property determines which point in the
frame is positioned at the sprite’s position.
My understanding of this is that if I change the Anchor Point, the sprite`s position should stay unchanged and only the texture rendering should be moved accordingly.
But when I set the anchor point, my sprite`s position actually changes! Take a look at this snippet:
/* debug */
if (self.currentState == self.editState) {
printf("B: relativeAnchorPoint = %.02f,%.02f ", relativeAnchorPoint.x, relativeAnchorPoint.y);
printf("position = %.02f,%.02f\n",self.position.x, self.position.y);
}
[self setAnchorPoint:relativeAnchorPoint];
/* debug */
if (self.currentState == self.editState) {
printf("A: relativeAnchorPoint = %.02f,%.02f ", relativeAnchorPoint.x, relativeAnchorPoint.y);
printf("position = %.02f,%.02f\n",self.position.x, self.position.y);
}
Output:
A: relativeAnchorPoint = 0.65,0.48 position = 1532.00,384.00
B: relativeAnchorPoint = 0.65,0.48 position = 1583.00,384.00
What am I missing?
Thanks in advance
*edit: additional info: *
it only happens when my sprite has xScale to -1 to invert image
I made a quick test to confirm your observation, and it is indeed correct.
As the xScale becomes negative the anchorPoint does actually affect the node's position.
I tend to think of this as a bug since there seems to be no correlation between the negative xScale and the increase in x position. And it can't be considered normal behavior.
Also this only happens when you change the anchorPoint after the xScale is already negative. You can set anchorPoint, then change xScale all you want and things will be fine, position will not change.
I confirmed this issue exists in both Xcode 5.1 (iOS 7) and Xcode 6 beta (iOS 8 beta).
If you run the following code in a newly created Sprite Kit project in place of its auto-created MyScene.m file you'll see that as anchorPoint changes randomly between 0.0 and 1.0 the position of the sprite always remains the same until the xScale property changes to a negative value. At that point position.x starts to increase significantly.
#import "MyScene.h"
#implementation MyScene
{
SKSpriteNode *sprite;
}
-(id) initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor colorWithRed:0 green:0 blue:0.2 alpha:1];
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite.anchorPoint = CGPointMake(0.2, 0.7);
[self addChild:sprite];
SKAction *action = [SKAction scaleXTo:-1.0 duration:10];
[sprite runAction:[SKAction repeatActionForever:action]];
}
return self;
}
-(void) update:(CFTimeInterval)currentTime
{
sprite.anchorPoint = CGPointMake(arc4random_uniform(10000) / 10000.0,
arc4random_uniform(10000) / 10000.0);
NSLog(#"pos: {%.1f, %.1f}, xScale: %.3f, anchor: {%.2f, %.2f}",
sprite.position.x, sprite.position.y, sprite.xScale,
sprite.anchorPoint.x, sprite.anchorPoint.y);
}
#end
There is a workaround for this bug:
If xScale is already negative, invert it, then set the anchorPoint, then re-invert xScale. You may need to do the same with yScale if that too can become negative.
The following update method incorporates this workaround and I confirmed that this is working as intended:
-(void) update:(CFTimeInterval)currentTime
{
BOOL didInvert = NO;
if (sprite.xScale < 0.0)
{
didInvert = YES;
sprite.xScale *= -1.0;
}
sprite.anchorPoint = CGPointMake(arc4random_uniform(10000) / 10000.0,
arc4random_uniform(10000) / 10000.0);
if (didInvert)
{
sprite.xScale *= -1.0;
}
NSLog(#"pos: {%.1f, %.1f}, xScale: %.3f, anchor: {%.2f, %.2f}",
sprite.position.x, sprite.position.y, sprite.xScale,
sprite.anchorPoint.x, sprite.anchorPoint.y);
}
The sprite.position now remains the same throughout the entire scaleXTo action duration.

Sprite Kit - SKSpriteNode drawn at wrong position

Whenever i try to draw a SKSpriteNode, it would be drawn lower than it should be.
But it seems that other SKSpriteNode works fine with no problems.
This is my current code:
func initMainGround() {
let gSize = CGSizeMake(self.size.width/4*3, 120);
ground = SKSpriteNode(color: SKColor.brownColor(), size: gSize);
ground.name = gName;
ground.position = CGPointMake(0, 0);
ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.size);
ground.physicsBody.restitution = 0.0;
ground.physicsBody.friction = 0.0;
ground.physicsBody.angularDamping = 0.0;
ground.physicsBody.linearDamping = 0.0;
ground.physicsBody.allowsRotation = false;
ground.physicsBody.usesPreciseCollisionDetection = true; //accurate collision
ground.physicsBody.affectedByGravity = false;
ground.physicsBody.dynamic = false;
ground.physicsBody.categoryBitMask = gBitmask;
ground.physicsBody.collisionBitMask = pBitmask;
self.addChild(ground);
}
func addBomb() {
let bomb = SKSpriteNode(imageNamed: "trap");
bomb.size = CGSizeMake(30, 30);
bomb.position = CGPointMake(ground.position.x, actualY+10);
bomb.physicsBody = SKPhysicsBody(circleOfRadius: bomb.size.width/2);
bomb.physicsBody.restitution = 0.0;
bomb.physicsBody.allowsRotation = false;
bomb.physicsBody.usesPreciseCollisionDetection = true;
bomb.physicsBody.affectedByGravity = false;
bomb.physicsBody.dynamic = false;
bomb.physicsBody.categoryBitMask = bBitmask;
bomb.physicsBody.collisionBitMask = pBitmask;
bomb.physicsBody.contactTestBitMask = pBitmask;
self.addChild(bomb);
}
Although the bomb is suppose to be almost directly above the ground, but it seems that the bomb is almost 100+ above the ground instead.
The ground is suppose to fill up almost one third of the screen height since the game is in landscape, but it is way lower than normal.
Why is it that the ground is drawn at the wrong position, but the bomb is drawn at the correct position?
This sounds like the same problem I had when starting to work with SpriteKit. If your game uses an .sks file to present it's main scene (as it does by default), this scene uses arbitrary dimension values defined in the .sks file.
Try setting the dimensions of your scene dynamically to see if this is the case.
In your didMoveToView function, add something like this at the top of the function:
self.size = view.bounds.size
This way the dimension values from the .sks file will be overridden with your actual screen dimensions.
Hope this helps!
Set the anchorPoint on your ground image.
ground.anchorPoint = CGPointZero // same as CGPointMake(0, 0)
ground.position = CGPointZero
The default anchorPoint is (0.5, 0.5), the center of the image. So without it set, the center of the ground image is drawn in the lower left corner of the screen (0, 0).
Your bomb draws where you expect it for the same reason, the center is placed at the position you specified.

Define map bounds and center on player node?

I'm working on a sidescroller with Spritekit and Swift. I don't understand how to define a playable area bigger than the screen and center the camera on the player. How can this be done?
This is my current code, I tried to create a "world node" which I could move around to simulate the camera, however it's somehow disassociated from it's shape and I haven't been able to get the player inside it.
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPointMake(0.5, 0.5)
self.physicsWorld.contactDelegate = self
self.size = CGSizeMake(view.bounds.size.width, view.bounds.size.height)
// Add world
let r : CGRect = CGRectMake(0, 0, 500, 500)
world = SKShapeNode(rectOfSize: r.size)
world.physicsBody = SKPhysicsBody(edgeLoopFromRect: r)
world.strokeColor = SKColor.blackColor()
world.position = CGPointMake(0, 0)
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 50, height: 50))
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 50, height: 50))
player.fillColor = SKColor.blackColor()
player.position = CGPointMake(0, 0)
world.addChild(player)
// Accelerometer updates
motionManager.startAccelerometerUpdates()
}
The example in Apple's documentation is in the Advanced Scene Processing section. Apple suggests making a "World" SKNode as a child of the Scene, and a "Camera" SKNode as a child of the world.
They suggest constantly moving the world so that it centers on the Camera during the didSimulatePhysics step. This method allows you to perform actions or simulate physics on the Camera itself, if you so choose. If you center the camera prior to this step, you won't be able to use physics to affect the Camera Node.
If you specifically only want left and right scrolling, simply restrict the movement to the X-axis.
Edit:
The current problem is because of your creation of the world and the physicsBody from a Rect that has its position predetermined. This is causing trouble with your anchorPoint setting (the world & physicsBody are being created with their lower left corners starting at the Scene's anchor point).
This can be fixed by creating the World and Player without using a Rect with position set. SKShapeNode's shapeNodeWithRectOfSize works, as does SKSpriteNode's spriteNodeWithColor:size: PhysicsBody is a bit trickier, and should likely use bodyWithEdgeLoopFromPath: world.path
EDIT: For future persons interested in creating a side-scroller with a camera always focused on the player, this is probably the simplest way to get one to work:
var player = SKShapeNode()
var world = SKShapeNode()
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPointMake(0.5, 0.5)
self.size = CGSizeMake(view.bounds.size.width, view.bounds.size.height)
// Add world
world = SKShapeNode(rectOfSize: CGSizeMake(300, 300))
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
world.fillColor = SKColor.blackColor()
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 50, height: 50))
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 50, height: 50))
player.fillColor = SKColor.blackColor()
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x
world.position.y = -player.position.y
}
I followed Ray Wenderlich's tutorial on a side scrolling game Super Koalio (or something like that). That game is a side scroller where the game map is larger sideways than the screen. The following code is for my game which is a vertical scrolling game.
In the update method I call a method called setViewPointCenter.
-(void)update:(CFTimeInterval)currentTime
{
// Other things are called too
[self setViewPointCenter:self.player.position];
}
Then in that method you just update the view
- (void)setViewPointCenter:(CGPoint)position
{
NSInteger x = MAX(position.x, self.size.width / 2);
NSInteger y = MAX(position.y, self.size.height /2);
x = MIN(x, (self.map.mapSize.width * self.map.tileSize.width) - self.size.width / 2);
y = MIN(y, (self.map.mapSize.height * self.map.tileSize.height) - self.size.height / 2);
CGPoint actualPostion = CGPointMake(x, y);
CGPoint centerOfView = CGPointMake(self.size.width/2, self.size.height/2);
CGPoint viewPoint = CGPointSubtract(centerOfView, actualPostion);
self.map.position = viewPoint;
}
Now my character is always in the center of the screen. I did change some items from horizontal to vertical, but at least you get an idea. Also, I used a tile map that is why you see mapSize and tileSize. You might want to take a look at Ray's tutorial. And of course you will need to convert into the methods into Swift!!

Resources