Swift 3 (SpriteKit): SKShapeNode not moving when its image is changed - swift

I have come across an issue with SKShapeNodes that I'm struggling to understand. To help explain and show my problem, I have created a test program. At the start I create a line like this:
var points = [CGPoint(x: -372, y: 0), CGPoint(x: 372, y: 0)]
var Line = SKShapeNode(points: &points, count: points.count)
addChild(Line)
At this point, I am able to change the position of the node, and it prints the position I expect it to be:
Line.position.y = Line.position.y - 50
print("LINEPOS =", Line.position)
It prints:
LINEPOS = (0.0, -50.0)
This makes sense because the SKShapeNode's original position is always (0, 0). However, the next part of the program doesn't work in the same way. I make the new points equal the coordinates of where the Line is now on the screen, and let the Line equal them like this:
points = [CGPoint(x: -372, y: -50), CGPoint(x: 372, y: -50)]
Line = SKShapeNode(points: &points, count: points.count)
Then I change the position of the Line again (this time I moved it down by 200 so the change in position is more noticeable) like this:
Line.position.y = Line.position.y - 200
print("LINEPOS =", Line.position)
It prints:
LINEPOS = (0.0, -200.0)
Even though it prints the position correctly, it is clear that the Line hasn't moved at all on the screen. It is still at the same position as it was when I let the line equal the new points. This means that after letting the SKShapeNode equal a new set of points, it no longer moves on the screen but in the code it says it does.
How can I fix this? If anyone knows please help because I'm really confused.

Try this code:
import SpriteKit
class GameScene:SKScene {
override func didMove(to view: SKView) {
var points = [CGPoint(x: -372, y: 0), CGPoint(x: 372, y: 0)]
var Line = SKShapeNode(points: &points, count: points.count)
addChild(Line)
Line.position.y = Line.position.y - 50
print("LINEPOS =", Line.position)
points = [CGPoint(x: -372, y: -50), CGPoint(x: 372, y: -50)]
Line.removeFromParent()
Line = SKShapeNode(points: &points, count: points.count)
addChild(Line)
Line.position.y = Line.position.y - 200
print("LINEPOS =", Line.position)
}
}
From your code, when you for the second time do something like Line = SKShapeNode = ... the new shape is created, which doesn't have a parent yet. It is not in a node tree. What you are seeing is the old shape, which is not removed from a parent. So, remove that one, and add a new one... Or, don't create a new shape every time, and just change its position.
Also, this can be a bit confusing. Even if you remove the line which says:
Line.removeFromParent()
the code will compile (but you will see two lines).
So, even though you are working on a same variable and a fact that a node can have only one parent, the code still compiles if you don't remove the Line from parent, before you make another addChild(Line) call. How is that possible?
Well, that works because of this:
You create a Line which is a reference to a SKShapeNode which is added to a parent right after its creation. Now, because it is added to a tree, there is another reference pointing to it, because it is a part of a scene's children array.
Then you instantiate a Line variable again, so now it shows on a different SKShapeNode in memory (but the old SKShapeNode is still in a tree). This new SKShapeNode is not added to a tree, and its parent proprety is a nil. Because of this, you can add it again to a tree, without having compiler yelling at you.
EDIT:
To respond to your comments about how to change a path of an SKShapeNode, you could do something like this:
let path = CGMutablePath()
points = [CGPoint(x: -372, y: -250), CGPoint(x: 372, y: -250)]
path.move(to: CGPoint(x: points[0].x, y: points[0].y))
path.addLine(to: CGPoint(x: points[1].x, y: points[1].y))
Line.path = path

just check this once
import SpriteKit
var Line:SKShapeNode!
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var points = [CGPoint(x: -372, y: 0), CGPoint(x: 372, y: 0)]
Line = SKShapeNode(points: &points, count: points.count)
addChild(Line)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
Line.position.y = Line.position.y + 200
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}

Related

SpriteKit CGPath and SpriteMovement

