How to make a node replace another in chronological order? - swift

Lets say I have five dice like so:
When I tap, Id like the die face with one dot to replace the one with two dots and (moving the 1 die across the row while moving the other die down the row) and if the last die is at the end, get it to replace the first die in the row. Would replacing the SKTextures have something to do with this? Thank you in advance.
Edit:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "DieFace1"))
DieFace2.runAction(texRight)
}
}
Edit 2:
import SpriteKit
var arrayOfDieFaces = [onE, twO, threE, fouR, fivE]
class GameScene: SKScene {
}
func replace() {
var count = 1
while count <= 5 {
let changeTexture = SKAction.setTexture(SKTexture(imageNamed: "Dice\(count)"))
if count == 5 {
arrayOfDieFaces[0].runAction(changeTexture)
}
else{
arrayOfDieFaces[count].runAction(changeTexture)
}
count += 1
}
arrayOfDieFaces.append(arrayOfDieFaces[0])
arrayOfDieFaces.dropFirst()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
replace()
}
}

This approach changes the position of each dice instead of changing its texture. By changing the positions, it maintains the ability to uniquely identify each dice (e.g., by name).
First, create an SKNode called dice and define the size and spacing between each dice.
let dice = SKNode()
let diceSize = CGSizeMake(10, 10)
let spacing:CGFloat = 2
Second, create your dice nodes and add them, in order, to the dice SKNode and set the positions of each dice with
for i in 0..<6 {
let sprite = SKSpriteNode ...
sprite.physicsBody = SKPhysicsBody( ...
sprite.physicsBody?.categoryBitMask = UInt32(0x1 << i)
sprite.position = CGPointMake(CGFloat(i)*(diceSize.width+spacing) , 0)
dice.addChild (sprite)
}
// Center SKNode in the view
dice.position = CGPointMake (CGRectGetMidX(view.frame),CGRectGetMidY(view.frame))
addChild (dice)
Note that the position of each dice is based on the position of the node within the array.
Lastly, add the following to your code.
func rotate() {
// Get last element in the array
if let last = dice.children.last {
// Remove the last dice from the SKNode
last.removeFromParent()
// Move the last to the front of the array
dice.insertChild(last, atIndex: 0)
// Update the positions of the
for i in 0..<dice.children.count {
dice.children[i].position = CGPointMake(CGFloat(i)*(diceSize.width+spacing) , 0)
}
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for _ in touches {
rotate()
}
}

I think this should work (not tested) assuming your DieFaces are from 1 to 6:
var arrayOfDieFaces = [DieFace1, DieFace2, DieFace3, DieFace4, DieFace5, DieFace6]
func changeTextures() {
var count = 1
while count <= 6 {
let changeTexture = SKAction.setTexture(SKTexture(imageNamed: "DieFace\(count)"))
if count == 6 {
arrayOfDieFaces[0].runAction(changeTexture)
}
else{
arrayOfDieFaces[count].runAction(changeTexture)
}
count += 1
}
arrayOfDieFaces.append(arrayOfDieFaces[0])
arrayOfDieFaces.dropFirst()
}
Just call this fucntion inside the touchesBegan() function.

Related

Creating a stopwatch in swift

I have tried searching other answers and I cannot find one that applies to my scenario. I am writing a game in swift and want to create a stopwatch that determines how the long the player is playing. When the user touches down, the stopwatch will start and when a certain action happens, then the timer stops and resets. I'd like to use minutes, seconds, milliseconds (i.e. 00:00.00).
Currently, the time function kind of works. It doesn't start at 0, it starts at the current seconds in time (I know thats when I start it, but I don't know how to start it at 0). It also only updates when I touch the screen, I need it to count from 00:00.00 and update on its own until the cancel action is fired.
Thank you for your time.
Here is what I have so far:
class GameScene: SKScene {
var activeTimer = SKLabelNode()
//var bestTime = SKLabelNode()
var startTime = TimeInterval()
//var currentTime = TimeInterval()
//var countingTime = TimeInterval()
var updateTimerAction = SKAction()
override func didMove(to view: SKView) {
activeTimer = self.childNode(withName: "active_timer") as! SKLabelNode
//bestTime = self.childNode(withName: "best_time") as! SKLabelNode
startTime = TimeInterval(Calendar.current.component(.second, from:Date()))
updateTimerAction = SKAction.sequence([SKAction.run(updateTimer), SKAction.wait(forDuration: 1.0)])
}
func startGame() {
// Other code
startGameTimer()
}
func resetGame() {
// Other code
stopGameTimer()
}
func startGameTimer() {
run(SKAction.repeatForever(updateTimerAction), withKey: "timer")
}
func updateTimer() {
activeTimer.text = stringFromTimeInterval(interval: startTime) as String
}
func stringFromTimeInterval(interval: TimeInterval) -> NSString {
let ti = NSInteger(interval)
let ms = Int((interval.truncatingRemainder(dividingBy: 1)) * 1000)
let seconds = ti % 60
let minutes = (ti / 60) % 60
return NSString(format: "%0.2d:%0.2d.%0.2d",minutes,seconds,ms)
}
func stopGameTimer() {
removeAction(forKey: "timer")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startGame()
for touch in touches {
// other code
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
// other code
}
}
override func update(_ currentTime: TimeInterval) {
updateTimer()
if <action> {
// stop timer and reset game
resetGame()
}
}
Instead of using a TimeInterval object, it might be better to use a Timer object which calls a function that increments the value of a label (or an internal variable) from 0 onwards accordingly after every 1 second(or can be made lesser). And for stopping the clock just call the Timer object's invalidate function.
Check the documentation for more info: https://developer.apple.com/documentation/foundation/timer
The following lines of code come from an actual macOS game that I submitted to Mac App Store some 3 weeks ago. It counts down a number to 0. In your case, change
self.currentSeconds -= 1
to
self.currentSeconds += 1
timeBackNode is an empty node that holds SKLabelNode objects timeNode0 and timeNode1. So create it when the game starts with didMove.
var currentSeconds = Int() // currentSeconds
var timeNode0 = SKLabelNode() // timeNode0
var timeNode1 = SKLabelNode() // timeNode1
func setupTimeGoal() {
enumerateChildNodes(withName: "//TimerBack", using: { timeBackNode, _ in
let goal = self.timeDict["num"] as! Int
self.currentSeconds = goal // In your case, goal is 0 since you are going to count up.
self.timeNode0 = SKLabelNode(fontNamed: "Your font's font name")
self.timeNode0.text = self.makeTimeWithSeconds(secs: goal)
self.timeNode0.fontSize = 26
self.timeNode0.fontColor = SKColor.black
self.timeNode0.horizontalAlignmentMode = .center
self.timeNode0.position = CGPoint(x: 1, y: -22)
timeBackNode.addChild(self.timeNode0)
self.timeNode1 = SKLabelNode(fontNamed: "Your font's font name")
self.timeNode1.text = self.makeTimeWithSeconds(secs: goal) // this function translate a counting number into a time code like 00:00:00
self.timeNode1.fontSize = 26
self.timeNode1.fontColor = SKColor.cyan
self.timeNode1.horizontalAlignmentMode = .center
self.timeNode1.position = CGPoint(x: 0, y: -23)
timeBackNode.addChild(self.timeNode1)
})
}
func repeatTime() {
let waitAction = SKAction.wait(forDuration: 1.0)
let completionAction = SKAction.run {
if self.currentSeconds == 0 && self.gameState == .playing {
self.removeAction(forKey: "RepeatTime")
self.proceedLoss()
return
}
self.currentSeconds -= 1
self.timeNode0.text = self.makeTimeWithSeconds(secs: self.currentSeconds)
self.timeNode1.text = self.makeTimeWithSeconds(secs: self.currentSeconds)
}
let seqAction = SKAction.sequence([waitAction, completionAction])
let repeatAction = SKAction.repeatForever(seqAction)
self.run(repeatAction, withKey: "RepeatTime")
}
Here is what I ended up doing. While El Tomato's answer appears to work, given the setup of my code I went a little different route. After talking with some other friends, I chose to user a timer due to the design of my application. If I happen to run into any problems using a Timer object I will update this question.
The code below will start on a touch, update in the format: "00:00.00" and stop when the action is fired. This time will pause until another touch and the timer will then start from zero.
class GameScene: SKScene {
var seconds = 0
var timer = Timer()
var timeStarted = Bool()
var activeTimer = SKLabelNode()
//var bestTime = SKLabelNode()
override func didMove(to view: SKView) {
activeTimer = self.childNode(withName: "active_timer") as! SKLabelNode
//bestTime = self.childNode(withName: "best_time") as! SKLabelNode
timeStarted = false
}
func startGame() {
startGameTimer()
timeStarted = true
}
func resetGame() {
timeStarted = false
stopGameTimer()
seconds = 0
}
func startGameTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)
}
func updateTimer() {
seconds += 1
activeTimer.text = timeString(time: TimeInterval(seconds))
}
func timeString(time:TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i.%02i", hours, minutes, seconds)
}
func stopGameTimer() {
timer.invalidate()
removeAction(forKey: "timer")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !timeStarted {
startGame()
}
for touch in touches {
// other code
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
// other code
}
}
override func update(_ currentTime: TimeInterval) {
if timeStarted {
updateTimer()
}
if <action> {
// stop timer and reset game
resetGame()
}
}
}

