Superimpose two textures on an SKSpriteNode - ios

I would like to achieve the effect shown in this gif.
Currently I do this with a series of ~7 png images with a red background and a white line, these are animated through the sprite with an SKAction.
There are a few others additions I would like to make to the sprite, that can change depending on situation, and also I would like to repeat this with a number of colours.
This results in: 6 colours, 7 shine effects, 5 edge effects and 4 corner effects resulting in 136 combinations of textures I would need to create and store.
I feel like there has to be a way to superimpose png's with transparent backgrounds when setting the texture of a sprite but I cannot seem to find a way to do this anywhere.
Is this possible so that I can reduce the number of assets to 22 or do I have to make all 136 and build in logic to the class to decide which to use?

I wanted an effect like this for my game, I tried a lot of options. I tried using particles for performance but couldn't even get close. I know you can accomplish it with Shaders, but i didn't go that route and in iOS 12 Shaders won't be support Open GL anyway. In the end I opted to go with CropNodes.
This is my glare image, it is hard to see because it slightly transparent whiteish image.
This is the results I achieved using CropNodes
class Glare: SKSpriteNode {
var glare = SKSpriteNode()
private var cropNode = SKCropNode()
init(color: UIColor, size: CGSize) {
super.init(texture: nil, color: color, size: size)
alpha = 0.7
zPosition = 10
setupGlare()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupGlare() {
let buffer: CGFloat = 10
let mask = SKSpriteNode(texture: nil, color: .black, size: self.size)
let texture = SKTextureAtlas(named: "Sprites").textureNamed("glare")
glare = SKSpriteNode(texture: texture)
glare.position = CGPoint(x: 0 - (self.size.width / 2 + buffer), y: self.size.height / 2 + buffer)
glare.setScale(3.50)
glare.zPosition = 1
cropNode.zPosition = 2
cropNode.maskNode = mask
cropNode.addChild(glare)
let random = Double(CGFloat.random(min: 0, max: 1))
let pause = SKAction.wait(forDuration: random)
let move = SKAction.moveBy(x: self.size.width + buffer * 2, y: 0 - (self.size.height + buffer * 2), duration: 0.5)
let wait = SKAction.wait(forDuration: 1.0)
let reset = SKAction.moveBy(x: 0 - (self.size.width + buffer * 2), y: self.size.height + buffer * 2, duration: 0.0)
let seq = SKAction.sequence([move, wait, reset])
let repeater = SKAction.repeatForever(seq)
let repeaterSeq = SKAction.sequence([pause, repeater])
glare.run(repeaterSeq)
}
func showGlare(texture: SKTexture) {
let mask = SKSpriteNode(texture: texture)
cropNode.maskNode = mask
glare.isHidden = false
if cropNode.parent == nil { self.addChild(cropNode)}
}
func hideGlare() {
glare.isHidden = true
//remove cropnode from the node tree
cropNode.removeFromParent()
}
}
and then in my GameScene...
I add my glares to a glare layer but that isn't necessary. I also go through when the game loads and create my glares for all 15 slots ahead of time and put them in an array. This way I do not have to create them on the fly and I can just turn on slot 10 any time I want and turn it off as well.
private var glares = [Glare]()
let glare = Glare(color: .clear, size: CGSize(width: kSymbolSize, height: kSymbolSize))
glare.position = CGPoint(x: (CGFloat(x - 1) * kSymbolSize) + (kSymbolSize / 2), y: 0 - (CGFloat(y) * kSymbolSize) + (kSymbolSize / 2))
glare.zPosition = 100
glareLayer.addChild(glare)
glares.append(glare)
When I want to show the glare on a slot
EDIT texture here for you would just be a blank square texture the size of your tile.
glares[index].showGlare(texture: symbol.texture!)
When I want to hide it
glares[index].hideGlare()

Related

SKSpriteNode init(texture: atlas!.textureNamed("something") loading incorrectly on iPhone X

G'Day,
I have a problem that has appeared in a game I wrote for my kids. After some investigation I have "discovered" the following issue:
In the original game sprite are loaded via
let sprite = SKSpriteNode(texture: atlas!.textureNamed("something")
The texture atlas used has a large number of small textures, "images" in it. This results in a working and happy game on the older devices , iPhone 7, iPad Air, iPad Pro 12.9 inch. However on the iPhone X etc., and on the iPhone X simulator any of the images loaded from the atlas are displayed either as tiny little dots or not at all. There are no compiler errors and no runtime errors. I am assuming this is something to do with screen resolution, but what? If I load the images via an init(imageNamed: "something") call the self same images are correctly scaled and displayed.
I have created a small stand alone program that illustrates this issue, code below: I also included two screen grabs showing the self same code loaded and running on an iPad and an iPhone Xsmax.
The iPad image shows two platforms and the bunnies whilst the iPhone image shows them missing.
The two atlas files contain the platform and GEM and bunny images, the first atlas also contains about 90 small images in total. As you can see the first atlas images are incorrectly loaded, however if I reduce the number of images contained below about 30 images it all works. Oh and the compiler is not complaining or splitting the atlas files up either. So I suppose the final question is in two parts:
1 What is actually happening?
2 How to fix it?
The code is as follows:
//
// GameScene.swift
// SpriteTest
//
//
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "player")
var atlas :SKTextureAtlas?
var atlas2 :SKTextureAtlas?
var tile : SKNode?
var tile2 : SKNode?
override func didMove(to view: SKView) {
// White background for visibility
backgroundColor = SKColor.white
player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
addChild(player)
// first platform "does not appear on iPhone X"
atlas = SKTextureAtlas(named: "Tiles")
tile = SKSpriteNode(texture: atlas!.textureNamed("platform"))
tile?.zPosition = 50
tile?.physicsBody?.isDynamic = true
tile?.physicsBody?.affectedByGravity = false
tile?.position = CGPoint(x: size.width * 0.1, y : size.height * 0.2)
addChild(tile!)
atlas2 = SKTextureAtlas(named: "Smallatlas")
// Second platform always appears
tile2 = SKSpriteNode(texture: atlas2!.textureNamed("platform"))
tile2?.zPosition = 50
tile2?.physicsBody?.isDynamic = true
tile2?.physicsBody?.affectedByGravity = false
tile2?.position = CGPoint(x: size.width * 0.6, y : size.height * 0.2)
addChild(tile2!)
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(addMonster),
SKAction.wait(forDuration: 1.0)
])
))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addMonster() {
// First monster "bunny" does not appear on iPhone X
let monster = SKSpriteNode(texture: atlas!.textureNamed("chocolate-bunny150"))
// Determine where to spawn the monster along the Y axis
let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
// Add the monster to the scene
addChild(monster)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
// Create the actions
let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY),
duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
monster.run(SKAction.sequence([actionMove, actionMoveDone]))
addMonster2()
}
func addMonster2() {
// Second monster always appears
let monster2 = SKSpriteNode(texture: atlas2!.textureNamed("gem"))
// Determine where to spawn the monster along the Y axis
let actualY = random(min: monster2.size.height/2, max: size.height - monster2.size.height/2)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster2.position = CGPoint(x: size.width + monster2.size.width/2, y: actualY)
// Add the monster to the scene
addChild(monster2)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
// Create the actions
let actionMove = SKAction.move(to: CGPoint(x: -monster2.size.width/2, y: actualY),
duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
monster2.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
""

SpriteKit: sprite looks blurry (with ghosting) at high velocity but fine at low velocity

When using high velocity (linear or angular) in SpriteKit, sprites look blurry as if there are "ghosts" trailing the sprite. The sprite looks fine at slow speeds.
Below is a screenshot and GIF illustrating the blurriness/ghosting problem with high linear velocity, but the problem also occurs with the angularVelocity property.
Ball Code (use SKScene below to reproduce blurriness):
let radius = CGFloat(8)
let body = SKPhysicsBody(circleOfRadius: radius)
body.isDynamic = true
body.affectedByGravity = false
body.allowsRotation = true
body.friction = 0
body.restitution = 0.0
body.linearDamping = 0.0
body.angularDamping = 0
body.categoryBitMask = categoryBitMask
let ball = SKShapeNode(circleOfRadius: radius)
ball.physicsBody = body
ball.physicsBody?.velocity.dx = 0
ball.physicsBody?.velocity.dy = -1200
Looks fine:
ball.physicsBody?.velocity.dy = -200
Looks blurry:
ball.physicsBody?.velocity.dy = -1200
Screenshot:
GIF:
SKScene (drop in project and present scene to see blurriness):
import Foundation
import SpriteKit
class TestScene : SKScene, SKPhysicsContactDelegate {
let BallBitMask : UInt32 = 0x1 << 1
let BottomWallBitMask : UInt32 = 0x1 << 3
let TopWallBitMask : UInt32 = 0x1 << 4
let RightWallBitMask : UInt32 = 0x1 << 5
let LeftWallBitMask : UInt32 = 0x1 << 6
let SceneBackgroundColor = UIColor(red: 58/255.0, green: 50/255.0, blue: 96/255.0, alpha: 1.0)
let HorizontalWallHeight = CGFloat(10)
let VerticallWallWidth = CGFloat(5)
override init() {
super.init()
}
override init(size: CGSize) {
super.init(size: size)
doInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func doInit() {
// Set background
backgroundColor = SceneBackgroundColor
// Set scale mode
scaleMode = .resizeFill
// Set anchor point to screen center
anchorPoint = CGPoint(x: 0.5, y: 0.5)
// Add walls
layoutWalls()
// Create ball
let radius = CGFloat(8)
let body = SKPhysicsBody(circleOfRadius: radius)
body.isDynamic = true
body.affectedByGravity = false
body.allowsRotation = true
body.friction = 0
body.restitution = 0.0
body.linearDamping = 0.0
body.angularDamping = 0
body.categoryBitMask = BallBitMask
body.collisionBitMask = TopWallBitMask | RightWallBitMask | BottomWallBitMask | LeftWallBitMask
let ball = SKShapeNode(circleOfRadius: radius)
ball.fillColor = UIColor.orange
ball.physicsBody = body
ball.physicsBody?.velocity.dx = 0
ball.physicsBody?.velocity.dy = -1200
// Add ball to scene
addChild(ball)
}
fileprivate func layoutWalls() {
// Set wall offset
let wallOffset = CGFloat(3)
// Layout bottom wall
let bottomWallSize = CGSize(width: UIScreen.main.bounds.width, height: HorizontalWallHeight)
let bottomWall = SKSpriteNode(color: UIColor.red, size: bottomWallSize)
bottomWall.position.y = -UIScreen.main.bounds.height/2 - bottomWallSize.height/2 - wallOffset
bottomWall.physicsBody = createWallPhysics(categoryBitMask: BottomWallBitMask, wallSize: bottomWallSize)
addChild(bottomWall)
// Layout top wall
let topWallSize = CGSize(width: UIScreen.main.bounds.width, height: HorizontalWallHeight)
let topWall = SKSpriteNode(color: UIColor.red, size: topWallSize)
topWall.position.y = UIScreen.main.bounds.height/2 + topWallSize.height/2 + wallOffset
topWall.physicsBody = createWallPhysics(categoryBitMask: TopWallBitMask, wallSize: topWallSize)
addChild(topWall)
// Layout right wall
let rightWallSize = CGSize(width: VerticallWallWidth, height: UIScreen.main.bounds.height)
let rightWall = SKSpriteNode(color: UIColor.blue, size: rightWallSize)
rightWall.position.x = UIScreen.main.bounds.width/2 + rightWallSize.width/2 + wallOffset
rightWall.physicsBody = createWallPhysics(categoryBitMask: RightWallBitMask, wallSize: rightWallSize)
addChild(rightWall)
// Layout left wall
let leftWallSize = CGSize(width: VerticallWallWidth, height: UIScreen.main.bounds.height)
let leftWall = SKSpriteNode(color: UIColor.blue, size: leftWallSize)
leftWall.position.x = -UIScreen.main.bounds.width/2 - leftWallSize.width/2 - wallOffset
leftWall.physicsBody = createWallPhysics(categoryBitMask: LeftWallBitMask, wallSize: leftWallSize)
addChild(leftWall)
}
fileprivate func createWallPhysics(categoryBitMask: UInt32, wallSize: CGSize) -> SKPhysicsBody {
// Create new physics body for wall
let physicsBody = SKPhysicsBody(edgeLoopFrom: CGRect(x: -wallSize.width/2, y: -wallSize.height/2, width: wallSize.width, height: wallSize.height))
physicsBody.isDynamic = true
physicsBody.friction = 0
physicsBody.restitution = 1.0
physicsBody.linearDamping = 0
physicsBody.angularDamping = 0.0
physicsBody.categoryBitMask = categoryBitMask
// Return body
return physicsBody
}
}
Which one of these looks more ghosty?
The "trick" is being performed by the eye. We're not equipped to deal with screens at a lowly 60fps with fast moving objects. We sustain an image on the screen and in position through a faux persistence of vision so our brains and consciousness can figure out how fast something is "moving" on the screen.
In real life we get a near infinite number of "frames" to process movement with, and depth and all sorts of other cues, so we rarely do this anywhere near as much.
We still do it, but it's much less perceptible because we've got that near infinite number of frames to call on.
The below three images do different things to reveal this in different ways.
The first one is linear speed, accelerates instantly to its velocity of rotation and stops instantly.
The second has a ramp up and ramp down to its rotational speed, which has a higher peak speed of rotation. This has an interesting effect on the brain that permits it to prepare for the velocity that's going to be achieved.
The final has a lot of fake motion blur (too much for real world motion graphics usage) that shows how effective blur is at solving the effect of this problem, and why slow shutter speeds are so incredibly important to movie making.
Linear rotation rate:
Accel and decel:
Heavily blurred:

SpriteKit animation

I need your advice, I'm new in SpriteKit I need to make animation strips. I have 3 solutions to make it, but I need advice that better and less costly for CPU.
1 solution: each stripe - SKSpriteNode with animation and texture
2 solution: background video
3 solution: each stripe - SKShapeNode with animation
This is a simple task , you don't need to build an atlas animation or use SKShapeNode, you can use SKSpriteNode as this code:
var bar = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(40, 200))
barra.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(barra)
Build n bars with random size, and use SKAction to move them.
Whith this approach your animation will be different everytime you launch it.
Code in details:
class GameScene: SKScene {
var bars: [SKSpriteNode]!
var totBars : Int = 50
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor(red: 131/255, green: 190/255, blue: 177/255, alpha: 1)
let redBarColor = SKColor(red: 204/255, green: 75/255, blue: 75/255, alpha: 1)
let yellowBar = SKColor(red: 253/255, green: 242/255, blue: 160/255, alpha: 1)
// add here your colors
var colorSelected:SKColor = redBarColor
bars = [SKSpriteNode]()
for i in 0..<totBars-1 {
let colorNum = randomNumber(1...2)
switch (colorNum) {
case 1:
colorSelected = redBarColor
case 2:
colorSelected = yellowBar
default:
break
}
let randomWidth = randomCGFloat(5,max:40)
let randomHeight = randomCGFloat(30,max:400)
let bar = SKSpriteNode.init(color: colorSelected, size: CGSizeMake(randomWidth, randomHeight))
bar.zRotation = -45 * CGFloat(M_PI / 180.0)
bar.name = "bar\(i)"
self.addChild(bar)
bars.append(bar)
}
animateBars()
}
func animateBar(bar:SKSpriteNode) {
print("- \(bar.name) start!")
let deltaX = self.randomCGFloat(0,max:self.frame.maxX)
let deltaY:CGFloat = self.frame.maxY/2
let rightPoint = CGPointMake(self.frame.maxX + deltaX,self.frame.maxY + deltaY)
let leftPoint = CGPointMake(-self.frame.maxX + deltaX,-self.frame.maxY + deltaY)
bar.position = rightPoint
let waitBeforeExit = SKAction.waitForDuration(Double(self.randomCGFloat(1.0,max:2.0)))
let speed = self.randomCGFloat(150,max:300)
let move = SKAction.moveTo(leftPoint, duration: self.getDuration(rightPoint, pointB: leftPoint, speed: speed))
bar.runAction(SKAction.sequence([waitBeforeExit,move]), completion: {
print("\(bar.name) reached position")
self.animateBar(bar)
})
}
func animateBars() {
for bar in bars {
animateBar(bar)
}
}
func getDuration(pointA:CGPoint,pointB:CGPoint,speed:CGFloat)->NSTimeInterval {
let xDist = (pointB.x - pointA.x)
let yDist = (pointB.y - pointA.y)
let distance = sqrt((xDist * xDist) + (yDist * yDist));
let duration : NSTimeInterval = NSTimeInterval(distance/speed)
return duration
}
func randomCGFloat(min: CGFloat, max: CGFloat) -> CGFloat {
return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) * (max - min) + min
}
func randomNumber(range: Range<Int> = 1...6) -> Int {
let min = range.startIndex
let max = range.endIndex
return Int(arc4random_uniform(UInt32(max - min))) + min
}
}
If your stripes are simply plain rectangles, you can use SKSpriteNodes and only give them dimensions and a color, then use actions to animate them. you can rotate the rectangles to give the effect you show in the picture.
You could actually build the whole animation in Xcode using the editor, save it in its own SKS file and load the animation using a SKReferenceNode.
To answer your question, Solution 3 is the least costly method for your CPU. Here're several reasons why this is true:
1. The first solution that you're suggesting SKSpriteNode with animation and texture" require the computer to load the texture to the view. If you have multiple .png files, this would mean that the computer would need to load all of these files. But if you were to use SKShapeNode, you would avoid having to do this and you would cheaply create the same looks as the SKShapeNode is not based on an image.
2. Solution 2 is the most costly to your CPU because running a video. This takes a lot of space in your memory which might even create lags if you run it on your phone.
Also, just another thing to note: If you were to extensively use Solution 1 and 2, you would end up using so much memory. But if you use the Solution 3, you will not deal with this.
Another option, in addition to the ones put forth already, would be to write a GLSL shader for the background. If you just want a cool backdrop that doesn't interact with the rest of your game, this would probably be the most performant option, since it would all happen on the GPU.
You could even, conceivably, render the rectangles without requiring any images at all, since they are just areas of color.
There's a decent introduction to the topic here: Battle of Brothers GLSL Introduction.

