How can I loop my Transition? - transition

Okay I am fairly new to programming and am trying to create a simple game. In the background I'm having an object move from one side of the screen to another and then off the screen using SKAction and SKTransition. All I need to do is loop this transition so when the object goes back off the screen it starts again and comes back on. I'm using SpriteKit.
Here is my code.
//Walls
Walls = SKSpriteNode(imageNamed: "Walls")
Walls.position = CGPoint(x: 1080 + Walls.frame.width / 2, y: self.frame.height / 2)
Walls.zPosition = 1
Walls.runAction(SKAction.moveTo(CGPoint(x: -300 + Walls.frame.width / 2, y: self.frame.height / 2),duration: 6.0))
self.addChild(Walls)
Where can I add in the reapeatActionForever code command or something similar?
Thanks for your help in advance. Sam. :)

From what I gather, you're looking for how to use repeatActionForever... Please clarify the question so people don't have to guess
runAction(SKAction.repeatActionForever(/*SKAction or SKSequence etc...*/))
I'd put this code in didMoveToView if you want it to start immediately after the scene loads. Otherwise, put it in a block or function where you want it to start. To stop it, change it to:
runAction(SKAction.repeatActionForever(/*SKAction or SKSequence etc...*/), withKey: "actionKeyName")
Then to stop it:
removeAction(forKey: "actionKeyName")

Related

Adding SKShapeNodes - using while loop blanks UI

I have a game which is running well but as soon as I introduce a while loop my entire UI goes blank.
I have some code which generates a sprite from an array and moves it down the screen
func addALetter() {
let randomX = CGFloat.random(in: 50...size.width - 50)
let shrink = SKAction.scale(to: 0.1, duration: 0.01)
let grow = SKAction.scale(to: 1, duration: 0.5)
let wait = SKAction.wait(forDuration: 0.7)
let spawn = SKAction.move(to: CGPoint(x: randomX, y: size.height - tileSize), duration: 0.001)
let move = SKAction.moveTo(y: -500, duration: 7)
let sequence = SKAction.sequence([shrink, spawn, grow, move, wait])
// scroll through the lettersArray
if activeLetter < lettersArray.count - 1 {
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
activeLetter += 1
} else {
// go back to first in letter array
activeLetter = 0
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
}
}
It is working fine triggered using an SKAction in my didMove to view run(SKAction.repeatForever(SKAction.sequence([SKAction.run(addALetter), SKAction.wait(forDuration: spawnTime)])))
but I have problems with that as I get to the end of the array because the action repeats too frequently making sprites disappear before I want them too.
So I tried using a while loop instead...
while gameOn == true {
addALetter()
}
to repeat the action. But then I get a completely blank UI - I assume because it's then stuck in the loop there's no time to update the UI?
Looking for a solid way to repeat the function that I can vary the frequency as the array gets to low numbers
It seems likely that the problem is that spawnTime is small enough that you're wrapping around through the letters too quickly and start doing bubbles[activeLetter].removeAllActions() before the bubble's previous action sequence has finished.
I think the most best way to deal with this sort of situation is to coordinate through completion blocks. I.e., use the run method with a closure to be called after the action finishes, https://developer.apple.com/documentation/spritekit/sknode/1483103-run. That way you don't wind up trying to adjust delays explicitly in an effort to keep actions from being prematurely cancelled. The completion blocks will update state that you can use to coordinate, or they can run other actions directly.
It's not clear from your question what behavior you want. But, e.g., you might have addALetter set a flag for the bubble when it starts the action sequence and include a completion block for the sequence to clear the flag. Then before addALetter restarts the sequence for a bubble it can make sure that the flag is clear; if it's not (the bubble is still running the previous sequence), just return without doing anything.

Is there a way to add a fading trail or stroke to a moving SKSpriteNode?