func zombieAI() {
let zombieGreen = SKSpriteNode(imageNamed: "Zombie")
zombieGreen.setScale(0.3)
zombieGreen.zPosition = 3
zombieGreen.physicsBody?.affectedByGravity = false //change this later
zombieGreen.position = CGPoint(x: 200, y: 200)
self.addChild(zombieGreen)
let goToTurret = CGMutablePath()
goToTurret.move(to: CGPoint(x: 0, y: 0))
zombieGreen.run(SKAction.follow(goToTurret, speed: 1.0))
}
I am still learning about CGPaths and my objective of this code was to make the zombie move to the point 0,0. Currently the zombie spawns and just sits where it spawned. I didn't want to use moveTo, which I am more comfortable with, because the zombie may be obstructed by something, so I want the zombie to move at a speed rather than getting to the point in a certain amount of time. Any suggestions on how to use the CGPath correctly or what I could change to achieve my goal? I am very new to this so please respectfully judge my code :)
Try this (not tested):
Replace this line
goToTurret.move(to: CGPoint(x: 0, y: 0))
with
goToTurret.move(to: zombieGreen.position)
goToTurret.addLine(to: CGPoint(x: 0, y: 0))
So you start the path off with the zombie's current position, then add a line from that position to the zombie's intended position.
Hope this helps!

How to keep a 30˚ distance between two lines anchored at a point

