Sprite Kit Animations and Texture Atlases in Swift 4 - swift

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!

Related

Fast-paced SpriteKit game has irregular CPU activity and is jittery/lags despite frame rate staying high - Swift

I'm having the issue on a simple but fast-paced SpriteKit game, but I've reduced my code just to a bouncing ball and still get the issue to a lesser extent:
override func didMove(to view: SKView) {
super.didMove(to: view)
physicsWorld.contactDelegate = self
physicsWorld.speed = 1
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
borderBody.contactTestBitMask = BallCategory
addBall()
}
func addBall() {
let size = CGSize(width: 20, height: 20)
let position = CGPoint(x: frame.width / 2, y: 50)
let texture = SKTexture(image: #imageLiteral(resourceName: "whiteCircle"))
let ball = SKSpriteNode(texture: texture, size: size)
ball.position = position
ball.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
ball.fillColor = .white
ball.lineWidth = 0
addStandardProperties(node: ball, name: "ball", z: 5, contactTest: 0, category: BallCategory)
ball.physicsBody?.isDynamic = true
addChild(ball)
launchBall()
}
func addStandardProperties(node: SKNode, name: String, z: CGFloat, contactTest: UInt32, category: UInt32) {
node.name = name
node.zPosition = z
node.physicsBody?.isDynamic = false
node.physicsBody?.affectedByGravity = false
node.physicsBody?.mass = 0
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0
node.physicsBody?.linearDamping = 0
node.physicsBody?.angularDamping = 0
node.physicsBody?.angularVelocity = 0
node.physicsBody?.contactTestBitMask = contactTest
node.physicsBody?.categoryBitMask = category
}
func launchBall() {
let ball = childNode(withName: "ball")!
ball.physicsBody?.velocity = CGVector(dx: 0, dy: 500)
}
This code results in a ball (SKSpriteNode) bouncing up and down. When I run this, CPU usage starts at around 10% on my iPhone 6s and then after increases to around 25-30% after maybe 30-60 seconds (no idea why it's increasing). Throughout all of this, the frame rate stays very close to 60 FPS, usually going no lower than 58 FPS (it's the same way when I run the full game).
Almost any time an alert pops up (e.g., text messages, logging into Game Center, etc.), the lag shows up and shows up at random times when I'm running the full game.
I've also tried deleting and re-running the app, cleaning the project, deleting derived data and running in Release mode. None of these worked permanently.
Should I give up on SpriteKit and try another framework? If so, which? Cocos2D?
Any help is appreciated.
This is the result of Apple prioritising system calls over just about everything else.
When the system wants to know something, check something or otherwise do its thing it does so at the mercy of everything else.
No other engine will be able to help with this, there's no way to silence the system's constant activities through code.
You can get a slight improvement by putting on Flight Mode and turning off WIFI and Bluetooth. The system seems to be somewhat aware that it's in a quieter mode and does less because it's got no 4G or other connectivity it can go communicating with.
Further, there's been some pretty big changes to palm rejection in iOS 11 that's played havoc with the first round of iPad Pro models and creative software, creating multi-second rejection of all touch input. When this kind of thing can make it through to a GM you can be pretty sure they're slipping other messiness through.
Here's some complaints about iOS 11 performance: https://www.macrumors.com/2017/09/25/ios-11-app-slowdowns-performance-issues/
Turns out I had 2 SKViews in my view controller. By default, when you start a project as a SpriteKit game, Xcode sets the view controller root/superview of the GameViewController as an SKView. At some point, I had added a second SKView because I didn't intend for the scene to take up the entire screen and I apparently didn't realize that the VC root view was still set as an SKView. So every time GameViewController loaded, it was loading two SKViews, which is why I saw 120 FPS in Xcode.
I fixed the issue by simply removing the SKView class designation from the VC root view.

Positioning SkSpriteNode inside SkAction completion handler

I have run into this strange issue. I am adding an enemy(SKSpriteNode) from inside the GameScene didMove(to view: SKView) using addChild.
The enemy has been positioned to x:100, y: 100 and it appears correctly.
I also have another animation , the completion of which I am adding another enemy at the same location . But the enemy appears at a different location.The completion block is as shown below.
holeExplosion.runHoleExplosionAction {[unowned self] in
//self.addEnemy(enemyCount: 1, hole: holeExplosion)
var modEnemy: ParentEnemy? = nil
modEnemy = Enemy1(imageNamed: "Zombie1Jump1.png", healthPower:30)
print(" \(self.scene?.position.x) \(self.scene?.parent) ")
self.addChild(modEnemy!)
modEnemy!.enemySpeed = self.enemy1Speed
modEnemy!.name = "enemy1"
modEnemy!.position = CGPoint (x: 100 , y: 100)
modEnemy!.zPosition = 2
}
Any help would be appreciated. Thanks.
Your enemy class have physicsBody delegate?? maybe you can see that first because if it has it, you have to search your isDynamic property. You can't have 2 bodies in the same space when the property is equal to true.

In swift, is there a way to select a sprite by its name?

I'm new to coding in Swift so I apologize if this is a silly question.
I have a function that creates a series of Sprites. These Sprites move around and change sizes. Through the function, each is given a unique name.
What I would like to do is have their position/animation/size/texture change when a user presses a separate set of sprites. In other words, I need pressing another sprite to call a function which changes the first set of sprites.
However, I'm having trouble doing this. It seems like I can make it work if I hardwire the particular variable name of a sprite in. However, because there are many, they may change over time, and I may want to cycle through many of them hardwiring is not good.
Essentially, I want to be able to select a sprite and animate it once another sprite is touched.
Any suggestions?
You can make your own subclass of SKSpriteNode, then call them from their object name (the name of the variable or let constant). This means you don't have to hardwire, and you can use any sort of logic / function to change the animation or which names of sprites being called / used etc.
In this demo, I make two objects... one a lightbult, another a lightswitch. When you click the lightswitch, the lightbulb will turn on.
Read the comments to learn how to customize this. You can have any object tell any other sprite to play their personal animation:
class TouchMeSprite: SKSpriteNode {
// This is used for when another node is pressed... the animation THIS node will run:
var personalAnimation: SKAction?
// This is used when THIS node is clicked... the below nodes will run their `personalAnimation`:
var othersToAnimate: [TouchMeSprite]?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Early exit:
guard let sprites = othersToAnimate else {
print("No sprites to animate. Error?")
return
}
for sprite in sprites {
// Early exit:
if sprite.scene == nil {
print("sprite was not in scene, not running animation")
continue
}
// Early exit:
guard let animation = sprite.personalAnimation else {
print("sprite had no animation")
continue
}
sprite.run(animation)
}
}
}
Here is the GameScene file that shows off the lightswitch and bulb demo:
class GameScene: SKScene {
override func didMove(to: SKView) {
// Bulb:
let lightBulb = TouchMeSprite(color: .black, size: CGSize(width: 100, height: 100))
// Lightbulb will turn on when you click lightswitch:
lightBulb.personalAnimation = SKAction.colorize(with: .yellow, colorBlendFactor: 1, duration: 0)
lightBulb.position = CGPoint(x: 0, y: 400)
lightBulb.isUserInteractionEnabled = true
addChild(lightBulb)
// Switch:
let lightSwitch = TouchMeSprite(color: .gray, size: CGSize(width: 25, height: 50))
// Lightswitch will turn on lightbulb:
lightSwitch.othersToAnimate = [lightBulb]
lightSwitch.isUserInteractionEnabled = true
lightSwitch.position = CGPoint(x: 0, y: 250)
addChild(lightSwitch)
}
}