Still new, but slowly building my first app/game, and am slowly getting there.
I'd like to be able to add a fading streak or trail as one of my SKSpriteNodes moves. Whether it is moving due to touchesMoved or being sent back to its original spot by code. I just want to add a nice visual effect.
The only thing I can think of is calculating the distance, breaking it down into x movements, then gradually move the Sprite a little, with some fade options, and then repeat in a loop till it gets back to home base, using a lot of nested SKActions and sequences.
I know that has to be wrong because it's just so ugly.
When I look at the Sprite Kit's Particle File, it has so few options. And I'm not really sure that's what I should be looking at. I have also looked at SKAction's options, and if there's an option there I'm missing it.
Surely in Swift's huge animation library there has to be something?
Let's create a basic sprite and an emitter, and make the emitter a child of the sprite so that it follows it:
let sprite = SKSpriteNode(color: .white, size: CGSize(width: 20, height: 10))
let emitter = SKEmitterNode() // better to use the visual designer in Xcode...
emitter.particleLifetime = 1.0
emitter.particleBirthRate = 50.0
emitter.particleSpeed = 100.0
emitter.emissionAngleRange = .pi / 5
emitter.particleTexture = SKTexture(imageNamed: "spark")
emitter.particleScale = 0.1
emitter.particleAlphaSpeed = -1.0
emitter.emissionAngle = -.pi
sprite.addChild(emitter) // attach it to the sprite
emitter.position.x = -15 // but not to the center
scene.addChild(sprite)
sprite.run(SKAction.group([ // let's run some actions to test it:
SKAction.sequence([
SKAction.move(to: CGPoint(x: 200, y: 200), duration: 5),
SKAction.move(to: CGPoint(x: 50, y: 50), duration: 5),
]),
SKAction.rotate(byAngle: .pi * 2.0, duration: 10)
]))
(Click to open animated GIF if it doesn't display correctly:)
To the casual observer, it looks fine… Except that, after some scrutiny, you'll realize what's off: the particles emitted live in the universe of the parent sprite, moving and rotating with it, even long after they were emitted! That's not right!
That's because the targetNode of the emitter is its parent, and it should be the scene!
So let's insert this line somewhere:
emitter.targetNode = scene // otherwise the particles would keep moving with the sprite
(Click to open animated GIF if it doesn't display correctly:)
Okay, this is a no-go: the particles now stay in the "universe" of the scene, but apparently their emission angle fails to follow that of the parent (which looks like a bug to me).
Luckily, we can attach a custom action to the emitter which keeps aligning this angle to the parent sprite:
emitter.run(SKAction.repeatForever(
SKAction.customAction(withDuration: 1) {
($0 as! SKEmitterNode).emissionAngle = sprite.zRotation + .pi
_ = $1
}))
(Click to open animated GIF if it doesn't display correctly:)
Okay, now new particles are launched in the correct direction, and keep moving that way even if the sprite moves or rotates in the meantime. This seems to be the most realistic simulation so far, though there may still be ways to improve it by modifying the behavior of the emitter on the fly.
Sorry for the jaggy GIFs, apparently my Mac is too slow to render and capture at the same time. The animations themselves run just fine.

Check if the player has hit a margin

I'm making a little game using swift, a Snake Game. I've already tried to do this game using python and it worked! To run the game I used a while loop. In this way, I could always change the player position and check if he had hit the margins. Now I have to do this again, but I don't know how to tell to my program to check all the time if the snake has hit the margins or himself.
I should do something like that
if player.position.x <= 0 || player.position.x >= self.size.width || player.position.y <= 0 || player.position >= self.size.height{
death()
}
So, if the player position on x or y axis is major than the screen size or minor than 0, call the function "death()".
If I had to do this with python, I'd have just put all this code inside the "main while loop". But with swift there's not a big while I can use... any suggestions?
Use SpriteKit. You can select this when creating a new project, choose Game, and then select SpriteKit. It's Apple's framework to make games. It has what you will need, and is also faster - don't make games in UIKit.
Within SpriteKit, you have the update() method, as part of SKScene. Do NOT use a while loop. These are highly unreliable, and the speed of the snake will vary between devices.
Here is a small piece of code to help you get started on the SKScene:
import SpriteKit
class Scene: SKScene {
let timeDifference: TimeInterval = 0.5 // The snake will move 2 times per second (1 / timeDifference)
var lastTime: TimeInterval = 0
let snakeNode = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
override func update(_ currentTime: TimeInterval) {
if currentTime > lastTime + timeDifference {
lastTime = currentTime
// Move snake node (use an SKSpriteNode or SKShapeNode)
}
}
}
After doing all this, you can finally check if the snake is over some margins or whatever within this update() loop. Try to reduce the code within the update() loop as much as possible, as it gets run usually at 60 times per second.
If I understood you correctly: You are looking for a loop in Swift that does all the calculation and drawing for your game. Here you have some tutorials about loops in Swift. In fact there are while loops that you could use in order to answer your question. I hope I could help.

How to make a delay in a loop in SpriteKit?

I have made some search here and there but I didn't find (or understand) how to make a delay in a loop in SpriteKit.
This is the idea : I have some SKSpriteNodes sorted in an array and I want to display them on the screen, one every second. The problem is... I simply don't manage to do it (sorry, I'm quite new to this).
let sprite1 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite1.position = CGPoint(x: 100, y: 100)
let sprite2 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite2.position = CGPoint(x: 100, y: 300)
let sprite3 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite3.position = CGPoint(x: 300, y: 100)
let sprite4 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite4.position = CGPoint(x: 300, y: 300)
let allSprites : [SKSpriteNode] = [sprite1, sprite2, sprite3, sprite4]
let delay = 1.0
var i = 0
while i <= 3
{
let oneSprite = allSprites[i]
self.addChild(oneSprite)
DispatchQueue.main.asyncAfter(deadline : .now() + delay) {
i = i + 1
}
}
If you're asking : this doesn't work at all. It seems that what is inside the DispatchQueue.main.asyncAfter isn't read.
So, if you can help me to understand why, that would be great. I'm not picky, I can't take the answer with a "for" loop.
Regards,
This is a common beginner misunderstanding of event-driven / run-loop-based / GUI programming systems. Some key points to help get on the right track:
SpriteKit is trying to render the scene 60 (or so) times per second.
That means SpriteKit internally has a loop where, once per frame, it calls your code asking what's new (update), then runs its own code to draw the changes (render).
Setup code, or things that happen in response to events (clicks/taps/buttons) are external to this loop, but feed into it: events change state, which the render loop reacts to.
So, if you have a loop in an update method, an event handler, or initial setup code, everything that happens in that loop will happen before SpriteKit gets a chance to draw any of it. That is, if we ignore for a moment the "delay" part of your question, a loop like this...
var i = 0
while i <= 3 {
let oneSprite = allSprites[i]
self.addChild(oneSprite)
}
... will result in none of the sprites being visible right before the loop starts, and all of the sprites being visible after it completes. No amount of introducing delays within the loop will change that, because SpriteKit doesn't get its first chance to draw until after your loop is finished.
Fixing the problem
The best way to do animations and delays in a system like SpriteKit is to make use of the tools it gives you for treating animation declaratively. That is, you tell SpriteKit what you want to have happen over the next several (hundred) frames, and SpriteKit makes that happen — each time it goes through that update/render loop, SpriteKit determines what changes need to be made in the scene to accomplish your animation. For example, if you're running at 60 fps, and you ask for a fade-out animation on some sprite lasting one second, then each frame it'll reduce that sprite's opacity by 1/60.
SpriteKit's tool for declarative animations is SKAction. There are actions for most of the things you'd want to animate, and actions for composing other actions into groups and sequences. In your case, you probably want some combination of the wait and unhide and run(_:onChildWithName) actions:
Give each of your sprites a unique name:
let sprite1 = // ...
sprite1.name = "sprite1"
// etc
Add all the nodes to your scene, but keep the ones you don't want visible yet hidden:
let allSprites : [SKSpriteNode] = [sprite1, sprite2, sprite3, sprite4]
for sprite in allSprites {
sprite.isHidden = true
self.addChild(sprite)
}
Create a sequence action that alternates delays with telling each node to unhide, and run that action on your scene:
let action = SKAction.sequence([
run(.unhide(), onChildNodeWithName: "sprite1"),
wait(forDuration: 1.0),
run(.unhide(), onChildNodeWithName: "sprite2"),
wait(forDuration: 1.0),
// etc
])
self.run(action)
That should accomplish what you're looking for. (Disclaimer: code written in web browser, may require tweaking.) Read on if you want to better understand the situation...
Other alternatives
If you really want to get your head around how the update/render loop works, you could participate directly in that loop. I wouldn't recommend it in general, because it's a lot more code and bookkeeping, but it's useful to know.
Keep a queue (array) of nodes that you want to add or unhide.
In you scene's update method, keep track of how much time has passed.
If it's been at least 1.0 second (or whatever delay you're after) since you last showed a node, add the first one in the array, and remove it from the array.
When the array is empty, you're done.
About DispatchQueue.asyncAfter...
This isn't essential to completing your task, but helpful to understand so you don't get into similar problem later: the word "async" in the call you were using to try delaying things is short for "asynchronous". Asynchronous means that the code you're writing doesn't execute in the order you write it in.
A simplified explanation: Remember that modern CPUs have multiple cores? When CPU 0 is executing your while loop, it gets to the asyncAfter call and passes a note to CPU 1 saying to wait one second and then execute the closure body attached to that asyncAfter call. As soon as it passes that note, CPU 0 continues merrily on its way — it doesn't wait for CPU 1 to receive the node and complete the work that note specifies.
Just from reading the code, I'm not 100% clear on how this approach fails, but fail it certainly does. Either you have an infinite loop, because the while condition never changes, because the work to update i is happening elsewhere and not propagating back to local scope. Or the the changes to i do propagate back, but only after the loop spins an indeterminate number of times waiting for the four asyncAfter calls to finish waiting and execute.

SKSpriteNodes spawning in the wrong place - is the game scene itself moving?

[Long post ahead.]
I'm really confused as to why this is happening.
I have a game in which sprites (SKSpriteNodes) spawn at regular-ish intervals off the right side of the screen and move to the left.
The spawning sprites are children of a 'world' SKSpriteNode, which is a child of the game scene. The world is declared here (in didMove(to: view)):
world = self.childNode(withName: "world") as! SKSpriteNode
The spawns are added to the 'world' like this:
nowSpawn.position = CGPoint(x: 350, y: 0)
world.addChild(nowSpawn)
nowSpawn.run(SKAction.moveBy(x: -1000, y: 0, duration: 10))
This spawn-adding is inside the function addSpawn() which is called in another function called spawnDelays() that calculates the slight variations in intervals at which to add the spawns. spawnDelays() is called in didMove(to : view):
func spawnDelays () {
let randDuration = Double(arc4random_uniform(UInt32(2))) + (universalDuration/8)
let rerunSpawnDelays = SKAction.run{spawnDelays()}
run(SKAction.sequence([
SKAction.run(addSpawn),
SKAction.wait(forDuration: Double(randDuration)),
rerunSpawnDelays,
]), withKey: "spawning"
)
}
spawnDelays()
However, something weird is happening. For about the first 10-15 spawns it works as expected, with the spawns emerging from the right of the screen and moving to the left. However, soon after, the spawns start appearing further to the left (i.e. on screen), and keep appearing further to the left until they are completely off the left side of the screen.
To try and see what's going on, I've been printing the .position of each spawn and also of the world sprite every time a new spawn appears. However, even if the spawn is spawning in the wrong place (e.g. in the middle of the screen, whose position would be near (0,0)), the console still prints its position as being (350, 0) - i.e. the position where I add the spawns.
To check that the world sprite isn't moving, I've also been printing its position every time a spawn is spawned - and it is consistently (0.0000.., 0.0....).
My only possible explanation is that the world's parent - which is the game scene - is inching leftwards, and therefore the relative position of all the children sprites is shifted to the left. But that makes no sense.