SKLabelNode Moves. But action does not repeat - swift

I have created a Test Scene to practice some basic Swift 3 and SpriteKit. I'm trying to learn by understanding the basics before moving on to more complex goals.
Here I have a SKLabelNode that is created and then moves to the left. I have created a sequence to repeat the action but it does not work. Please could you help me understand where it fails. NodeCount notes that there is only 1 node.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var testShape = SKLabelNode()
override func didMove(to view: SKView) {
func createShape() {
testShape = SKLabelNode(text: "TEST")
testShape.position = CGPoint(x: 0.5, y: 0.5)
testShape.zPosition = 1
addChild(testShape)
}
let moveTestShape = SKAction.moveBy(x: -500, y: 0, duration: 5)
func repeater() {
createShape()
testShape.run(moveTestShape)
}
let delay = SKAction.wait(forDuration: 2)
let repeatingAction = SKAction( repeater() )
let sequence = SKAction.sequence([ delay, repeatingAction ] )
run(SKAction.repeatForever(sequence))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
}
}

Are you not getting compiler errors?
Why are you creating methods in didMoveToView?
Your code should look more like this
class GameScene: SKScene {
var testShape = SKLabelNode()
override func didMove(to view: SKView) {
let delay = SKAction.wait(forDuration: 2)
let repeatingAction = SKAction.run(repeater)
let sequence = SKAction.sequence([ delay, repeatingAction ] )
run(SKAction.repeatForever(sequence))
}
func createShape() {
testShape = SKLabelNode(text: "TEST")
testShape.position = CGPoint(x: 0.5, y: 0.5)
testShape.zPosition = 1
addChild(testShape)
}
func repeater() {
createShape()
let moveTestShape = SKAction.moveBy(x: -500, y: 0, duration: 5)
testShape.run(moveTestShape)
}
}
This is how you call functions/code blocks in SKActions.
let repeatingAction = SKAction.run(repeater)
or
let repeatingAction = SKAction.run {
repeater()
}
Also remember our are only repeating the spawn action for new labels. The actual action to move the labels is non repeating. So what you should see is 1 label created and moved once, than 2 seconds later a new label gets created and moved once etc
Hope this helps

Related

SpriteKit creating a button issues

Im attempting to use some of the code from a solution found on this page for creating a button Create Button in SpriteKit: Swift
class GameScene: SKScene {
let button = SKSpriteNode(imageNamed: "yourImgName")
override func didMoveToView(view: SKView) {
button.name = "btn"
button.size.height = 100
button.size.width = 100
button.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) + 50)
self.addChild(button)
//Adjust button properties (above) as needed
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name {
if name == "btn" {
let yourNextScene = YourNextScene(fileNamed: "YourNextScene")
self.view?.presentScene(yourNextScene!)
}
}
}
}
and the current code that I have is supposed to make the player jump when the button is pressed, but nothing is currently happening when its pressed
import SwiftUI
import SpriteKit
import UIKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let button = SKSpriteNode(imageNamed: "playerOBJ")
let player = SKSpriteNode(imageNamed: "playerOBJ")
let playerRadius = player.frame.width / 2.0
player.position = CGPoint(x: 200, y: 500)
player.name = "Jimmy"
addChild(player)
player.physicsBody = SKPhysicsBody(circleOfRadius: playerRadius)
player.physicsBody?.allowsRotation = false
player.physicsBody?.friction = 0
player.physicsBody?.restitution = 0
player.zPosition = 100
// Button
button.name = "btn"
button.size.height = 100
button.size.width = 100
button.position = CGPoint(x: 100, y: 100)
self.addChild(button)
// Physics
physicsBody = SKPhysicsBody(edgeLoopFrom: frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)))
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
player.physicsBody?.applyForce(CGVector(dx: 100, dy: 1000))
}
func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "btn" {
player.physicsBody?.applyForce(CGVector(dx: 0, dy: 10000))
}
}
}
}
override func update(_ currentTime: TimeInterval) { }
}
I'm thinking that maybe this is an issue with the press not being resitered at all but I'm not fully sure
Your main problem is you put all of your code inside the didMove function. You put the touchesBegan function inside the didMove function. When the didMove function finishes, touchesBegan goes away so none of your touches are going to be handled in the game.
You also declared the button and the player as local variables inside the didMove function.
override func didMove(to view: SKView) {
let button = SKSpriteNode(imageNamed: "playerOBJ")
let player = SKSpriteNode(imageNamed: "playerOBJ")
// Rest of function omitted
}
When the didMove function finishes, the button and player go away too. You also have the same image name for the player and button.
The fix is to make the button and player variables properties of the GameScene class. Move touchesBegan out of the didMove function too.
class GameScene: SKScene {
// Declare button and player here.
var button = SKSpriteNode()
var player = SKSpriteNode()
override func didMove(to view: SKView) {
// Initialize button, player, and everything else here.
}
func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Code in touchesBegan func goes here.
}
}

