Swift Game Scene alter vertically moving background with time? - ios

I have a moving background which is 1500 x 600 pixels and constantly moves vertically down the screen using this code:
let bgTexture = SKTexture(imageNamed: "bg.png")
let moveBGanimation = SKAction.move(by: CGVector(dx: 0, dy: -bgTexture.size().height), duration: 4)
let shiftBGAnimation = SKAction.move(by: CGVector(dx: 0, dy: bgTexture.size().height), duration: 0)
let moveBGForever = SKAction.repeatForever(SKAction.sequence([moveBGanimation, shiftBGAnimation]))
var i: CGFloat = 0
while i < 3 {
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: self.frame.midX, y: bgTexture.size().height * i)
bg.size.width = self.frame.width
bg.zPosition = -2
bg.run(moveBGForever)
self.addChild(bg)
i += 1
}
I now want a new background to come onto the screen after x amount of time to give the feel the player is moving into a different part of the game.
Could I put this code into a function and trigger it with NSTimer after say 20 seconds but change the start position of the new bg to be off screen?

The trouble with repeatForever actions is you don't know where they are at a certain moment. NSTimers are not as precise as you'd like, so using a timer may miss the right time or jump in too early depending on rendering speeds and frame rate.
I would suggest replacing your moveBGForever with a bgAnimation as a sequence of your move & shift actions. Then, when you run bgAnimation action, you run it with a completion block of { self.cycleComplete = true }. cycleComplete would be a boolean variable that indicates whether the action sequence is done or not. In your scene update method you can check if this variable is true and if it is, you can run the sequence action once again. Don't forget to reset the cycleComplete var to false.
Perhaps it sounds more complex but gives you control of whether you want to run one more cycle or not. If not, then you can change the texture and run the cycle again.
Alternatively you may leave it as it is and only change the texture(s) after making sure the sprite is outside the visible area, e.g. its Y position is > view size height.
In SpriteKit you can use wait actions with completion blocks. This is more straightforward than using a NSTimer.
So, to answer your question - when using actions for moving the sprites on-screen, you should not change the sprite's position at any time - this is what the actions do. You only need to make sure that you update the texture when the position is off-screen. When the time comes, obviously some of the sprites will be displayed, so you can't change the texture of all 3 at the same time. For that you may need a helper variable to check in your update cycle (as I suggested above) and replace the textures when the time is right (sprite Y pos is off-screen).

Related

chasing enemy in an endless runner

I've been trying to code this for days now, without success.
My game is a 2D endless runner. My hero is locked onto a specific point on the X-axis. The obstacles are randomly spawned (height and distance) buildings.
I'd like to add a chasing enemy to the game. Currently for visuals only.
chasingCopNode = CopSprite.newInstance(position: CGPoint(x: -90, y: 0))
let follow = SKAction.moveTo(x: thiefNode.position.x - 50, duration: 5)
let wait = SKAction.wait(forDuration: 2)
let fallBehind = SKAction.moveTo(x: -90, duration: 4)
let chasingSequence = SKAction.sequence([follow, wait, fallBehind])
chasingCopNode.run(chasingSequence)
chasing = true
self.addChild(chasingCopNode)
I created this to move tha chasing sprite along the X-axis.
In my update function, I simply update the Y position of the enemy with the hero's Y position.
That is working, but not quite the thing I need. Since the Y positions are the same, the enemy sprite is flying sometimes (because there is no building under it).
I couldn't come up with any working ideas for the movement on the Y-axis.
I was thinking maybe add a function that calculates the needed Y impulse for the enemy to land on the next building, but I was not able to do that correctly.
thank you for your help!

Animation to Remove Sprite

