how to repetitively spawn a SKnode while a button is active - swift

I'm creating a game and I am trying to create a shooting method. i have the bullets spawning at a gun and moving in the direction f a joystick. here is how i am spawning bullets at a consistent rate (a fire rate) with the gun and moving in the direction toward the joystick.
override func didMoveToView(view: SKView) {
if fireWeapon == true {
NSTimer.scheduledTimerWithTimeInterval(0.25, target: self,
selector: Selector ("spawnBullet1"), userInfo: nil, repeats: true)
}
}
func spawnBullet1(){
self.addChild(bullet1)
bullet1.position = CGPoint (x: hero.position.x , y:hero.position.y)
bullet1.xScale = 0.5
bullet1.yScale = 0.5
bullet1.physicsBody = SKPhysicsBody(rectangleOfSize: bullet1.size)
bullet1.physicsBody?.categoryBitMask = PhysicsCategory.bullet1
bullet1.physicsBody?.contactTestBitMask = PhysicsCategory.enemy1
bullet1.physicsBody?.affectedByGravity = false
bullet1.physicsBody?.dynamic = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (CGRectContainsPoint(joystick.frame, location)) {
stickActive = true
if stickActive == true {
fireWeapon = true
}
This method only launches the first bullet then right before it launches the second one the app crashes. everything with the bullet movement is perfect just spawning a stream of bullets for firing. I can't think of an alternative way to create a fire speed.

You are always trying to add the same child when you call self.addChild(bullet1)
You need to create a new instance every time in the spawnBullet1 function and add that object as a child.

Related

TouchesMoved Lag with Pathfinding

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?

Cannot disable, then reenable touch, after an SKAction animation

I am working on an interactive, animated scene. I want all touches on the scene to be disabled on entry. Then, once the objects (which are subclassed nodes) in the scene finish rotating/moving, I want to re-enable all touches on the screen to allow interaction. I have disabled user interaction using this code:
override func didMove(to view: SKView) {
setupNodes()
view?.isUserInteractionEnabled = false
spinLocations()
}
This is the code, within the scene file, for spinLocations:
func spinLocations() {
var allLocationArrays = [[String : CGPoint]]()
var previousArray = hiddenLocationPositions
for _ in 0...SearchConstant.numSpins {
let freshArray = generateNewLocationArray(previous: previousArray)
allLocationArrays.append(freshArray)
previousArray = freshArray
}
for (item, _) in hiddenLocationPositions {
let node = fgNode.childNode(withName: item) as! LocationNode
node.spin(position: allLocationArrays) // this is function below
}
hiddenLocationPositions = previousArray
}
This is the code for the animations in the node class:
func spin(position: [[String : CGPoint]]) {
var allActions = [SKAction]()
for array in position {
let action = SKAction.move(to: array[self.name!]!, duration: 2.0)
allActions.append(action)
}
let allActionsSeq = SKAction.sequence(allActions)
self.run(SKAction.sequence([SKAction.wait(forDuration: 5.0), allActionsSeq, SKAction.run {
self.position = position[position.count - 1][self.name!]!
},]))
}
This is the code for passing back the touches to the main scene from this class:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let parent = self.parent else { return }
}
As you can see, touch is not disabled here.
I do not want to add a "waitForDuration" SKAction to the runBlock to change the view status after the previous action; I want the program to determine when the animations are finished executing and then re-enable touches.
In order to do this, I theorised using a completion handler might work, but it only re-enables touches immediately (e.g. handling a handler to spin causes the touches to be detected again). Previously, I also tried to disable the view in the runBlock, but of course, that is run instantaneously. How do I ensure that the touches are re-detected following the animation without using "waitForDuration."?
So, this is a simple example that shows how you can:
1) Disable touches completely
2) Spin a node
3) When node is done with spinning, to enable touches
Here is the code (you can copy/paste it to try how it works):
class Object:SKSpriteNode{
func spin(times:Int,completion:#escaping ()->()) {
let duration = 3.0
let angle = CGFloat(M_PI) * 2.0
let oneRevolution = SKAction.rotate(byAngle: angle , duration: duration)
let spin = SKAction.repeat(oneRevolution, count: times)
let sequence = SKAction.sequence([spin,SKAction.run(completion)])
run(sequence, withKey:"spinning")
}
}
class WelcomeScene: SKScene {
override func didMove(to view: SKView) {
view.isUserInteractionEnabled = false
print("Touches Disabled")
let object = Object(texture: nil, color: .purple, size: CGSize(width: 200, height: 200))
addChild(object)
object.spin(times: 3, completion: {[weak self] in
self?.view?.isUserInteractionEnabled = true
print("Touches Enabled")
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch detected")
}
deinit {
print("Welcome scene deinited")
}
}
Here, you disable touches when scene is loaded, start spinning the object, and you pass a completion block to it... That block of code is used here:
let sequence = SKAction.sequence([spin,SKAction.run(completion)])
So after spinning, that block will be executed. Now, there are different ways to do this...Personally, I would use delegation, but I thought this can be less confusing... I can write an example for delegation too if needed, but basically, what you would do, is to set a scene as a delegate of your custom node, and notify it about spinning is done, so the scene can tell the view to re-enable the touches.

Attemped to add a SKNode which already has a parent swift

I am creating a game and i keep getting this error for my bullet spawn method linked with a joystick. I want to repetitively spawn nodes when the joystick is active Here is how i am creating a firing method
override func didMoveToView(view: SKView) {
if fireWeapon == true {
NSTimer.scheduledTimerWithTimeInterval(0.25, target: self,
selector: Selector ("spawnBullet1"), userInfo: nil, repeats: true)
}
}
func spawnBullet1(){
self.addChild(bullet1)
bullet1.position = CGPoint (x: hero.position.x , y:hero.position.y)
bullet1.xScale = 0.5
bullet1.yScale = 0.5
bullet1.physicsBody = SKPhysicsBody(rectangleOfSize: bullet1.size)
bullet1.physicsBody?.categoryBitMask = PhysicsCategory.bullet1
bullet1.physicsBody?.contactTestBitMask = PhysicsCategory.enemy1
bullet1.physicsBody?.affectedByGravity = false
bullet1.physicsBody?.dynamic = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (CGRectContainsPoint(joystick.frame, location)) {
stickActive = true
if stickActive == true {
fireWeapon = true
}
the first bullet launches as planned and works great however, every time the second bullet launches the app crashes and i get the error. can someone tell me an alternative way to create a fire rate
Instead of self.addChild(bullet1) you need something like self.addChild(SKSpriteNode(imageNamed: "bullet.png"))
This because you need to create a new bullet every time you fire, you do not want to move the same bullet every single time.

How to make SKSpriteNode with fixed position (after adding physics)?

I have several programmatically made SKSpriteNode's. Some of them I want to move around, some I want to be static (have a fixed position). When adding physics to the nodes (need that to be able to do collision detection, right?) and set physicsBodyXXXX.dynamic = false they stay in the same position when moving other object over them. That's fine!
But, I'm still able to grab the node I want to be statically positioned, and move them around. How can I mask out the node I don't want to move in touches function? Or is there another solution?
Tried to find a property like static which made the node's position fixed, but can't find it...
Here's my code for auto generating nodes (in override func didMoveToView(view: SKView):
for Character in englishWord{
// Make letters:
let letterToMove = SKSpriteNode(imageNamed: "\(Character)")
//then setting size and position
var physicsBodyLetterToMove = SKPhysicsBody(rectangleOfSize: letterToMove.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterToMove.allowsRotation = false
physicsBodyLetterToMove.dynamic = false
letterToMove.physicsBody = physicsBodyLetterToMove
self.addChild(letterToMove)
// Make empty boxes for the letters:
let letterRecBox = SKSpriteNode(imageNamed: "EmptyBox")
//then setting size and position
var physicsBodyLetterRecBox = SKPhysicsBody(rectangleOfSize: letterRecBox.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterRecBox.dynamic = false
letterRecBox.physicsBody = physicsBodyLetterRecBox
self.addChild(letterRecBox)
}
So the touches func's:
var selected: [UITouch: SKNode] = [:]
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
selected = [:]
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
selected[touch as UITouch] = nodeAtPoint(location)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
for (touch, node) in selected{
if !contains([self], node){
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
}
Any idea?
Setting dynamic to false will make the node unaffected by physics. SKActions and touch events are not considered physics so they will still affect your nodes that are not dynamic.
You could do something like:
YourSpriteNode.name = #"staticNode"; //Right after you create it
Then alter your touch method:
for (touch, node) in selected{
if !contains([self], node){
if(![node.name isEqualToString:#"staticNode"])
{
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
The newly introduced if statement will prevent any node named "staticNode" from getting moved due to your SKActions. Other nodes will move as expected.
Thanx, that worked.
Only, I had to write it like this:
letterRecBox.name = "staticNode"
...
if !(node.name == "staticNode"){
...then do something...
}

swift moving and releasing object with touch

I'm new to coding. I have created a scene in swift sprite kit with a ball and floor. When executed, the ball drops and bounces on the floor. I am looking for a bit advise on how to move and drop the ball with touches.
To detect drag ball you have to create UIPanGestureRecognizer and add it to the ball's view
let dragBall = UIPanGestureRecognizer(target: self, action:"dragBall:")
ball.addGestureRecognizer(dragBall)
Next step is implementing "dragBall" function which will handle drag event, example:
#IBAction func dragBall(recognizer: UIPanGestureRecognizer) {
let point = recognizer.locationInView(self.view);
ball.center.x=point.x
ball.center.y=point.y
}
You should also disable gravity for this object for time of dragging, you can check if Pan gesture ended by checking state of gesture recognizer, if state is UIGestureRecognizerStateEnded you should add gravity again
import SpriteKit
class GameScene: SKScene {
let ball = SKSpriteNode(imageNamed: "ball")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
ball.position = CGPoint(x:500,y:500)
ball.anchorPoint = CGPoint(x:0.0,y:0.0)
ball.size = CGSize(width: 60, height: 60)
// physics
ball.physicsBody = SKPhysicsBody(circleOfRadius: 1)
// this defines the mass, roughness and bounciness
ball.physicsBody.friction = 0.2
ball.physicsBody.restitution = 0.8
ball.physicsBody.mass = 0.1
// this will allow the balls to rotate when bouncing off each other
ball.physicsBody.allowsRotation = true
self.addChild(ball)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)
{ ball.position = touches.anyObject().locationInNode(self); }
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
by the end of the day i worked out this bit of code but it seems very hard to keep hold of ball