SpriteKit - change animation speed dynamically - swift

I am new to the SpriteKit framework and cannot figure out how to do the following:
In the middle of the scene I have a sprite to which I apply an .animateWithTextures SKAction. Now I simply want to increase the speed of that animation, or decrease its duration for the same effect.
I made the SKAction a property of my GameScene class so I can access its properties from everywhere, but neither changing its speed nor its duration affects the animation.
I read several threads, the closest to my problem being this one:
How to change duration of executed SpriteKit action
which is from seven years ago, and none of the answers is working for me.
Here is basically what I do:
class GameScene: SKScene {
var thePlayer: SKSpriteNode = SKSpriteNode()
var playerAnimation: SKAction?
...
...
func setInitialAnimation() {
let walkAnimation = SKAction.repeatForever(SKAction(named: "PlayerWalk", duration: 1)!)
self.playerAnimation = walkAnimation
self.thePlayer.run(walkAnimation)
}
func changeAnimationDuration(to duration: CGFloat) {
self.playerAnimation?.duration = duration
}
}
The animation starts when the setInitialAnimation method is called, but changing the action's duration has no effect. The only thing that works so far is removing the old action from the player sprite and running a new one.
Should it not be possible to change the properties of a running SKAction or is that some kind of fire and forget mechanism?

Play around with the timePerFrame to get the correct animation speed.
var frames = [SKTexture]()
for i in 1...10 {
frames.append(SKTexture(imageNamed: "YourTextureAnimationFrame\(i)"))
}
let animate = SKAction.animate(with: frames, timePerFrame: 0.025)
//timePerFrame setting the speed of the animation.
node.run(animate)

Related

Virtual Joystick moving sprite while changing animations

I'm working on a project where I'll have a virtual joystick on the screen which moves a character around the board. I found a few good examples and the one I'm using currently is located # https://github.com/Ishawn-Gullapalli/SpritekitJoystick/tree/master/SpritekitJoystick
While the player is moved around just like I want, I'm unable to figure out how by using this method I can change the player animation based on the direction he's going. I'm able to determine direction but my usual methods of invoking a SKAction doesn't animate the player, it just moves him to the new location while pausing the idle animation. I'm moving my player in the update statement like below and am looking for tips on how to change animations appropriately.
I'm including my update statement below. I've also tried to pass in a CGPoint value with the same effect. Thanks in advance for looking.
override func update(_ currentTime: TimeInterval) {
if joystick.joyStickTouched == true {
mainHero.position.x += joystick.moveRight(speed: movementSpeed)
// Example of me trying to animate my player
if joystick.moveRight(speed: movementSpeed) > 0 {
walkRight()
}
mainHero.position.x -= joystick.moveLeft(speed: movementSpeed)
mainHero.position.y += joystick.moveUp(speed: movementSpeed)
mainHero.position.y -= joystick.moveDown(speed: movementSpeed)
joystick.update(currentTime)
}
}
func walkRight() {
mainHero.isPaused = false
let idleAnimation = SKAction(named: "MainWalkRight")!
let rightGroup = SKAction.group([idleAnimation])
mainHero.run(rightGroup, withKey: "Right")
}

Sprite Kit Animations and Texture Atlases in Swift 4

Currently working on an application that requires a little bit of animations through an array of images. I've done tons of research online on how to resolve my issue but couldn't run into anything for Swift 4. Below is my code, it shows the first picture in the loop when I run the app but no animation at all. Everything looks fine, I don't see a problem with my code but maybe you guys can help. Appreciate it in advanced!
let atlas = SKTextureAtlas(named: “mypic”)
var TextureArray = [SKTexture]()
var person = SKSpriteNode()
override func didMove(to view: SKView) {
person = SKSpriteNode(imageNamed: "red_1.png")
person.size = CGSize(width: 150, height: 129)
person.position = CGPoint(x: 0, y: 0)
person = SKSpriteNode(imageNamed: atlas.textureNames[0])
for i in 1...atlas.textureNames.count {
let Name = "red_\(i).png"
TextureArray.append(SKTexture(imageNamed: Name))
}
self.addChild(person)
}
override func update(_ currentTime: TimeInterval) {
let myAnimation = SKAction.animate(with: TextureArray, timePerFrame: 0.1)
person.run(SKAction.repeatForever(myAnimation))
}
The animation action is placed in update, which is executed once every frame. So if the game runs at 60 FPS, update gets called 60 times in one second. This means that every second person gets 60 new myAnimation actions that it needs to run.
To fix this, consider placing the animation action somewhere else in your code, e.g. right after adding the person to the scene. Since this is a repeatForever action, the animation will run as you intended until either the action is removed from the node, or the node is removed from the scene.
Hope this helps!

