How to make a shot in spritekit? - ios

I have created a method called attackButton. Inside this function, I wrote this way in order to fire a shot. hero below is a node where I want a shot to be born.
func attackButton() {
shot.physicsBody = SKPhysicsBody(edgeLoopFromRect: shot.frame)
shot.position = CGPointMake(hero.position.x, hero.position.y)
shot.physicsBody?.categoryBitMask = shootingCategory
shot.physicsBody?.contactTestBitMask = enemyCategory
addChild(shot)
let run = SKAction.moveByX(2, y: 0, duration: 1)
shot.runAction(run)
}
The shot node is displayed on the scene, but the node never moves and after a button is pressed twice, an error occurs. I guess it is because of lack of code which is removeFromSuperview(), or removeFromParent, though I am unsure about that. So, why does the node not move to x direction? Should I write this inside the update(currentTime: CFTimeInterval) method? Though to me this sound a bit too technical. This attackButton is detected by being written in the touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)by specifying the name of the node like this:
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "attackButton" {
attackButton()
}
}
If I write this function in the update(currentTime: CFTimeInterval), how is the method is triggered? I can't figure out as the method does not know if I press the attackButton, because this attackButton is a node and the name of the node has to be specified, right? Maybe what I say does not make sense, but I appreciate if you understand me, and could you give me some code that enables firing a shot? Thanks!

Related

Can I have physics bodies collide without pushing each other?

I have a game made of little SKNodes moving around. They can not overlap, so I use physics bodies, so that they move around each other. However, in the case that one sknode is animated to follow another, it pushes the sknode ahead. Setting the collision bitmask to 0 makes them overlap, so that is not an option. But otherwise, they push each other way beyond my desired speed. I need a way to get rid of the 'pushing' without overlapping using skphysics bodies. Is there a property I can set to fix this?
Notes: I use skanimations to move my nodes around. If you need any pertinent code, tell me... I don't know where to start. I am using Swift 3.0, but I will accept answers with 2.2/2.3 syntax.
EDIT: The real solution was to change the node's velocity instead of animating movement with SK Actions.
Change the restitution on your physics body of the moving object to 0 when a contact happens, and set the weight of the object you do not want to move really high
Have you tried setting the dynamic property to false on the body being pushed?
After you have set contactTestBitMask you could set this:
myHero.physicsBody!.collisionBitMask = 0
to prevent collisions but permit contacts.
Updating position of dynamic physics body with SKAction doesn't work well. It's just like saying - Hey, no matter what physics world rules say just move my node like I want.
First solution - to make one node follow another with some particular distance use SKConstraint.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let nodeA = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 50, height: 50))
nodeA.name = "nodeA"
addChild(nodeA)
let nodeB = SKSpriteNode(color: SKColor.redColor(), size: nodeA.size)
nodeB.name = "nodeB"
addChild(nodeB)
let followConstraint = SKConstraint.distance(SKRange.init(lowerLimit: nodeB.size.width * 1.5, upperLimit: nodeB.size.width * 1.5), toNode: nodeA)
nodeB.constraints = [followConstraint]
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else {
return
}
childNodeWithName("nodeA")!.runAction(SKAction.moveTo(touch.locationInNode(self), duration: 2))
}
}
If you still want physics bodies and SKActions...
Set "dynamic" property of every physics body to false.
Check in didBeginContact (SKPhysicsContactDelegate protocol) if they are overlapping. If it's true than remove all the actions that are changing their positions. Tip - make the physics body little bigger than it's node.

Track the position of SKSpriteNode while doing a SKAction moveTo

