Adding multiple sprite nodes to the scene using single function/method in swift - swift

Let's say I have 10 nodes, where all of the nodes are the dots image, which are node1 thru node10. I create node1 as the following:
func createNode1() -> SKNode {
let spriteNode = SKNode()
spriteNode.position = CGPointMake(CGRectGetMidX(self.frame)/1.35, CGRectGetMidY(self.frame)/1.32)
let sprite = SKSpriteNode(imageNamed: "dot_1")
sprite.zPosition = 3.0
sprite.name = "A1_Dot"
spriteNode.addChild(sprite)
return spriteNode
}
I create the rest of nodes by creating 9 more functions, where next one would be as func createNode2etc, all the way up to 10 functions, where the only difference between them is node's name and its location. Basically each node has different location in the scene and of course different image name. Is there a way to load of the 10 nodes to the scene at once and manipulate node's locations at the time of use.? I'm looking for a way to load all 10 nodes to scene using a single function or method and assign node's positions within this same function. Thanks.

You need to use a loop to iterate through an array of positions, and move your code that adds the node to the scene into the loop:
let positions = [CGPointMake(CGRectGetMidX(self.frame)/1.35, CGRectGetMidY(self.frame)/1.32), ... /*add your 9 other positions here*/]
positions.enumerate().forEach { (index, point) in
let spriteNode = SKNode()
spriteNode.position = point
let sprite = SKSpriteNode(imageNamed: "dot_\(index + 1)")
sprite.zPosition = 3.0
sprite.name = "A\(index + 1)_Dot"
spriteNode.addChild(sprite)
// Add spriteNode to the scene here
}

You can either use a loop as Jugale suggested or you could just pass the values you want into the method
For example
func createNode1(imageNamed imageNamed: String, name: String, pos: CGPoint) -> SKNode {
let spriteNode = SKNode()
spriteNode.position = pos
let sprite = SKSpriteNode(imageNamed: imageNamed)
sprite.zPosition = 3.0
sprite.name = name
spriteNode.addChild(sprite)
return spriteNode
}
And now in your scene you can add the nodes like so
let node1Pos = ...
node1 = createNode1(imageNamed: "...", name: "A1_Dot", pos: node1Pos)
let node2Pos = ...
node2 = createNode1(imageNamed: "...", name: "A1_Dot", pos: node2Pos)
I am saying ImageNamed twice in the create Node function because when you pass stuff into functions Swift by default does not require the first description to be typed when calling the method. (see below)
So if would say imageNamed only once than you would call it like so.
node1 = createNode1("...", pos: node1Pos)
Also your creating node function could be simplified, unless you specifically want to return a SKNode in the method.
func createNode1(imageNamed imageNamed: String, name: String, pos: CGPoint) -> SKSpriteNode {
let sprite = SKSpriteNode(imageNamed: imageNamed)
sprite.position = pos
sprite.zPosition = 3.0
sprite.name = name
addChild(sprite)
return sprite
}

Either way is acceptable solution. Here are full details. In baseScene we create function createNodes and call that function in didMoveToView where nodeA1 is given position and added to the scene as shown below.
override func didMoveToView(view: SKView) {
let nodeA1Pos = CGPointMake(CGRectGetMidX(self.frame)/1.5, CGRectGetMidY(self.frame)/2.0)
nodeA1 = createNodes(imageNamed: "dot_1", name: "A1_Dot", pos: nodeA1Pos)
addChild(nodeA1)
}
Then in Level1Scene which is subclass of baseScene we just give a new position to nodeA1 which will override position originally set in baseScene:
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
nodeA1.position = CGPointMake(CGRectGetMidX(self.frame)/1.3, CGRectGetMidY(self.frame)/0.67)
}
This way of subclassing saves a lot of time and code as only one function is used to generate all common sprite nodes.
All thanks to crashoverride777 !!!!

Related

How can I handle a touch as a consequence of a previous touch? (Swift, SpriteKit)