Filtering Fast User Touch Input

I am coding an app in sprite-kit and swift where when you touch the screen a sprite(the player) throws a projectile at another sprite moving towards it. If the player hits the other sprite then the projectile and the sprite disappear. A problem with the game is that if the player rapidly touches the screen he can easily run up his score in the game. How can I make the code only recognize that the screen is being touched every let's say .3 seconds?
In SpriteKit/GameplayKit games, most of your code is running inside a game loop where you are constantly being passed the current time. That's what this function in an SKScene is:
override public func update(_ currentTime: TimeInterval) {
}
In here it's common to keep track of time and enable/disable things. To keep it simple:
Add the following vars
var firingEnabled = true
var enableFiringAtTime: TimeInterval = 0
var currentTime: TimeInterval = 0
When they fire, add this code
if firingEnabled {
firingEnabled = false
enableFiringAtTime = self.currentTime + 0.3
// your fire code here
}
And in the update override
self.currentTime = currentTime
if currentTime > enableFiringAtTime {
firingEnabled = true
}

SpriteKit scene transition good practices

I am writing a game using SpriteKit with Swift and have run into a memory concern.
The layout of my game is such that the GameViewController (UIViewController) presents the first SKScene (levelChooserScene) in the viewDidLoad Screen. This scene does nothing more than display a bunch of buttons. When the user selects a button the scene then transitions to the correct scene using skView.presentScene, and when the level is complete, that scene then transitions back to the levelChooserScene and the game is ready for the user to select the next level.
The problem is that when the transition back to the levelChooserScene occurs the memory allocated for the game play scene is not deallocated, so after selecting only a few levels I start receiving memory errors.
Is my design correct in transitioning from SKScene to SKScene, or should I instead return to the GameViewController each time and then transition to the next SKScene from there?
I have found a few posts on here that say I should call skView.presentScene(nil) between scenes, but I am confused on how or where to implement that.
I simply want to transition from one SKScene to another and have the memory used from the outgoing scene to be returned to the system.
This is an example of how I have implemented the SKScene:
class Level3: SKScene
{
var explodingRockTimer = NSTimer()
var blowingUpTheRocks = SKAction()
override func didMoveToView(view: SKView)
{
NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "dismissTheScene:", userInfo: nil, repeats: false)
var wait = SKAction.waitForDuration(0.5)
var run = SKAction.runBlock{
// your code here ...
self.explodeSomeRocks()
}
let runIt = SKAction.sequence([wait,run])
self.runAction(SKAction.repeatActionForever(runIt), withKey: "blowingUpRocks")
var dismissalWait = SKAction.waitForDuration(5.0)
var dismissalRun = SKAction.runBlock{
self.removeActionForKey("blowingUpRocks")
self.dismissTheScene()
}
self.runAction(SKAction.sequence([dismissalWait,dismissalRun]))
}
func explodeSomeRocks()
{
println("Timer fired")
}
//MARK: - Dismiss back to the level selector
func dismissTheScene()
{
let skView = self.view as SKView?
var nextScene = SKScene()
nextScene = LevelChooserScene()
nextScene.size = skView!.bounds.size
nextScene.scaleMode = .AspectFill
var sceneTransition = SKTransition.fadeWithColor(UIColor.blackColor(), duration: 1.5) //WithDuration(2.0)
//var sceneTransition = SKTransition.pushWithDirection(SKTransitionDirection.Down, duration: 0.75) //WithDuration(2.0)
//var sceneTransition = SKTransition.crossFadeWithDuration(1.0)
//var sceneTransition = SKTransition.doorwayWithDuration(1.0)
sceneTransition.pausesOutgoingScene = true
skView!.presentScene(nextScene, transition: sceneTransition)
}
}
Well the thing that was causing my trouble was inserting particle emitters every half second for 5 seconds using SKAction.repeatActionForever() to call the emitter insert function.
This repeatAction apparently was not killed by transitioning to another scene, and was causing the memory for the whole scene to be retained. I switched to SKAction.repeatAction() instead and specify how many time it should fire. The scene now returns all of its memory when I transition to the new scene.
I am not sure I understand this behavior though.
SpriteKit it's not strongly documented when it comes to create complex games. I personally had a problem like this for days until I managed to figure it out.
Some objects retain the reference, so it doesn't deinit. (SKActions, Timers, etc)
Before presenting a new scene I call a prepare_deinit() function where I manually remove the strong references which are usually not deallocated by swift.
func prepare_deinit()
{
game_timer.invalidate() // for Timer()
removeAction(forKey: "blowingUpRocks") // for SKAction in your case
// I usually add the specific actions to an object and then remove
object.removeAllActions()
// If you create your own object/class that doesn't deinit, remove all object
//actions and the object itself
custom_object.removeAllActions()
custom_object.removeFromParent()
}
deinit
{
print("GameScene deinited")
}
The last problem I encountered was that the new scene was presented much faster than my prepare_deinit() so I had to present the new scene a little later, giving the prepare_deinit() enough time to deallocate all objects.
let new_scene =
{
let transition = SKTransition.flipVertical(withDuration: 1.0)
let next_scene = FinishScene(fileNamed: "FinishScene")
next_scene?.scaleMode = self.scaleMode
next_scene?.name = "finish"
self.view?.presentScene(next_scene!, transition: transition)
}
run(SKAction.sequence([SKAction.run(prepare_deinit), SKAction.wait(forDuration: 0.25), SKAction.run(exit_to_finish)]))

