Different sprite position with the same code - ios

I have a weird issue with my sprite position, I tried clean build, restart xcode, and run in different schemes(iPhone5s, iPhone6), they all return the same strange issue.
I tried to set the position of the sprite by:
balls.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + self.frame.size.width)
so when I println(balls.position) to the console, it returns (0.0, 0.0)
But when I tried
println(CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + self.frame.size.width)
it returns (512.0,1408.0), and this is correct position where the ball should be.
I'm having issue with the last function, func ballPosition, it is used to determind the position of the sprite "balls". for some reason it is always (0, 0).
Here are the complete code from my test project:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var circle = SKShapeNode()
var balls = SKShapeNode()
var ballColor = ["red", "blue", "green", "yellow"]
var points = ["up", "down", "left", "right"]
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
// set circle position and size
circle = SKShapeNode(circleOfRadius: 100 ) // Size of Circle
circle.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame)) //Middle of Screen
circle.strokeColor = SKColor.whiteColor()
circle.fillColor = SKColor.orangeColor()
self.addChild(circle)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
circleRotate()
ballPosition()
// test ball position, this is the part with the issue I mentioned above.
println(balls.position) // (0, 0)
println(CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + self.frame.size.width)) // (512.0,1408.0)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func circleRotate() {
let circleAction = SKAction.rotateByAngle(CGFloat(-M_PI * 2 / 3), duration: 0.1)
circle.runAction(SKAction.repeatAction(circleAction, count: 1))
}
func ballMove() {
let ballMovement = SKAction.moveTo(CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame)), duration: 5)
balls.runAction(ballMovement)
}
func randomColor() {
let ballColorIndex = Int(arc4random_uniform(UInt32(ballColor.count)))
balls = SKShapeNode(circleOfRadius: 10 )
if ballColorIndex == 0 {
balls.strokeColor = SKColor.whiteColor()
balls.fillColor = SKColor.redColor()
// balls.zPosition = 10
ballMove()
} else if ballColorIndex == 1 {
balls.strokeColor = SKColor.whiteColor()
balls.fillColor = SKColor.blueColor()
// balls.zPosition = 10
ballMove()
} else if ballColorIndex == 2 {
balls.strokeColor = SKColor.whiteColor()
balls.fillColor = SKColor.greenColor()
// balls.zPosition = 10
ballMove()
} else if ballColorIndex == 3 {
balls.strokeColor = SKColor.whiteColor()
balls.fillColor = SKColor.yellowColor()
// balls.zPosition = 10
ballMove()
}
}
func ballPosition() {
let ballPointIndex = Int(arc4random_uniform(UInt32(points.count)))
if ballPointIndex == 0 {
balls.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + self.frame.size.width)
randomColor()
self.addChild(balls)
} else if ballPointIndex == 1 {
balls.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - self.frame.size.height)
randomColor()
self.addChild(balls)
} else if ballPointIndex == 2 {
balls.position = CGPoint(x:CGRectGetMidX(self.frame) - self.frame.size.width, y:CGRectGetMidY(self.frame))
randomColor()
self.addChild(balls)
} else if ballPointIndex == 3 {
balls.position = CGPoint(x:CGRectGetMidX(self.frame) + self.frame.size.width, y:CGRectGetMidY(self.frame))
randomColor()
self.addChild(balls)
}
}
}

