SpriteKit: How to spawn object in exact place? - swift

I'm in the middle of making a simple game in XCode 7, using sprite kit. I currently have a square in the middle of the scene and I have set up swipe gestures which make smaller squares "shoot" out of the left, right, top of bottom of the square, depending on the way of the swipe gesture. They go in a straight direction and I would like other random "Enemy" squares to come in the opposite way, again in the same line of path as the squares that are shot out so they can collide... Any ideas on how to do this?
Here's the basic idea, same for the right, up and down:
func swipedLeft(){
print("SwipedLeft")
let SmallSquare1 = SKSpriteNode(imageNamed: "Square")
SmallSquare1.position = MainSquare.position
SmallSquare1.size = CGSize(width: 30, height: 30)
SmallSquare1.physicsBody = SKPhysicsBody(circleOfRadius: SmallSquare1.size.width / 2)
SmallSquare1.physicsBody?.affectedByGravity = false
self.addChild(SmallSquare1)
let moveLeft = SKAction.moveByX(-frame.size.width, y: 0, duration: 2)
SmallSquare1.runAction(moveLeft)
Here's the Enemy function I'm stuck on...
func Enemies(){
let Enemy = SKSpriteNode(imageNamed: "Square")
Enemy.size = CGSize(width: 20, height: 20)
Enemy.color = UIColor(red: 0.9, green: 0.1, blue: 0.1, alpha: 1.0)
Enemy.colorBlendFactor = 1.0
}

For the one on the left:
Enemy.position = CGPoint(x: (any X position), y: MainSquare.position.y)
And do the same for the right side.
And for above and below just do the MainSquare.position.x as the x position

Related

Moving SpriteNode Texture but not PhysicBody

Working on a game right now, I've faced a problem regarding management of SkSpriteNodes. I have a SpriteNode whose texture size is lower than physicsBody size assigned to it. It is possible to move, thanks to something similar to an SKAction, only the texture of a node and not its physicsBody?
To explain the problem in other terms, I will give you a graphic example:
As you can see, what I want to achieve is not to modify physicsBody proprieties(in order to not affect collision or having problems with continuos reassigning of PhysicsBody entities), but changing its texture length and adjusting its position. How can I achieve this programmatically?
A bit of code for context, which is just illustrative of the problem:
let node = SKSpriteNode(color: .red, size: CGSize(width: 8, height: 60)
node.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 60)
self.addChild(node)
//what I've tried is something like that
//It causes glitches in visualisation... and I need to move the object since resizing is towards the center.
let resize = SKAction.scaleY(to: 0.5, duration: 5)
let move = SKAction.move(to: node.position - CGPoint(x:0, y:node.size.height*0.5), duration: 5)
let group = SKAction.group([resize, move])
node.run(group)
//And this is even worse if I add, in this specific example, another point fixed to the previous node
let node2 = SKSpriteNode(color: .blue, size: CGSize(width: 8, length: 8)
node2.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 8))
node2.position = CGPoint(x: 0, y: -node.height)
self.addChild(node2)
self.physicsWorld.add(SKPhysicsJointFixed.joint(withBodyA: node.physicsBody! , bodyB: node2.physicsBody!, anchor: CGPoint(x: 0, y: -node.size.height)))
I get your problem.
let resize = SKAction.scaleY(to: 0.5, duration: 5)
This line will cause the physicsBody to scale the x and y axis uniformly. While your texture will just scale the y axis.
Its not so straight forward changing physicsBody shapes to match actions
One way to do it though would be to call a method from
override func didEvaluateActions()
Something like this:
var group1: SKAction? = nil
var group2: SKAction? = nil
var touchCnt = 0
var test = SKSpriteNode(texture: SKTexture(imageNamed: "circle"), color: .blue, size: CGSize(width: 100, height: 100))
func setActions() {
let newPosition = CGPoint(x: -200, y: 300)
let resize1 = SKAction.scaleY(to: 0.5, duration: 5)
let move1 = SKAction.move(to: newPosition, duration: 5)
group1 = SKAction.group([resize1, move1])
let resize2 = SKAction.scaleY(to: 1, duration: 5)
let move2 = SKAction.move(to: position, duration: 5)
group2 = SKAction.group([resize2, move2])
}
func newPhysics(node: SKSpriteNode) {
node.physicsBody = SKPhysicsBody(texture: node.texture!, size: node.size)
node.physicsBody?.affectedByGravity = false
node.physicsBody?.allowsRotation = false
node.physicsBody?.usesPreciseCollisionDetection = false
}
override func sceneDidLoad() {
physicsWorld.contactDelegate = self
test.position = CGPoint(x: 0, y: 300)
setActions()
newPhysics(node: test)
addChild(test)
}
override func didEvaluateActions() {
newPhysics(node: test)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if touchCnt == 0 {
if !test.hasActions() {
test.run(group1!)
touchCnt += 1
}
} else {
if !test.hasActions() {
test.run(group2!)
touchCnt -= 1
}
}
}
if you put the above code in your gameScene, taking care to replace any duplicated methods and replacing the test node texture. then when you tap the screen the sprite should animate as you want while keeping the physics body is resized at the same time. There are a few performance issues with this though. As it changes the physics body on each game loop iteration.

