It's the third time I'm looking for help in this question.
I have a class name BallNode of kind SKShapeNode. Inside my code I also have a function the spawn ball every 3 second from the top side of the screen. Now, I want to set a function that locate the ball position every 1 second, and so, if the ball.position.y > 200 to print a message to the console.
The purpose of this is that if any ball will be at this position (not while it's falling down) the I will call another function. I tried to do it via SKAction, update(_ currentTime: CFTimeInterval), Timer but I didn't succeed and I really have no idea what to do...
update - my current code:
var timeType1: CFTimeInterval = 0.0
var timeType2: CFTimeInterval = 2.0
override func update(_ currentTime: CFTimeInterval) {
if (currentTime - timeType1 > timeType2){
print("time")
self.checkBallsPosition()
self.timeType1 = currentTime
}
self.enumerateChildNodes(withName: "color.BallNode") { node, _ in
self.checkBallsPosition()
}
}
func checkBallsPosition() {
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
let x = self.createTopBorder()
x.isHidden = true
let wait2 = SKAction.wait(forDuration: 1)
let action2 = SKAction.run {
let position = Double(node.position.y)
if position < 200 {
}
else if position > 200 {
print("bolbo")
node.removeFromParent()
}
}
self.run(SKAction.sequence([wait2,action2]))
}
}
thats what I try do to so far, as I said the problem is that I want to get the ball last position. because the ball fall down the screen the last position should be when it touch the bottom border of the screen or when it touches another ball. if I set it at update I get the ball position every frame or (as I did) every second - but not the last. another problem is that the ball position can always change depends on another balls (when collision occurs).
update #2 - another functions:
func spawnBalls() {
let wait = SKAction.wait(forDuration: 3)
let action = SKAction.run {
self.createBall()
}
run(SKAction.repeatForever((SKAction.sequence([wait, action]))))
}
func createBall(){
let ball = BallNode(radius: 65)
print(ball.Name)
print(ball._subName!)
ball.position.y = ((frame.size.height) - 200)
let ballXPosition = arc4random_uniform(UInt32(frame.size.width))
ball.position.x = CGFloat(ballXPosition)
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.collisionBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.topBorder
ball.delegate = self
addChild(ball)
}
You did not post the entire code you are using. Not knowing how you are spawning your balls, how is the hierarchy and how you are moving them, I can't reproduce it here.
I am not sure if I understand it right, but I believe it might be simpler than what you are doing. See if the code below helps you, I am making the balls fall with SKAction (you can do it with physics also), and checking when they go below zero, when I remove them.
private let ballSpeed : CGFloat = 400
private let ballRadius : CGFloat = 10
private func spawnBall(atPoint pos : CGPoint){
let b = SKShapeNode(circleOfRadius: ballRadius)
b.name = "ball"
b.position = pos
addChild(b)
b.run(SKAction.moveTo(y: -size.height, duration: TimeInterval((pos.y+size.height)/400)))
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
enumerateChildNodes(withName: "ball") { (node, _) in
if node.position.y < 0 {
node.removeFromParent()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let pos = touch.location(in: self)
spawnBall(atPoint: pos)
}
}
Related
I'm having troubles with animations, physics and stuff like these.
Anyway, what I'm trying to do is creating a pulsing effect for a sphere where the pulse itself is able to collide with elements on the screen, so I created another spriteNode (child of the sphere) that I want to scale continuously, without depending on touches.
I read that in these cases is better not to use SKActions.
My question is: how do I scale or increase the size of a spriteNode via update function?
The code represents how I worked so far for movement for example.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var entities = [GKEntity]()
var graphs = [String: GKGraph]()
var movingSquareLv1 = SKSpriteNode()
var pulsingSphereLv2 = SKSpriteNode()
var lv2SpherePulse = SKSpriteNode()
var xVeloicty: CGFloat = 100
override func sceneDidLoad() {
movingSquareLv1 = self.childNode(withName: "movingSquare") as!SKSpriteNode
movingSquareLv1.physicsBody = SKPhysicsBody(rectangleOf: movingSquareLv1.size)
movingSquareLv1.physicsBody ? .affectedByGravity = false
pulsingSphereLv2 = self.childNode(withName: "pulsingSphere") as!SKSpriteNode
pulsingSphereLv2.physicsBody ? .affectedByGravity = false
lv2SpherePulse = pulsingSphereLv2.childNode(withName: "lv2SpherePulse") as!SKSpriteNode
lv2SpherePulse.physicsBody ? .affectedByGravity = false
}
func touchDown(atPoint pos: CGPoint) {
}
func touchMoved(toPoint pos: CGPoint) {
}
func touchUp(atPoint pos: CGPoint) {
}
override func touchesBegan(_ touches: Set < UITouch > , with event: UIEvent ? ) {
for t in touches {
self.touchDown(atPoint: t.location( in: self))
}
}
override func touchesMoved(_ touches: Set < UITouch > , with event: UIEvent ? ) {
for t in touches {
self.touchMoved(toPoint: t.location( in: self))
}
}
override func touchesEnded(_ touches: Set < UITouch > , with event: UIEvent ? ) {
for t in touches {
self.touchUp(atPoint: t.location( in: self))
}
}
override func touchesCancelled(_ touches: Set < UITouch > , with event: UIEvent ? ) {
for t in touches {
self.touchUp(atPoint: t.location( in: self))
}
}
override func update(_ currentTime: TimeInterval) {
rectMovement()
}
func rectMovement() {
if movingSquareLv1.frame.maxX >= self.size.width / 2 {
xVeloicty = -100
} else if movingSquareLv1.frame.minX <= -self.size.width / 2 {
xVeloicty = 100
}
let rate: CGFloat = 0.5
let relativeVelocity: CGVector = CGVector(dx: xVeloicty - movingSquareLv1.physicsBody!.velocity.dx, dy: 0)
movingSquareLv1.physicsBody!.velocity = CGVector(dx: movingSquareLv1.physicsBody!.velocity.dx + relativeVelocity.dx * rate, dy: 0)
}
}
This is the object composed of two sprites, the one I want to work on is the largest one.
Whoever told you to not use SKActions is wrong. This is exactly when you want to use an SKAction, and if you have multiple circles pulsing, you use the same action on all circles.
You really want to keep your update function as small as possible. Trust in appleās optimizations because they have better access to the API than you do.
I don't know why whatever you read said that actions aren't appropriate, but if you want to change a node's size from update explicitly, then you can set its scaling.
myNode.setScale(2.5) // 2.5x as big as normal
myNode.setScale(0.5) // half size
myNode.xScale = 1.5
myNode.yScale = myNode.xScale * 2 // Set x and y separately for non-uniform scaling
See the documentation in the "Scaling and Rotation" section of SKNode:
https://developer.apple.com/documentation/spritekit/sknode
I'm making a game where the player follows a touch. The game has obstacles and pathfinding, and I'm experiencing extreme lag with touches moved. Upon loading the game, everything works, but after a few seconds of dragging my finger around (especially around obstacles) the game freezes to the point where moving a touch will freeze all physics (0 fps) until the touch is ended.
I'm assuming the culprit is my function makeGraph(), which finds a path for the player from player.position:CGPoint() to player.destination:CGPoint(), storing a path in player.goto:[CGPoint()], the update function then takes care of moving to the next goto point.
This function is called every cycle of touchesmoved, so the player can switch directions if the finger moves over an obstacle
My question is: 1) what in this code is causing the unbearable lag, 2) how can I make this code more efficient?
My code:
initializing vars:
var obstacles = [GKPolygonObstacle(points: [float2()])]
var navgraph = GKObstacleGraph()
setting up a graph (called at start of level):
func setupGraph(){
var wallnodes :[SKSpriteNode] = [SKSpriteNode]()
self.enumerateChildNodes(withName: "wall") {
node, stop in
wallnodes.append(node as! SKSpriteNode)
}
self.enumerateChildNodes(withName: "crate") {
node, stop in
wallnodes.append(node as! SKSpriteNode)
}
obstacles = SKNode.obstacles(fromNodeBounds: wallnodes)
navgraph = GKObstacleGraph(obstacles: obstacles, bufferRadius: Float(gridSize/2))
}
touchesmoved: I keep track of multiple touches using strings. only one finger may act as the "MoveFinger." this string is created at touchesbegan and emptied at touchesended
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
touchloop: for touch in touches {
if MoveFinger == String(format: "%p", touch) {
let location = touch.location(in: self)
player.destination = location
makeGraph()
player.moving = true
player.UpdateMoveMode()
continue touchloop
}
// .... more if statements ....
}
}
and graph function (called every touchesmoved cycle):
func makeGraph(){
let startNode = GKGraphNode2D(point: float2(Float(player.position.x), Float(player.position.y)))
let endNode = GKGraphNode2D(point: float2(Float(player.destination.x), Float(player.destination.y)))
let graphcopy = navgraph
graphcopy.connectUsingObstacles(node: startNode)
graphcopy.connectUsingObstacles(node: endNode)
let path = graphcopy.findPath(from: startNode, to: endNode)
player.goto = []
for node:GKGraphNode in path {
if let point2d = node as? GKGraphNode2D {
let point = CGPoint(x: CGFloat(point2d.position.x), y: CGFloat(point2d.position.y))
player.goto.append(point)
}
}
if player.goto.count == 0 {
//if path is empty, go straight to destination
player.goto = [player.destination]
} else {
//if path is not empty, remove first point (start point)
player.goto.remove(at: 0)
}
}
any ideas?
I am creating a game ad i am having a hard time creating a jump button. I have created the jump up and fall down SKaction sequence which works perfect here is how it works.
func JumpArrow () {
self.addChild(jumpArrow)
jumpArrow.position = CGPointMake(60, 145)
jumpArrow.xScale = 1
jumpArrow.yScale = 1
}
func heroJumpMovement () {
let heroJumpAction = SKAction.moveToY(hero.position.y + 85,
duration: 0.5)
let heroFallAction = SKAction.moveToY(hero.position.y , duration:
0.5)
let jumpWait:SKAction = SKAction.waitForDuration(CFTimeInterval(1))
let heroMovementSequence:SKAction =
SKAction.sequence([heroJumpAction, heroFallAction ,jumpWait])
hero.runAction(heroMovementSequence)
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == jumpArrow {
heroJumpMovement()
}
however, I have a problem. if you quickly tap the button the player will fly off the screen. I hope that i can create a UItapGestureRecognizer and create a delay for the tap so you can't tap the button 2-4 times per second you will only be able to tap it once. If this is the wrong way to go about this please tell me
Adding a delay would be the wrong way to go about it.
Instead, in your touchesBegan function, before you call heroJumpMovement() you should check to see if your player is on the ground or not.
Another alternative would be to check if the last jump SKActionSequence has completed or not.
To do the above, you would have something like this (code not checked):
var canJump = true; // Variable will be true if we can jump
func JumpArrow () {
self.addChild(jumpArrow)
jumpArrow.position = CGPointMake(60, 145)
jumpArrow.xScale = 1
jumpArrow.yScale = 1
}
func heroJumpMovement () {
let heroJumpAction = SKAction.moveToY(hero.position.y + 85,
duration: 0.5)
let heroFallAction = SKAction.moveToY(hero.position.y , duration:
0.5)
let jumpWait:SKAction = SKAction.waitForDuration(CFTimeInterval(1))
let heroMovementSequence:SKAction =
SKAction.sequence([heroJumpAction, heroFallAction ,jumpWait])
canJump = false; // We are about to jump so set this to false
hero.runAction(heroMovementSequence, completion: {canJump = true;}) // Set the canJump variable back to true after we have landed
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == jumpArrow {
if (canJump) { // Make sure we are allowed to jump
heroJumpMovement()
}
}
Notice the canJump variable.
I have tried to find a way to detect if a SKSpriteNode left the screen (I would like to call a Game Over function).
I have declared the node within a function (It is not global if I get that right?) and made and SKAction that moves the Node out of the screen and removes it afterwards.
This is what I came up with:
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
Now what I need is to call GameOver() if the node left the screen.
I am very thankful for every answer!
In your scene you have to remember the reference for node you want to check and then in update method you just do the following:
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
Edit:
class MyScene: SKScene {
// ....
//here is the list of nodes which you want to check
var nodesToCheck = [SKSpriteNode]()
//here is your spawn function
func spawnNewNode() {
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
nodesToCheck.append(node)
}
//and here is the update method
override func update(currentTime: NSTimeInterval) {
super.update(currentTime)
// ... every other update logic
for node in nodesToCheck {
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
}
}
func gameOver() {
println("Damn!")
}
}
Dont forget to remove your node from nodeToCheck array when they are no longer scene members.
I am using Swift and Sprite Kit to develop a game on XCode Beta 6.
In order to detect if all nodes are sleeping, i check their physicsBody.resting property.
In update method i print out the result.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var hero:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
hero = SKSpriteNode(imageNamed: "Spaceship")
hero.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
hero.zPosition = 10.0
hero.physicsBody = SKPhysicsBody(circleOfRadius: hero.size.width/2)
hero.physicsBody.allowsRotation = false
hero.physicsBody.linearDamping = 0.5
self.addChild(hero)
}
override func update(currentTime: CFTimeInterval) {
if hero.physicsBody.resting {
println("resting")
} else {
println("moving")
}
}
}
To my surprise, the results are:
moving
resting
moving
(n times the same)
moving
resting
So why the hero is moving, although i didn't do anything. The node moves N times and takes a break(resting), after that goes on moving.
Can anyone explain that behaviour? Is that a bug or do i miss something? Thanks in advance.
If you examine the velocity of a physics body, you'll see that it is indeed moving but at a rate that is not perceivable. That's why the resting property is not set. A more reliable way to check if a SKPhysicsBody is at rest is to test if its linear and angular speeds are nearly zero. Here's an example of how to do that:
func speed(velocity:CGVector) -> Float {
let dx = Float(velocity.dx);
let dy = Float(velocity.dy);
return sqrtf(dx*dx+dy*dy)
}
func angularSpeed(velocity:CGFloat) -> Float {
return abs(Float(velocity))
}
// This is a more reliable test for a physicsBody at "rest"
func nearlyAtRest(node:SKNode) -> Bool {
return (self.speed(node.physicsBody.velocity)<self.verySmallValue
&& self.angularSpeed(node.physicsBody.angularVelocity) < self.verySmallValue)
}
override func update(_ currentTime: TimeInterval) {
/* Enumerate over child nodes with names starting with "circle" */
enumerateChildNodesWithName("circle*") {
node, stop in
if (node.physicsBody.resting) {
println("\(node.name) is resting")
}
if (self.nearlyAtRest(node)) {
println("\(node.name) is nearly resting")
}
}
}