I am trying to create two lines that are anchored at a certain point (sprite) and rotate to form a 30 degree angle between them. Below is an image what I want to achieve.
This is what I've done so far:
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var anchorSprite = SKSpriteNode(imageNamed: "anchorSprite")
var armLeft = SKSpriteNode(imageNamed: "lineSprite")
var armRight = SKSpriteNode(imageNamed: "lineSprite")
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -1.8)
self.physicsWorld.contactDelegate = self
var tealBg = SKSpriteNode(imageNamed: "tealBg")
tealBg.position = CGPoint(x: frame.midX, y: frame.midY)
tealBg.zPosition = 10
addChild(tealBg)
anchorSprite.position = CGPoint(x: frame.midX, y: frame.midY + frame.midY/2)
anchorSprite.zPosition = 20
anchorSprite.physicsBody = SKPhysicsBody(rectangleOf: anchorSprite.frame.size)
anchorSprite.physicsBody?.categoryBitMask = pinCategory
anchorSprite.physicsBody?.isDynamic = false
addChild(anchorSprite)
armRight.anchorPoint = CGPoint(x: 0.5, y: 1)
armRight.position = anchorSprite.position
armRight.zPosition = 20
armRight.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
armRight.zRotation = CGFloat(Double(15).degreesToRadians)//CGFloat(Double.pi/6)
armRight.physicsBody!.isDynamic = true
addChild(armRight)
armLeft.anchorPoint = CGPoint(x: 0.5, y: 1)
armLeft.position = anchorSprite.position
armLeft.zPosition = 20
armLeft.physicsBody = SKPhysicsBody(rectangleOf: armRight.frame.size)
armLeft.zRotation = CGFloat(Double(-15).degreesToRadians)//CGFloat(-Double.pi/6)
armLeft.physicsBody!.isDynamic = true
addChild(armLeft)
//Pin joints
var pinAndRightArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armRight.frame.maxY))
self.physicsWorld.add(pinAndRightArmJoint)
var pinAndLeftArmJoint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: armLeft.physicsBody!, anchor: CGPoint(x: anchorSprite.position.x, y: self.armLeft.frame.maxY))
self.physicsWorld.add(pinAndLeftArmJoint)
}
Below is an image from running the above code (they are close together).
How can I make sure the lines are always 30˚ apart and maintain 30˚ apart even when rotated?
To keep your two lines separated by exactly 30°, you can use an SKPhysicsJointFixed, which is just what it sounds like: it pins two physicsBodies together in a fixed position. Since you already have them positioned the way you want, just add this code where you have the other SKPhysicsJoints to hold them that way:
let fixArms = SKPhysicsJointFixed.joint(withBodyA: armLeft.physicsBody!, bodyB: armRight.physicsBody!, anchor: CGPoint.zero)
self.physicsWorld.add(fixArms)
Result:
If you make the line nodes children of the anchor sprite (instead of the scene), rotating the anchor sprite node will rotate all the lines along with it without doing anything special with physics. You just need to mind the anchor points so that they align properly (i.e. line's anchor at its extremity rather than center)

Swift 3 (SpriteKit): Aligning SKPhysicsBody to SKShapeNode

I am creating SKShapeNodes in my program using this:
let points = [CGPoint(x: x, y: y), CGPoint(x: x2, y: y2)]
let line = SKShapeNode(points: &points, count: points.count)
The problem that I am having is that whenever I add a physicsBody to the line, the physicsBody is not aligned with the line. I realise that the issue is because the line's position is always CGPoint(x: 0, y: 0) so the physicsBody is always in the centre of the screen regardless of where the line is. Here is my code for creating the physicsBody:
line.physicsBody = SKPhysicsBody(rectangleOf: (line.frame.size))
If anyone knows how to align the physicsBody to the line, then please reply with your solution. Thanks!
If you want to make a physics body from one point to the other, you may use something like this:
class GameScene:SKScene {
override func didMove(to view: SKView) {
var points = [CGPoint(x: 22, y: 22), CGPoint(x: 155, y: 155)]
let line = SKShapeNode(points: &points, count: points.count)
print(line.frame.size)
line.physicsBody = SKPhysicsBody(edgeFrom: points[0], to: points[1])
addChild(line)
}
}
This will create an edge based physics body using two points. An edge physics body is static by default so keep that in mind. To register a contact (or make a collision with this body) the other body has to be dynamic.
If you want to have this body dynamic, then look for volume based initializers.

2 bezier path to skspritenode

I would like to create SKSpriteNode from 2 independent bezier paths.
It is similar like road borders. Those paths don't cross but I would like to get 1 SpriteNode as a border of player movement.
The reason is that I want to get equal distance between them. I think that it will be easier for me if I make 1 SkSpriteNode from 2 paths.
I need SKSpriteNode to get contact when player touches that "border"
How to do that?
Thank you
Add an SKSpriteNode:
let container = SKSpriteNode.init(color: UIColor.redColor(), size: CGSizeMake(800, 400))
container.position=CGPointMake(300, 300)
self.addChild(container)
Create an array of CGPoint's. I used just a triangle you can use whatever you want:
let firstPathArray:[CGPoint]=[CGPoint(x: 0, y: 0),CGPoint(x: 100, y: 0),CGPoint(x: 50, y: 100)]
Add path to container:
addbezierPathToContainer(container,pathArray: firstPathArray)
Add path:
func addbezierPathToContainer(container:SKSpriteNode,pathArray:[CGPoint]) -> Void {
let newpath = UIBezierPath()
var startpoint:Bool=false;
for path in pathArray {
if(!startpoint)
{
newpath.moveToPoint(CGPoint(x: path.x, y: path.y))
startpoint=true;
}
else
{
newpath.addLineToPoint(CGPoint(x: path.x, y: path.y))
newpath.addLineToPoint(CGPoint(x: path.x, y: path.y))
}
}
//close path
newpath.closePath()
UIColor.blackColor().setStroke()
newpath.stroke()
let pathNode = SKShapeNode(path: newpath.CGPath)
container.addChild(pathNode);
}

Playable Game Area in Swift 3.0

I'm creating a mario clone for mac to help me learn swift programming. An issue I have come across is setting a playable game area. As of now, the 2 backgrounds I have ("background" and "level") will move when the left or right keys are pressed but grey areas at the sides will become visible. I want to be able to make the backgrounds move, but set an area at which they will stop moving.
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "mario")
let background = SKSpriteNode(imageNamed: "background")
let level = SKSpriteNode(imageNamed: "level")
override func didMove(to view: SKView) {
/* Setup your scene here */
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
level.size = self.size
level.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
level.zPosition = 1
self.addChild(level)
background.size = self.size
background.position = CGPoint(x: level.size.width + 511, y: self.size.height/2)
background.zPosition = 0
self.addChild(background)
player.setScale(0.23)
player.position = CGPoint(x: self.size.width/8, y: 120)
player.zPosition = 2
self.addChild(player)
}
override func keyDown(with theEvent: NSEvent) {
let keyCode = theEvent.keyCode
//Moving Right
if keyCode == 124 {
level.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
background.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
player.xScale = 0.23
}
//Moving Left
if keyCode == 123 {
background.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
level.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
player.xScale = -0.23
}
//Jump
if keyCode == 126{
player.run(SKAction.sequence([SKAction.moveTo(y: 250, duration: 0.15),SKAction.moveTo(y: 120, duration: 0.2)]))
}
}
}
I'm sure there is a much better, more clever solution, but off the top of my head you could add:
let LEFTBOUND: Int = //Insert the left limit here.
let RIGHTBOUND: Int = //Insert the right limit here.
Inside func keyDown(), in the moving right/left if-statements, nest these:
if level.position.x < RIGHTBOUND:
//Move level so long as it is less than the RIGHTBOUND value.
//TODO: set the level.position to RIGHTBOUND so it doesn't keep updating with new values while keyDown is true.
if level.position.x > LEFTBOUND:
//Move level so long as it is greater than the LEFTBOUND value.
//TODO: set the level.position to LEFTBOUND so it doesn't keep updating with new values while the keyDown is true.
//Repeat theses steps for background.position.x
You should also restrict the player movement so the player doesn't run out of the level unrecoverably.