I am currently making a Sprite and I want it to animate before it disappears.
For example: I want it to animate it in the sense that it disappears from the top of the block until the bottom. To put it another way, I want to the size to decrease slowly until there is nothing left. But I want to give it the appearance that it is disappearing rather than scaling to nothing.
let hand = SKSpriteNode(imageNamed: "hand")
hand.size = CGSize(width: size.width/10, height: size.height/30)
hand.position = CGPoint(x: CGFloat(posX-2)*size.width/10+offsetX, y:CGFloat(posY)*size.height/30+offsetY)
addChild(hand)
tl;dr is it possible to make this sort of effect using SpriteKit in Swift.
Ideal Animation: https://ibb.co/sPsffmK
SKAction is an animation that is executed by a node in the scene. Actions are used to change a node in some way (like move its position over time), but you can also use actions to change the scene, like doing a fadeout.
In your case, you can apply multiple animations:
let move_action = SKAction.moveBy(x: -100, y: 0, duration: 1.0)
let scale_action = SKAction.scale(to: 0.0, duration: 1.0)
let fade_action = SKAction.fadeAlpha(to: 0.0, duration: 1.0)
hand.run(move_action)
hand.run(scale_action)
//hand.run(fade_action)
In the previous example, the hand runs the move and scale animation at the same time. But you can also make the hand to move to the position, and after it reaches, to scale it.
let sequence = SKAction.sequence([move_animation,scale_animation])
hand.run(sequence)
There are lots of animations that SKAction has, here you can find the complete list.

SKAction Moveto not completing within the duration?

I am trying to make something like a clock with sweeping hands.
I pull the minutes from NSDate.
Then somewhat calculate a radian value and find an XY position where my sprite should move to in 1 min,
Then at the beginning of the next min I calculate the next XY position where my sprite should move to in that min.
so i have these codes inside the update function
//Get Current Minute
var currentTime = NSDate();
var nowCal = NSCalendar(calendarIdentifier: NSCalendar.Identifier.ISO8601)
var currMin = nowCal?.component(.minute, from: currentTime as Date)
//Print current minute
print(currMin)
// slicing the circle into 60 segments and getting which angle in radians the sprite should move to in the next min
var minAng = (2*CGFloat.pi)/60 * CGFloat(currMin!)
//calculating point on the circle where the sprite should move to in the next min
var newPt = CGPoint(x: (300 * cos(minAng)), y: -(300 * sin(minAng)))
//print out where the sprite currently is and where it should move to
print(hand.position)
print(newPt)
//move the sprite to the new location over 60 seconds, making sure the movement is linear
let moveHand = SKAction.move(to: newPt, duration: 60)
moveHand.timingMode = SKActionTimingMode.linear
hand.run(moveHand)
//move another placeholder sprite to the final destination instantly to visualise movment by waiting for the moving sprite.
handToBe.run(SKAction.move(to: newPt, duration: 0))
Assuming i am understanding everything correctly, it should move through the segment in 1 minute, reaching the end of the segment before needing to move to the next segment.
however, my sprite never reaches the end of the segment before needing to move to the next segment. the printouts shows that it is always too slow.
is there something i am not understanding about SKAction.move(to:), or is my logic flawed in this instance?
Try removing the code from the update function and just run it in a function that you call yourself. update gets called every frame, so the sprite starts to move, then the next frame it is told to move to another position, so it now has two move actions it needs to run at the same time. The frame after that it has 3 move actions it needs to run, etc. This could mean that it never really reaches its first intended position because of the other move actions that are affecting it at the same time.

Jumping with Sprite Kit and Swift

I am creating an app in Sprite Kit and Swift and want to create a sprite that jumps upwards while the screen is pressed, therefore jumping higher as the screen is pressed higher. I have achieved that effect, but gravity in the physics engine is not being applied until after the screen is released. Therefore the jumps are infinite (increasing exponentially) rather than levelling off at a certain point (like a quadratic equation / parabola).
How does one apply gravity actively during the motion of a sprite?
Here is my basic movement code:
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// touched is true while screen is touched
if touched {
nodeLeft.physicsBody?.applyImpulse(CGVector(dx: -5, dy: 0))
}
}
NOTE: The object is jumping right and left rather than up and down
The gravity should be working constantly, and probably is. As you apply an impulse on every tick however, this force is much stronger than the gravity.
What you need to do is to decrease the effect of the impulse over time.
This can be achieved in a number of ways, for instance by using the position of the sprite as a base for the impulse: The higher the sprite position, the lower the impulse.
if touched {
let minX: CGFloat = 200 // some suitable value
let multiplier: CGFloat = 10
let force = max(0, (nodeLeft.position.x / minX - 1) * multiplier)
nodeLeft.physicsBody?.applyImpulse(CGVector(dx: -force, dy: 0))
}
The minX value in the above example probably makes no sense. But the logic is fairly sound I believe. You obviously need to experiment and tweak this (and the multiplier) to suit your needs.

