so I'm working with a game in SpriteKit and I am using this code to pause the game.
self.pauseButton.alpha = 0
self.playButton.alpha = 1
self.settingsBackground.alpha = 0.85
self.isPaused = true
self.pauseButton.alpha = 1
self.playButton.alpha = 0
self.settingsBackground.alpha = 0
The code which is run before pausing is to change the appearance of the pause and the code afterward reverts it. The problem is that the code before pausing is not getting run and instead the game is just pausing before changing the visuals around. I've tried adding delays, making the steps SKActions, and testing just the code before the pause. When I run just the first 3 lines the visuals change properly but obviously, the game doesn't pause. When I run the whole thing the game pauses but the visuals don't change. Help!
the problem is that even when a scene isPaused the code still get run. Yes you are pausing the scene, yes the visuals do get run...but the visuals after the isPaused line also get run and reset the visuals that you just changed.
here is a really simple example of how this working. there are 3 boxes on the scene; the bottom box has an action that repeatedly scales up and down, and will stop when the scene is paused. the top box will pause the game when pressed and will reveal the middle box which will unpause the game.
class GameScene: SKScene {
private var test: SKSpriteNode!
private var test2: SKSpriteNode!
private var test3: SKSpriteNode!
override func didMove(to view: SKView) {
backgroundColor = .clear
test = SKSpriteNode(color: .blue, size: CGSize(width: 200, height: 200))
test.position = CGPoint(x: 0, y: 350)
addChild(test)
test2 = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200))
test2.position = CGPoint(x: 0, y: 0)
test2.alpha = 0
addChild(test2)
test3 = SKSpriteNode(color: .yellow, size: CGSize(width: 200, height: 200))
test3.position = CGPoint(x: 0, y: -350)
addChild(test3)
let scaleDown = SKAction.scale(to: 0.1, duration: 1.0)
let scaleUp = SKAction.scale(to: 1.0, duration: 1.0)
let sequence = SKAction.sequence([scaleDown, scaleUp])
let repeater = SKAction.repeatForever(sequence)
test3.run(repeater)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: self)
if test.contains(touchLocation) {
print("pausing")
self.test2.alpha = 1
self.test.alpha = 0
self.isPaused = true
}
if test2.contains(touchLocation) {
print("unpausing")
self.test2.alpha = 0
self.test.alpha = 1
self.isPaused = false
}
}
}
}
Related
Working on a game right now, I've faced a problem regarding management of SkSpriteNodes. I have a SpriteNode whose texture size is lower than physicsBody size assigned to it. It is possible to move, thanks to something similar to an SKAction, only the texture of a node and not its physicsBody?
To explain the problem in other terms, I will give you a graphic example:
As you can see, what I want to achieve is not to modify physicsBody proprieties(in order to not affect collision or having problems with continuos reassigning of PhysicsBody entities), but changing its texture length and adjusting its position. How can I achieve this programmatically?
A bit of code for context, which is just illustrative of the problem:
let node = SKSpriteNode(color: .red, size: CGSize(width: 8, height: 60)
node.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 60)
self.addChild(node)
//what I've tried is something like that
//It causes glitches in visualisation... and I need to move the object since resizing is towards the center.
let resize = SKAction.scaleY(to: 0.5, duration: 5)
let move = SKAction.move(to: node.position - CGPoint(x:0, y:node.size.height*0.5), duration: 5)
let group = SKAction.group([resize, move])
node.run(group)
//And this is even worse if I add, in this specific example, another point fixed to the previous node
let node2 = SKSpriteNode(color: .blue, size: CGSize(width: 8, length: 8)
node2.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 8))
node2.position = CGPoint(x: 0, y: -node.height)
self.addChild(node2)
self.physicsWorld.add(SKPhysicsJointFixed.joint(withBodyA: node.physicsBody! , bodyB: node2.physicsBody!, anchor: CGPoint(x: 0, y: -node.size.height)))
I get your problem.
let resize = SKAction.scaleY(to: 0.5, duration: 5)
This line will cause the physicsBody to scale the x and y axis uniformly. While your texture will just scale the y axis.
Its not so straight forward changing physicsBody shapes to match actions
One way to do it though would be to call a method from
override func didEvaluateActions()
Something like this:
var group1: SKAction? = nil
var group2: SKAction? = nil
var touchCnt = 0
var test = SKSpriteNode(texture: SKTexture(imageNamed: "circle"), color: .blue, size: CGSize(width: 100, height: 100))
func setActions() {
let newPosition = CGPoint(x: -200, y: 300)
let resize1 = SKAction.scaleY(to: 0.5, duration: 5)
let move1 = SKAction.move(to: newPosition, duration: 5)
group1 = SKAction.group([resize1, move1])
let resize2 = SKAction.scaleY(to: 1, duration: 5)
let move2 = SKAction.move(to: position, duration: 5)
group2 = SKAction.group([resize2, move2])
}
func newPhysics(node: SKSpriteNode) {
node.physicsBody = SKPhysicsBody(texture: node.texture!, size: node.size)
node.physicsBody?.affectedByGravity = false
node.physicsBody?.allowsRotation = false
node.physicsBody?.usesPreciseCollisionDetection = false
}
override func sceneDidLoad() {
physicsWorld.contactDelegate = self
test.position = CGPoint(x: 0, y: 300)
setActions()
newPhysics(node: test)
addChild(test)
}
override func didEvaluateActions() {
newPhysics(node: test)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if touchCnt == 0 {
if !test.hasActions() {
test.run(group1!)
touchCnt += 1
}
} else {
if !test.hasActions() {
test.run(group2!)
touchCnt -= 1
}
}
}
if you put the above code in your gameScene, taking care to replace any duplicated methods and replacing the test node texture. then when you tap the screen the sprite should animate as you want while keeping the physics body is resized at the same time. There are a few performance issues with this though. As it changes the physics body on each game loop iteration.
I am changing the alpha value of spriteNode like this,
if (firstBody.node?.name)! == "Player" && secondBody.node?.name == "Alpha 1" {
var item: SKSpriteNode?
item = SKSpriteNode(imageNamed: "BG")
item!.alpha = 0.1
score += 1
scoreLabel?.text = String(score)
}
This is what it says in the documentation (use node.alpha = value), but I am not sure why it isn't working.
you can fade the alpha by using a Action, I have left an example below.
class GameScene: SKScene {
var player: SKSpriteNode!
override func didMove(to view: SKView) {
player = SKSpriteNode(color: .purple, size: CGSize(width: 100, height: 100))
player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(player)
}
func fadePlayer() {
let fadeAlpha = SKAction.fadeAlpha(to: 0.1, duration: 1.0)
self.run(fadeAlpha)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
fadePlayer()
}
}
When a touch begins the SpriteNode will fade out to the alpha value you insert, it will fade out gradually during a time you insert in the duration also, This action fades the SpriteNode's alpha to 0.1 gradually over 1 second.
I'm trying to get my "Player" (A circle in the middle) to increase in size once the screen is touched by running a timer.
Once the timer is over 0 seconds, it increases in size. Once the timer is over 3 seconds, it decreases to its original scale size and once the timer is over 7 seconds, it resets and this repeats forever.
What am I doing wrong?
import SpriteKit
class GameScene: SKScene {
var Center = SKSpriteNode()
var Player = SKSpriteNode()
var timer = NSTimer()
var seconds = 0
override func didMoveToView(view: SKView) {
Center = SKSpriteNode(imageNamed: "Center")
Center.size = CGSize(width: 80, height: 80)
Center.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(Center)
Player = SKSpriteNode(imageNamed: "Player")
Player.size = CGSize(width: 80, height: 80)
Player.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
self.addChild(Player)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: #selector(GameScene.playerScaleUp), userInfo: nil, repeats: true)
}
func playerScaleUp(){
if seconds > 0{
Player.runAction(SKAction.scaleBy(4, duration: 2))
}
}
func playerScaleDown(){
if seconds > 3{
Player.runAction(SKAction.scaleBy(-4, duration: 2))
}
}
func resetScale(){
if seconds > 7{
timer.invalidate()
}
}
override func update(currentTime: CFTimeInterval) {
}
}
This can be done in a few different ways:
1) Using SKActions (which my example uses)
2) Using update: method and its passed currentTime parameter
3) Using NSTimer which I wouldn't recommend because it is not affected by scene's, view's or node's paused state, so it can lead you into troubles in the future. Read more in this StackOverflow answer
4) Well, probably some more, but I will stop here.
My example uses SKAction. This means that I don't really check how much time is passed, but rather I organize actions into sequences (where actions are executed sequentially) and into groups (where actions are organized parallel). Means I use SKActions like I am playing with LEGO's :)
Here is the code ...I left debugging code intentionally, because it can help you to learn how you can use SKActions in different situations.
class GameScene: SKScene, SKPhysicsContactDelegate {
let player = SKSpriteNode(color: .blackColor(), size: CGSize(width: 50, height: 50))
var timePassed = 0.0 {
didSet {
self.label.text = String(format: "%.1f",timePassed)
}
}
let label = SKLabelNode(fontNamed: "ArialMT")
override func didMoveToView(view: SKView) {
/* Debugging - Not really needed, I added it just because of a better example */
let wait = SKAction.waitForDuration(0.1)
let updateLabel = SKAction.runBlock({[unowned self] in self.timePassed += wait.duration})
self.label.position = CGPoint(x: frame.midX, y: frame.midY+100.0)
addChild(self.label)
let timerSequence = SKAction.sequence([updateLabel, wait])
self.runAction(SKAction.repeatActionForever(timerSequence), withKey: "counting")
//This is used later, at the end of scaleUpAndDownSequence to reset the timer
let resetTimer = SKAction.runBlock({[unowned self] in self.timePassed = 0.0})
/* End Debugging */
self.player.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(self.player)
let waitBeforeScaleToOriginalSize = SKAction.waitForDuration(3.0)
let waitBeforeRestart = SKAction.waitForDuration(4.0)
let scaleUp = SKAction.scaleTo(2.0, duration: 1)
let scaleDown = SKAction.scaleTo(1.0, duration: 1)
let scaleUpGroup = SKAction.group([waitBeforeScaleToOriginalSize, scaleUp])
let scaleDownGroup = SKAction.group([scaleDown, waitBeforeRestart])
//One cycle, scale up, scale down, reset timer
let scaleUpAndDownSequence = SKAction.sequence([scaleUpGroup, scaleDownGroup, resetTimer])
let loop = SKAction.repeatActionForever(scaleUpAndDownSequence)
self.player.runAction(loop, withKey: "scalingUpAndDown")
}
}
So here, I have two groups of actions:
1) scaleUpGroup
2) scaleDownGroup
scaleUpGroup group of actions has two actions in it: a scaleUp action, and an action which says how much to wait before the scale down action should occur. Because we want scaling up to happen immediately, we run it in parallel with the waitBeforeScaleToOriginalSize.
Same logic goes for scaleDownGroup. When scaleUpGroup is finished (its duration is determined by the longest action in the group) we start scaleDownGroup which scales down the player to its default size, and waits certain amount of time to repeat the whole thing.
Here is the result:
I start an animation on touch ( I've removed that code) and as you can see, scale up animation starts immediately, then after 3 seconds the player get scaled down to its original size, and after 4 seconds the whole animation repeats (player gets scaled up again etc).
I want to creat a dynamic 2D water, i', following this unity tutorials
http://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143
http://gamedevelopment.tutsplus.com/tutorials/make-a-splash-with-2d-water-effects--gamedev-236
And also this one
http://blog.prime31.com/water2d-part1/
Here is my code
class GameScene: SKScene, SKPhysicsContactDelegate {
var box: SKSpriteNode!
var nodes:[SKNode] = []
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.anchorPoint = CGPointMake(0.5, 0.5)
// Set physics body
let borderBody: SKPhysicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame);
borderBody.friction = 0.0;
self.physicsBody = borderBody;
// Set contact delegate
self.physicsWorld.contactDelegate = self;
box = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 20))
box.position = CGPointMake(0, 0)
box.physicsBody = SKPhysicsBody(rectangleOfSize: box.size)
box.physicsBody!.dynamic = false
self.addChild(box)
let one = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 20, height: 20))
one.position = CGPointMake(box.position.x - box.frame.size.width/2, box.position.y + box.frame.size.height * 2)
one.physicsBody = SKPhysicsBody(rectangleOfSize: one.size)
one.physicsBody!.allowsRotation = false
self.addChild(one)
nodes.append(one)
self.attachPoint(box, point2: one, box: true)
let two = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 20, height: 20))
two.position = CGPointMake(box.position.x, box.position.y + box.frame.size.height * 2)
two.physicsBody = SKPhysicsBody(rectangleOfSize: two.size)
two.physicsBody!.allowsRotation = false
self.addChild(two)
nodes.append(two)
self.attachPoint(box, point2: two, box: true)
let three = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 20, height: 20))
three.position = CGPointMake(box.position.x + box.frame.size.width/2, box.position.y + box.frame.size.height * 2)
three.physicsBody = SKPhysicsBody(rectangleOfSize: three.size)
three.physicsBody!.allowsRotation = false
self.addChild(three)
nodes.append(three)
self.attachPoint(box, point2: three, box: true)
self.attachPoint(one, point2: two, box: false)
self.attachPoint(two, point2: three, box: false)
}
func attachPoint(point1: SKSpriteNode, point2: SKSpriteNode, box: Bool){
if(box == true){
let newPoint1 = CGPointMake(self.frame.size.width/2 + point2.position.x, self.frame.size.height/2 + point1.position.y)
let newPoint2 = CGPointMake(self.frame.size.width/2 + point2.position.x, self.frame.size.height/2 + point2.position.y)
// create a joint between two bodies
let joint: SKPhysicsJointSpring = SKPhysicsJointSpring.jointWithBodyA(point1.physicsBody, bodyB: point2.physicsBody, anchorA: newPoint1, anchorB: newPoint2)
joint.damping = 2.0
joint.frequency = 9.0;
self.physicsWorld.addJoint(joint)
} else {
let newPoint1 = CGPointMake(self.frame.size.width/2 + point1.position.x, self.frame.size.height/2 + point1.position.y)
let newPoint2 = CGPointMake(self.frame.size.width/2 + point2.position.x, self.frame.size.height/2 + point2.position.y)
// create a joint between two bodies
let joint: SKPhysicsJointSpring = SKPhysicsJointSpring.jointWithBodyA(point1.physicsBody, bodyB: point2.physicsBody, anchorA: newPoint1, anchorB: newPoint2)
joint.damping = 2.0
joint.frequency = 9.0;
self.physicsWorld.addJoint(joint)
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
let node = nodes[2]
node.physicsBody?.velocity.dy = 20000
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Here is the result
This result without changes
After touch, execute code in touchesBegan method
After step 2, the nodes start to rotate and fall bellow the node
If they don't rate, following the the tutorials I will have to make SKShapeNode with the color of the node. But SKShapeNode is expensive, is there another way this effect can be accomplished?
The easiest way is to use image for the top part of the water and make it move back and forth, but it won't be dynamic..
If you have ever play Tiny Wings, the water there is implemented exactly the same way in the tutorials.
I don't know, maybe this can't be made in SpriteKit or I just don't know how
Right now, I am creating a new game that will involve a ball that moves up and down the screen by itself without touching a button or the screen. I have been looking everywhere for a solution, but I haven't found one. I know how to make the ball move up and down in objective-c, but not in swift or spritekit. I don't know if the following code will help or not. Any solution is accepted.
class GamePlayScene: SKScene, SKPhysicsContactDelegate {
var ball = SKSpriteNode(imageNamed: "green ball.png")
override func didMoveToView(view: SKView) {
//setup scene
physicsWorld.gravity = CGVector.zeroVector
self.scene?.backgroundColor = UIColor.blackColor()
self.scene?.size = CGSize(width: 640, height: 1136)
ball.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 0.9)
ball.size = CGSize (width: 80, height: 82)
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
self.addChild(ball)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
if ball.position.y == 600 {
self.ball.runAction(moveBallUp)
} else {
self.ball.runAction(moveBallDown)
}
}
}
I suggest you use repeatActionForever. If you start your ball position from the bottom of the screen this sequence should work. If you want to start the ball from the top then switch the positions of the actions in moveUpAndDown with each other.
func moveSprite() {
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
let moveUpAndDown = SKAction.sequence([moveBallUp, moveBallDown])
let moveUpAndDownForever = SKAction.repeatActionForever(moveUpAndDown)
ball.runAction(moveUpAndDownForever)
}