Move sprite forever

I am trying to move sprite to the right when I press a button on the screen. However, when I try doing it I only have a solution to move the sprite to a certain point. So... I want the sprite to move to the right forever or until i do something else.
This is in Xcode using Swift in SpriteKit.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch : AnyObject in touches{
let pointInTouch = touch.locationInNode(self)
let tappedNode = nodeAtPoint(pointInTouch)
let tappeNodeName = tappedNode.name
if tappeNodeName == "Btn"{
player.physicsBody?.velocity = CGVectorMake(0, 0)
let action = SKAction.applyImpulse(CGVectorMake(400, 0), duration: 1)
player.runAction(SKAction.repeatActionForever(action))
print("Touched!")
}
}
}
You can simply move your node to the right untill it does exist the scene
class GameScene: SKScene {
private var player: SKSpriteNode!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else { return }
let pointInTouch = touch.locationInNode(self)
let tappedNode = nodeAtPoint(pointInTouch)
if tappedNode.name == "Btn"{
let moveToRight = SKAction.moveToX(self.frame.width + player.frame.width, duration: 5)
player.runAction(moveToRight)
}
}
}
Update: constant speed
If you want the player to move with constant speed you can use this code.
As you can see I am calculating the duration using space / speed. You just need to find the best constant value for speed for your scenario.
class GameScene: SKScene {
private var player: SKSpriteNode!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else { return }
let pointInTouch = touch.locationInNode(self)
let tappedNode = nodeAtPoint(pointInTouch)
let deltaX = self.frame.width + player.frame.width - player.position.x
let speed: CGFloat = 10 // <-- change this to find the best value for you
let duration: NSTimeInterval = NSTimeInterval(deltaX / speed)
if tappedNode.name == "Btn"{
let moveToRight = SKAction.moveByX(deltaX, y:0, duration: duration)
player.runAction(moveToRight)
}
}
}