This is a chess game in Swift using SpriteKit.
What I would like to happen is for the touched piece to move to the square touched on the next touch. When I run, the piece does not move when I try this. I know for certain that my code works up to and including 'case: "p"', and that the sprite is detecting the first touch.
In the image I've tried to implement this second touch using 'touch2', is this the correct thing to do? Thanks.
This is how I'm detecting the first touch and identifying the piece which has been touched. touchedPiece is the identified piece. moveset is an attribute determining the possible moves.
for touch in touches
{
let location = touch.location(in:self)
let firstTouchedNode = atPoint(location)
let touchedPiece:SKSpriteNode = (firstTouchedNode as? SKSpriteNode)!
switch touchedPiece.moveset {
case "p":
let locationToMove = touch.location(in:self)
let moveto = findpawnmoves(piece: touchedPiece)
spawnMoves(arr: moveto)
And here is the attempt to move the piece after locationToMove has been decided.
if moveto[0].contains(locationToMove){
move(piece: touchedPiece, location: moveto[0].position)
whiteMoved = true
deleteMoves(arr: moveto)
This is an example of how I am creating an array of possible squares to move to. I'll use the knight moves because it's the most concise.
func createMoveNode(piece: SKSpriteNode, up:Int, across:Int) -> SKSpriteNode{
let moveNode = SKSpriteNode(color: .clear, size: CGSize(width: CGFloat(cellsize), height: CGFloat(cellsize)))
moveNode.anchorPoint = CGPoint(x:0.5, y:0.5)
moveNode.position = CGPoint(x: piece.position.x + CGFloat(Double(across)*d), y: piece.position.y + CGFloat(Double(up)*d))
return moveNode
}
func findknightmoves(piece: SKSpriteNode) -> Array<SKSpriteNode>{
let move1 = createMoveNode(piece: piece, up:2, across:-1)
let move2 = createMoveNode(piece: piece, up:2, across:1)
let move3 = createMoveNode(piece: piece, up:-2, across:-1)
let move4 = createMoveNode(piece: piece, up:-2, across:1)
return [move1,move2,move3,move4]
}
func move(piece: SKSpriteNode, location: CGPoint){
piece.position = location
}
func spawnMoves(arr: Array<SKSpriteNode>){
for i in 0...arr.count-1{
arr[i].zPosition = 1
self.addChild(arr[i])
}
}
func deleteMoves(arr: Array<SKSpriteNode>){
for i in 0...arr.count-1{
arr[i].removeFromParent()
}
}

Giving yourself a physics body in AR kit?

In my program, I am trying to make myself collidable with other objects so that I can test collisions. Is there a way to add a physics body that moves with myself in the new AR Kit which follows you? I can make one which is at my place at the beginning of the AR but I'm sure there is a way to add it to your actual self.
Good day!
There is a solution to your problem.
First of all you need to create a collider. For example:
func addCollider(position: SCNVector3, name: String) {
let collider = SCNNode(geometry: SCNSphere(radius: 0.5))
collider.position = position
collider.geometry?.firstMaterial?.diffuse.contents = UIColor.clear
collider.physicsBody?.categoryBitMask = 0 // example
collider.physicsBody?.contactTestBitMask = 1 // example
collider.name = name
sceneView.scene.rootNode.addChildNode(collider)
}
Don't forget to add proper bit masks, to collide with other objects.
Next you need to implement ARSCNViewDelegate protocol and call method willRenderScene. Next you need to locate your point of view and transform it into the matrix. Than you need to extract values from third column of the matrix to get current orientation. To get current location you need to extract values of forth column from your matrix. And finally you need to combine your location and orientation to get your current position.
To combine to SCNVector3 values you need to write global function "+". Example:
func +(left: SCNVector3, right: SCNVector3) -> SCNVector3 {
return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
}
Be sure, that you wrote this method outside of class or struct.
Final accord! A function that will move a collider accordingly to your current position.
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
guard let pontOfView = self.sceneView.pointOfView else { return }
let transform = pontOfView.transform
let orientation = SCNVector3(-transform.m31, -transform.m32, -transform.m33)
let location = SCNVector3(transform.m41, transform.m42, transform.m43)
let currentPosition = orientation + location
for node in sceneView.scene.rootNode.childNodes {
if node.name == "your collider name" {
node.position = currentPosition
}
}
}
P.S. Don't forget to add
self.sceneView.delegate = self
to your viewDidLoad.
Best of luck. I hope it helped!

How to change node value

func createBall(){
ballS = SKNode()
let ball = SKSpriteNode(imageNamed: "Ball")
saw.zPosition = 2
ballS.addChild(ball)
self.addChild(ballS)
}
This creates a Ball node with z position 2. But while running the game, I want to create another ball that has a z position at 1. How can I do this, or do I have to make a whole new function that makes a ball node with z position 1?
If each ball sprite has to be a child of the ballS node, then you could do the following.
Define ballS as a property (i.e. after the class definition but before any functions)
let balls = SKNode()
in didMove(to:), add the ballS Node:
addChild(ballS)
Create a addBall function: (don't call it createBall unless all it does create the ball)
func addBall(withZPosition zPos: Int) {
let newBall = SKSpriteNode(imageNamed: "Ball")
newBall.zPosition = zPos
ballS.addChild(newBall)
}
Then simply call addBall(withZPosition:) to create and add a new ball:
addBall(withZPosition: 2)
If you want more control over the ball that is created (e.g. you want to change some of it's properties before adding it to the scene), you can make a createBall function that creates a new ball and returns the new node:
func createBall(withZPosition zPos: Int) -> SKSpriteNode {
let newBall = SKSpriteNode(imageNamed: "Ball")
newBall.zPosition = zPos
return newBall
}
and use this as follows:
let newBall = createBall(withZPosition: 2)
newBall.size = CGSize(x: 50, y:50)
ballS,addchild(newBall)
U can have a global variable so that whenever you want to change u set it and create the ball
so create a variable
var myZposition = 2
and create a ball at the start in didmove to view by calling createBall()
func createBall(){
ballS = SKNode()
let ball = SKSpriteNode(imageNamed: "Ball")
saw.zPosition = myZPosition
ballS.addChild(ball)
self.addChild(ballS)
}
then after these if you want to change the zPosition just set it at the end of didmove to view
myZposition = 1
Then whenever u create balls from here your zPosition would be 1. This would be an easy setup if you have lots of balls and implement a change in zPosition for reaching a specific area of the game
Hope this helped
I'd suggest to use an argument, like:
func createBall(z: Int) {
ballS = SKNode()
let ball = SKSpriteNode(imageNamed: "Ball")
saw.zPosition = z
ballS.addChild(ball)
self.addChild(ballS)
}
And use it as:
createBall(z: 2)

Why when I use SKSpriteNode with an image it doesn't bounce but when I use SKShapeNode it does work?

I am very new to Swift and I am trying to create a ball that bounces up and down. When I use:
import Foundation
import SpriteKit
class Ball {
let movingObject: SKShapeNode
init() {
movingObject = SKShapeNode(circleOfRadius: 25)
movingObject.physicsBody = SKPhysicsBody(circleOfRadius: 25)
movingObject.physicsBody?.affectedByGravity = true
movingObject.physicsBody?.restitution = 1
movingObject.physicsBody?.linearDamping = 0
}
}
it works fine. However, when I try to use an image, it doesn't bounce.
class Ball {
let movingObject: SKSpriteNode
let picture: String
init(picture: String) {
self.picture = picture
movingObject = SKSpriteNode(imageNamed: picture)
movingObject.physicsBody = SKPhysicsBody(circleOfRadius: movingObject.size.width * 0.5)
movingObject.physicsBody?.affectedByGravity = true
movingObject.physicsBody?.restitution = 1
movingObject.physicsBody?.linearDamping = 0
}
}
The only difference between your two physics bodies is the radius. Therefore, this has to be the problem.
Try setting the radius to 25 like you did with the shape node to confirm, then try to reason about why movingObject.size.width * 0.5 isn't coming out to a reasonable value. You can set a breakpoint and use the debugger to print movingObject.size to help.
About the SKSpriteNode sources:
/**
Initialize a sprite with an image from your app bundle (An SKTexture is created for the image and set on the sprite. Its size is set to the SKTexture's pixel width/height)
The position of the sprite is (0, 0) and the texture anchored at (0.5, 0.5), so that it is offset by half the width and half the height.
Thus the sprite has the texture centered about the position. If you wish to have the texture anchored at a different offset set the anchorPoint to another pair of values in the interval from 0.0 up to and including 1.0.
#param name the name or path of the image to load.
*/
public convenience init(imageNamed name: String)
In the first case you use 25 as radius, in the next you must check if movingObject.size.width` * 0.5 is a valid measure.
When you are in debug phases try to help yourself by turning on the showsPhysics property:
Code of example:
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsPhysics = true
...
}
}
You can easily view the physicBody boundaries of your objects and you can notice it immediately if something is wrong.

How to randomly choose a SKSpriteNode?

I want to choose from 4 enemies using random and present it on scene. For that purpose I've made this:
func enemyPicker() -> SKSpriteNode {
var enemyArray = [mouse, robot, drone, block, bird]
var countArray = UInt32(enemyArray.count)
var pickOneEneny = arc4random_uniform(countArray)
var randomElement = Int(pickOneEnemy)
return enemyArray.randomElement
}
But Xcode says to me that SKSpriteNode does not have a member named randomElement. And it surely doesn't, but how would I say to my function that I need it to pick and assign that random Int to an actual enemy from array?
I tried to use this answer but it's not working for me. I also tried to change -> SKSpriteNode to SKTexture, String and "T" and had not any luck with it.
My SpriteNodes are declared like:
var mouse = SKSpriteNode()
let mouseAtlas = SKTextureAtlas(named: "mouse")
var mouseArray = [SKTexture]()
mouseArray.append(mouseAtlas.textureNamed("mouse_0"));
mouseArray.append(mouseAtlas.textureNamed("mouse_1"));
mouseArray.append(mouseAtlas.textureNamed("mouse_2"));
mouse = SKSpriteNode(texture: mouseArray[0]);
self.mouse.position = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMidY(self.frame) - 138)
self.mouse.size = CGSizeMake(self.mouse.size.width, self.mouse.size.height + mouse.size.height / 2)
self.mouse.name = "mouse"
self.addChild(mouse)
func enemyPicker() -> SKSpriteNode {
let enemyArray = [mouse, robot, drone, block, bird]
return enemyArray[Int(arc4random_uniform(UInt32(enemyArray.count)))]
}