1

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)

        }

    }

}
f_qi
  • 689
  • 8
  • 21
  • the middle of the screen is (512.0, 384.0), the sprite should move from the top of the screen into the screen. but instead, the sprite always comes from outside of the lower left of the screen. the position in the console always prints (0, 0) – f_qi Apr 06 '15 at 03:27
  • Thanks, I'll edit the code as well, so people can read it easily. – f_qi Apr 06 '15 at 04:07
  • I found the problem and included an example of a working version of your code. You may find that you want to replace nodes or add multiple nodes, but that would be a minor tweak. – clearlight Apr 06 '15 at 18:38

1 Answers1

0

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)")
    }
}
clearlight
  • 12,255
  • 11
  • 57
  • 75
  • 1
    Thanks a lot. This really helps me a lot, as I'm new to programing. I really appreciate the effort you put in and helping me out! I'm sure I'll learn a lot by going over the example you provided. Again, thank you very much! – f_qi Apr 07 '15 at 01:47
  • It was basically very good code and intelligently done. A respectable effort for a new programmer. I've been programming for 35 years, most of it professionally working for Silicon Valley companies and many substantial projects. If you can understand the changes I made and why I made them, and sense how it helped me find your bug and will help you write more maintainable code that's easier to troubleshoot, you will be on your way to being a very solid and competent programmer. Thanks for the check mark and letting me know! Good luck! – clearlight Apr 07 '15 at 01:53
  • @f_qi BTW: If I had to make further improvements, I would try (as a rule) to avoid setting global variables within functions, and instead pass parameters to function and return the results via the return value. You can run into problems when different functions start modifying shared data from places and situations that can be hard to see and track. And it *really* becomes a mess when you start dealing with multithreaded code. So study up on function parameters and return values. – clearlight Apr 07 '15 at 01:57
  • Globals used in Swift/iOS are convenient but a controversial part of the language. You can do a lot of things more easily but you can also write crappy code if you're not at least aware of safe programming practices and try to minimize global use. And when you do things like that, try to write your code in as self-documenting a way as possible, and so you'll be likely to quickly comprehend what you were doing if you come back to look at it months or years later. If you haven't discovered this already, soon you'll see how the most 'straightforward' code can be difficult to figure out later. – clearlight Apr 07 '15 at 02:10
  • Name your functions and variables *very* consciously and descriptively, and you will really save yourself a lot of trouble because then your code reads more like a book. It practically tells you what it is doing. If you have to work to decipher everything later it is such a hassle and waste of time. And you will forget what you were thinking much faster than you assume you will :-) Discipline, good habits and clean code can help you avoid some real debugging nightmares – clearlight Apr 07 '15 at 02:13
  • One more question about the example, every time when I touch the screen, it only changes the color of the ball, the ball.position didn't reset. What I'm trying to achieve is every time touch the screen, there will be a new ball on the screen, the previous one will keep moving to the center. I tried to add a new function to reset the color and position of the ball, it doesn't work. Any ideas? Thanks. – f_qi Apr 07 '15 at 03:11
  • Same code as you posted. the ball.position only reset when the ball is at the center of the screen. – f_qi Apr 07 '15 at 03:15
  • Because the SKAction.moveTo() in ballMove() is running on another thread for 5 seconds, and it is updating the ball position property, so you reset it, but then it comes along very quickly and updates it until it completes its animation. Have to figure out how to interrupt or cancel that thread. Worst case you'd have to break it up into multiple threads of shorter intervals. – clearlight Apr 07 '15 at 03:19
  • Look at this question: http://stackoverflow.com/questions/19041610/is-it-possible-to-end-an-skaction-mid-action – clearlight Apr 07 '15 at 03:21
  • You won't find the cancel on SKAction because the action was run on the SKNode using an SKNode method, therefore the action has to be removed using an SKNode function like [removeAllActions()](http://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKNode_Ref/#//apple_ref/occ/instm/SKNode/removeAllActions). – clearlight Apr 07 '15 at 03:26
  • I was able to use ball.removeAllAction() to reset ball.position. however this is part of what I want to achieve. What I'm trying to achieve is every time touch the screen, there will be a new ball on the screen, the previous one will keep moving to the center. So instead of using removeAllAction(), I think I might need to add new notes for ball. – f_qi Apr 07 '15 at 03:39
  • Absolutely. You need a node for each ball on the screen which are each animated by their own action. You have to add them as subviews to the parent as well. And you'll need to use an array, dictionary or some mechanism for tracking them and identifying them. – clearlight Apr 07 '15 at 03:56
  • @f_qi If you don't want to create your own internal structures to track your subviews (e.g. nodes) (often it makes sense too but not always), you can also walk through the list UIView subviews (it's an array) and use the related methods. You can set the "tag" property (int) with a unique number of your choosing on views to identify them optionally. You can look at the class hierarchy of SpriteKit classes and see which ones inherit from UIView and allow you to do that. – clearlight Apr 07 '15 at 14:51