How can I make my backgrounds use less memory?

I am trying to add a vertical scrolling background to my project. From what I scene on the internet. My background consists of 8 images, each [320x1000px].png files. So what I ended up doing for it was this:
//Layered Nodes
var backgroundNode: SKNode!
override init(size: CGSize) {
super.init(size: size)
scaleFactor = self.size.width / 320.0
// Background
backgroundNode = createBackgroundNode()
addChild(backgroundNode)
}
func createBackgroundNode() -> SKNode {
let backgroundNode = SKNode()
let ySpacing = 1000.0 * scaleFactor
for index in 0...3 {
let node = SKSpriteNode(imageNamed:String(format: "bg%d", index + 1))
node.setScale(scaleFactor)
node.anchorPoint = CGPoint(x: 0.5, y: 0.0)
node.position = CGPoint(x: self.size.width / 2, y: ySpacing * CGFloat(index))
backgroundNode.addChild(node)
}
return backgroundNode
}
Problem is, they use up to 50Mb of the project. I am trying to find a way to do it so it would take much less memory off my game but I can't seem to find it. Is there anything wrong with this? If not, should I best focus on other parts of the project and keep it this way?
You can create background on the go from vector drawing using paint-code app.
Another point. Are your images optimized? If no you can use ImageOptim for free.