Rounded rectangle with SKShapeNode

I have the following code that rounds an already existing rectangle from the scene builder...
btOk.path = UIBezierPath(roundedRect: CGRect(x: -62.5,y:-25,width: 125, height: 50), cornerRadius: 10).cgPath
However, if i try to use the same init to round the corners of another rectangle that is much larger, it does not even come close to working. It just makes the width HUUUUGE (imagine Trump).
scene.enumerateChildNodes(withName: "//*"){
node,stop in
if let name = node.name{
if name.contains("round"){
if let shapeNode = node as? SKShapeNode{
print(shapeNode.frame.width) //500
shapeNode.path = UIBezierPath(roundedRect: CGRect(x: -250,y:-100,width: 500, height: 200), cornerRadius: 50).cgPath
print(shapeNode.frame.width) //5735
}
}
}
}
btOk is a SKShapeNode as well. What am i missing between the two that is so different? One thing to note is i am enumerating through the children of the scene like this because this scene is in a SKReferenceNode. Perhaps that has something to do with it?
EDIT
Taking direction from #Ron Myschuk, i've solved this and since it's such a PITA, i also created an extension. So now i can round the corners of any SKShapeNode very easily when needed. Even if it was created in the scene editor. Note, this should only be used if there are no children of the shape node. Otherwise those children will be removed also.
extension SKShapeNode {
func roundCorners(topLeft:Bool,topRight:Bool,bottomLeft:Bool,bottomRight:Bool,radius: CGFloat,parent: SKNode){
let newNode = SKShapeNode(rect: self.frame)
newNode.fillColor = self.fillColor
newNode.lineWidth = self.lineWidth
newNode.position = self.position
newNode.name = self.name
self.removeFromParent()
parent.addChild(newNode)
var corners = UIRectCorner()
if topLeft { corners = corners.union(.bottomLeft) }
if topRight { corners = corners.union(.bottomRight) }
if bottomLeft { corners = corners.union(.topLeft) }
if bottomRight { corners = corners.union(.topRight) }
newNode.path = UIBezierPath(roundedRect: CGRect(x: -(newNode.frame.width / 2),y:-(newNode.frame.height / 2),width: newNode.frame.width, height: newNode.frame.height),byRoundingCorners: corners, cornerRadii: CGSize(width:radius,height:radius)).cgPath
}
}
And use it like so...
aShapeNode.roundCorners(topLeft: true, topRight: true, bottomLeft: false, bottomRight: false, radius: 5,parent: popup)
Not what you're going to want to hear but it's because you cannot set the width of an SKShapeNode in the Scene editor (To my knowledge). In order to get that ShapeNode to have a width of 500 you would have had to adjust the xScale. The xScale then reapplies itself to the path when you adjust it (kind of growing exponentially). If you create the SKShapeNode in code there is no problem adjust the rounded corners
let round = SKShapeNode(rect: CGRect(x: 0, y: -150, width: 500, height: 200))
round.fillColor = .red
addChild(round)
print(round.frame.width)
round.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(round.frame.width)
Edit
If you have your heart set on using the Scene editor you can place your ShapeNode and stretch it to where you want it then you could just do a small conversion in code to get the results that you want
if let round = self.childNode(withName: "biground") as? SKShapeNode {
let biground = SKShapeNode(rect: round.frame)
biground.fillColor = round.fillColor
biground.position = round.position
addChild(biground)
round.removeFromParent()
print(biground.frame.width)
biground.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(biground.frame.width)
}
this just recreates the shape in code based on what you outlined in the Scene editor and rounds the edges perfectly
edit 2
I've always been under the impression that SKShapeNodes are really inefficient (i'm pretty sure they leaked memory as well). So i always setup my round rectangles as so.
let outsideTexture = SKTexture(imageNamed: "round_square_white")
let outside = SKSpriteNode(texture: outsideTexture)
let insetX: CGFloat = 20
let insetY: CGFloat = 20
let cellSize = CGSize(width: 500.0, height: 500.0)
outside.centerRect = CGRect(x: CGFloat(insetX / outside.size.width), y: CGFloat(insetY / outside.size.height), width: CGFloat((outside.size.width - insetX * 2) / outside.size.width), height: CGFloat((outside.size.height - insetY * 2) / outside.size.height))
//outside.position = CGPointMake(gameModel.gameWidth / 2, (outside.size.height) / 2);
outside.xScale = cellSize.width / outside.size.width
outside.yScale = cellSize.height / outside.size.height
outside.zPosition = 10
outside.position = CGPoint(x: CGFloat(0), y: CGFloat(-0.35 * self.size.height / 2))
self.addChild(outside)
worth noting that this lays out a rounded square/rectangle perfectly however similar to the scale issues from the scene editor you have to place an empty cell over this to add children to, otherwise they scale to the rounded square.