How to add an overlay start scene in swift?

can someone code for me please how to add an overlay start scene in swift?
I want to add a startMenu like in Flappy Bird, and then when you click play it just moves/fades away and the game begins.
Can someone help me achieve this effect?
Looking to achieve this.
I will be more than thankful!
Try to post some code of what you have tried so far when asking a question on stack overflow, people tend to not write code for you.
You can basically do this 2 ways.
1)
Either create a StartScene and transition to GameScene with your preferred SKTransition when the play button is pressed e.g SKTransition.crossFade
2)
If you want to do it in the same scene you can just create a SKNode/SKSpriteNode class to use as a menu.
class Menu: SKSpriteNode {
lazy var playLabel: SKLabelNode = {
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.text = "Play"
label.fontSize = 22
label.fontColor = .yellow
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return label
}()
/// Init with size
init(size: CGSize) {
super.init(texture: nil, color: .red, size: size)
addChild(playLabel)
}
}
}
Than in your GameScene Scene you can add the menu like so with your preferred size, in this example the size of the scene.
class GameScene: SKScene {
lazy var menu: Menu = Menu(size: self.size)
override func didMove(to view: SKView) {
addChild(menu)
}
}
You can add other nodes for UI, including sprites for buttons etc, to that menu class.
Than in GameScene in touches Began you can look for nodes that are touched, e.g the play button and than do your thing e.g to animate out the menu
let action1 = SKAction.fadeAlpha(to: 0, duration: 0.5)
let action2 = SKAction.removeFromParent()
let sequence = SKAction.sequence([action1, action2])
menu.run(sequence)
If you stuck there and want more coding samples you should google how to do this, is basic SpriteKit stuff that you need to know and there is plenty tutorials available. This includes samples of how to make button subclasses using SKSpriteNodes.
Hope this helps

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!