The problem is you're overwriting ball in randomColor() - you're creating a new node which hasn't been added to the parent view.
The underlying problem is you've structured your code in a way that leads to confusion and mistakes. You should keep your functions single purposed. They should do what they say they do. A function called randomColor() should not move the move the ball or set the ball size. The position function should not set the color.
I rearranged the code and ran it. That bug is fixed. You can see what I changed and should be able to get further now. Good luck.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var circle = SKShapeNode()
var ball = SKShapeNode(circleOfRadius: 10)
var ballColor = ["red", "blue", "green", "yellow"]
var points = ["up", "dowm", "left", "right"]
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
circle = SKShapeNode(circleOfRadius: 100 ) // Size of Circle
circle.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame)) //Middle of Screen
circle.strokeColor = SKColor.whiteColor()
circle.fillColor = SKColor.orangeColor()
self.addChild(circle)
self.addChild(ball)
ball.position = CGPointMake(150, 0)
println("Initial Ball Pos: \(ball.position)")
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
circleRotate()
setRandomBallPosition()
setRandomColor()
ballMove()
// test ball position
println("--------------")
println("Ball Pos: \(ball.position)")
println("Circle pos: \(circle.position)")
println("Midpoint: \(CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + self.frame.size.width))")
println("--------------")
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func circleRotate() {
let circleAction = SKAction.rotateByAngle(CGFloat(-M_PI * 2 / 3), duration: 0.1)
circle.runAction(SKAction.repeatAction(circleAction, count: 1))
}
func ballMove() {
let ballMovement = SKAction.moveTo(CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame)), duration: 5)
ball.runAction(ballMovement)
}
func setRandomColor() {
ball.strokeColor = SKColor.whiteColor()
let ballColorIndex = Int(arc4random_uniform(UInt32(ballColor.count)))
switch(ballColorIndex) {
case 0:
ball.fillColor = SKColor.redColor()
case 1:
ball.fillColor = SKColor.blueColor()
case 2:
ball.fillColor = SKColor.greenColor()
case 3:
ball.fillColor = SKColor.yellowColor()
default:
println("Unexpected random index value ", ballColorIndex)
}
}
func setRandomBallPosition() {
let ballPointIndex = Int(arc4random_uniform(UInt32(points.count)))
println("ballPointIndex = \(ballPointIndex)")
switch(ballPointIndex) {
case 0:
ball.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) + (self.frame.size.width / 2))
case 1:
ball.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - (self.frame.size.height / 2))
case 2:
ball.position = CGPoint(x:CGRectGetMidX(self.frame) - (self.frame.size.width / 2), y:CGRectGetMidY(self.frame))
case 3:
ball.position = CGPoint(x:CGRectGetMidX(self.frame) + (self.frame.size.width / 2), y:CGRectGetMidY(self.frame))
default:
println("Unexpected random index value: ", ballPointIndex)
}
println("ball position = \(ball.position)")
}
}

Related

SpriteKit keep moving player in current direction while falling after touchesEnded