How to rotate a particle with a specific angle programmatically in SceneKit?

I would like to rotate a particle, it is a simple line, emitted once in the center of the screen.
After I touch the screen, the method is called, and the rotation changes all the time. With 10° or 180°, around the x or z axis, the result is the same: the angle is N°, then Y°, then Z° (always a different number, with a random difference between one another : with 10°, it is not offset by 10 each time, but by a random number). Would you know why?
func addParticleSceneKit(str:String){
var fire = SCNParticleSystem(named: str, inDirectory: "art.scnassets/Particles")
fire.orientationMode = .Free
fire.particleAngle = 90
//fire.propertyControllers = [ SCNParticlePropertyRotationAxis : [1,0,0] ] // should it be a SCNParticlePropertyController? I don't know how to use it then. But it would not be for an animation in my case.
emitter.addParticleSystem(fire)
Thanks
The particleAngleVariation property controls the random variation in initial particle angles. Normally that defaults to zero, meaning particle angle isn't randomized, but you're loading a particle system from a file, so you're getting whatever is in that file — setting it to zero should stop the randomization you're seeing. (You can also do that to the particle system in the file you're loading it from by editing that file in Xcode.)
By the way, you're not adding another new particle system to the scene every time you want to emit a single particle, are you? Sooner or later that'll cause problems. Instead, keep a single particle system, and make emit more particles when you click.
Presumably you've already set its emissionDuration, birthRate, and loops properties in the Xcode Particle System Editor so that it emits a single particle when you add it to the scene? Then just call its reset method, and it'll start over, without you needing to add another one to the scene.
Also, regarding your comment...
fire.propertyControllers = [ SCNParticlePropertyRotationAxis : [1,0,0] ]
should it be a SCNParticlePropertyController? I don't know how to use it then. But it would not be for an animation in my case.
Reading the documentation might help with that. But here's the gist of it: propertyControllers should be a dictionary of [String: SCNParticlePropertyController]. I know, it says [NSObject : AnyObject], but that's because this API is imported from ObjC, which doesn't have typed collections. That's why documentation is important — it says "Each key in this dictionary is one of the constants listed in Particle Property Keys, and the value for each key is a SCNParticlePropertyController object..." which is just long-winded English for the same thing.
So, passing a dictionary where the key is a string and the value is an array of integers isn't going to help you.
The docs also say that property controllers are for animating properties, and that you create one from a Core Animation animation. So you'd use a property controller for angle if you wanted each particle to rotate over time:
let angleAnimation = CABasicAnimation()
angleAnimation.fromValue = 0 // degrees
angleAnimation.toValue = 90 // degrees
angleAnimation.duration = 1 // sec
let angleController = SCNParticlePropertyController(animation: angleAnimation)
fire.propertyControllers = [ SCNParticlePropertyAngle: angleController ]
Or for rotation axis if you wanted particles (that were already spinning freely due to orientation mode and angular velocity) to smoothly transition from one axis of rotation to another:
let axisAnimation = CABasicAnimation()
axisAnimation.fromValue = NSValue(SCNVector3: SCNVector3(x: 0, y: 0, z: 1))
axisAnimation.toValue =NSValue(SCNVector3: SCNVector3(x: 0, y: 1, z: 0))
axisAnimation.duration = 1 // sec
let axisController = SCNParticlePropertyController(animation: axisAnimation)
fire.propertyControllers = [ SCNParticlePropertyRotationAxis: axisController ]

Resources