Multiple circles drawn SpriteKit

I'm working on simple game which will based on paiting background by player. In left and right corner there will be buttons which will move character to left or right. I've already implemented that (character is moving and lefts painted background behind), but with adding another circles fps's drops really fast. Is there any solution to that?
import SpriteKit
class GameScene: SKScene {
var playerDot:PlayerDot = PlayerDot(imageNamed:"player")
var isTurningLeft:Bool = false
var isTurningRight:Bool = false
var lastLocation:CGPoint = CGPoint()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Helvetica")
myLabel.name = "left"
myLabel.text = "Left"
myLabel.fontSize = 30
myLabel.horizontalAlignmentMode = .Left
myLabel.position = CGPoint(x:CGRectGetMinX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel)
let myLabel2 = SKLabelNode(fontNamed:"Helvetica")
myLabel2.name = "right"
myLabel2.text = "Right"
myLabel2.fontSize = 30
myLabel2.horizontalAlignmentMode = .Right
myLabel2.position = CGPoint(x:CGRectGetMaxX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel2)
playerDot.position = CGPoint(x:CGRectGetMaxX(self.frame)/2, y:CGRectGetMinY(self.frame))
self.addChild(playerDot)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = true
isTurningRight = false
}
else if theName == "right" {
isTurningRight = true
isTurningLeft = false
}
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func update(currentTime: CFTimeInterval) {
if(isTurningLeft){
playerDot.increaseAngle()
} else if (isTurningRight){
playerDot.decreaseAngle()
}
//calculates new character position based on angle of movement changed
playerDot.updatePosition()
drawCircle()
}
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let circle = SKShapeNode(circleOfRadius: 10 )
circle.position = playerDot.position
circle.fillColor = SKColor.orangeColor()
circle.strokeColor = SKColor.orangeColor()
self.addChild(circle)
lastLocation = playerDot.position
}
}
func distanceFromCGPoints(a:CGPoint,b:CGPoint)->CGFloat{
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
}
EDIT:
drawCircle with SKShapeNode replaced with SKSpriteNote
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(imageNamed:"player")
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
If you are having frame rate drops then some parts of your code are slowing down execution too much. You need to optimise your code. Use Instruments (Time Profiler) to find out which lines/functions are causing problems speed wise. If you have never used it before read up on it and use it a couple times to get the gist, then I recommend watching the WWDC video Profiling In-Depth
Based on your edit (switching to SKSpriteNode), this is how you could do it:
Declare a texture as a property of your scene. This way, you load it once and reuse it later:
let yourTexture = SKTextureAtlas(named: "yourAtlas").textureNamed("yourTexture")
Then in your drawCircle method:
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(texture: yourTexture)
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
This way (by using SKSpriteNode instead of SKShapeNode) you have reduced number of draw calls required to render all those circles. Also because you are reusing the same texture, instead of allocating it every time using imageNamed you have reduced greatly an amount of memory that app consumes. Now, SpriteKit can render hundreds of nodes #60 fps if those nodes are rendered in batches. Still there is a limit before fps start dropping. And that depends on a device.