How do I make a trail behind my character in SpriteKit?

I'm using SpriteKit (with Xcode 6 and Swift) and I have a character on the screen that I move around with on screen joysticks, and I want a little trail to follow behind him. How do I do that?
How big would my image need to be, and what would it need to look like?
Also what would I use in my code?
You should take a look at SKEmitterNode; it will "emit" particles that you can use as your trail. You can design the look and feel of your particles right in Xcode by adding a "SpriteKit Particle File" to your project:
You'd then load the particle file in to a new SKEmitterNode like so:
let emitter = SKEmitterNode(fileNamed: "CharacterParticle.sks")
Then you'll need to set the SKEmitterNode's targetNode property to your SKScene so that the particles it emits don't move with your character (i.e. they leave a trail):
emitter.targetNode = scene
Then add your emitter to your character's SKNode. Lets assume you have an SKNode for your character called character, in that case the code would simply be:
character.addChild(emitter)
Typically this sort of thing would be done in your scene's setup method (in Apple's SpriteKit template, it's usually in didMoveToView). It could also be done in your character's custom SKNode or SKSpriteNode class, if you have one. If you put it in didMoveToView, it would look something like:
override func didMoveToView(view: SKView) {
// ... any character or other node setup ...
let emitter = SKEmitterNode(fileNamed: "CharacterParticle.sks")
emitter.targetNode = self
character.addChild(emitter)
// ... any other setup ...
}
Although SKEmitterNode is a fine option. I would suggest you use a SKSpriteNode instead. The Emitters in Xcode cause a lot of lag when used frequent and in sequence.
The best way to create a trail in my opinion is by preloading a SKTexture when loading up the application. For this I would suggest creating a class like this.
class AssetsManager {
private init() {};
static let shared = AssetsManager();
func preloadAssets(with texture: SKTexture) {
texture.preload {
print("Sprites preloaded")
}
}
And than calling it as so in either your AppDelegate or MenuScene:
AssetsManager.shared.preloadAssets(with: SKTexture(imageNamed: "yourImage"))
Than for the "Creating a trail part":
Create a timer
var timer: Timer!
Start your timer
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(ballTrail), userInfo: nil, repeats: true)
Create the ballTrail function
#objc func ballTrail() {
let trail = SKSpriteNode(texture: SKTexture(imageNamed: "your Image"))
trail.size = size
trail.position = player.position
trail.zPosition = player.positon - 0.1
addChild(trail)
trail.run(SKAction.scale(to: .zero, duration: seconds))
trail.run(SKAction.fadeOut(withDuration: seconds)
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
trail.removeFromParent()
}
}
You can fiddle around with the actions and timings you would like to use. Hopefully this will help someone!