Sprite-Kit animations — using SKTextureFilteringMode.nearest - swift

I'm trying to apply SKTextureFilteringMode.nearest to all the frames in my animation.
Previously, when I was using a non-animated sprite, the following worked:
super.init(texture: texture, color: .clear, size: playerSize)
self.texture?.filteringMode = SKTextureFilteringMode.nearest;
Now I've added animation frames (see full code below), this doesn't work — the sprite is blurry. I can't work out how to add this filtering mode to all frames.
class Player: SKSpriteNode {
private var playerAtlas: SKTextureAtlas {
return SKTextureAtlas(named: "Player")
}
private var playerRunTextures: [SKTexture] {
return [
playerAtlas.textureNamed("run1"),
playerAtlas.textureNamed("run2"),
playerAtlas.textureNamed("run3"),
playerAtlas.textureNamed("run4")
]
}
func startRunAnimation() {
let runAnimation = SKAction.animate(with: playerRunTextures, timePerFrame: 0.1)
self.run(SKAction.repeatForever(runAnimation), withKey: "playerRunAnimation")
}
init() {
let texture = SKTexture(imageNamed: "player")
let playerSize = CGSize(width: 30, height: 50)
super.init(texture: texture, color: .clear, size: playerSize)
self.texture?.filteringMode = SKTextureFilteringMode.nearest;
self.position = CGPoint(x: 100, y: 400)
self.startRunAnimation()
}
...

set SKTextureFilteringMode for each texture when you create the sprite animation array:
private var playerRunTextures: [SKTexture] {
let spritesheet = [
"run1",
"run2",
"run3",
"run4"
]
return spritesheet.map { name in
let t1 = playerAtlas.textureNamed(name)
t1.filteringMode = .nearest //<--- for each texture in the atlas
return t1
}
}

Related

How can I animate a SKPhysicsBody texture to match an SKTextureAtlas

I implemented #mfessenden suggestion regarding using a custom SKAction to match the SKPhysicsBody to follow the animation using alphaThreshold. ( https://stackoverflow.com/a/54096147 ) But it does not appear to be functioning for me. Where have I gone wrong? Can you help #mfessenden?
var rockFrames: [SKTexture] = []
let rockAnimatedAtlas = SKTextureAtlas(named: "funny")
let numImages = rockAnimatedAtlas.textureNames.count
for i in 1...numImages {
let rockTextureName = "\(i)"
rockFrames.append(rockAnimatedAtlas.textureNamed(rockTextureName))
}
let firstFrameTexture = rockFrames[0]
rock = SKSpriteNode(texture: firstFrameTexture)
rock.position = CGPoint(x: self.frame.midX, y: self.frame.midY + 100)
rock.size = CGSize(width: 480 * wR, height: 270 * hR)
self.addChild(rock)
let rockaction = SKAction.animate(with: rockFrames,
timePerFrame: (1/24),
resize: false,
restore: true)
rock.run(SKAction.repeatForever(rockaction))
typealias Frame = (texture: SKTexture, duration: TimeInterval)
let timePerFrame: TimeInterval = 1/24
let dogFrames: [Frame] = rockFrames.map {
return ($0, timePerFrame)
}
rock = SKSpriteNode(texture: rockFrames.first)
let dogAnimationAction = animateTexturesWithPhysics(dogFrames)
rock.run(dogAnimationAction)
public func animateTexturesWithPhysics(_ frames: [(texture: SKTexture, duration: TimeInterval)], repeatForever: Bool=true) -> SKAction {
var actions: [SKAction] = []
for frame in frames {
// define a custom action for each frame
let customAction = SKAction.customAction(withDuration: frame.duration) { node, _ in
// if the action target is a sprite node, apply the texture & physics
if node is SKSpriteNode {
let setTextureGroup = SKAction.group([
SKAction.setTexture(frame.texture, resize: false),
SKAction.wait(forDuration: frame.duration),
SKAction.run {
node.physicsBody = SKPhysicsBody(texture: frame.texture, alphaThreshold: 0.5, size: frame.texture.size())
node.physicsBody?.isDynamic = false
node.physicsBody?.categoryBitMask = self.arrowCategory
// add physics attributes here
}
])
node.run(setTextureGroup)
}
}
actions.append(customAction)
}
// add the repeating action
if (repeatForever == true) {
return SKAction.repeatForever(SKAction.sequence(actions))
}
return SKAction.sequence(actions)
}

Skspritenode texture animation doesn't work

I just tried to animate some coins in my scene. In my other projects the animation works. But recently not here.
func addCoins() {
for coins in map.coinSpawns {
var coin = SKSpriteNode(imageNamed: "coin1")
coin.position = coins
coin.size = CGSize(width:map.tileSize - 10,height: map.tileSize - 10)
let action = SKAction.repeatForever(SKAction.animate(withNormalTextures: [SKTexture(imageNamed: "coin1.png"),SKTexture(imageNamed: "coin2.png"),SKTexture(imageNamed: "coin3.png"),SKTexture(imageNamed: "coin4.png")], timePerFrame: 0.5, resize: false, restore: true))
self.addChild(coin)
coin.run(action)
self.coins.append(coin)
}
}
Try to refactor a bit your code, removing .png from the file name (should be the fix) and extracting the textures array outside the coin loop (optimization), so your code might be:
func addCoins() {
let textures = ["coin1", "coin2", "coin3", "coin4"].flatMap { SKTexture(imageNamed: $0) }
for coins in map.coinSpawns {
var coin = SKSpriteNode(imageNamed: "coin1")
coin.position = coins
coin.size = CGSize(width:map.tileSize - 10,height: map.tileSize - 10)
self.addChild(coin)
self.coins.append(coin)
let action = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.5, resize: true, restore: false))
coin.run(action)
}
}

