How can I create a visual effect of traveling wave like this in Swift SpriteKit?
I am using an extension to the SKAction that performs the oscillatory movement in the node, but I still do not know how to create the trail. I though in creating copies but it did not worked.
extension SKAction {
static func oscillation(scene: SKScene, amplitude a: CGFloat, timePeriod t: CGFloat, midPoint: CGPoint) -> SKAction {
let action = SKAction.customAction(withDuration: Double(t)) { node, currentTime in
let displacement = a * sin(2 * .pi * currentTime / t)
node.position.y = midPoint.y + displacement
let copy = node.copy() as! SKSpriteNode
copy.position.x = node.position.x
copy.position.y = node.position.y
scene.addChild(copy)
}
return action
}
}
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let node = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 50, height: 50))
node.position = CGPoint(x: 25, y: size.height / 2)
self.addChild(node)
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: 1, midPoint: node.position)
node.runAction(SKAction.repeatActionForever(oscillate))
node.runAction(SKAction.moveByX(size.width, y: 0, duration: 5))
}
}
I would try a particle emitter attached to the moving node. It could be set to produce dot shaped particles quickly enough that they would overlap and make a line. The particles would move at a constant speed to the left, with no variations or fade. Set the count high enough that the oldest particles go out of view before disappearing. If you don’t need a solid line, you could cut down the emitter rate and make a dotted line showing the traveling wave. There are oodles of options in particle emitters, so you could play around with the settings to get other effects if appropriate for your project.
Related
I am creating a 2D macOS game using SpriteKit and I want a continuous scrolling background from left to right.
I have a background image that is the same size as my frame. The image is duplicated side-by-side: initially the left one is centred and the right is off-screen. Both images (SKSpriteNode) are animated to slide across the screen and then reset their positions once moved by a full frame-width.
Here is the relevant code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var background = SKSpriteNode()
func makeBackground() {
let texture = SKTexture(imageNamed: "bg-empty")
let scroll = SKAction.move(by: CGVector(dx: -texture.size().width, dy: 0), duration: 30)
let reset = SKAction.move(by: CGVector(dx: texture.size().width, dy: 0), duration: 0)
let animation = SKAction.repeatForever(SKAction.sequence([scroll, reset]))
for idx in 0...1 {
background = SKSpriteNode(texture: texture)
background.position = CGPoint(x: CGFloat(idx)*texture.size().width, y: self.frame.midY)
background.zPosition = -1
background.run(animation)
self.addChild(background)
}
}
override func didMove(to view: SKView) {
makeBackground()
}
}
Although this works, I notice a black (~1 pixel vertical strip) flicker that appears ad-hoc at the seam of the connection.
What is causing this flicker and how do I get rid of it?
You are encountering floating point rounding errors. This is going to lead to a situation where your first BG rounds down, and your second BG rounds up, giving you a 1 pixel gap.
instead, try the following code
class GameScene: SKScene {
private var background = SKNode()
func makeBackground() {
let texture = SKTexture(imageNamed: "bg-empty")
let scroll = SKAction.move(by: CGVector(dx: -texture.size().width, dy: 0), duration: 30)
let reset = SKAction.move(by: CGVector(dx: texture.size().width, dy: 0), duration: 0)
let animation = SKAction.repeatForever(SKAction.sequence([scroll, reset]))
for idx in 0...1 {
let subNode = SKSpriteNode(texture: texture)
subNode.position = CGPoint(x: CGFloat(idx)*texture.size().width, y: self.frame.midY)
background.addChild(subNode)
}
background.zPosition = -1
background.run(animation)
self.addChild(background)
}
override func didMove(to view: SKView) {
makeBackground()
}
}
Now you can avoid the gap because you are not running 2 different actions.
If you want to be able to scroll both ways. then place a texture to the left of your bg.
This will place a background node before and after your main background, and essentially turn it into 1 big node, with only the middle texture showing in its entirety.
If you have multiple background images, then just place the last frame of your background also to the left
I'm having trouble using SKNode.contains() on scaled sprites, and I can't figure out what important concept I'm missing.
I have "gremlin" sprites that move around inside a "jail" sprite. Gremlins can go through walls sometimes, and I want to know when they've left the jail. I thought I could simply use jail.contains(gremlin) (or some variant), perhaps with a bit of math done on it to get the scale right, but no joy. I can't seem to figure out which sprite or which frame, or which size, or what kind of transform, or whatever, to use with contains().
I've read quite a bit about coordinate systems, frames and bounds, hit testing, scaling, frames, sizes, origins, anchor points, everything I've been able to find. I've read this SO question, and this, this, this, and this, and quite a few others.
I'm missing something. Here's some stripped-down code that shows the problem I'm having. This is the cleaned-up version. I've tried every permutation of convertPoint(), shrinking and growing size, shrinking and growing scale, changing who is whose parent, and various other desperate attempts to understand. No luck.
In this sample code, just to get my head around the problem, I'd like to control which of the big rectangles is to serve as the container for the small ones. The way it's set right now, I'm trying to get the little rectangles outside the red rectangle to be gray. As you can see, it seems that the red rectangle's hit-test area is the same size as that of the blue rectangle. That is, everything is green except the ones out at the very edges of the blue rectangle.
Help!
class GameScene: SKScene {
static var shared: GameScene?
var bottomSprite: SKSpriteNode!
var middleSprite: SKSpriteNode!
var setupComplete = false
var spriteTexture: SKTexture?
var textureAtlas: SKTextureAtlas?
var topSprite: SKSpriteNode!
func getMarkerColor(outerSprite: SKSpriteNode, innerSprite: SKSpriteNode) -> NSColor {
return outerSprite.frame.contains(innerSprite.frame) ? .green : .gray
}
override func didMove(to view: SKView) {
GameScene.shared = self
spriteTexture = SKTexture(imageNamed: "debugRectangle")
}
func drawContainerSprite(parent: SKNode, scale: CGFloat, color: NSColor) -> SKSpriteNode {
let sprite = SKSpriteNode(texture: spriteTexture)
sprite.color = color
sprite.colorBlendFactor = 1.0
sprite.anchorPoint = CGPoint(x: 0.5, y: 0.5)
sprite.setScale(scale)
sprite.size = scene!.size
parent.addChild(sprite)
sprite.position = CGPoint.zero
return sprite
}
func drawMarkerSprite(parent: SKNode, scale: CGFloat) -> SKSpriteNode {
let sprite = SKSpriteNode(texture: spriteTexture)
sprite.size = CGSize(width: bottomSprite.size.width * 0.05, height: bottomSprite.size.height * 0.05)
sprite.colorBlendFactor = 1.0
sprite.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let x = Int.random(in: Int(-self.bottomSprite.size.width)..<Int(self.bottomSprite.size.width))
let y = Int.random(in: Int(-self.bottomSprite.size.height)..<Int(self.bottomSprite.size.height))
sprite.position = CGPoint(x: x, y: y)
parent.addChild(sprite)
return sprite
}
override func update(_ currentTime: TimeInterval) {
if !setupComplete {
bottomSprite = drawContainerSprite(parent: self.scene!, scale: 0.5, color: .blue)
middleSprite = drawContainerSprite(parent: bottomSprite, scale: 0.5, color: .orange)
topSprite = drawContainerSprite(parent: middleSprite, scale: 1.0, color: .red)
setupComplete = true
}
let markerSprite = drawMarkerSprite(parent: self.scene!, scale: 1.0)
markerSprite.color = getMarkerColor(outerSprite: topSprite, innerSprite: markerSprite)
}
}
Here is what you're missing: SKNode.contains(_:) is not the same as CGRect.contains(_:).
SKNode.contains(_:) is not about rectangles. It's about hit-testing whether a point is inside a parent node. CGRect.contains(_:) is about rectangles, and it knows nothing about nodes, or scaling, or SpriteKit. If you're going to use a sprite's frame to check whether the gremlins are getting out of jail, you'll have to scale it yourself.
I'm making a SpriteKit game. I have six cannons that I've made rotate back and fourth. I want to shoot cannonballs from each cannon that sync with the rotation of each cannon. I want a duration of one second between each cannonball.
So basically: A cannon that is in constant rotation is shooting cannonballs in the same direction as the rotation.
For the cannons i'm using an extension:
extension CGFloat {
func degreesToRadians() -> CGFloat {
return self * CGFloat.pi / 180
}
}
I'm gonna put the code for just one cannon, since I should be able to figure out how to adjust one of the cannonball movements to the others. Here is one:
func createCannons() {
let cannonLeftBottom = SKSpriteNode(imageNamed: "Cannon")
cannonLeftBottom.anchorPoint = CGPoint(x: 0.5, y: 0.5)
cannonLeftBottom.position = CGPoint(x: -325, y: -420)
cannonLeftBottom.zPosition = 4
cannonLeftBottom.setScale(0.4)
cannonLeftBottom.zRotation = CGFloat(65).degreesToRadians()
let rotateLB = SKAction.rotate(byAngle:
CGFloat(-65).degreesToRadians(), duration: 2)
let rotateBackLB = SKAction.rotate(byAngle:
CGFloat(65).degreesToRadians(), duration: 2)
let repeatRotationLB =
SKAction.repeatForever(SKAction.sequence([rotateLB,rotateBackLB]))
cannonLeftBottom.run(repeatRotationLB)
self.addChild(cannonLeftBottom)
}
Here is my function for the cannonball:
func createBalls() {
let cannonBallLB = SKSpriteNode(imageNamed: "Ball")
cannonBallLB.name = "CannonBall"
cannonBallLB.position = CGPoint(x: -325, y: -420)
cannonBallLB.physicsBody = SKPhysicsBody(circleOfRadius:
cannonBallLB.size.height / 2)
cannonBallLB.physicsBody?.affectedByGravity = false
cannonBallLB.zPosition = 3
cannonBallLB.setScale(0.1)
self.addChild(cannonBallLB)
}
THX!
You need to convert from Polar Coordinates to Rectangular Coordinates.
You do this by using sin and cos
E.G.
let speed = 100 //This would mean move 100 points per second
let force = CGVector(dx:cos(cannon.zRotation) * speed,dy:sin(cannon.zRotation) * speed)
cannonBall.applyForce(force)
Note: Now unless they changed this, force used to be in units of points, if they fixed it to units of meters, then you need to divide your speed by 150, since 150 points = 1 meter in Spritekit
I am trying to implement an endless scrolling ground with Swift and Spritekit. I managed to get the scrolling ground to work if the ground is a flat surface. But I want the ground to be on a decline like it is going down a hill. How can I do this? Here is the code I currently have:
func createGround() {
for i in 0...2 {
let grounds = SKSpriteNode(color: UIColor.white, size: CGSize(width: 600, height: 200))
grounds.name = "Ground"
grounds.anchorPoint = CGPoint(x: 0.5,y: 0.5)
grounds.position = CGPoint(x: CGFloat(i) * grounds.size.width, y: -300)
self.addChild(grounds)
}
}
func moveGround() {
self.enumerateChildNodes(withName: "Ground", using: ({
(node, error) in
node.position.x -= 6
node.position.y += 1
if node.position.x < -((self.scene?.size.width)!) {
node.position.x += (self.scene?.size.width)! * 3
node.position.y =
-400
}
}))
}
This creates the ground and scrolls them through the screen at an angle, but the ground nodes do not line up with each other. I imagine I will need some trigonometric function to make them line up? Here is what is currently looks like:
The white rectangles are my ground nodes. The grey is just a placeholder ground node which is not moving and I plan on deleting once I get the scrolling ground working.
I am fairly new to iOS Swift and sprite kit, but I am currently developing a game where I would like items (SKSpriteNodes) to appear at random times and when they do appear, they don't overlap with existing nodes in the game scene.
Below is my code so far for producing a random node and a specified with range time interval to appear across the scene width/height. However, I haven't incorporated the code that allows them to not overlap or be on top of other present nodes in the scene.
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration: 10, withRange: 5)
let spawn = SKAction.run {
self.randomFood()
}
let spawning = SKAction.sequence([wait,spawn])
self.run(SKAction.repeat((spawning), count: 5))
}
func randomFood() {
//supposed to pick random point within the screen width
let xPos = randomBetweenNumbers(firstNum: 0, secondNum: frame.width )
let foodNode = SKSpriteNode(imageNamed: "burgerFood") //create a new food item each time
foodNode.position = CGPoint(x: xPos, y: self.frame.size.height/2)
foodNode.setScale(0.10)
foodNode.physicsBody = SKPhysicsBody(circleOfRadius: foodNode.size.width/2)
foodNode.zPosition = 10
foodNode.alpha = 1
foodNode.physicsBody?.affectedByGravity = false
foodNode.physicsBody?.categoryBitMask = 0
foodNode.physicsBody?.contactTestBitMask = 0
addChild(foodNode)
}
func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}