I'm trying to figure a way to track a postions for multiple nodes, that spwan randomly on the screen so i can make a changes to them while moving when the reach random postion.
the nodes just move along the x axis and i want to be able to generate random number from 0 to postion.x of the ball, and change the color when it reachs the postion
override func update(currentTime: CFTimeInterval)
i tried tacking changes in update method but as soon as new node appers i lost track of the previos one
i also tried
let changecolor = SKAction.runBlock{
let wait = SKAction.waitForDuration(2, withRange: 6)
let changecoloratpoint = SKAction.runBlock { self.changecolorfunc(self.ball)}
let sequence1 = SKAction.sequence([wait, changecoloratpoint])
self.runAction(SKAction.repeatAction(sequence1, count: 3))
}
but it doesn't give me any control over the random postion
You have already all you needed.
Suppose you have a reference for your node:
var sprite1: SKSpriteNode!
And you want to spawn it to a random position (an example method..):
self.spawnToRandomPos(sprite1)
And suppose you want to moveTo your sprite1:
self.sprite1.runAction( SKAction.moveToY(height, duration: 0))
Everytime you check his position you know where is it:
print(sprite1.position)
So to know always your sprite1 position you could do this code and you see all it's movements:
override func update(currentTime: CFTimeInterval) {
print(sprite1.position)
}
P.S.:
In case you dont have references about your object spawned you can also give a name to a generic object based for example by a word followed to a counter (this is just an example):
for i in 0..<10 {
var spriteTexture = SKTexture(imageNamed: "sprite.png")
let sprite = SKSpriteNode(texture: spriteTexture, size: spriteTexture.size)
sprite.name = "sprite\(i)"
self.addChild(sprite)
}
After this to retrieve/re-obtain your sprite do:
let sprite1 = self.childNodeWithName("sprite1")
There are probably a hundred different ways of doing this. Here is how I would do it.
in your update func
checkForCollisions()
this will just scroll through "obstacles" that you randomly generate, and place whoever you want the color trigger to happen. They can be anything you want just change the name to match. if they overlap your "ball" then you can color one or the other.
func checkForCollisions() {
self.enumerateChildNodesWithName("obstacles") { node, stop in
if let obstacle: Obstacle = node as? Obstacle {
if self.ball.intersectsNode(obstacle) {
//color node or ball whichever you want
}
}
}
}

How to spawn a node on a scheduled timer loop in a override touch began method

So I am creating a shooting game for iOS and I am having trouble looping a bullet spawn method that is activated by touching and moving a joystick. I've tried what seems like everything but with coding there is always a right way.
Here is my bullet defined:
func spawnBullet1(){
self.addChild(bullet1)
bullet1.position = CGPoint (x: gun1.position.x , y:
gun1.position.y) // Bullet spawn # anchor point
bullet1.xScale = 0.5
bullet1.yScale = 0.5
//physics, not important right now
bullet1.physicsBody = SKPhysicsBody(rectangleOfSize: bullet1.size)
bullet1.physicsBody?.categoryBitMask = PhysicsCategory.bullet1
bullet1.physicsBody?.contactTestBitMask = PhysicsCategory.enemy1
bullet1.physicsBody?.affectedByGravity = false
bullet1.physicsBody?.dynamic = false
let wait:SKAction = SKAction.waitForDuration(0.5) //bullet spawn wait
let block:SKAction = SKAction.runBlock(spawnBullet1) //re-spawn bullet
let seq3:SKAction = SKAction.sequence( [ wait, block ]) //sequence
self.runAction(seq3) //execution
}
My joystick in my touches begins here:
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
}else{
stickActive = false
}
if stickActive == true {
spawnBullet1()
}
Obviously I left out some details about the joystick because that's not the part I'm having trouble with. The problem I am having is that my first bullet launches just as planned and works great, however, when the second bullet is spawned the app crashes, saying that it cannot add SKnode when node already has a parent or something like that, basically its saying that I am duplicating the bullet the wrong way and I can't figure out what the right way is.

Draw straight line in Swift with Sprite Kit, creating multiple lines

I'm trying to make a game in iOS, using Swift and Sprite Kit. I have the following bit of codes, but it's creating multiple lines. What I want to do is to create a single line, end of which will follow the tip of the finger of the user as the user drags the finger across the screen until the he or she takes the finger off the screen, then for the line to remain there. I found a similar question on stack overflow at Drawing straight line with spritekit and UITouch creates multiple lines, but it is written in Objective-C. And I don't think I understood the answer entirely to be honest. I'm using Xcode 7 and Swift 2.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
}
let path = CGPathCreateMutable()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let position1 = touch!.locationInNode(self)
CGPathMoveToPoint(path, nil, position1.x, position1.y)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let position2 = touch!.locationInNode(self)
CGPathAddLineToPoint(path, nil, position2.x, position2.y)
CGPathCloseSubpath(path)
let line = SKShapeNode()
line.path = path
line.strokeColor = UIColor.blackColor()
line.lineWidth = 5
self.addChild(line)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Any thoughts or suggestions would be greatly appreciated. Thanks.
Ryan
I may be wrong because I don't know too much about game development with Swift. But it appears you are creating a line every time you more your finger, have you tried creating the line in touchesEnded?
Did you try your code without the line
CGPathCloseSubpath(path)
According to the documentation, this is what it does:
Appends a line from the current point to the starting point of the
current subpath and ends the subpath.
and I don't think you want to have an additional line from your last touch to the start of the line with every new touch. Closing of a CGPath is only needed if you want a line that ends where it started.
I would cheat to do this:
Create a very thin sprite, in the line colour you want, and stretch its end point to where you want it to go.
This is effectively a rectangular box made out of an SKSpriteNode that's stretched from the origin to the current point of touch.
Rotating and then stretching a SKSpriteKit node is best done (for me) by attaching it to an SKNode, and rotating the node as needed, then stretching the nested SKSpriteNode as far as is needed to reach the user's current touch point.
Put the origin of the SKNode and the SKSpriteNode, at the same place, the middle left edge, and rotate around that. Or whatever works for you mathematically... This is makes the most sense to me, but it could be the centre points of any edge of the rectangle and you then stretch accordingly.