SpriteKit calling function from class

This is from a simple game in SpriteKit with a Ball() class that has a function shieldOn() which, for the moment, simply replaces the texture of a single ball to that of a ball surrounded by a shield.
The ball is created like this in GameScene:
func getBall() {
let ball = Ball()
ball.createBall(parentNode: self)
}
Here is the Ball class
class Ball: SKSpriteNode {
func createBall(parentNode: SKNode) {
let ball = SKSpriteNode(texture: SKTexture(imageNamed: "ball2"))
ball.physicsBody = SKPhysicsBody(circleOfRadius: 25)
ball.name = "ball"
parentNode.addChild(ball)
ball.size = CGSize(width: 50, height: 50)
ball.position = CGPoint(x: 20, y: 200)
launch(spriteNode: ball, parentNode: parentNode)
}
private func launch(spriteNode: SKSpriteNode, parentNode: SKNode) {
spriteNode.physicsBody?.applyImpulse(CGVector(dx: 5, dy: 0))
}
func shieldOn() {
self.texture = SKTexture(imageNamed: "ballShield")
}
func shieldOff() {
self.texture = SKTexture(imageNamed: "ball2")
}
}
In the main section of my code (GameScene.swift) I don't have a reference to the ball. So I cycle through all of the nodes on the screen and try to cast the matching one as shown below. I crash with an error saying that it could not cast value of type SKSpriteNode to Ball.
for node in self.children {
if node.name == "ball" {
let ball = node as! Ball
ball.shieldOn()
}
}
I've tried a few variations with no luck. Am I at least working in the right direction? Thanks!
With the new information I think you want something like this:
Ball Class:
class Ball: SKSpriteNode{
init() {
let texture = SKTexture(imageNamed: "ball2")
let size = CGSize(width: 50, height: 50)
super.init(texture: texture, color: UIColor.clear, size: size)
self.name = "ball"
self.physicsBody = SKPhysicsBody(circleOfRadius: size.height/2)
self.physicsBody?.applyImpulse(CGVector(dx: 5, dy: 0))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func shieldOn() {
self.texture = SKTexture(imageNamed: "ballShield")
}
func shieldOff() {
self.texture = SKTexture(imageNamed: "ball2")
}
}
Then use this to create the ball:
func getBall() {
let ball = Ball()
ball.position = CGPoint(x: 20, y: 200)
scene?.addChild(ball)
}
Perhaps a better way to do this would be to keep an array of all Balls created and added to the scene. Then you could just iterate through your array and update their texture. You would not need to enumerate them on the screen, which can decrease performance if there are many moving sprites.
As far as your code goes, it looks like you might be affected by this bug:
https://forums.developer.apple.com/thread/26362

Swift: Player Jump Animation

I'm trying to get my player to play the jump animation when the touch begins. The player is in his own swift file and I have the GameScene.swift call his file so everything that deals with his movement is in player.swift file. Any help would be awesome!
import SpriteKit
struct ColliderType {
static let PLAYER: UInt32 = 0
static let GROUND: UInt32 = 1
static let ROCKET_AND_COLLECTABLES: UInt32 = 2
}
class Player: SKSpriteNode {
private var textureAtlas = SKTextureAtlas()
private var playerAnimation = [SKTexture]()
private var animatePlayerAction = SKAction()
let jumpAnimation1 = [SKTexture]()
let jumpAnimation2 = [SKTexture]()
var jumpAnimation = SKAction ()
func initializePlayer() {
name = "Player"
for i in 1...7 {
let name = "Player \(i)"
playerAnimation.append(SKTexture(imageNamed: name))
}
animatePlayerAction = SKAction.animate(with: playerAnimation,
timePerFrame: 0.08, resize: true, restore: false)
//Animate the player forever.
self.run(SKAction.repeatForever(animatePlayerAction))
physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:
self.size.width-60, height: self.size.height-80))
physicsBody?.affectedByGravity = true
physicsBody?.allowsRotation = false
//Restitution is Boucniess
physicsBody?.restitution = 0
physicsBody?.categoryBitMask = ColliderType.PLAYER
physicsBody?.collisionBitMask = ColliderType.GROUND
physicsBody?.contactTestBitMask =
ColliderType.ROCKET_AND_COLLECTABLES
}
func move (){
self.position.x += 10
}
func reversePlayer() {
physicsBody?.velocity = CGVector(dx: 0, dy: 0)
physicsBody?.applyImpulse(CGVector(dx: 0, dy: 300))
playerNode?.removeAction(forKey:"animate") playerNode?.run(attackAnimation,completion:{
self.playerNode?.run(self.animation,withKey: "animate")
}
}
Replace the part where you set textures, create and run the action with the following code snippet. It works perfectly for me and is the simplest way to do it.
let hero = SKSpriteNode(imageNamed: "hero")
hero.size = CGSize(width: 45, height: 125)
let f1 = SKTexture.init(imageNamed: "h1")
let f2 = SKTexture.init(imageNamed: "h2")
let f3 = SKTexture.init(imageNamed: "h3")
let frames: [SKTexture] = [f1, f2, f3]
let hero = SKSpriteNode(imageNamed: "h1")
let animation = SKAction.animate(with: frames, timePerFrame: 0.1)
hero.run(SKAction.repeatForever(animation))
Also, it is possible that your animation action cannot be accessed as it is in another file. Declare the player and animations globally i.e. outside of any class or function. Hope it works for you!

How do I create a collision with SpriteKit with an already created SKSpriteNode?

I want the pacman to restart from its original position when it collides with blinky, that is moving.
How can I make them collide considering I have already declared them?
You move the pacman, but blinky moves alone. I want it to work like the pacman game.
public class PacmanScene: SKScene {
let playerSpeed: CGFloat = 40.0
var pacman: SKSpriteNode?
var playerTextures: [SKTexture] = []
var lastTouch: CGPoint? = nil
var blinky: SKSpriteNode?
var clyde: SKSpriteNode?
var inky: SKSpriteNode?
var pinky: SKSpriteNode?
override public init(size: CGSize) {
let pacmanTexture = SKTexture(imageNamed: "pacman01.png")
pacman = SKSpriteNode(texture: pacmanTexture)
pacman?.name = "pacman"
pacman?.position = CGPoint(x:30, y:30)
pacman?.zPosition = 1.0
pacman?.physicsBody = SKPhysicsBody(texture: pacmanTexture, size: CGSize(width: (pacman?.size.width)!, height: (pacman?.size.height)!))
pacman?.physicsBody?.allowsRotation = true
pacman?.physicsBody?.affectedByGravity = false
pacman?.physicsBody?.mass = 2
let blinkyTexture = SKTexture(imageNamed: "blinky.png")
blinky = SKSpriteNode(texture: blinkyTexture)
blinky?.name = "blinky"
blinky?.position = CGPoint(x: 15, y: 60)
blinky?.zPosition = 1.0
blinky?.physicsBody = SKPhysicsBody(texture: pacmanTexture, size: CGSize(width: (blinky?.size.width)!, height: (blinky?.size.height)!))
blinky?.physicsBody?.allowsRotation = false
blinky?.physicsBody?.affectedByGravity = false
blinky?.physicsBody?.mass = 1000
super.init(size: size)
addChild(pacman!)
addChild(blinky!)
override public func didMove(to view: SKView) {
let bmoveUp = SKAction.moveBy(x: 0, y: 450, duration: 4.0)
let bmoveRight = SKAction.moveBy(x:20, y:0, duration: 1.0)
let bmoveDown = SKAction.moveBy(x:0, y: -450, duration: 4.0)
let bmoveLeft = SKAction.moveBy(x:-20, y:0, duration: 1.0)
let bsequence = SKAction.sequence([bmoveUp, bmoveRight, bmoveDown, bmoveLeft])
let bendlessAction = SKAction.repeatForever(bsequence)
blinky?.run(bendlessAction)
}
If iv got this right you want your "blinky" to follow your "pacman" to do this you would have to work out the position of the pacman then add an SKAction to your blinky to move to that position.
Try something like this
//Speed blinky moves
let blinkySpeed = 100
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
updateBlinky()
}
func updateBlinky() {
//Set the point that blinky moves to
let point = CGPoint(x: pacman.position.x, y: pacman.position.y)
//Get the distance its got to travel
let distance = distanceBetweenPoints(first: pacman.position, second: blinky.position)
//Get the time is got to take from the speed and distance
let time = distance / blinkySpeed
//Create and run the action
let action = SKAction.move(to: point, duration: TimeInterval(time))
blinky.run(action)
}
//work out the distance between the sprites
func distanceBetweenPoints(first: CGPoint, second: CGPoint) -> Int {
return Int(hypot(second.x - first.x, second.y - first.y))
}
The end result would be something like this
Edit:
Okay I think from your question you already have "blinky" moving you just want to detect collisions.
First you need to add the SKPhysicsContactDelegate to your class
public class PacmanScene: SKScene, SKPhysicsContactDelegate {
Then you need to add a category bit mask to both the sprites then handle the collisions in didBegin(_ contact: SKPhysicsContact) method.
What I would do
//Create Physics category struct
struct PhysicsCategory {
static let pacman : UInt32 = 0x1 << 1
static var blinky : UInt32 = 0x1 << 2
}
Then where you are setting up pacman and blinky set the category bitmask
//Set the category bit mask for pacman
pacman?.physicsBody?.categoryBitMask = PhysicsCategory.pacman
//Set what categories you want to test contact
pacman?.physicsBody?.contactTestBitMask = PhysicsCategory.blinky
//Set what categories you want to collide with each other
pacman?.physicsBody?.collisionBitMask = PhysicsCategory.blinky
//Set the category bit mask for blinky
blinky?.physicsBody?.categoryBitMask = PhysicsCategory.blinky
//Set what categories you want to test contact
blinky?.physicsBody?.contactTestBitMask = PhysicsCategory.pacman
//Set what categories you want to collide with each other
blinky?.physicsBody?.collisionBitMask = PhysicsCategory.pacman
Then you would need to implement the didBegin(_ contact: SKPhysicsContact) method to handle the collisions
func didBegin(_ contact: SKPhysicsContact) {
//Work out which contact was pacman
let other = contact.bodyA.categoryBitMask == PhysicsCategory.pacman ? contact.bodyB : contact.bodyA
//Test what it hit
switch other.categoryBitMask {
case PhysicsCategory.blinky:
print("pacman hit blinky")
//Move pacman
pacman?.position = CGPoint(x:30, y:30)
default:
break
}
}
Hope this helps