How to make object move farther longer you hold down?

I am trying to create a program where the longer you hold down the screen the higher an object will move. I want to do it where the longer you hold down the longer the function repeats increasing the distance. However, for some reason it is not working.
import SpriteKit
import GameplayKit
var calculateTimer = Timer()
var distance = 10
var choice = 1
class GameScene: SKScene, SKPhysicsContactDelegate {
var planet = SKNode()
override func didMove(to view: SKView) { // viewdidload
let planetTexture = SKTexture(imageNamed: "mercury.jpg")
planet = SKSpriteNode(texture: planetTexture)
planet.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addChild(planet)
}
func calculateDistance() {
distance += 10
print(distance)
print(choice)
}
func touchDown(atPoint pos : CGPoint) {
calculateTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.calculateDistance), userInfo: nil, repeats: true)
print("test")
}
func touchUp(atPoint pos : CGPoint) {
planet.physicsBody!.isDynamic = true
//planet.physicsBody?.velocity = (CGVector(dx: 0, dy: 0))
planet.physicsBody!.applyImpulse(CGVector(dx: 0, dy: distance))
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
This is my code. If anyone has any suggestions they would be much appreciated!
In touchDown, set a flag to indicate a touch has started.
In touchUp, clear/remove the flag.
Whilst the flag is set, keep performing your movement code.

SKSpriteNode not working with Random Number Array

I have a SKSpriteNode that is spawn repeatedly. I have created a random number generator and have tried to associate the returns with a different Sprite Image. However, it does not work. The numbers are being generated randomly but even if "2" is returned the SKSpriteNode is drawn as "Fire Barrel 1".
class GameScene: SKScene {
var firstObstacle = SKSpriteNode()
var player = SKSpriteNode()
var randomValue = Int()
override func didMove(to view: SKView) {
createPlayer()
let delay = SKAction.wait(forDuration: 3)
let repeatingAction = SKAction.run(repeatingSequence)
let sequence = SKAction.sequence([ delay, repeatingAction ] )
run(SKAction.repeatForever(sequence))
}
func randomSelector() {
let array = [0, 1, 2, 3]
let randomValue = Int(arc4random_uniform(UInt32(array.count)))
print(array[randomValue])
}
func createPlayer() {
player = SKSpriteNode(imageNamed: "Player")
player.setScale(0.4)
player.position = CGPoint(x: -player.size.width, y: 0)
player.zPosition = 1
addChild(player)
}
func createObstacle() {
if randomValue == 2 {
firstObstacle = SKSpriteNode(imageNamed: "Fire Barrel 2")
}
else {
firstObstacle = SKSpriteNode(imageNamed: "Fire Barrel 1")
}
firstObstacle.position = CGPoint(x: self.frame.width / 2, y: -self.frame.height / 2 + firstObstacle.size.height / 2)
firstObstacle.zPosition = 1
addChild(firstObstacle)
}
func repeatingSequence() {
randomSelector()
createObstacle()
let moveObstacle = SKAction.moveBy(x: -self.frame.width - firstObstacle.size.width, y: 0, duration: 5)
firstObstacle.run(moveObstacle)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
}
}
Your issue is
let randomValue = Int(arc4random_uniform(UInt32(array.count)))
You are doing what is called "variable shadowing". randomValue also exists at the class instance level, but here because you use let you've declared a local scoped version.
The actual value of self.value is Int() which is 0. Hence why it is "First Barrel l".
The generalized design you're using is not really good. Unless you have some real reason where you need to hold randomValue, you should not make it a class instance variable. Instead pass it by argument.
This would look something like:
func randomSelector() -> Int {
let array = [0, 1, 2, 3]
return Int(arc4random_uniform(UInt32(array.count)))
}
func createObstacle(type:Int) {
if (type == 2) {
// Type 2 stuff
} else {
// Other stuff
}
// Rest of your stuff
}
createObstacle(type: randomSelector())
Although given the nature of how this works, you don't even need the argument, you could just do:
func createObstacle() {
if (randomSelector() == 2) {
// Type 2 stuff
} else {
// Other stuff
}
// Rest of your stuff
}

Performing actions in order (not asynchronously) on different nodes

Here is an illustration of what I am trying to do:
Here is my code so far:
import SpriteKit
class GameScene: SKScene {
let mySquare1 = SKShapeNode(rectOfSize:CGSize(width: 50, height: 50))
let mySquare2 = SKShapeNode(rectOfSize:CGSize(width: 50, height: 50))
override func didMoveToView(view: SKView) {
mySquare1.position = CGPoint(x: 100, y:100)
mySquare2.position = CGPoint(x: 300, y:100)
mySquare1.fillColor = SKColor.blueColor()
mySquare2.fillColor = SKColor.blueColor()
self.addChild(mySquare1)
self.addChild(mySquare2)
let moveAction1 = SKAction.moveTo(CGPoint(x:250, y:100), duration: 1)
mySquare1.runAction(moveAction1)
let moveAction2 = SKAction.moveTo(CGPoint(x:300, y:350), duration: 1)
mySquare2.runAction(moveAction2)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func update(currentTime: CFTimeInterval) {
}
}
My problem is, I am trying to move the rectangles synchronously (not asynchronously). That is, I want my first rectangle start moving, finish its movement, stop. And then, start my second rectangle moving, finish its movement and stop.
Currently what happens is that, when I run my program, they both start moving at the same time.
I also found SKAction.sequence for actions to play in order, however, I only can use this for actions on the same object. Not in different objects like in my example.
If you want to move the two rectangles sequentially (not in parallel), you could use the completion property of the first action like this (apple docs):
import SpriteKit
class GameScene: SKScene {
let mySquare1 = SKShapeNode(rectOfSize:CGSize(width: 50, height: 50))
let mySquare2 = SKShapeNode(rectOfSize:CGSize(width: 50, height: 50))
override func didMoveToView(view: SKView) {
mySquare1.position = CGPoint(x: 100, y:100)
mySquare2.position = CGPoint(x: 300, y:100)
mySquare1.fillColor = SKColor.blueColor()
mySquare2.fillColor = SKColor.blueColor()
self.addChild(mySquare1)
self.addChild(mySquare2)
let move1 = SKAction.moveToX(250, duration: 1.0)
let move2 = SKAction.moveToY(250, duration: 1.0)
self.mySquare1.runAction(move1,completion: {
self.mySquare2.runAction(move2)
})
}
}
You could use the SKAction for running blocks. Then sequence them from the scene.
For example:
func example() {
let firstRectMove = SKAction.runBlock({
let move1 = SKAction.moveToX(20, duration: 1.0)
let move2 = SKAction.moveToY(20, duration: 1.0)
rec1.runAction(SKAction.sequence([move1,move2]))
})
let actionWait = SKAction.waitForDuration(2.0)
let secondRectMove = SKAction.runBlock({
let move1 = SKAction.moveToX(20, duration: 1.0)
let move2 = SKAction.moveToY(20, duration: 1.0)
rec2.runAction(SKAction.sequence([move1,move2]))
})
//self is an SKScene or any other node really...
self.runAction(SKAction.sequence([firstRectMove,actionWait,secondRectMove]))
}
As Alessandro Ornano suggests, a way to accomplish this is run your first action with a completion block, where a block of code runs after the completion of the original action. A downside to this approach is that it can create a pyramid of doom when you need to chain together more than two actions at a time.
An alternative way to avoid to the "pyramid" is to define a method that calls itself recursively for each subsequent action in an array:
func runInSequence(actions:[(node:SKNode,action:SKAction)], index:Int) {
if index < actions.count {
let node = actions[index].node
let action = actions[index].action
node.runAction(action) {
// Avoid retain cycle
[weak self] in
self?.runInSequence(actions, index: index+1)
}
}
}
To use this, define/create an array that stores the nodes and actions you want to run in sequence, and call the method with the starting index:
let nodesAndActions:[(node:SKNode,action:SKAction)] = [
(mySquare1,moveAction1),
(mySquare2,moveAction2)
]
runInSequence(nodesAndActions, index: 0)

Swift SpriteKit: Use of unresolved identifier "player"?

When I try to run an action on a sprite I've set a constant for in didMoveToView, I use the same name for it in the touchesBegan function and get a "use of unresolved identifier: "player" error. I have another game where I so the exact same thing and it runs perfectly. Need help! Here is my code:
import SpriteKit
class GameScene: SKScene {
var movingGround : MCTGround!
var fruitGenerator : MCTFruitGen!
var cloudGenerator: MCTCloudGen!
var isStarted = false
override func didMoveToView(view: SKView) {
let player = SKSpriteNode(imageNamed: "koala_idle")
player.position = CGPointMake(95, 150)
addChild(player)
backgroundColor = UIColor(red: 159.0/255.0, green: 201.0/255.0, blue: 244.0/255.0, alpha: 1.0)
movingGround = MCTGround(size: CGSizeMake(view.frame.width, 20))
movingGround.position = CGPointMake(0, view.frame.size.height / 4)
addChild(movingGround)
fruitGenerator = MCTFruitGen(color: UIColor.clearColor(), size: view.frame.size)
fruitGenerator.position = view.center
addChild(fruitGenerator)
let frames = [
SKTexture(imageNamed: "koala_idle"),
SKTexture(imageNamed: "koala_walk01"),
SKTexture(imageNamed: "koala_walk02"),
]
let duration = 1.5 + drand48() * 1.0
let move = SKAction.animateWithTextures(frames, timePerFrame:0.10)
let wait = SKAction.waitForDuration(duration)
let rest = SKAction.setTexture(frames[0])
let sequence = SKAction.sequence([move, rest])
player.runAction(SKAction.repeatActionForever(sequence))
cloudGenerator = MCTCloudGen(color: UIColor.clearColor(), size: view.frame.size)
cloudGenerator.position = view.center
addChild(cloudGenerator)
cloudGenerator.populate(7)
cloudGenerator.startGeneratingWithSpawnTime(1)
}
func start() {
isStarted = true
cloudGenerator.startGeneratingWithSpawnTime(1)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
jumpPlayer()
movingGround.start()
fruitGenerator.startGeneratingFruitEvery(1)
}
override func update(currentTime: CFTimeInterval) {
}
func jumpPlayer() {
let jumpUpAction = SKAction.moveByX(0, y: 40, duration: 0.5)
let jumpDownAction = SKAction.moveByX(0, y: -40, duration: 0.5)
let jumpSequence = SKAction.sequence([jumpUpAction, jumpDownAction])
player.runAction(jumpSequence) // This is where my error is
}
}
Make player a class scope variable (property). Declare it not in a function, but a level above.
var player: SKSpriteNode?
override func didMoveToView(view: SKView) {
player = SKSpriteNode ...
then access it via player?. The safest way.
Your problem is that you are declaring player inside didMoveToView. This makes it private to the didMoveToView method. All you need to do is move let player = SKSpriteNode(imageNamed: "koala_idle") to where you declare movingGround and the other variables with it.
The beginning your code should look like this:
import SpriteKit
class GameScene: SKScene {
var movingGround : MCTGround!
var fruitGenerator : MCTFruitGen!
var cloudGenerator: MCTCloudGen!
let player = SKSpriteNode(imageNamed: "koala_idle")
var isStarted = false
override func didMoveToView(view: SKView) {
player.position = CGPointMake(95, 150)
addChild(player)
Go to File Inspector (right click on the file referenced in the error and select Show File Inspector). In Target Membership make sure your actual app is selected.
In my case only the Test modules were selected.
So as far as I can tell (I'm new to all this), the referenced file was hidden/unregistered with the ViewController.