SpriteKit Node gives me nil when subclassed (maybe?) to another node | Swift

Okay so i am trying to learn how to create a game... I want a node to be the camera, so that i can move it and center the view to my player node. When i subclass (i don't know if it's right to say that i subclass it, maybe not...) the player node to the world node, the application crashes. When player is simply a node, a "subclass" of GameScene (and not of the "world" node), it goes "fine", i mean i can move my player (yeah but the camera doesn't work).
so here is my code (few // lines in italian but not relevant)
import SpriteKit
class GameScene: SKScene {
var world: SKNode? //root node! ogni altro nodo del world dev'essere sottoclasse di questo
var overlay: SKNode? //node per l'HUD e altre cose che devono stare sopra al world
var camera: SKNode? //camera node. muovo questo per cambiare le zone visibili
//world sprites
var player: SKSpriteNode!
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPoint(x: 0, y: 0) //center the scene's anchor point at the center of the screen
//world setup
self.world = SKNode()
self.world!.name = "world"
self.addChild(world!)
//camera setup
self.camera = SKNode()
self.camera!.name = "camera"
self.world!.addChild(self.camera!)
//UI setup
self.overlay = SKNode()
self.overlay?.zPosition = 10
self.overlay?.name = "overlay"
addChild(self.overlay!)
player = world!.childNodeWithName("player") as SKSpriteNode!
}
var directionToMove = CGVectorMake(0, 0)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
directionToMove = CGVectorMake((directionToMove.dx + (location.x - player!.position.x)), (directionToMove.dy + (location.y - player!.position.y)))
}
}
override func update(currentTime: CFTimeInterval) {
//*these both print the string*
if player == nil {
println("player is nil")
}
if player?.physicsBody == nil {
println("player.physicsBody is nil")
}
//*here it crashes*
player!.physicsBody!.applyForce(directionToMove)
}
override func didFinishUpdate() {
if self.camera != nil {
self.centerOnNode(self.camera!)
}
}
func centerOnNode(node: SKNode) {
let cameraPositionInTheScene: CGPoint = node.scene!.convertPoint(node.position, fromNode: node.parent!)
node.parent!.position = CGPoint(x: node.parent!.position.x - cameraPositionInTheScene.x, y: node.parent!.position.y - cameraPositionInTheScene.y)
}
}
thanks in advance : )
Your player is nil because you are accessing it from the world node, which is empty. By default, the player is a child of scene. You can change this in the scene editor by setting the node's parent property (see Figure 1).
Figure 1. SpriteKit Scene Editor's Property Inspector
I suggest you make the following changes:
Create the world, overlay, and camera nodes in the scene editor and access them with childNodeWithName
Add the player to the scene, not the world, and have it fixed in middle of the scene
Move the camera node not the player. The code in didFinishUpdate will automatically adjust the world to center the camera in the scene. You can add a physics body to the camera and move it by applying a force/impulse or by setting its velocity.
Add the other sprites to the world (instead of to the scene)
If you add the other sprites to the world, they will move appropriately when you adjust the world's position to center the camera. You will then need to move the other sprites relative to the world. The scene is just a window to view a portion of the world at a time.
I'm not sure why you are using optionals everywhere. You only need to use an optional when a variable might be nil. Are your world, overlay, and camera ever going to be nil? I would guess probably not.
To answer your question:
Youre trying to get player from world. I dont see that youve added any player sprite to the world node. It doesnt find it, and youre unwrapping an optional that isnt there. So you get an error.
You're going to have a lot more luck if you only use optionals when you need them. If you use them for everything youre going to add unnecessary complexity to your code.
Optionals should be the exception, not the rule. I could try to fix your code for you, but I'm not sure what you intended the player sprite to be?
Follow simple tutorials and start small. Things will make more and more sense over time.

Resources