teaching myself spritekit while recouping from back surgery. Any help or instruction you may provide is greatly appreciated.
I have a function that adds a "monster" sprite with a child SKEmitter. I would like to update this emitter to "turn on" and "turn off" the SKEmitter. This functions as long as there is only a single sprite on the screen. The moment a second node is created from the function, the first one stops turning on and off.
It appears to me that the SKAction is only acting on the latest node. I just can't anything that would tell me how to fix this.
Example of what I see in the UI.
My code:
flameThrowerSplash = SKEmitterNode(fileNamed: "flamesThrowerSplash.sks")!
let newX = flameThrowerSplash.position.x - 475
let newY = flameThrowerSplash.position.y + (zombie1.size.height * 0.7)
let newLocation = CGPointMake(newX, newY)
flameThrowerSplash.position = newLocation
flameThrowerSplash.targetNode = currentScene
let wait = SKAction.waitForDuration(0.5)
let pauseEmitter = SKAction.runBlock({self.flameThrowerSplash.particleBirthRate = 0})
let startEmitter = SKAction.runBlock({ self.flameThrowerSplash.particleBirthRate = 455})
let wait2 = SKAction.waitForDuration(0.5)
let run = SKAction.sequence([wait, startEmitter,wait2, pauseEmitter])
zombie1.runAction(SKAction.repeatActionForever(run))
it doesnt look like you want flameThrowerSplash to be a scene property.
instead of this
flameThrowerSplash = SKEmitterNode(fileNamed: "flamesThrowerSplash.sks")!
just declare it as a new variable
let flameThrowerSplash = SKEmitterNode(fileNamed: "flamesThrowerSplash.sks")
then you can add it to each zombie independently.
you were reassigning it for each new zombie.. thats why it was disappearing.
Related
init () {
super.init(texture: nil, color: .clear, size: initialSize)
// Create physicsBody based on a frame of the sprite
// basically giving it gravity and physics components
let bodyTexture = textureAtlas.textureNamed("MainCharacterFlying1")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
// Lose momentum quickly with high linear dampening
self.physicsBody?.linearDamping = 0.9
// weighs around 30 kg
self.physicsBody?.mass = 30
// no rotation
self.physicsBody?.allowsRotation = false
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
}
// Animations to make the main character seem like it is flying
func createAnimations() {
let rotateUpAction = SKAction.rotate(byAngle: 0, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(byAngle: -1, duration: 0.8)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying1"), textureAtlas.textureNamed("MainCharacterFlying2"),textureAtlas.textureNamed("MainCharacterFlying3"), textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying5"),textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying3"),textureAtlas.textureNamed("MainCharacterFlying2")]
var flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.1)
flyAction = SKAction.repeatForever(flyAction)
flyAnimation = SKAction.group([flyAction,rotateUpAction])
let soarFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying5")]
var soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAction = SKAction.repeatForever(soarAction)
let soarAnimation = SKAction.group([soarAction,rotateDownAction])
}
When I run this code on the IOS Simulator, I have to click on the screen once in order for my main sprite to show up on the screen, or else it will not. And when I click on the sprite, the sprite will start flapping its wings and go up (I have other code for that) however, the rotateUpAction and rotateDownAction are not showing up at all on the Simulator. So I was wondering if there were any solutions and anyone willing to answer.
Thank you for your time. Also, this code from the class of the main character, the name of the class is "Player"
you declare let soarAnimation inside your createAnimations function, meaning it's not in scope when you call self.run(soarAnimation). Solution: declare soarAnimation as a class property. Alternate solution: have createAnimations() return the SKAction and grab it in init that way
I am making a game where I would like to spawn obstacles at random times. The obstacles are going to make contact with the player.
Here the code that I have:
var obstacle : SKSpriteNode?
func createObstacles() {
let obstaclesArray = ["obstacle_1", "obstacle_2", "obstacle_3", "obstacle_4", "obstacle_5"]
let randomObs = obstaclesArray.randomElement()
let selectedTexture = SKTexture(imageNamed: randomObs!)
obstacle = SKSpriteNode(imageNamed: randomObs!)
obstacle?.physicsBody = SKPhysicsBody(texture: selectedTexture, size: selectedTexture.size())
obstacle?.position = CGPoint(x: scene!.size.width/2 + selectedTexture.size().width, y: -120)
obstacle?.zPosition = -15
if let obs = obstacle?.physicsBody {
obs.affectedByGravity = true
obs.allowsRotation = false
obs.isDynamic = true
obs.restitution = 0
obs.categoryBitMask = obstacleCategory
obs.collisionBitMask = floorCategory
obs.contactTestBitMask = heroCategory
}
addChild(obstacle!)
}
func spawnObstacles() {
let wait = SKAction.wait(forDuration: 1, withRange: 0.4)
let spawn = SKAction.run {
createObstacles()
}
let moveLeft = SKAction.moveBy(x: -scene!.size.width - obstacle.size.width - 10, y: 0, duration: 2)
let sequence = SKAction.sequence([wait, spawn, moveLeft, SKAction.removeFromParent()])
self.run(SKAction.repeatForever(sequence))
}
I read some a similar questions but the response is my same code but it is not working.
Spawning a Spritekit node at a random time
I also tried other way:
var randDelay = Double.random(in: 0.7 ..< 1.4)
DispatchQueue.main.asyncAfter(deadline: randDelay, execute: {
if self.canCreateObstacle == true {
self.spawnObstacle()
}})
But it is not working and everytime I restart the game it seems like the function is being called two times, if I restart the game a third time it is called 3 times and so on.
Anyone with a good and clean solution to spawn objects at random times?
Do not use DispatchQueue Set up a repeating sequential action using wait(forDuration:withRange:) like you previously had.
https://developer.apple.com/documentation/spritekit/skaction/1417760-wait
First, create a generic node used to spawn obstacles, then attach this generic node to the scene.
Finally assign the repeating sequential action to this node.
Boom, you are done.
The reason why you want to assign it to a random node is because you want to be able to give your game the opportunity to stop generating obstacles, plus you alter the speed property to make the node generate nodes faster or slower.
You also want to detach the spawning/waiting from the moving/destroying, because as of right now, your code is confused. You are saying move the scene left for 2 seconds, then wait a random amount of time to spawn the next enemy, but I think you are trying to just spawn enemies on a time interval and move the enemy to the left.
Your scene code should look something like this
class GameScene : SKScene{
let obstacleGenerator = SKNode()
func didMove(to: view){
let wait = SKAction.wait(forDuration: 1, withRange: 0.4)
let spawn = SKAction.run({createObstacles()})
let sequence = SKAction.sequence([wait, spawn])
obstacleGenerator.run(SKAction.repeatForever(sequence))
addChild(obstacleGenerator)
}
func createObstacles() {
let obstaclesArray = ["obstacle_1", "obstacle_2", "obstacle_3", "obstacle_4", "obstacle_5"]
let randomObs = obstaclesArray.randomElement()
let selectedTexture = SKTexture(imageNamed: randomObs!)
obstacle = SKSpriteNode(imageNamed: randomObs!)
obstacle.position = CGPoint(x: scene!.size.width/2 + selectedTexture.size().width, y: -120)
obstacle.zPosition = -15
let body = SKPhysicsBody(texture: selectedTexture, size: selectedTexture.size())
body.affectedByGravity = true
body.allowsRotation = false
body.isDynamic = true
body.restitution = 0
body.categoryBitMask = obstacleCategory
body.collisionBitMask = floorCategory
body.contactTestBitMask = heroCategory
obstacle.physicsBody = body
addChild(obstacle!)
let moveLeft = SKAction.moveBy(x: -scene!.size.width - obstacle.size.width - 10, y: 0, duration: 2)
let seq = SKAction.sequence([moveLeft,SKAction.removeFromParent()])
obstacle.run(seq)
}
}
Now as for your spawning increasing with each reset, you never post how you are resetting. I am going to assume you never remove the previous action upon reset, and this is why your rate increases. It is always better to just create a new GameScene instance when doing a reset.
I am trying to make a sprite move towards a point while avoiding some obstacles. The graph that I am using is a GKObstacleGraph, obtained from this scene:
For simplicity I decided not to use a circular physics body for the obstacles, but it's enough to use the sprites' bounds for now. So this is how I created the graph:
lazy var graph:GKObstacleGraph? =
{
guard let spaceship = self.spaceship else { return nil }
let obstacles = SKNode.obstacles(fromNodeBounds: self.rocks)
let graph = GKObstacleGraph(obstacles: obstacles, bufferRadius: Float(spaceship.size.width))
return graph
}()
When the user taps on any location of the scene, the spaceship should start moving toward that position by avoiding the obstacles:
func tap(locationInScene location:CGPoint)
{
guard let graph = self.graph else { return }
guard let spaceship = self.spaceship else { return }
let startNode = GKGraphNode2D(point: vector_float2(withPoint: spaceship.position))
let endNode = GKGraphNode2D(point: vector_float2(withPoint: location))
graph.connectUsingObstacles(node: startNode)
graph.connectUsingObstacles(node: endNode)
let path = graph.findPath(from: startNode, to: endNode)
print(path)
let goal = GKGoal(toFollow: GKPath(graphNodes: path, radius: Float(spaceship.size.width)) , maxPredictionTime: 1.0, forward: true)
let behavior = GKBehavior(goal: goal, weight: 1.0)
let agent = GKAgent2D()
agent.behavior = behavior
graph.remove([startNode, endNode])
spaceship.entity?.addComponent(agent)
// This is necessary. I don't know why, but if I don't do that, I
// end up having a duplicate entity in my scene's entities array.
self.entities = [spaceship.entity!]
}
But when I tap on a point on the scene, the spaceship starts moving indefinitely upwards. I tried to print the position of the GKAgent at every frame, and this is what I got:
With very low values, the spaceship still keeps moving upwards without stopping, ever.
This is my project on GitHub.
Here's my code:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let origin = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
let delay = SKAction.waitForDuration(2.0)
for (var i = 0; i < 6; i++) {
//generate a random point on a circle centered at origin with radius of 500
let point = randomPointOnCircle(500, center: origin)
let chanceToSpawn = arc4random_uniform(100)
switch chanceToSpawn {
case 0...60:
self.runAction(delay){
let smallBall = SKSpriteNode(imageNamed: "smallBall”)
self.addChild(smallBall)
smallBall.xScale = 0.05
smallBall.yScale = 0.05
smallBall.position = point
let finalDest = CGPointMake(origin.x, origin.y)
let moveAction = SKAction.moveTo(finalDest, duration: 5.0)
smallBall.runAction(SKAction.sequence([SKAction.waitForDuration(0.1), moveAction, SKAction.runBlock({
smallBall.removeFromParent()
})]))
delay
}
break
case 61...80:
//same as case 0…60, smallBall changed to “mediumBall”
break
case 81...99:
//same as case 0…60, smallBall changed to “largeBall”
break
default:
break
} // ends switch statement
}//ends for loop
}//end didMoveToView
Basically I have balls that spawn on a random point on a circle and move towards the center. But the problem I am having is that no matter how I rearrange things (or rewrite things, i.e., trying different loops or defining functions for spawning the ball and the movement of the ball and simply calling them from inside the switch statement) all of the balls spawn at the same time. I am trying to make it so they spawn one after the other (meaning a ball spawns, the thread waits a few seconds, then spawns another ball.), and I can't seem to figure out why each ball is spawning at the same time as all the other balls.
I'm fairly new to Swift/Spritekit programming and I feel like I'm overlooking some small detail and this is really bugging me. Any help appreciated!
For loops are executed faster than you think! Your for loop is probably executed instantly.
"Why is that? I have already called self.runAction(delay) to delay the for loop!" you asked. Well, the delay action doesn't actually delay the for loop, unfortunately. It just delays the other actions you run on self.
To fix this, try delaying different amounts, dependind on the for loop counter:
self.runAction(SKAction.waitForDuration(i * 2 + 2)) { ... }
This way, balls will appear every two seconds.
try this, may be due to same time u are adding the actions for each balls,
smallBall.runAction(SKAction.sequence([SKAction.waitForDuration(0.1 + (i * 0.35/2)), moveAction, SKAction.runBlock({
smallBall.removeFromParent()
})])) // delay added (0.1 + (increment the daly))
try this
Your problem is you are calling runAction on each iteration of your for loop. This means that all your actions will run concurrently.
What you want to do is create an array of actions. then after the for loop, run a sequence of actions.
(Note: I took the liberty of cleaning up your code)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let origin = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
let delay = SKAction.waitForDuration(2.0)
var sequence = [SKAction]()
for (var i = 0; i < 6; i++) {
//generate a random point on a circle centered at origin with radius of 500
let point = randomPointOnCircle(500, center: origin)
let chanceToSpawn = arc4random_uniform(100)
var ballname = "smallBall”
switch chanceToSpawn {
case 61...80:
//same as case 0…60, smallBall changed to “mediumBall”
ballname = "mediumBall”
case 81...99:
//same as case 0…60, smallBall changed to “largeBall”
ballname = "largeBall”
default: ()
} // ends switch statement
sequence.append(delay)
sequence.append(
SKAction.run()
{
[unowned self] in
let ball = SKSpriteNode(imageNamed: ballName)
self.addChild(ball)
ball.xScale = 0.05
ball.yScale = 0.05
ball.position = point
let finalDest = CGPointMake(origin.x, origin.y)
let moveAction = SKAction.moveTo(finalDest, duration: 5.0)
let briefWait = SKAction.waitForDuration(0.1)
let remove = SKAction.removeFromParent()
let moveSeq = SKAction.sequence([briefWait ,moveAction,remove])
ball.runAction(moveSeq)
}
)
}//ends for loop
self.run(SKAction.sequence(sequence))
}//end didMoveToView
just a quick question about projectiles in SpriteKit using swift.
I have the following code for my "monster" that I want to shoot out shuriken smoothly however, it is right now just popping up at the randomized point, instead of smoothly shooting it out like from my character when I code it in the touhcesBegan function
Here's the code:
func monsterShoots() {
let monsterShuriken = SKSpriteNode(imageNamed: "projectile-1")
monsterShuriken.position = monster.position
addChild(monsterShuriken)
monster.position.y += CGFloat(arc4random_uniform(30)) - CGFloat(arc4random_uniform(30))
monsterShuriken.position.x -= CGFloat(arc4random_uniform(1000))
let actionMoveMonster = SKAction.moveTo(monsterShuriken.position, duration: 1.0)
let actionMoveDoneMonster = SKAction.removeFromParent()
monsterShuriken.runAction(SKAction.sequence([actionMoveMonster, actionMoveDoneMonster]))
}
and I repeat the action in didMoveToView with:
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(monsterShoots),
SKAction.waitForDuration(1.0)
])
))
Thanks
I think you need to write this:
monster.position.y += CGFloat(arc4random_uniform(30)) - CGFloat(arc4random_uniform(30))
before this:
monsterShuriken.position = monster.position