Swift: Make translucent overlapping lines of the same color not change color when intersecting

Currently I have drawn 2 lines on the screen which are the same color but both have an alpha value less than 1. When these lines intersect, the intersection is a different color than the rest of the lines. There was a previous post addressing the same issue: swift drawing translucent lines, how to make overlapping parts not getting darker? However, this post wasn't sufficiently answered. I draw the lines currently like this:
var points = [CGPoint]()
points = [CGPoint(x: -100, y: 100), CGPoint(x: 100, y: -100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 0.5)
FirstLine.lineWidth = 30
addChild(FirstLine)
points = [CGPoint(x: 100, y: 100), CGPoint(x: -100, y: -100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 0.5)
SecondLine.lineWidth = 30
addChild(SecondLine)
Here is what it looks like:
I understand why this occurs, but is there any way to make the intersection look the same so it looks much better?
Edit: I have decided to implement #Confused's answer. However, the problem now is that the texture always centres to the middle of the screen. This is an example:
The red cross is in its correct position as it is connecting the specified points together. However, once I make the red cross a texture, it always centres to the middle of the screen (the green cross is the red cross as a texture). Can I use any code that could re-position the texture to its correct position. Note: this code can't just work for this example, I need one that works all the time regardless of the red cross' position.
FINAL EDIT FOR PEOPLE WITH THE SAME PROBLEM
First, setup everything like this:
var points = [CGPoint]()
let crossParent = SKNode()
addChild(crossParent)
Please note THAT YOU MUST create a parent SKNode for the texture otherwise everything on the screen will become the texture and not just the node that you want. Then, add that parent node to the scene.
After, create the lines that you want (in this case the green cross):
//The first line of the green cross
points = [CGPoint(x: -300, y: 300), CGPoint(x: -100, y: 100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1.0)
FirstLine.lineWidth = 30
crossParent.addChild(FirstLine)
Remember to NOT add the first line that you create to the scene, but rather to the parent SKNode that you made at the beginning. Also, set the alpha value to 1.0 for every line that you draw. Then add your other lines:
//The second line of the green cross
points = [CGPoint(x: -100, y: 300), CGPoint(x: -300, y: 100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1.0)
SecondLine.lineWidth = 30
FirstLine.addChild(SecondLine)
Note that you MUST add that line to the first line like this and not to the scene. If you are adding more than one line after adding the first line, then add it to the first line like I have done here too for each consecutive
line that you add.
Now, create the texture like this:
let tex = view?.texture(from: FirstLine)
let cross = SKSpriteNode(texture: tex, color: .clear, size: (tex?.size())!)
cross.alpha = 0.5
addChild(cross)
After doing this, whatever you call cross will be your texture, and you can change the alpha value like I have done here to anything you like and the image will not have varying colors. Remember to then add that texture to the scene.
Finally, you may notice that the texture is not in the same position as where you originally put the points. You can put it back into the same position like this:
cross.position = CGPoint(x: (FirstLine.frame.midX), y: (FirstLine.frame.midY))
Hope this helps :) Thank you to #Confused for the texture part of the program :D
This is a technique. It does not solve your problem, nor directly answer your question. Instead it offers a way to have the desired result, but without the flexibility and inherent nature of lines you actually want.
You can create textures from anything you draw with any number of nodes, with any number of techniques.
You do this (easiest way) by attaching all the drawing elements to a single node, within an SKView space that you have access to, and then render the "parent" node of your drawn objects to a texture.
How does this help?
I'm glad you asked:
You can draw everything at an opacity level of 100%, and render it to a texture, then take that texture of your drawings, put it where you like, and reduce its opacity to any percentage you like, and get an even result. No bright spots where things overlay each other.
Here's code that does all the above:
var points = [CGPoint]()
points = [CGPoint(x: -100, y: 100), CGPoint(x: 100, y: -100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
FirstLine.lineWidth = 30
// ^^ Note the FirstLine is not added to the Scene
points = [CGPoint(x: 100, y: 100), CGPoint(x: -100, y: -100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
SecondLine.lineWidth = 30
FirstLine.addChild(SecondLine)
// ^^ Note SecondLine being added to FirstLine, and that they both have alpha of 1
// Now the magic: use the view of the SKScene to render FirstLine and its child (SecondLine)
// They are rendered into a texture, named, imaginatively, "tex"
let tex = view.texture(from: FirstLine)
let cross = SKSpriteNode(texture: tex, color: .clear, size: (tex?.size())!)
cross.alpha = 0.5
// ^^ The alpha of the above sprite is set to your original desire of 0.5
// And then added to the scene, with the desired result.
addChild(cross)
and here's the result:
NO! I think is, unfortunately, the answer.
There are the following blend modes, none of which provide the result you're looking for:
case alpha // Blends the source and destination colors by multiplying the source alpha value.
case add // Blends the source and destination colors by adding them up.
case subtract // Blends the source and destination colors by subtracting the source from the destination.
case multiply // Blends the source and destination colors by multiplying them.
case multiplyX2 // Blends the source and destination colors by multiplying them and doubling the result.
case screen // Blends the source and destination colors by multiplying one minus the source with the destination and adding the source.
case replace // Replaces the destination with the source (ignores alpha).
The only option that might work is a custom shader that figures out if it's overlapping itself, somehow. But I have NO CLUE where to even start making something like that, or if it's even possible.
Make the stroke color fully opaque.
Add your path nodes to an SKEffectNode.
Set the alpha of the effect node to the desired value.
Put the effect node into your main scene.
While doing this you can also put all lines in one shape node:
let path = CGMutablePath()
path.move( to: CGPoint(x: -100, y: 100))
path.addLine(to: CGPoint(x: 100, y: -100))
path.move( to: CGPoint(x: 100, y: 100))
path.addLine(to: CGPoint(x: -100, y: -100))
let shape = SKShapeNode(path: path)
shape.strokeColor = UIColor(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
shape.lineWidth = 30
let effect = SKEffectNode()
effect.addChild(shape)
effect.alpha = 0.5
addChild(effect)
The problem is you are doing the alpha blend at the color level, do the alpha blending at the node level
var points = [CGPoint]()
var cross = SKEffectNode()
points = [CGPoint(x: -100, y: 100), CGPoint(x: 100, y: -100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
FirstLine.lineWidth = 30
cross.addChild(FirstLine)
points = [CGPoint(x: 100, y: 100), CGPoint(x: -100, y: -100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
SecondLine.lineWidth = 30
cross.addChild(SecondLine)
cross.alpha = 0.5
addChild(cross)

Sprite positioning at wrong points in y axis

so I know this is a dumb question, but maybe it'll help other people like me out too. Basically, I'm making a sprite appear at the top of the screen and then move to the bottom but in a random x position. There's nothing wrong with moving to the random x position, the problem is that it doesn't start out at the top of the screen. Here is my code:
let sprite = SKSpriteNode(imageNamed: "comet")
sprite.size = CGSize(width: 150, height: 100)
sprite.position = CGPoint(x: self.frame.size.width/2, y: 0)
sprite.zPosition = 100
self.addChild(sprite) `
let randomNum = CGPoint(x:Int (arc4random() % 1000), y: 1)
let actionMove = SKAction.moveTo(randomNum, duration: 1)
let actionComet = SKAction(actionMove)
sprite.runAction(actionComet)
Any help is appreciated.
You are placing your sprite at location (0,0) which is left bottom of the screen. If you want to place the sprite at the top use:
sprite.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height + sprite.size.height / 2.0)

Swift - sprite kit - make SKPhysicsJointPin have less motion

I'm trying to create a snake with multiple body parts that moves left and right. Im using a pin, but upon the snake stopping, the body keeps moving and doesn't stop. I've messed around with the max and min angles, and the torque, but nothing seems to work. Should I use a different type of joint?
Sorry i cant log into my other account, but heres the code. Basically the second part just wobbles a lot. I wish to add 7-8 parts, but they just keep on wobbling, especially after moving "head". i would like a fluid "swoop" motion when i move the snake.
self.physicsWorld.gravity = CGVectorMake(0, -100)
self.physicsWorld.contactDelegate = self
head.size = CGSize(width: 25,height: 25)
head.physicsBody = SKPhysicsBody(texture: head.texture, size: head.size)
head.position = CGPointMake(100,400)
head.anchorPoint = CGPoint(x: 0.5, y: 1)
head.physicsBody!.dynamic = false
head.zPosition = 3
self.addChild(head)
var p1 = SKSpriteNode(imageNamed: "snakeBodyPart.png")
p1.size = CGSize(width: 25,height: 25)
p1.physicsBody = SKPhysicsBody(texture: p1.texture, size: p1.size)
p1.position = CGPointMake(100,380)
p1.anchorPoint = CGPoint(x: 0.5, y: 1)
p1.physicsBody!.dynamic = true
addChild(p1)
var joint = SKPhysicsJointPin.jointWithBodyA(head.physicsBody, bodyB: p1.physicsBody, anchor: CGPoint(x: CGRectGetMidX(head.frame), y: CGRectGetMinY(head.frame) + 10))
joint.upperAngleLimit = 0.1
joint.rotationSpeed = 0.1
self.physicsWorld.addJoint(joint)