Search code examples
iosswiftsprite-kitskphysicsbody

Simulating sushi train - Swift SpriteKit


I'm learning to create a game using SprikeKit and the game has something to do with sushi train.

I have something like 20 dishes that follows along a rectangular path in order to simulate the look that all dishes are on a moving conveyor belt.

I've tried with a scene editor to place a couple sprite nodes and made them move on a square path like so.

let dish_1 = self.childNode(withName: "dish_1");
let dish_2 = self.childNode(withName: "dish_2");

let square = UIBezierPath(rect: CGRect(x: 0, y: 0,  width: 200, height: 200));
let followSquare = SKAction.follow(square.cgPath, asOffset: true, orientToPath: false, duration: 5.0);

dish_1?.run(SKAction.sequence([followSquare]))
dish_2?.run(SKAction.sequence([followSquare]))

At the moment, the two dishes are using the same square and the square position is relative to each dish so they look like they are on two different rectangular path as their starting point is different.

Is this the reasonable way to simulate the look I'm after? The reason I'm asking this is that to make all dishes look like they are moving on a same path, I will need to tweak x, y positions of each dish and it will be like 20 dishes.

I was wondering if it would be possible to use physics kit and let one rectangle node makes the all dishes move along the path as long as they are within the rectangle node area.


Solution

  • Well I would have probably done it in a similar way, and I think using SKAction.follow is a solution here.

    But there are a few improvements that you could do in your code. The first is that you don't need to write your SKAction as a sequence because it is composed of only one action, so you could simplify your code to this:

        let squarePath = UIBezierPath(rect: CGRect(x: 0, y: 0,  width: 200, height: 200))
    
        let followAction = SKAction.follow(path: squarePath.cgPath, duration: 5.0)
    
        dish?.run(followAction)
    

    Now an other thing that is important is, in order to save your memory usage, instead of recreating a new dish every time (20 times), you could just use copy() on your SKSpriteNode to create many different copies of it. Depending on what you need to achieve, you can of course custom each copy, changing its color, or position, etc. This would be way more efficient.

    You can create a copy of your SKSpriteNode like so:

    import GameplayKit
    
     if let dishCopy = dish?.copy() as? SKSpriteNode {
          dishCopy.position = CGPoint(x: GKRandomDistribution(lowestValue: 0, highestValue: 200).nextInt(), y: GKRandomDistribution(lowestValue: 0, highestValue: 500).nextInt())
          self.addChild(dishCopy)
     }
    

    For the position of each dish copy, you can of course adjust to your own values, but here I used GameplayKit and its very useful random values generator.

    Update:

    If you want to set a unique name to each of your dishes while still using copy(), you have several options.

    One possibility would be to generate a unique name using a counter that you would increment, another possibility would be using currentTime in the update method for example.

    But another more elegant way of doing this, and to keep tracks of where are all your copies, is to create an array that will store them all.

    At the top of your scene subclass, declare your dishes array like so:

     var dishes = [SKSpriteNode]()
    

    At the moment, it's still empty, so you need to add each copy to the array at the time you create each of them:

        // Create (safely) a dish copy:
    
        if let dishCopy = dish?.copy() as? SKSpriteNode {
    
            // Add the dish copy to the dishes array:
    
            dishes.append(dishCopy)
    
            // And add the dish copy to the world node:
    
            worldNode?.addChild(dishCopy)
        }
    

    If you want to create many copies like this, you could use a for loop, or you could play with the update(_ currentTime:) method by specifying that you want to create a new sushi dish every 5 seconds for example :).

    And well, if at any time you need to access any of these copies, just do so by accessing the dishes array.

    For example, this is how you would remove all the copies (you would have to deal with the original dish if you added it to the scene though) :

    // And just iterate through all of your dish copies:
    
    for dish in dishes {
    
       dish.removeFromParent()
    
       // Do whatever you need here :)
    
    }
    

    It's pretty straightforward and it allows you to control perfectly all the objects and/or copies that you added to your worldNode.

    Just let me know if you have any other questions I'd be glad to help !

    By the way tonight I will actually go to the sushi restaurant you're talking about in your question, the one with the little train.. Nice coincidence ! :D