I'm looking for some help understanding why my speedToPoint variable is always 1 when it gets to the wait action.
This is part of an SKSpriteNode extension i wrote for making a bird fly to a random point in a predefined rectangle. The speedToPoint is also randomized between 1 and 4 and used as the duration for the moveTo action. However, i also need to use that TimeInterval for my wait block in the action sequence.
speedToPoint is indeed being randomized in the run block (i've confirmed). How can i use that same randomized number in the wait block in the next part of the sequence?
var speedToPoint:TimeInterval = 1
self.run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run{
speedToPoint = TimeInterval(Globals.sharedInstance.randomF(min: 1, max: 4))
var pointX = Globals.sharedInstance.randomF(min: left,max: right)
let pointY = Globals.sharedInstance.randomF(min: top,max:bottom)
while abs(self.position.x.distance(to: pointX)) < 200 {
pointX = Globals.sharedInstance.randomF(min: left,max: right)
}
self.xScale = pointX < self.position.x ? -1 : 1
self.run(SKAction.move(to: CGPoint(x:pointX,y:pointY),duration: speedToPoint))
},
SKAction.wait(forDuration: speedToPoint)])
),withKey: "inFrame")
To clarify, what i'm really tring to do is have the bird fly to a point, once it's arrived at that point, fly to another point. I'm still wrapping my heard around action sequences and whether or not they actually wait for completion to move on. Which from what i've read, they do, but not for any move actions. That's why the wait is in there. Perhaps there is another way?
An SKAction, once created, can't be modified, and it is meant to be reused eg. you can't modify the duration parameter, or change other passed parameters. This means that you have to re-create it if you need it changed. Of course you can change the speed property of an existing action, or you can pause the action but that's pretty much it when it comes to modifying the existing action.
To solve your issue, you could do next:
1) Create an action which moves a sprite to a specific location
2) Once the action is completed, you create a new one which does the same
you can do this using recursion, like this (just copy & paste the code to see how it works):
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
private var bird = SKSpriteNode(color: .purple, size: CGSize(width: 100, height: 100))
override func didMove(to view: SKView) {
addChild(bird)
recursive()
}
func recursive(){
let sequence = SKAction.sequence([
SKAction.move(to: self.randomPoint(inRect: self.frame), duration: TimeInterval(self.random(between: 1, and: 3))),
SKAction.run({[unowned self] in NSLog("Block executed"); self.recursive()})
])
self.bird.run(sequence, withKey: "aKey")
}
func random(between minimum: CGFloat, and maximum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(minimum - maximum) + min(minimum, maximum)
}
func randomPoint(inRect rect:CGRect)->CGPoint{
let x = random( between: -rect.size.width / 2.0 , and: rect.origin.x + rect.size.width/2.0)
let y = random(between: -rect.size.height / 2.0 , and: rect.origin.y + rect.size.height/2.0)
return CGPoint(x: x, y: y)
}
}
To stop this action, remove the key associated with it.
Related
I am creating a card game and having trouble having SKActions run in a sequence across multiple SK Objects (Nodes, Textures, Labels, etc.). Any help would be much appreciated! In the example below, I am trying to create a "dealing" motion. Where 1 card gets dealt, some time elapses, then the next card gets dealt. I tried using this, but with 52 cards, it's not exactly the simplest approach.
First, I tried creating an array of SKActions for each card. However, I don't believe I can run the below using one command based on my understanding of the documentation. Each action needs to be run against the specific Sprite Object as opposed to running a whole sequence of actions across multiple Sprite Objects.
let dealAction = SKAction[]()
for card in deck {
let move = SKAction....
dealAction.append(move)
}
run.SKAction.sequence(dealAction) // This will not work.
Then I tried this with the hope that the loop would complete each cards block of code before moving on to the next card. However, all the actions run at the same time. Now, I am a bit lost and I don't know exactly how to implement this efficiently. The only thing I could think of was creating a "timingIndex", where .2 seconds gets added to the wait time for each card. So even though they are all running at the same time, the wait time grows for each card. Not sure if this is the best way to approach the problem however and was hoping there was a more elegant solution.
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: 1)
card.run(SKAction.sequence[move, wait])
}
// Possible Solution
let timingIndex = 0.2
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: timingIndex)
card.run(SKAction.sequence[move, wait])
timingIndex += 0.2
}
import SpriteKit
import UIKit
let screenH = 100
let screenW = 50
class Player {
var handLocation: CGPoint
var pile = [Card]()
init() {
handLocation = CGPoint(x: 100, y: 583)
}
}
class Card: SKSpriteNode {
}
struct Game {
var player1 = Player()
var player2 = Player()
var deck = [Card]()
func dealDeck() {
for card in deck {
player1.pile.append(card) // This would theoretically loop through both players
card.position = CGPoint(x: screenW / 2, y: screenH / 2)
let move = SKAction.move(to: player1.handLocation, duration: 1)
card.run(move)
}
}
}
This worked! Worried about the use of this overall and if there exists a better solution still, but for the time being, this worked.
// Possible Solution
let timingIndex = 0.2
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: timingIndex)
card.run(SKAction.sequence[move, wait])
timingIndex += 0.2
}
I am making a game and I have objects which fall from the top of the screen to the bottom. I want to spawn choose between the objects and drop one of them. I currently the drop all at the same time.
func ShapePicker() -> SKSpriteNode{
let shapeArray = [purpleOctagon, coin, greenTriangle, orangeHexagon]
let MaxValue = self.size.width / 2 - 200
let MinValue = self.size.width / 3 * 0.95
let rangeMax = UInt32(MaxValue)
let rangeMin = UInt32(MinValue)
purpleOctagon.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(purpleOctagon)
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(greenTriangle)
coin.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(coin)
return shapeArray[Int(arc4random_uniform(UInt32(shapeArray.count)))]
}
I would like the program to randomly .addChild because right now it just puts them on the screen.
Your code implies that you want all of them to be on the screen, and then one randomly drops... So you do want to continue to .addChild. What you want, is for them to NOT drop all at once.
So, you need to change the .physicsBody.pinned to true to keep them at the top of the screen.
Then, in your update() you can check for how much time has passed, etc, and after a certain # of seconds you can do an arc4random_uniform and use that result to change the .pinned property of one of the nodes (thus causing that one and that one only to fall).
So, if the coin is 0, triangle is 1, and octagon is 2, then, in your .update keep track of the time elapsed and after say 3 seconds, do a random check 0-2, and that check will perform:
switch result {
case 0: // coin
coin.physicsBody?.pinned = false // makes it drop
case 1: // triangle
...
Just make sure that your nodes are in the proper scope so that you can do the logic in update()
If I read your Q wrong, and you only want to spawn and then drop just one, then you would still need the above switch statement, but instead of changing physicsBody you would do .addChild instead.
so inside of your func would be more like:
// This can be global or inside of your GameScene:
var myGlobalCurrentTime: CFTimeInterval
override func update(currentTime: CFTimeInterval) {
myGlobalCurrentTime = myTimerUpdateTime(currentTime)
func myDropFunc() {
... // Initialize your nodes
let result = myRandomNumber(3)
switch result {
case 0:
coin.position
= CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax),
y: self.size.height)
self.addChild(coin)
case 1:
...
}
}
// Execute the func:
myDropFunc()
}
I'm still a bit confused by your code and question, so please clarify in the comments so I can update this answer if needed.
You can move the addChild call out of this function, it returns a SKSpriteNode, which you can then add.
let sprite = ShapePicker()
addChild(sprite)
Or, just add the one randomly chosen and return it. You don't need to addChild nodes that you're not planning on using
I have two dots that fall parallel to each other from the top of my screen that are to be matched with two circles on the bottom that can be rotated with touch. I have them generated like this:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(1.0)])))
}
func generateDots() {
let count = 2
let index=Int(arc4random_uniform(UInt32(count)))
let dots = SKSpriteNode(imageNamed: "Color\(index+1)")
dots.physicsBody = SKPhysicsBody(circleOfRadius: 10)
dots.physicsBody?.dynamic = true
dots.physicsBody?.affectedByGravity = false
for i in 0..<2 {
dots.physicsBody?.categoryBitMask = UInt32(0x1 << index)
dots.physicsBody?.contactTestBitMask = UInt32(0x1 << index)
}
addChild(dots)
dots.size = CGSizeMake(45, 45)
dots.position = CGPointMake(150, 400)
dots.runAction(
SKAction.moveByX(0, y: -900,
duration: NSTimeInterval(11.5)))
}
}
Is there any way to gradually speed up either how fast they're falling or gradually change the waitForDuration so that over time it will produce a node every 3 sec, then 2 sec, then 1 sec and so forth?
This is completely doable! You just need to add some variables.
If you want to change how fast they fall then you need to make a variable like
Var droptime:NSTimeInterval = 11.5
Then in your "dropdot()" method you need to do two things.
At the beginning subtract or devide your droptime variable like...
Droptime -= 1
Then at the end when you generate the falling action make it
Duration: droptime
Instead of what it was before.
If you want to make the generation time be shorter then you need to make a function that you can trigger each time you want to make your action that the scene runs (like you did in the viewdidload) and edit it so that it has variable wait and triggers itself. Also you will need to self trigger it once in your didMoveToView method.
func controlMethod() {
waitdur -= 1
runAction(SKAction.repeatActionForever( SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(waitdur),
SKAction.runBlock(controlMethod)
])))
}
Good luck!
So sorry for the formatting! I'm on mobile... Hopefully someone can fix it.
I just start to learn coding with Swift last month. This is my second personal project.
I'm creating a table top game like "Monopoly" or something. Here an image of my prototype:my table top board game
I'm using a arc4random for the dices and moving the character/player with this code:
if positionPlayer1 == 1 {
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 1 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
player1.runAction(SKAction.moveByX(-66, y: 0, duration: 1))
}
} else if positionPlayer1 == 2 {
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 1 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
player1.runAction(SKAction.moveByX(-44, y: 0, duration: 1))
}
I also tried :
player1.runAction(SKAction.moveByX(-66, y: 0, duration: 1))
sleep(1)
The problem is: the character waits the total time e runs directly to the final position. Let's say the dice give an 6, the character waits for 6 seconds and runs to the last position. This is not a problem on a straight line, but if the movement pass through a corner, the character cuts in diagonal directly to the final position instead of pass through every house as intended.
Any help will be appreciated.
Edit: I decided to try physics. I created a lot of SKSpriteNode "walls". To all nodes I'm using:
squareBlock02.physicsBody?.allowsRotation = false
squareBlock02.physicsBody?.pinned = true
squareBlock02.physicsBody?.dynamic = false
The result is that the player hits those walls and move them. I tried mass, density... nothing prevents the player from push all walls from its way.
Thank you all people that try to help.
I finally found the answer at this site: http://www.gamefromscratch.com/post/2014/07/02/Game-development-tutorial-Swift-and-SpriteKit-Part-4-Actions.aspx
import SpriteKit
// Solution found: http://www.gamefromscratch.com/post/2014/07/02/Game-development-tutorial-Swift-and-SpriteKit-Part-4-Actions.aspx
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "Player_01-2.png")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
player.anchorPoint = CGPoint(x:0.5,y:0.5)
player.position = CGPoint(x:view.bounds.midX,y:view.bounds.midY)
self.addChild(player)
doStuff()
}
func doStuff(){
var actions = Array<SKAction>()
actions.append(SKAction.moveTo(CGPoint(x:300,y:300), duration: 1))
actions.append(SKAction.rotateByAngle(6.28, duration: 1))
actions.append(SKAction.moveBy(CGVector(dx: 150,dy: 0), duration: 1))
actions.append(SKAction.colorizeWithColor(SKColor.redColor(), colorBlendFactor: 0.5, duration: 1))
let sequence = SKAction.sequence(actions)
player.runAction(sequence)
}
}
I have a function in my Sprite kit game where the game character dies and has a death animation. In this same method I set some attributes to let my update function know that the game is over so that the score can stop being incremented and some other things. But when this runs, the deathAnimation function seems to slow down the execution of the other variables that are being set. So the score keeps being incremented when it should stop for example. Why is this? is it something to do with my update function or does the animation with a time duration slow the entire method from being executed right away?
Thanks for the help in advance!
Here is my deathAnimation method
func deathAnimation() {
//set shield for death
self.yourDead = true
self.shield.position = CGPointMake(self.frame.maxX * 2, self.frame.maxY + self.ape.size.height * 10)
self.shield.hidden = true
self.shieldActivated = false
//set Ape image to default
self.ape.runAction(SKAction.setTexture(SKTexture(imageNamed: "Ape"), resize: true))
self.ape.zRotation = 0
//changes physicsBody values so He doesn't collide
self.ape.physicsBody?.dynamic = false
self.ape.physicsBody?.categoryBitMask = ColliderType.Asteroid.rawValue
self.ape.physicsBody?.contactTestBitMask = ColliderType.Ape.rawValue
self.ape.physicsBody?.collisionBitMask = ColliderType.Ape.rawValue
self.ape.zPosition = 10 //bring the ape to the front
let death = SKAction.sequence([
SKAction.group([
SKAction.scaleBy(4, duration: 0.5),
SKAction.moveTo(CGPointMake(self.frame.minX + ape.size.width * 2, self.frame.minY - ape.size.width * 2), duration: 2),
SKAction.repeatAction(SKAction.rotateByAngle(CGFloat(M_PI_4), duration: 0.2), count: 8)
]),
SKAction.runBlock({self.moveToGameOverView();})
])
ape.runAction(death) //run the animation sequence
}
Here is my code where I check if the player is Dead or not and this is within the update function. I didn't include all of the update function because it is probably more than you would care to look at.
//take Asteroids off the screen and increment score
enumerateChildNodesWithName("asteroid", usingBlock: {(node: SKNode!, stop: UnsafeMutablePointer <ObjCBool>) -> Void in
//move the asteroids off the screen
node.position = CGPointMake(node.position.x, node.position.y + self.gravity)
//if it is out of screen
if node.position.y > self.frame.size.height + self.largeAsteroid.size.width {
node.removeFromParent()
if !self.yourDead { //if your not dead
self.score++
self.scoreText.text = String(self.score)
//increase Asteroid speed
if self.score > 20 * self.tensCounter {
self.gravity++
self.tensCounter++
}
}
}
})
The code provided looks fine. However you could try checking a few things.
Make sure that you are not calling deathAnimation() over and over again.
Make sure you are not doing your enumerateChildNodesWithName before deathAnimation().
Make sure you aren't incrementing the score somewhere else.
Those are the only reasons I can think that your score would continue to go up after you set self.yourDead = true Hopefully that helps.