I'm making my own Mario Bros. replica for the first level to learn how to make games with iOS, with my own assets. So far I've managed to place three SKSpriteNodes for the controls (left, right, up), and my player node can move in those three directions, but if I make my player jump while running in either direction, as soon as I remove my finger from the "left control", the player loses all its momentum and falls right there (as if it hit a wall) instead of following the parabola.
I don't know what might be needed in this case to be an MRE, so this is basically the whole thing that can reproduce the issue, along with some attempts I've made to make it work.
Basically I tried to apply an impulse / set the velocity / change the position directly and this last one was the one with better results (yet it still makes the player node to fall as soon as I remove the finger from the direction controls).
Here's a video demonstrating the issue.
This is the GameScene
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var player = SKSpriteNode()
private var bg = SKSpriteNode()
private var leftArrow = SKSpriteNode()
private var rightArrow = SKSpriteNode()
private var upArrow = SKSpriteNode()
private var floor = [SKSpriteNode]()
private var isLeftTouched = false
private var isRightTouched = false
private var selectedNodes: [UITouch:SKSpriteNode] = [:]
override func didMove(to view: SKView) {
addBackground()
addFloor()
addPlayer(xOffset: 0, yOffset: 0)
addControls()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
let touch = touches.first! as UITouch
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
for touch in touches {
let location = touch.location(in:self)
if let node = self.atPoint(location) as? SKSpriteNode {
if let name = touchedNode.name {
selectedNodes[touch] = node
if name == "up" {
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 60))
} else if name == "left" {
isLeftTouched = true
} else if name == "right" {
isRightTouched = true
}
}
}
}
if let name = touchedNode.name {
if name == "up" {
player.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 60))
} else if name == "left" {
isLeftTouched = true
} else if name == "right" {
isRightTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//let direction = ((touches.first?.location(in: self).x)! < (touches.first?.previousLocation(in: self).x)!) ? Direction.LEFT : Direction.RIGHT
//runIn(direction: direction)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if selectedNodes[touch] != nil {
if selectedNodes[touch]?.name == "left" {
isLeftTouched = false
} else if selectedNodes[touch]?.name == "right" {
isRightTouched = false
}
selectedNodes[touch] = nil
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if isLeftTouched {
runIn(direction: Direction.LEFT)
}
if isRightTouched {
runIn(direction: Direction.RIGHT)
}
}
// MARK: INTERACTION METHODS
func runIn(direction: Direction) {
let x = player.position.x + (direction == Direction.RIGHT ? 5 : -5)
let position = CGPoint(x: x, y: player.position.y)
if position.x >= self.frame.maxX || position.x <= self.frame.minX {
return
}
player.position = position
//player.physicsBody?.velocity = CGVector(dx: direction == Direction.RIGHT ? 50 : -50, dy: 0)
//player.physicsBody?.applyImpulse(CGVector(dx: direction == Direction.RIGHT ? 5 : -5 , dy: 0))
}
// MARK: UI METHODS
func addBackground() {
let bgTexture = SKTexture(imageNamed: "bg")
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
bg.size.height = self.frame.height
bg.zPosition = -10
self.addChild(bg)
}
func addPlayer(xOffset: CGFloat, yOffset: CGFloat) {
let playerTexture = SKTexture(imageNamed: "player")
player = SKSpriteNode(texture: playerTexture)
//let xPos = calculateXOffset(for: player, from: self.frame.midX, offset: xOffset)
//let yPos = calculateXOffset(for: player, from: self.frame.midY, offset: yOffset)
player.position = CGPoint(x: self.frame.midX,
y: self.frame.midY)
player.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: player.frame.width, height: player.frame.height))
player.physicsBody?.isDynamic = true
self.addChild(player)
}
func addFloor() {
let blockTexture = SKTexture(imageNamed: "block")
for i in 0 ... (Int) (self.frame.width / blockTexture.size().width) {
let blockNode = SKSpriteNode(texture: blockTexture)
blockNode.position = CGPoint(x: self.frame.minX + (blockNode.frame.width * CGFloat(i)),
y: self.frame.minY + blockNode.frame.height / 2)
blockNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: blockNode.frame.width, height: blockNode.frame.height))
blockNode.physicsBody?.isDynamic = false
floor.append(blockNode)
self.addChild(blockNode)
}
}
func addControls() {
addLeftArrow()
addRightArrow()
addUpArrow()
}
func addLeftArrow() {
let leftTexture = SKTexture(imageNamed: "left")
leftArrow = SKSpriteNode(texture: leftTexture)
leftArrow.name = "left"
leftArrow.position = CGPoint(x: calculateXOffset(for: leftArrow, from: self.frame.minX, offset: 50),
y: calculateXOffset(for: leftArrow, from: self.frame.minY, offset: 50))
self.addChild(leftArrow)
}
func addRightArrow() {
let rightTexture = SKTexture(imageNamed: "right")
rightArrow = SKSpriteNode(texture: rightTexture)
rightArrow.name = "right"
rightArrow.position = CGPoint(x: calculateXOffset(for: rightArrow, from: self.frame.minX, offset: 150),
y: calculateXOffset(for: rightArrow, from: self.frame.minY, offset: 50))
self.addChild(rightArrow)
}
func addUpArrow() {
let upTexture = SKTexture(imageNamed: "up")
upArrow = SKSpriteNode(texture: upTexture)
upArrow.name = "up"
upArrow.position = CGPoint(x: calculateXOffset(for: upArrow, from: self.frame.maxX, offset: -(125 + upTexture.size().width)),
y: calculateXOffset(for: upArrow, from: self.frame.minY, offset: 50))
self.addChild(upArrow)
}
// MARK: UTILITY FUNCTIONS
func calculateXOffset(for asset: SKSpriteNode, from coord: CGFloat, offset: CGFloat) -> CGFloat {
let width = asset.frame.width
return coord + offset + width;
}
func calculateYOffset(for asset: SKSpriteNode, from coord: CGFloat, offset: CGFloat) -> CGFloat {
let height = asset.frame.height
return coord + offset + height;
}
}
My Direction enum:
enum Direction {
case LEFT
case RIGHT
case UP
case DOWN
}
And the only change I made in GameViewController was this:
scene.scaleMode = .resizeFill
My GameScene.sks is 926 x 428, only supporting landscape. I also set the LaunchScreen to Main due to a bug in Xcode 12: Background is not filling the whole view SpriteKit
And these are all my assets:
Edit
I tried applying an impulse in my runIn method like this:
player.physicsBody?.applyImpulse(CGVector(dx: direction == Direction.RIGHT ? 2 : -2 , dy: 0))
This makes the player node move in the parabola but now from time to time it gets stuck and the only way to make it move is to make it jump until it happens again.
Here's a video demonstrating the issue again.
If I try to set the velocity instead, then I'm not able to jump while moving and it seems to glide when jumping and moving after.
I ended up following #JohnL suggestion in the comments above, to use an impulse as well to move my player node:
player.physicsBody?.applyImpulse(CGVector(dx: direction == Direction.RIGHT ? 2 : -2 , dy: 0))
The issue where the player node was stuck while moving was removed when changing the floor for a single asset rather than multiple blocks one next to each other.