How to move a node to the position I touch in SpriteKit with Swift?

I am creating a game like Doodle Jump (without the accelerometer).
I have been trying to figure this out, but I can't seem to make it run as smoothly as I've been hoping.
Here is my code for my touches functions:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
lastTouch = nil
}
override func update(currentTime: CFTimeInterval) {
if let touch = lastTouch {
let impulseVector = CGVector(dx: 400, dy: 400)
player.physicsBody?.applyImpulse(impulseVector)
}
}
From what I'm gathering, you want to change the impulseVector that is applied to the player based on where the touch is. I would imagine the code looking something like this:
// At the top of your code
let scale = 0.5
// Update function
override func update(currentTime: CFTimeInterval) {
if let touch = lastTouch {
let xOffset = (touch.x - player.position.x)*scale
let yOffset = (touch.y - player.position.y)*scale
let impulseVector = CGVector(dx: xOffset, dy: yOffset)
player.physicsBody?.applyImpulse(impulseVector)
}
}
This will pull the player node up with more force the further away it is from the touch. If you are trying to pull the player with the same amount of force, no matter where they are (which may be more likely in this case), you would do something like this:
// At the top of your code
let xPlayerForce = 20
let yPlayerForce = 30
// Update function
override func update(currentTime: CFTimeInterval) {
if let touch = lastTouch {
var xForce = 0.0
var yForce = 0.0
let xTouchOffset = (touch.x - player.position.x)
let yTouchOffset = (touch.y - player.position.y)
if xTouchOffset > 0.0 {
xForce = xPlayerForce
} else if xTouchOffset < 0.0 {
xForce = -xPlayerForce
} // else we do nothing
if yTouchOffset > 0.0 {
yForce = yPlayerForce
} // here you can choose whether you want it to push
// the player node down, using similar code from the
// above if statement
let impulseVector = CGVector(dx: xForce, dy: yForce)
player.physicsBody?.applyImpulse(impulseVector)
}
}

How to manipulate a sprite kit's SKNode?

I have coded a starry sky as follows. Now I would like to remove a star when a user touches it. The following code however removes all the stars on the sky. How can I access a single star node to manipulate it?
override func didMoveToView(view: SKView) {
for(var i = 0; i < stars ; i++) {
planetsLayer.addChild(createPlanet(view))
}
self.addChild(planetsLayer)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(planetsLayer)
let touchedLayer = nodeAtPoint(location)
let touchedNode = nodeAtPoint(location)
touchedNode.removeFromParent()
}
func createPlanet() -> SKShapeNode {
...
var shapeNode = SKShapeNode();
...
return shapeNode
}
Give your planet nodes a name when you create them, and check for this name when you remove them. This will keep you from deleting other nodes that are not planets.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(planetsLayer)
let touchedNode = nodeAtPoint(location)
if touchedNode.name == "planet" {
touchedNode.removeFromParent()
}
}
}
func createPlanet() -> SKShapeNode {
...
var shapeNode = SKShapeNode()
shapeNode.name = "planet"
...
return shapeNode
}