color match with sprite kit and swift

many thanks in advance.
I was trying to search on StackOverflow or just google it, maybe I used wrong key words or something else, I was unable to find an answer to my question.
So I'm new to iOS programing, here is what I did, I set a square in the middle of the screen with 4 different colors(this is a picture), every time I tap on the screen, it will rotate 90 degrees. There are also smaller balls that will come from outside of the screen with the colors, like red ball, green ball, blue ball, etc. When the ball contacts the square with the same color, score one point. Just like the game .
So how should I set up the square with different colors to accomplish this?
I thought it can only set one color to a single sprite. or I should put 4 triangles together to make the square?
You can setup the four colored Square using the following code.
class FourColorSquare : SKNode {
private var colors : [UIColor] = []
private var size : CGSize!
init(colors : [UIColor], size: CGSize) {
super.init()
self.colors = colors
self.size = size
self.setupNodes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupNodes() {
let node1 = SKSpriteNode(color: colors[0], size: self.size)
node1.position = CGPointMake(0, 0)
node1.anchorPoint = CGPointMake(1, 0)
addChild(node1)
let node2 = SKSpriteNode(color: colors[1], size: self.size)
node2.position = CGPointMake(0, 0)
node2.anchorPoint = CGPointMake(0, 0)
addChild(node2)
let node3 = SKSpriteNode(color: colors[2], size: self.size)
node3.position = CGPointMake(0, 0)
node3.anchorPoint = CGPointMake(0, 1)
addChild(node3)
let node4 = SKSpriteNode(color: colors[3], size: self.size)
node4.position = CGPointMake(0, 0)
node4.anchorPoint = CGPointMake(1, 1)
addChild(node4)
}
func rotate(angle : CGFloat, animated : Bool) {
var rotateAction : SKAction!
if animated {
rotateAction = SKAction.rotateByAngle(angle, duration: 0.6)
}
else {
rotateAction = SKAction.rotateByAngle(angle, duration: 0)
}
for node in self.children as [SKSpriteNode] {
node.runAction(rotateAction)
}
}
}
You can use it like this
let colors = FourColorSquare(colors: [.redColor(),.greenColor(),.blueColor(),.yellowColor()], size: CGSizeMake(50, 50))
colors.position = CGPointMake(200, 100)
addChild(colors)
colors.rotate(-3.14/2, animated: true)
You can setup separate physics bodies to each node in the FourColorSquare to detect collision with each color. Each color should have a separate categoryBitMask to test collision with the each colored ball.
You can use path, stroke and fill functions associated with the graphics CGContext and modify the code below to make it draw the pattern you want in the image and fill different sections with colors. The resulting UIImage would be the basis of a new Sprite. See the CGContext Documentation
import CoreImage
import CoreGraphics
int width = 100
int height = 100
var colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue)
var ctx = CGBitmapContextCreate(nil, width, height, 8, 0, colorspace, bitmapInfo)!
.
. draw into your ctx (context) here
.
var image = UIImage(CGImage: CGBitmapContextCreateImage(ctx))!

Resources