Thanks in advance. I currently have a sprite controlled by the tilting of the device and would like every so often(NSTimer) for another sprite to appear behind it following it with the same physics. I have it set up, but i need the position to update every second so it follow behind it. How would I go about this? This is what I've tried/done.
let snakeBodyY = snakeHead.position.y - 5
let snakeBodyX = snakeHead.position.x - 5
snakeHead = SKSpriteNode(imageNamed: "snakeHead")
snakeHead.size = CGSize(width: 60, height: 60)
snakeHead.position = CGPoint(x: self.frame.midX, y: self.frame.midY - 50)
self.addChild(snakeHead)
snakeBody = SKSpriteNode(imageNamed: "snakeBody")
snakeBody.size = CGSize(width: 50, height: 50)
snakeBody.position.x = snakeBodyX
snakeBody.position.y = snakeBodyY
self.addChild(snakeBody)
Depends on how advance you need it,
if this is a free range 2D world you could assign the each new body to the tail so that the positions are relative to the snake, and always try to make the body parts move to the center of the previous body part up to the head.
Set up your sprite like this:
let snake = SKNode...
let head = SKSpriteNode...
let tail = SKSpriteNode...
//make sure you set up your positions for each body part, they should be relative to snake, not the screen
//setup collision so that tail and head can collide
head.physicsBody = SKPhysicsBody...
head.categoryBitMask = 1
head.collisionBitMask = 1
tail.physicsBody = SKPhysicsBody...
tail.categoryBitMask = 1
tail.collisionBitMask = 1
//setup restitution so that they do not bounce when colliding
head.restitution = 0
tail.restitution = 0
//add the parts to snake
snake.addChild(head)
snake.addChild(tail)
//Now you have a snake with a head and tail create an action to move the snake tail to the center of head
let moveToCenter = SKAction.moveTo(head.position, duration:0)
tail.runAction(moveToCenter)
//this will move the talk to try to get to the center of head, but collision physics will stop it from overlapping.
//Now everytime you want to add a new body
let body = SKSpriteNode...
body.physicsBody = SKPhysicsBody...
body.categoryBitMask = 1
body.collisionBitMask = 1
body.restitution = 0
snake.addChild(body)
let moveToCenter = SKAction.moveTo(tail.position, duration:0)
body.runAction(moveToCenter)
tail = body //lets make the tail the new piece to add
//Then on every update loop. you want to go through the children and do something like this
let previousChild = head
for child in snake.children
{
if child == head
{
continue;
}
child.removeAllActions()
let moveToCenter = SKAction.moveTo(previousChild.position, duration:0)
child.runAction(moveToCenter)
previousChild = child
}
Now if you find that this code may end up running slow for you, and don't mind a more stiff approach, you could also look up SKAction.followPath. Now I am not going to get into the details here, but basically what you need to do is record how your snake moves, then you generate a path with it, and use SKAction.followPath to make all the body parts move along the path.
If you are working in a 2d tiled world that only allows vertical and horizontal movement, you create an array of your sprites, and every time your snake makes a movement, you go down the snake and change its position to the previous body parts position
Related
I've been trying to implement an infinite background animation, which should change between 4 images of equal height and then repeat the sequence. However, it does not seem to work properly.
Note anchorPoint = CGPoint(x: 0.5, y: 0)
func updateBackground(currentTime: TimeInterval){
var delta: CGFloat = 0.0
if lastUpdate != nil {
delta = CGFloat(currentTime - lastUpdate)
}
//First increment position
activeBackground1.position.y += delta*backgroundVelocity
activeBackground2.position.y += delta*backgroundVelocity
//Detect bounds surpass
if activeBackground1.position.y > activeBackground1.size.height + screen.height/2 {
lastSky = (lastSky + 1)%4
sky1 = SKTexture(imageNamed: "sky" + String(lastSky))
activeBackground1.texture = sky1
//Reposition: background1 new position is equal to minus the entire height of
//background2 + its y size.
activeBackground1.position.y = -abs(activeBackground2.size.height-activeBackground2.position.y)
}
if activeBackground2.position.y > activeBackground2.size.height + screen.height/2 {
lastSky = (lastSky + 1)%4
sky1 = SKTexture(imageNamed: "sky" + String(lastSky))
activeBackground2.texture = sky1
activeBackground2.position.y = -abs(activeBackground1.size.height-activeBackground1.position.y)
}
}
The update algorithm works fine, but when it is needed to reposition one of the two background, it seems there is an offset of about 10.0 CGFloat from one background to another. What am I doing wrong?
EDIT: It turned out that the error was located in my image, which presented some blank rows and therefore generated visualisation glitches. So my code works perfectly.
I do the test and most likely you should use something like:
activeBackground2.position.y = activeBackground1.size.height + activeBackground1.position.y
instead of
activeBackground2.position.y = -abs(activeBackground1.size.height-activeBackground1.position.y)
I did this example and it works correctly: https://github.com/Maetschl/SpriteKitExamples/tree/master/InfiniteBackground/InfiniteBackground
Feel free to see and use.
Your problem is floating point math causing rounding errors. I am on a phone right now so I can’t wrote code, but what you want to do is have 1 parent SKNode to handle your entire background.
Add your background slivers to the parent node.
You then place the moving action on the parent only.
As each sliver leaves the screen, you take the sliver, and move it to the end of the other slivers.
This jumping should always be done with integer math, leaving no holes.
Basically:
The floating point moving math is done on the parent node.
The integer based tile jumping is done on each of the slivers.
I'm creating a Snake Game using swift and the library SpriteKit.
To control the direction of the snake, the user has to swipe on the screen. To check which was the direction of the swipe, I use UISwipeGestureRecognizer .
Now, I have to pass the information (direction of the swipe) from the GameViewController.swift file to the GameScene.swift file. To to that, I declare a 4 items array called movesConoller:
movesController[false, false, false, false]
If the user swipes up, the first element of the array turns true, if he scrolls down, the second element turns true and the first one false... etc. etc.
Now, in the GameScene.swift file I have to say what the Snake has to do if the player moves up.
For this reason, I use 2 more variables:
var playerX:CGFloat = 0
var playerY:CGFloat = 0
and then I created this code
if movesController[0] == true {
playerY += 56
}
if movesController[1] == true {
playerY -= 56
}
if movesController[2] == true {
playerX += 56
}
if movesController[3] == true {
playerX -= 56
}
Now I create the movement
let startPoint = CGPoint(x: player.position.x, y: player.position.y )
let endPoint = CGPoint(x: playerX + player.position.x, y: playerY + player.position.y)
let moveIt = SKAction.move(to: endPoint, duration:0.1)
player.run(moveIt)
So, every time the program is executed, the playerX and playerYvariables become equal to 0. Then, based on the direction of the swipe, they are added or subtracted 56.
To move the snake I just say to the head to move from his startPointto his endPoint in 0.1 seconds.
Another thing I added is the tail. To create it I use two arrays, snakeX and snakeY.
To these two empty arrays (of type CGFloat) I add the previous position of the snake and then, if the score remains the same, the last element of each array is deleted. Else, the last element isn't deleted and remains in his array.
Whit this method, I make the tail growing when the snake eats an apple.
BUT the head moves 56 in 0.1seconds. This means that he makes a movement of 8 pixels at each execution. Because of that, I have to add to snakeX and snakeY the X and Y values every 7 execution of the program.
This is the problem. If the player swipes on the screen just after 7 execution of the program, the movement of the tail will be perfect. But if the player swipes before the 7 execution, there will be a problem. Let's suppose the snake is moving right and the player swipes up when the program is at its 4th execution.
//snakeX and snakeY values before the swipe
snakeX[168, 112, 56, 0] //all this numbers are multiple of 56
snakeY[224, 224, 224, 224] //the snake is moving right, the Y value doesn't change.
//snakeX and snakeY values after the swipe
snakeX[168 ,112 ,56 , 0]
snakeY[248, 224, 224, 224] //look at the first element
248 is not a multiple of 56. The snake moved Up at the 4th execution, after 3 execution his position will be added to the arrays. But in 3 execution he had moved of 24pixel.
Because of that, I'll get this bug
as you can see, the tail doesn't make a perfect corner.
The snake head doesn't complete his movement of 56 pixels. When I swipe, it leaves his movement and starts another one. Is there a way I can tell the head to always complete his movement before doing another one?
You're sort of trying to use a pixel-based approach to what's basically a grid-based game, but maybe something like this will work...
Make a variable controlling whether movement is allowed:
var moveAllowed = true
Guard your movement code with that variable:
if moveAllowed {
if movesController[0] == true {
playerY += 56
}
...
}
When you schedule the movement action, toggle moveAllowed, and add a completion handler to toggle it back after the action finishes:
...
moveAllowed = false
let moveIt = SKAction.move(to: endPoint, duration:0.1)
player.run(moveIt) { self.moveAllowed = true }
(The self.moveAllowed might be just moveAllowed depending on how you have things structured, but I can't tell from your fragments.)
Edit: I just realized that maybe you're setting snakeX and snakeY based on player.position. It's not really clear. In any case, then you'd use the same idea but copying from player.position only when moveAllowed is true. Basically that variable is telling you if the action is still running or not.
Hello I'm trying to spawn bullets at the bottom of my screen to travel upwards but the current code that I have spawns the bullets at the top of the screen. I've tried making the height negative and nothing happened. Here's the code I'm working with, thanks.
let randomBulletPosition = GKRandomDistribution(lowestValue: -300, highestValue: 300)
let position = CGFloat(randomBulletPosition.nextInt())
bullet.position = CGPoint(x: position, y: self.frame.size.height + bullet.size.height)
Some nice conversions will help you.
Now, do not do this all the time, this should be a one and done type deal, like in a lazy property.
First, we want to get the bottom of our view
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY) //In a UIView, 0,0 is the top left corner, so we look to bottom middle
Second, we want to convert the position to the scene
let sceneBottom = scene!.view!.convert(viewBottom, to:scene!)
Finally we want to convert to whatever node you need it to be a part of. (This is optional if you want to place it on the scene)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Code should look like this:
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY)
let sceneBottom = scene!.view!.convert(viewBottom!, to:scene!)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Of course, this is a little ugly.
Thankfully we have convertPoint and convert(_from:) to clean things up a little bit
let sceneBottom = scene.convertPoint(from:viewBottom)
Which means we can clean up the code to look like this:
let sceneBottom = scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY))
let nodeBottom = node.convert(sceneBottom,from:scene!)
Then we can make it 1 line as:
let nodeBottom = node.convert(scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY),from:scene!)
As long as the node is available to the class, we can make it lazy:
lazy var nodeBottom = self.node.convert(self.scene!.convertPoint(CGPoint(x:self.scene!.view!.midX,y:self.scene!.view!.frame.maxY),from:self.scene!)
This means the first time you call nodeBottom, it will do these calculations for you and store it into memory. Everytime after that, the number is preserved.
Now that you know where the bottom of the screen is in the coordinate system you want to use, you can assign the x value to whatever your random is producing, and you can subtract the (node.height * (1 - node.anchorPoint.y)) to fully hide your node from the scene.
Now keep in mind, if your node moves between various parents, this lazy will not update.
Also note, I unwrapped all optionals with !, you may want to be using ? and checking if it exists first.
I currently have two SKSpriteNodes on top of each other like so (The white part is one and the brown round circle part is the other one):
The white part is positioned 1/3 the way down on top of the round brown sprite node. In the picture, the brown round part has a SKPhysicsBody applied to it already as seen by the light blue outline around it. When I add a SKPhysicsBody around the top ovalish white part it pushes it up and not in the position I wanted it.
How can I have a SKPhysics body coving both bodies of sprites but not have the physics bodies push on one another which makes the white part move upwards? I would like the white part to stay in the position it was in the first image.
Thanks for anyone help!
Here's the code I used for the SKPhysicsBody's:
// create, position, scale & add the round body
roundBody = SKSpriteNode( imageNamed: "roundBody" )
roundBody.position = CGPoint( x: 207, y: 70 )
roundBody.zPosition = 1
roundBody.xScale = 0.3
roundBody.yScale = 0.3
// add sprite node to view
self.addChild( roundBody )
// create, position, scale & add the head
theHead!.position = CGPoint( x: 207, y: roundBody.frame.maxY / 1.15 )
theHead!.zPosition = 2
theHead!.xScale = 0.3
theHead!.yScale = 0.3
// setting up a SKPhysicsBody for the round body
roundBody.physicsBody = SKPhysicsBody( circleOfRadius: roundBody.size.width / 4 )
roundBody.physicsBody!.dynamic = true
roundBody.physicsBody!.affectedByGravity = true
roundBody.physicsBody!.allowsRotation = false
roundBody.physicsBody!.pinned = false
// setting up a SKPhysicsBody for the head
theHead!.physicsBody = SKPhysicsBody(circleOfRadius: theHead!.size.width / 2 )
theHead!.physicsBody!.dynamic = true
theHead!.physicsBody!.affectedByGravity = false
theHead!.physicsBody!.allowsRotation = false
theHead!.physicsBody!.pinned = false
I was able to figure out that if you use SKPhysicsJointPin it does the exact thing I needed! (Which was to basically pin a sprite head on it's body and share a physics body)
let joinTogether = SKPhysicsJointPin.jointWithBodyA(
roundBody.physicsBody!,
bodyB:theHead!.physicsBody!,
anchor: GPointMake(CGRectGetMidX(roundBody.frame),
CGRectGetMinY(theHead!.frame)))
scene!.physicsWorld.addJoint(joint)
Hope this helps someone in the future!
If you never want it to move, set it's .dynamic property to false. Then other objects may or may not bounce/collide with it (depending upon their collisionBitMask) but it won't move in response to those collisions.
Your own answer is correct and a better solution but just to explain further.
The reason of the bodies colliding is that by default a physics body's collision bit mask is set to all categories which means it will collide with everything. In your code you are not calling
roundBody.physicsBody?.collisionBitMask = ...
which is why its using the default values.
To change that you could give your body and head a different collisionBitMask.
Im sure you will deal with this sooner or later when you handle collisions
Also as a tip it's a better idea to not force unwrap the physics bodies unless you have too, even though you know they exist. So you should replace your ! with ? whenever possible.
I am using Sprite Kit to add some circle icons to a scene. I have added some code to create a border around the outside of the scene size this is used to detect contact and remove the node from parent.
// Outside border collision detection
var largeBorder = CGRectMake(0, 0, size.width, size.height)
largeBorder.origin.x -= (mainIconRef.size.width + mainIconRef.size.width/3)
largeBorder.origin.y -= (mainIconRef.size.height + mainIconRef.size.height/3)
largeBorder.size.width += ((mainIconRef.size.width + mainIconRef.size.width/3) * 2)
largeBorder.size.height += ((mainIconRef.size.height + mainIconRef.size.height/3) * 2)
let pathMainView = CGPathCreateWithRect(largeBorder, nil)
self.physicsBody = SKPhysicsBody (edgeLoopFromPath: pathMainView)
self.physicsBody?.dynamic = false
self.physicsBody?.categoryBitMask = ColliderCategory.Wall.rawValue
self.physicsBody?.contactTestBitMask = ColliderCategory.Tap1.rawValue | ColliderCategory.Tap2.rawValue | ColliderCategory.Tap3.rawValue | ColliderCategory.TapFire.rawValue
self.physicsBody?.usesPreciseCollisionDetection = true
This is all working as expected. What I would like to do now is add another path/border/box in the middle of the screen and detect when the icons contact this. This is so I can tell they are at least a certain part of the size on the screen/scene itself.
What I am not sure about is that we set the self.physicsBody above. I do not want to override to it, I just want to add an additional border which is invisible (not shown) that I can track contact with (not collision). Is this possible without adding as a node?
Why not use an invisible node? Just turn off the physics for the node and pick the right shape for the physicsbody.
Set the right collisionbitmask, categorybitmask, contactbitmask etc and the icons will pass through the node and register "contact".
It'll do all of what you want.
Why don't you want to use a node?