Why is my player not moving in SpriteKit when tilting?

I am trying to using the option of tilting the screen to move my player. So i followed the instructions of Ray Wenderlich and his space invaders tutorial. I just can't seem to figure out why my player is not moving from left to right when tilting the screen.
Please have a look at it to help me out.
import SpriteKit
import CoreMotion
enum BodyType: UInt32 {
case player = 2
case enemy = 3
}
let motionManager = CMMotionManager()
class GameScene: SKScene, SKPhysicsContactDelegate {
let points = SKLabelNode(text: "0")
let gamePlayerSize = CGSize(width: 30, height: 16)
let gamePlayerName = "gameplayer"
override func didMove(to view: SKView) {
//SET UP FRAME
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
//SETTING UP Player
func setupPlayer() {
// 1
let player = makePlayer()
// 2
player.position = CGPoint(x: 240, y: 28)
addChild(player)
}
func makePlayer() -> SKNode {
let player = SKSpriteNode(imageNamed: "block")
player.name = gamePlayerName
// 1
player.physicsBody = SKPhysicsBody(rectangleOf: player.frame.size)
// 2
player.physicsBody!.isDynamic = true
// 3
player.physicsBody!.affectedByGravity = false
// 4
player.physicsBody!.mass = 0.02
return player
}
setupPlayer()
motionManager.startAccelerometerUpdates()
//score label
let points = SKLabelNode(text: "0")
points.position = CGPoint(x: 280, y: 510)
points.fontColor = UIColor.black
points.fontSize = 50
addChild(points)
// setting border around game
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
//ENEMY SETTINGS START
//repeat enemy spawning
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(spawnEnemy),
SKAction.wait(forDuration: 1.0)])))
}
//Enemy settings
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
//spawn enemies
func spawnEnemy() {
// 2
let enemy = SKSpriteNode(imageNamed: "ball")
// 3
enemy.name = "enemy"
// 4
enemy.position = CGPoint(x: frame.size.width * random(min: 0, max: 1), y: frame.size.height + enemy.size.height/2)
// 5
addChild(enemy)
enemy.run(
SKAction.moveBy(x: 0.0 , y: -size.height - enemy.size.height,
duration: TimeInterval(random(min: 1, max: 2))))
//ENEMY SETTINGS END
func processUserMotion(forUpdate currentTime: CFTimeInterval) {
// 1
if let player = childNode(withName: gamePlayerName) as? SKSpriteNode {
// 2
if let data = motionManager.accelerometerData {
// 3
if fabs(data.acceleration.x) > 0.2 {
// 4 How do you move the ship?
player.physicsBody!.applyForce(CGVector(dx: 40 * CGFloat(data.acceleration.x), dy: 0))
}
}
}
}
func update(_ currentTime: TimeInterval) {
processUserMotion(forUpdate: currentTime)
}
} }
Have you nested your methods by accident? Looking at your code it seems so.
Did you check if the motion code is actually called?
Something like this should work
class GameScene: SKScene {
let motionManager = CMMotionManager()
override func didMove(to view: SKView) {
// setup player etc
motionManager.startAccelerometerUpdates()
}
func update(_ currentTime: TimeInterval) {
processUserMotion(forUpdate: currentTime)
}
}
Also as a tip, I would use optionals (?) when using your physics body instead of force unwrapping them (!). If it becomes nil at one point
you will crash when force unwrapping.
e.g
player.physicsBody?.affectedByGravity = false
Hope this helps

Swift SpriteKit change background image score based

i am developing a game using Swift and SpriteKit. I want that the background changes at a certain score. Here is the code:
class GameScene: SKScene {
var bg = SKSpriteNode()
override func didMoveToView(view: SKView) {
makeBg()
}
func makeBg() {
let bgTexture = SKTexture(imageNamed: "img/bg.png")
let moveBg = SKAction.moveByX(-bgTexture.size().width, y: 0, duration: 9)
let replaceBg = SKAction.moveByX(bgTexture.size().width, y: 0, duration:0)
let animateBg = SKAction.repeatActionForever(SKAction.sequence([moveBg, replaceBg]))
for var i: CGFloat = 0; i<3; i++ {
let bg = SKSpriteNode(texture: bgTexture)
bg.name = "background"
bg.position = CGPoint(x: bgTexture.size().width/2 + bgTexture.size().width * i, y: CGRectGetMidY(self.frame))
bg.size.height = self.frame.height
bg.runAction(animateBg)
addChild(bg)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if score == 0 {
bg.texture = SKTexture(imageNamed: "img/bg.png")
} else if score == 3 {
bg.texture = SKTexture(imageNamed: "img/bgOri.png")
}
}
But image doesn't change...where is the mistake?
So this is how you can change a texture on all the nodes created inside your for loop:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
makeBg()
}
func makeBg() {
let bgTexture = SKTexture(imageNamed: "img/bg.png")
let moveBg = SKAction.moveByX(-bgTexture.size().width, y: 0, duration: 9)
let replaceBg = SKAction.moveByX(bgTexture.size().width, y: 0, duration:0)
let animateBg = SKAction.repeatActionForever(SKAction.sequence([moveBg, replaceBg]))
for var i: CGFloat = 0; i<3; i++ {
let bg = SKSpriteNode(texture: bgTexture)
bg.name = "background"
bg.position = CGPoint(x: bgTexture.size().width/2 + bgTexture.size().width * i, y: CGRectGetMidY(self.frame))
bg.size.height = self.frame.height
bg.runAction(animateBg)
addChild(bg)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
enumerateChildNodesWithName("background", usingBlock: { node, stop in
//Make a check based on score and
(node as? SKSpriteNode)?.texture = //set the right texture
})
}
}
Note that you don't need bg property defined in the GameScene to accomplish this.

Sprite Kit Game Scene changes when it is not supposed to

In a game that I am currently building a person is supposed to catch balls that are falling from the sky. If the ball goes off the screen it means he didn't catch the ball, and so the scene is supposed to change to a game over scene. The problem is that even if the ball doesn't go below the screen the screen will change. But the screen will change to a blank screen so instead of the GameOverScene().
Here is the code for the GameScene()...
//
// GameScene.swift
// catch balls
//
// Created by Ankith Udupa on 8/10/15.
// Copyright (c) 2015 UdupaLabs. All rights reserved.
//
import SpriteKit
var score = 0
var lossFlag = false
class GameScene: SKScene, SKPhysicsContactDelegate {
var person = SKSpriteNode(imageNamed: "guyLeft_1.png")
var left = true
let kScoreHudName = "scoreHud"
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let Ball : UInt32 = 0b1
static let Person: UInt32 = 0b10
}
override func didMoveToView(view: SKView) {
var content = false
//set up screen
setUpScreen()
//set up the physics
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
//add ball
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addBall),
SKAction.waitForDuration(1.0)
])
))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
left = !left
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if ((person.position.x > person.size.width/2) && (person.position.x < size.width-(person.size.width/2))){
if left {
var leftMove = SKAction.moveByX(5, y: 0, duration: 0.1)
person.runAction(leftMove)
}
if !left { // or use an if-else construct
var rightMove = SKAction.moveByX(-5, y: 0, duration: 0.1)
person.runAction(rightMove)
}
}
}
//random number gen functions
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
//add ball function
func addBall(){
//create ball sprite
var ball = SKSpriteNode(imageNamed: "ball.png")
//create physics for ball
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size) // 1
ball.physicsBody?.dynamic = true // 2
ball.physicsBody?.categoryBitMask = PhysicsCategory.Ball // 3
ball.physicsBody?.contactTestBitMask = PhysicsCategory.Person // 4
ball.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
//generate random postion along x axis for ball to spawn
let actualX = random(min:ball.size.width/2+1, max: size.width - ball.size.width/2-1)
//set balls positon
ball.position = CGPoint(x: actualX, y: size.height - ball.size.width/2)
//add ball to scene
addChild(ball)
//determine speed of ball
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(5.0))
//create movement actions and run them
let actionMove = SKAction.moveTo(CGPoint(x:actualX, y: -ball.size.width/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let Loss = SKAction.runBlock() {
let reveal = SKTransition.crossFadeWithDuration(0.1)
let gameOverScene = GameOverScene()
self.view?.presentScene(GameOverScene(), transition: reveal)
}
ball.runAction(SKAction.sequence([actionMove, Loss, actionMoveDone]))
}
//setUpScreen
func setUpScreen(){
self.backgroundColor = SKColor.whiteColor()
var ground = SKShapeNode(rectOfSize: CGSizeMake(self.frame.size.width, self.frame.size.height * 0.2))
ground.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height * 0.1)
ground.fillColor = SKColor.blueColor()
self.addChild(ground)
person.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height * 0.2)
setUpPersonPhysics()
self.addChild(person)
}
//set up person physics
func setUpPersonPhysics(){
person.physicsBody = SKPhysicsBody(rectangleOfSize: person.size)
person.physicsBody?.dynamic = true
person.physicsBody?.categoryBitMask = PhysicsCategory.Person
person.physicsBody?.contactTestBitMask = PhysicsCategory.Ball
person.physicsBody?.collisionBitMask = PhysicsCategory.None
person.physicsBody?.usesPreciseCollisionDetection = true
}
func didBeginContact(contact: SKPhysicsContact) {
// 1
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
// 2
if ((firstBody.categoryBitMask & PhysicsCategory.Ball != 0) &&
(secondBody.categoryBitMask & PhysicsCategory.Person != 0)) {
personDidCollideWithBall(secondBody.node as! SKSpriteNode, ball: firstBody.node as! SKSpriteNode)
}
}
//called when person collides with ball
func personDidCollideWithBall(person:SKSpriteNode, ball:SKSpriteNode) {
println("hit")
ball.removeFromParent()
score++
}
}
and here is the code for the gameOverScene()...
//
// gameOverScene.swift
// catch babies
//
// Created by Ankith Udupa on 8/12/15.
// Copyright (c) 2015 UdupaLabs. All rights reserved.
//
import Foundation
import SpriteKit
class GameOverScene: SKScene {
var message = "Game Over"
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
setUpTextOutPut()
}
func setUpTextOutPut(){
let gameOverLabel = SKLabelNode(fontNamed: "Superclarendon-Black")
gameOverLabel.text = message
gameOverLabel.fontSize = 40
gameOverLabel.fontColor = SKColor.orangeColor()
gameOverLabel.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(gameOverLabel)
let scoreLabel = SKLabelNode(fontNamed: "Superclarendon-Black")
scoreLabel.text = "\(score)"
scoreLabel.fontSize = 40
scoreLabel.fontColor = SKColor.orangeColor()
scoreLabel.position = CGPoint(x: size.width/2, y: size.height/2-50)
addChild(scoreLabel)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
}
}
The error is with your addBall method,
//add ball function
func addBall(){
//create ball sprite
var ball = SKSpriteNode(imageNamed: "ball.png")
//create physics for ball
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size) // 1
ball.physicsBody?.dynamic = true // 2
ball.physicsBody?.categoryBitMask = PhysicsCategory.Ball // 3
ball.physicsBody?.contactTestBitMask = PhysicsCategory.Person // 4
ball.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
//generate random postion along x axis for ball to spawn
let actualX = random(min:ball.size.width/2+1, max: size.width - ball.size.width/2-1)
//set balls positon
ball.position = CGPoint(x: actualX, y: size.height - ball.size.width/2)
//add ball to scene
addChild(ball)
//determine speed of ball
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(5.0))
//create movement actions and run them
let actionMove = SKAction.moveTo(CGPoint(x:actualX, y: -ball.size.width/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let Loss = SKAction.runBlock() {
let reveal = SKTransition.crossFadeWithDuration(0.1)
let gameOverScene = GameOverScene()
self.view?.presentScene(GameOverScene(), transition: reveal)
}
ball.runAction(SKAction.sequence([actionMove, Loss, actionMoveDone]))
}
If you look at the method properly, you ask to run the sequence to the sprite and inside runBlock, you move to another scene. Do you need to check if the ball is outside bounds inside this block and only then present your game over scene ?
Should it be something like this,
let Loss = SKAction.runBlock() {
if ball.position.x > self.size.width + ball.frame.size.width * 0.5 || ball.position.y < ball.frame.size.height * 0.5 {
let reveal = SKTransition.crossFadeWithDuration(0.1)
let gameOverScene = GameOverScene()
self.view?.presentScene(GameOverScene(), transition: reveal)
}
}

How do I make a simple character controller in SpriteKit?

I'm making a game in sprite kit and in order to move my character I am applying an impulse but that is a problem because that moves him every 50 pixels or so. My question is, could someone please show me how to make a simple character controller so my character can move continuously until the user lets go from holding down the screen?
My GameScene.swift file:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let character = SKSpriteNode(texture: SKTexture(imageNamed: "character"))
var move = false
override func didMoveToView(view: SKView) {
/* Setup your scene here */
//world
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
//character
character.position = CGPointMake(self.frame.size.width * 0.6, self.frame.size.height * 0.6)
character.setScale(0.2)
character.physicsBody = SKPhysicsBody(rectangleOfSize: character.size)
character.physicsBody?.dynamic = true
character.physicsBody?.allowsRotation = false
self.addChild(character)
character.physicsBody?.affectedByGravity = true
//platform 1
var platform = SKSpriteNode(texture: SKTexture(imageNamed: "platform"))
platform.position = CGPointMake(self.frame.size.width * 0.6, CGRectGetMidY(self.frame))
platform.physicsBody = SKPhysicsBody(rectangleOfSize: platform.size)
platform.physicsBody?.dynamic = false
platform.setScale(0.25)
platform.physicsBody?.friction = 1
platform.physicsBody?.restitution = 0
platform.physicsBody?.linearDamping = 0
self.addChild(platform)
//platform 2
var platformTexture2 = SKTexture(imageNamed: "platform")
var platform2 = SKSpriteNode(texture: platformTexture2)
platform2.position = CGPointMake(self.frame.size.width * 0.4, self.frame.size.height * 0.3)
platform2.physicsBody = SKPhysicsBody(rectangleOfSize: platform2.size)
platform2.physicsBody?.dynamic = false
platform2.setScale(0.25)
platform2.physicsBody?.friction = 1
platform2.physicsBody?.restitution = 0
platform2.physicsBody?.linearDamping = 0
self.addChild(platform2)
//platform main
var platformTexture3 = SKTexture(imageNamed: "platform")
var platform3 = SKSpriteNode(texture: platformTexture2)
platform3.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame) + platform3.size.height / 3)
platform3.physicsBody = SKPhysicsBody(rectangleOfSize: platform3.size)
platform3.physicsBody?.dynamic = false
platform3.setScale(1)
platform3.size.width = platform3.size.width * CGFloat(2.0)
platform3.physicsBody?.friction = 1
platform3.physicsBody?.restitution = 0
platform3.physicsBody?.linearDamping = 0
self.addChild(platform3)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame){
character.physicsBody?.applyImpulse(CGVector(dx: -50, dy: 0))
} else if location.x > CGRectGetMidX(self.frame){
character.physicsBody?.applyImpulse(CGVector(dx: 50, dy: 0))
}
}
func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
character.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
};
}
One way to accomplish this is to maintain a variable to keep track of the state of the touch events (left, right, or none). Here are the basic steps:
In touchesBegan, set touch state variable to left or right
In update, apply impulse continuously while user is touching the screen
In touchesEnded, reset the touch state variable
Here's an example of how to implement this...
Above GameScene class definition, add the following
enum TouchState {
case Left
case Right
case None
}
In GameScene, add the following variable
var touchLocation:TouchState = .None
In GameScene, add or replace the following methods
override func update(currentTime: CFTimeInterval) {
// Apply impulse to physics body while users is touching the screen
switch (touchState) {
case .Left:
character.physicsBody?.applyImpulse(CGVector(dx: -1, dy: 0))
case .Right:
character.physicsBody?.applyImpulse(CGVector(dx: 1, dy: 0))
case .None:
break
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame) {
touchState = .Left
} else if location.x > CGRectGetMidX(self.frame) {
touchState = .Right
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
// Update touch status
touchState = .None
}
Here's the GameScene.swift...
import SpriteKit
enum TouchLocation {
case Left
case Right
case None
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var character = SKSpriteNode(imageNamed: "character")
var touchLocation:TouchLocation = .None
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scaleMode = .ResizeFill
//character
character.position = CGPointMake(view.frame.size.width * 0.5, view.frame.size.height * 0.5)
character.setScale(0.1)
character.physicsBody = SKPhysicsBody(rectangleOfSize: character.size)
character.physicsBody?.dynamic = true
character.physicsBody?.allowsRotation = false
self.addChild(character)
character.physicsBody?.affectedByGravity = true
//platform 1
var platform = SKSpriteNode(texture: SKTexture(imageNamed: "platform"))
platform.position = CGPointMake(view.frame.size.width * 0.6, CGRectGetMidY(view.frame))
platform.physicsBody = SKPhysicsBody(rectangleOfSize: platform.size)
platform.physicsBody?.dynamic = false
platform.setScale(0.25)
platform.physicsBody?.friction = 1
platform.physicsBody?.restitution = 0
platform.physicsBody?.linearDamping = 0
self.addChild(platform)
//platform 2
var platformTexture2 = SKTexture(imageNamed: "platform")
var platform2 = SKSpriteNode(texture: platformTexture2)
platform2.position = CGPointMake(view.frame.size.width * 0.4, view.frame.size.height * 0.3)
platform2.physicsBody = SKPhysicsBody(rectangleOfSize: platform2.size)
platform2.physicsBody?.dynamic = false
platform2.setScale(0.25)
platform2.physicsBody?.friction = 1
platform2.physicsBody?.restitution = 0
platform2.physicsBody?.linearDamping = 0
self.addChild(platform2)
//platform main
var platformTexture3 = SKTexture(imageNamed: "platform")
var platform3 = SKSpriteNode(texture: platformTexture2)
platform3.position = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMinY(view.frame) + platform3.size.height / 3)
platform3.physicsBody = SKPhysicsBody(rectangleOfSize: platform3.size)
platform3.physicsBody?.dynamic = false
platform3.setScale(1)
platform3.size.width = platform3.size.width * CGFloat(2.0)
platform3.physicsBody?.friction = 1
platform3.physicsBody?.restitution = 0
platform3.physicsBody?.linearDamping = 0
self.addChild(platform3)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
switch (touchLocation) {
case .Left:
character.physicsBody?.applyImpulse(CGVector(dx: -1, dy: 0))
case .Right:
character.physicsBody?.applyImpulse(CGVector(dx: 1, dy: 0))
case .None:
break
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame) {
touchLocation = .Left
} else if location.x > CGRectGetMidX(self.frame) {
touchLocation = .Right
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
touchLocation = .None
}
}

Resources