When I changed anchorPoint for the GameScene (from default 0,0 to 0.5,0.5) the physicsBody for the sprite disappeared from the screen debugging with kView.showsPhysics = true.
It still exists, because each ship collides with each other but it looks like position of the physicsBody was shifted (out of screen).
Why this change affects physicsBody of scene's child?
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0.5, 0.5)
physicsWorld.gravity = CGVector(0, 0)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 100)
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
sprite.runAction(SKAction.repeatActionForever(action))
self.addChild(sprite)
}
}
}
I did the same thing. I put the anchor point of the SKScene also in the middle (CGPointMake(0.5, 0.5)) and it totally destroyed the physics. You will notice this especially when adding joints to the physics scene, you will not be able to put the physics joint anchor points in the right place.
My advice, don't change the SKScene anchorpoint if using SKPhysics (costs me a lot of time and rewriting to find out).
Changing the anchor point of the scene does not change coordinate of physics world. The same goes with every node and its physics body. With your own nodes you can compensate using special SKPhysicsBody init with center: (the center in the owning node’s coordinate system).
With physics world as for now you can do nothing.
Related
I am pretty new to SpriteKit so I may be missing something quite obvious.
I am attempting to create an interactive map of the US. I have loaded PNG images for each state and placed a couple of them into the main SKScene using the scene editor.
My goal is wanting to detect when each state is tapped by the user. I initially wrote some code that would find the nodes within a touch location however, this meant that the user could tap the frame and it would be counted as a valid tap. I only want to register touches that happen within the state texture and not the frame. Reading online it was suggested to use SKPhysicsBody to register when a tap takes place. So I changed my code to the following.
class GameScene: SKScene {
override func didMove(to view: SKView) {}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location: CGPoint = self.convertPoint(fromView: touch.location(in: self.view))
let body = self.physicsWorld.body(at: location)
if let state = body?.node, let name = state.name {
state.run(SKAction.run({
var sprite = self.childNode(withName: name) as! SKSpriteNode
sprite.color = UIColor.random()
sprite.colorBlendFactor = 1
}))
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
Now, if I choose the Bounding circle body type everything works as expected (shown above in the screenshot). When I click within the boudning circle it runs the SKAction otherwise it does nothing. However, when I change the body type to Alpha mask (the body type I want) it suddenly stops detecting the state. In fact, it returns the SKPhysicsBody for the MainScene entity.
Any advice on how to fix this would be greatly appreciated.
Thanks.
i can reproduce this behavior (bug?) when using the scene editor. however it goes away if you skip the sks file and initialize your sprites in code. (i acknowledge that setting locations for 50 states is more tedious this way.)
class GameScene: SKScene {
let ok = SKSpriteNode(imageNamed: "ok")
let nm = SKSpriteNode(imageNamed: "nm")
let tx = SKSpriteNode(imageNamed: "tx")
override func didMove(to view: SKView) {
tx.name = "Texas"
nm.name = "New Mexico"
ok.name = "Oklahoma"
//set up physics bodies w/ alpha mask
tx.physicsBody = SKPhysicsBody(texture: tx.texture!, size: tx.texture!.size())
tx.physicsBody?.affectedByGravity = false
nm.physicsBody = SKPhysicsBody(texture: nm.texture!, size: nm.texture!.size())
nm.physicsBody?.affectedByGravity = false
ok.physicsBody = SKPhysicsBody(texture: ok.texture!, size: ok.texture!.size())
ok.physicsBody?.affectedByGravity = false
//then position your sprites and add them as children
}
}
How can I slide paddles from left to right individually and have the paddle the character jumped on stop while the rest keep sliding? how do I make 4 paddles slowly move to left and right back and forth at the same speed. Please and thank you.
In this code I am trying to make an SKPhysics body for all of them and I don't know how.
override func didMove(to view: SKView) {
background = self.childNode(withName: "background") as! SKSpriteNode
jellyghost = self.childNode(withName: "jellyghost") as! SKSpriteNode
paddle1 = self.childNode(withName: "paddle1") as! SKSpriteNode
paddle2 = self.childNode(withName: "paddle2") as! SKSpriteNode
paddle3 = self.childNode(withName: "paddle3") as! SKSpriteNode
paddle4 = self.childNode(withName: "paddle4") as! SKSpriteNode
jellyghost.physicsBody = SKPhysicsBody()
paddle1.physicsBody = SKPhysicsBody()
paddle2.physicsBody = SKPhysicsBody()
paddle3.physicsBody = SKPhysicsBody()
paddle4.physicsBody = SKPhysicsBody()
}
I am trying to make a SKPhysics body above and I don't how to do that.
and in the code below, I am trying to have each of the 4 paddles move on their own from left to right at the same speed without them moving to the touch and things are not working out for me. right now
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
paddle1.run(SKAction.moveTo(x: location.x,duration: 1.0))
paddle2.run(SKAction.moveTo(x: location.x, duration: 1.0))
paddle3.run(SKAction.moveTo(x: location.x, duration: 1.0))
paddle4.run(SKAction.moveTo(x: location.x, duration: 1.0))
}
}
I also want to make my character jump to the moving paddles but I don't know how I would make my character jump up by dragging the screen up and releasing it to the current location of the paddle. How would I make it not restricted to not jumping only straight up but be flexible enough to jump x and y. Thank you
Yes you can. You will need to give the player and each of the paddles a SKPhysicsBody and then use SKPhysicsContactDelegate in you scene to detect collisions between the paddle and the player.
When there is a collision between the paddle and the player you can run a func like the one in the example below to stop the paddle if you are moving your player using velocity.
func stopPaddle(paddle: SKSpriteNode) {
paddle?.physicBody.velocity(CGVector(dx: 0, dy:0)
}
Please upload your code in future, as it will be easier to help you.
I've got quite simple game. It's like the classic Labyrinth game which uses accelereometer but user is also able to change the size of the rectangle using 3D Touch.
The player's node is rectangle which moves thanks to the world's physics which gets data from the accelerometer (nothing special here):
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data, error) in
if let motionData = data{
self.physicsWorld.gravity = CGVector(dx: motionData.acceleration.x * 7, dy: motionData.acceleration.y * 7)
}
}
and touchesMoved :
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let maxForce = touch.maximumPossibleForce
let force = touch.force
let forceValue = force/maxForce
player.size = CGSize(width: 26-forceValue*16, height: 26-forceValue*16)
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
resetSize()
}
func resetSize(){
player.size = CGSize(width: 26, height: 26)
}
So when I was changing player's size it worked fine: the node was moving around the screen based on device's orientation and while pressing harder it was getting smaller. The problem was that the physicsBody (I've set view.showsPhysics = true in GameViewController.swift) was always the same so I've added player.physicsBody = SKPhysicsBody(rectangleOf: player.size) but now when I'm pressing the screen the player's node resizes but it also stops moving. How to change the node's size and physicsBody simultaneously and keep it in motion?
When you change the physicsBody you lost all physics parameters that invoved your previous physicsBody: you can copy velocity or angularVelocity or other available parameters but it's not enough, you will never have a continuity because the SpriteKit framework (on the actual version) don't have this option: dynamically change the physicsBody during the game.
You can also change the scaling of you node to modify the physicsBody but you always lost physics variables as for example the accumulated speed due to gravity : this happened because SpriteKit change automatically the physicsBody during the scaling to create others bodies without preserve the accumulated properties.
So my advice is to create a kind of "freezing time" when you touch your ball, where you can change you physicsBody when the ball is stopped.
I'm trying to draw a single, straight line using UITouch and Spritekit. Where the line keeps showing during the dragging motion of my finger. if anyone knows a certain tutorial or can run me through the way of doing it i would be thankful
Probably not the best way to implement it, but it's simple. Just copy and paste this into your GameScene.swift
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var line:SKShapeNode = SKShapeNode()
private var path:CGMutablePath = CGMutablePath()
private var initialPointSet:Bool = false
override func didMove(to view: SKView) {
line.strokeColor = UIColor.orange
line.lineWidth = 4
addChild(line)
}
func touchMoved(toPoint pos : CGPoint) {
if !initialPointSet {
path.move(to: pos)
initialPointSet = true
}
path.addLine(to: pos)
line.path = path
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
}
SpriteKit has some of the worst 2D drawing ever presented in a 2D game engine.
Actually, the worst.
SKShapeNode and its CGPath are atrocious nonsense. It's as if the
"designers" of SpriteKit have never once looked at the most primitive
of 2D drawing in anything like DirectX or OpenGL. Let alone the kinds
of things that animators might want to do with lines and shape
creation, mutation, distortion and progress and line animation. ~ Confused
Having gotten that little rant off my chest, in the vain hope that it recalibrates your expectations to not expect a drawing style solution, consider this:
SKSpriteNodes can be a simple box, and they can be scaled in both the X and Y axis, and they can be parented to a "dummy" SKNode that rotates to face the direction of the current touch, relative to the original position of the touch.
SO.... you can draw a skinny box, starting at the point of the initial touch, and as the touch is moved, scale the SKSpriteNode to that point by both rotating the SKDummyNode you create to be the parent of your "line", and then scaling it out along that length from the origin to the current position of the touch.
Viola, a LINE!
Of sorts.
The SKNode method moveToParent does not maintain its absolute position after moving to a new parent in the same scene. The node moves to another position.
The documentation for moveToParent says
Moves the node to a new parent node in the scene. The node maintains its current position in scene coordinates.
Did I miss something?
update_01:
I realize something new for me.
The moving node maintains its position only if its initial position is zero points.If it has a initial position, after moveToParent function, it moves, which makes me confuse as compared to apple documentation.
As I understand from "maintain", it does not move on the screen, just its position values are changed.
and please do not consider scene!.scaleMode, because it was tested without this(and ofcourse proper anchorPoint) and nothing changed.
This is my code and screenshots:
import SpriteKit
class GameScene: SKScene , UIGestureRecognizerDelegate{
var counter:Int = 0
var touchTracker : [UITouch : SKNode] = [:]
let yellowParentNode = SKSpriteNode()
let blueParentNode = SKSpriteNode()
override func didMoveToView(view: SKView)
{
scene!.scaleMode = SKSceneScaleMode.ResizeFill
let yellowPosition = CGPointMake(100, 100)
let bluePosition = CGPointMake(200[![enter image description here][1]][1], 100)
//yellow parent
yellowParentNode.name = "yellow"
yellowParentNode.color = UIColor.yellowColor()
yellowParentNode.size.height = 50
yellowParentNode.size.width = 50
yellowParentNode.anchorPoint = CGPoint(x: 0 , y: 0)
yellowParentNode.position = yellowPosition
self.addChild(yellowParentNode)
//blue parent
blueParentNode.name = "blue"
blueParentNode.color = UIColor.blueColor()
blueParentNode.size.height = 50
blueParentNode.size.width = 50
blueParentNode.position = bluePosition
blueParentNode.anchorPoint = CGPoint(x: 0 , y: 0)
self.addChild(blueParentNode)
//child
let childNode = SKSpriteNode()
childNode.color = UIColor.redColor()
childNode.size.width = 20
childNode.size.height = 20
childNode.name = "child"
childNode.position = CGPointMake(40, 40)
childNode.anchorPoint = CGPoint(x: 0 , y: 0)
yellowParentNode.addChild(childNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let touch = touches.first as UITouch!
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
touchTracker[touch as UITouch] = touchedNode
if touchedNode.name == "child"
{
yellowParentNode.childNodeWithName("child")!.moveToParent(blueParentNode)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touch = touches.first as UITouch!
let touchLocation = touch.locationInNode(self)
let touchedNode = touchTracker[touch as UITouch]
touchedNode?.position = touchLocation
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
{
// let touch = touches.first as UITouch!
touchTracker.removeAll()
}
override func update(currentTime: CFTimeInterval)
{
/* Called before each frame is rendered */
}
}
this is the initial screen:
and this is next screen,which is after touching the child node:
You may be misinformed here, and Alessandro's answer does not explain why things are happening.
The position property is always relative to the parent. This is not what scene coordinates are. If you want to get the scene coordinates, you need to convert the position to the scene.
Now when you use moveToParent, you obviously are moving the node's parent property to the new parent. This means that the nodes position has to change, since the parent is changing. Now the node position has to change in order to adjust for the new parent, so that is still stays on the same spot on the scene.
E.G.
Node 1 lies on 10, 10 to the scene, Node 2 lies on 20,10 to the scene.
Node A is a child of node 1, and is positioned at 0,0 (scene coordinate (10,10))
We then move Node A from node 1 to node 2.
If position never changes (stays at (0,0)), this means that when viewed on screen, the scene coordinate would be 20,10, which is wrong
The position would have to change to (-10,0) because you take the parent (20,10) and need to adjust it by (-10,0) to get the scene coordinate of Node A back to (10,10)
I think there might be a misunderstanding in the interpretation of the information provided by Apple. In fact "moves the node to a new parent node" it could understand that your node is already a child of a node in the same scene.
If you make for example:
let pos1 = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
let pos2 = CGPointMake(CGRectGetMidX(self.frame)/2,CGRectGetMidY(self.frame)/2)
// make a big yellow node
let bigNode1 = SKSpriteNode.init(color: UIColor.yellowColor(), size: CGSizeMake(400,400))
addChild(bigNode1)
bigNode1.position = pos1
// make a big blue node
let bigNode2 = SKSpriteNode.init(color: UIColor.blueColor(), size: CGSizeMake(400,400))
addChild(bigNode2)
bigNode2.position = pos2
// make a little red node as a child of bigNode1
let littleNode = SKSpriteNode.init(color: UIColor.redColor(), size: CGSizeMake(100,100))
bigNode1.addChild(littleNode)
littleNode.position = CGPointZero
after you have this output:
Now if you do :
littleNode.moveToParent(bigNode2)
the output not change, exactly as explained by Apple.
Update (after 0x141E comment):
#0x141E As you can see, the position remain always 10,10 so it's correct: