How to have endless scrolling ground at an angle with Swift and Spritekit? - swift

I am trying to implement an endless scrolling ground with Swift and Spritekit. I managed to get the scrolling ground to work if the ground is a flat surface. But I want the ground to be on a decline like it is going down a hill. How can I do this? Here is the code I currently have:
func createGround() {
for i in 0...2 {
let grounds = SKSpriteNode(color: UIColor.white, size: CGSize(width: 600, height: 200))
grounds.name = "Ground"
grounds.anchorPoint = CGPoint(x: 0.5,y: 0.5)
grounds.position = CGPoint(x: CGFloat(i) * grounds.size.width, y: -300)
self.addChild(grounds)
}
}
func moveGround() {
self.enumerateChildNodes(withName: "Ground", using: ({
(node, error) in
node.position.x -= 6
node.position.y += 1
if node.position.x < -((self.scene?.size.width)!) {
node.position.x += (self.scene?.size.width)! * 3
node.position.y =
-400
}
}))
}
This creates the ground and scrolls them through the screen at an angle, but the ground nodes do not line up with each other. I imagine I will need some trigonometric function to make them line up? Here is what is currently looks like:
The white rectangles are my ground nodes. The grey is just a placeholder ground node which is not moving and I plan on deleting once I get the scrolling ground working.

Related

How to create travelling wave in SpriteKit

How can I create a visual effect of traveling wave like this in Swift SpriteKit?
I am using an extension to the SKAction that performs the oscillatory movement in the node, but I still do not know how to create the trail. I though in creating copies but it did not worked.
extension SKAction {
static func oscillation(scene: SKScene, amplitude a: CGFloat, timePeriod t: CGFloat, midPoint: CGPoint) -> SKAction {
let action = SKAction.customAction(withDuration: Double(t)) { node, currentTime in
let displacement = a * sin(2 * .pi * currentTime / t)
node.position.y = midPoint.y + displacement
let copy = node.copy() as! SKSpriteNode
copy.position.x = node.position.x
copy.position.y = node.position.y
scene.addChild(copy)
}
return action
}
}
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let node = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 50, height: 50))
node.position = CGPoint(x: 25, y: size.height / 2)
self.addChild(node)
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: 1, midPoint: node.position)
node.runAction(SKAction.repeatActionForever(oscillate))
node.runAction(SKAction.moveByX(size.width, y: 0, duration: 5))
}
}
I would try a particle emitter attached to the moving node. It could be set to produce dot shaped particles quickly enough that they would overlap and make a line. The particles would move at a constant speed to the left, with no variations or fade. Set the count high enough that the oldest particles go out of view before disappearing. If you don’t need a solid line, you could cut down the emitter rate and make a dotted line showing the traveling wave. There are oodles of options in particle emitters, so you could play around with the settings to get other effects if appropriate for your project.

SpriteKit background image scrolling flickers at seam line

I am creating a 2D macOS game using SpriteKit and I want a continuous scrolling background from left to right.
I have a background image that is the same size as my frame. The image is duplicated side-by-side: initially the left one is centred and the right is off-screen. Both images (SKSpriteNode) are animated to slide across the screen and then reset their positions once moved by a full frame-width.
Here is the relevant code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var background = SKSpriteNode()
func makeBackground() {
let texture = SKTexture(imageNamed: "bg-empty")
let scroll = SKAction.move(by: CGVector(dx: -texture.size().width, dy: 0), duration: 30)
let reset = SKAction.move(by: CGVector(dx: texture.size().width, dy: 0), duration: 0)
let animation = SKAction.repeatForever(SKAction.sequence([scroll, reset]))
for idx in 0...1 {
background = SKSpriteNode(texture: texture)
background.position = CGPoint(x: CGFloat(idx)*texture.size().width, y: self.frame.midY)
background.zPosition = -1
background.run(animation)
self.addChild(background)
}
}
override func didMove(to view: SKView) {
makeBackground()
}
}
Although this works, I notice a black (~1 pixel vertical strip) flicker that appears ad-hoc at the seam of the connection.
What is causing this flicker and how do I get rid of it?
You are encountering floating point rounding errors. This is going to lead to a situation where your first BG rounds down, and your second BG rounds up, giving you a 1 pixel gap.
instead, try the following code
class GameScene: SKScene {
private var background = SKNode()
func makeBackground() {
let texture = SKTexture(imageNamed: "bg-empty")
let scroll = SKAction.move(by: CGVector(dx: -texture.size().width, dy: 0), duration: 30)
let reset = SKAction.move(by: CGVector(dx: texture.size().width, dy: 0), duration: 0)
let animation = SKAction.repeatForever(SKAction.sequence([scroll, reset]))
for idx in 0...1 {
let subNode = SKSpriteNode(texture: texture)
subNode.position = CGPoint(x: CGFloat(idx)*texture.size().width, y: self.frame.midY)
background.addChild(subNode)
}
background.zPosition = -1
background.run(animation)
self.addChild(background)
}
override func didMove(to view: SKView) {
makeBackground()
}
}
Now you can avoid the gap because you are not running 2 different actions.
If you want to be able to scroll both ways. then place a texture to the left of your bg.
This will place a background node before and after your main background, and essentially turn it into 1 big node, with only the middle texture showing in its entirety.
If you have multiple background images, then just place the last frame of your background also to the left

Shooting balls from a rotating cannon

I'm making a SpriteKit game. I have six cannons that I've made rotate back and fourth. I want to shoot cannonballs from each cannon that sync with the rotation of each cannon. I want a duration of one second between each cannonball.
So basically: A cannon that is in constant rotation is shooting cannonballs in the same direction as the rotation.
For the cannons i'm using an extension:
extension CGFloat {
func degreesToRadians() -> CGFloat {
return self * CGFloat.pi / 180
}
}
I'm gonna put the code for just one cannon, since I should be able to figure out how to adjust one of the cannonball movements to the others. Here is one:
func createCannons() {
let cannonLeftBottom = SKSpriteNode(imageNamed: "Cannon")
cannonLeftBottom.anchorPoint = CGPoint(x: 0.5, y: 0.5)
cannonLeftBottom.position = CGPoint(x: -325, y: -420)
cannonLeftBottom.zPosition = 4
cannonLeftBottom.setScale(0.4)
cannonLeftBottom.zRotation = CGFloat(65).degreesToRadians()
let rotateLB = SKAction.rotate(byAngle:
CGFloat(-65).degreesToRadians(), duration: 2)
let rotateBackLB = SKAction.rotate(byAngle:
CGFloat(65).degreesToRadians(), duration: 2)
let repeatRotationLB =
SKAction.repeatForever(SKAction.sequence([rotateLB,rotateBackLB]))
cannonLeftBottom.run(repeatRotationLB)
self.addChild(cannonLeftBottom)
}
Here is my function for the cannonball:
func createBalls() {
let cannonBallLB = SKSpriteNode(imageNamed: "Ball")
cannonBallLB.name = "CannonBall"
cannonBallLB.position = CGPoint(x: -325, y: -420)
cannonBallLB.physicsBody = SKPhysicsBody(circleOfRadius:
cannonBallLB.size.height / 2)
cannonBallLB.physicsBody?.affectedByGravity = false
cannonBallLB.zPosition = 3
cannonBallLB.setScale(0.1)
self.addChild(cannonBallLB)
}
THX!
You need to convert from Polar Coordinates to Rectangular Coordinates.
You do this by using sin and cos
E.G.
let speed = 100 //This would mean move 100 points per second
let force = CGVector(dx:cos(cannon.zRotation) * speed,dy:sin(cannon.zRotation) * speed)
cannonBall.applyForce(force)
Note: Now unless they changed this, force used to be in units of points, if they fixed it to units of meters, then you need to divide your speed by 150, since 150 points = 1 meter in Spritekit

SKPhysicsJointFixed in SpriteKit and Swift

I'm making a game in Sprite Kit and Swift and I have a Sprite at the bottom of the screen and falling Sprites from the top which I want to catch and stick to the Sprite at the bottom, so I'm trying to use SKPhysicsJointFixed but when the objects collide instead of the falling object sticking to the one at the bottom which is supposed to catch and have it attached, it seems as if the bottom Sprite adapts the physics of the falling sprite and then falls off the screen with it. Here's the code I have in my didBeginContact method. and skewer is the name of the Sprite at the bottom which should always be at the bottom and not disappear.
if contact.bodyA.node!.name == "Skewer"
{
let boundX = skewer.physicsBody?.node?.position.x
let fixedJoint = SKPhysicsJointFixed.jointWithBodyA(contact.bodyA.node!.physicsBody, bodyB: contact.bodyB.node!.physicsBody, anchor: CGPoint(x: boundX!, y: boundY))
physicsWorld.addJoint(fixedJoint)
// contact.bodyB.node!.removeFromParent()
}
else
{
contact.bodyA!.node!.removeFromParent()
}
and the physics for the bottom screen Sprite are here
func makeSkewer()
{
skewer.name = "Skewer"
skewer.position = CGPoint(x: size.width * 0.5, y: size.height * 0.244)
skewer.physicsBody = SKPhysicsBody(rectangleOfSize: skewer.size)
skewer.physicsBody?.affectedByGravity = false
skewer.physicsBody?.categoryBitMask = kSkewerCategory
skewer.physicsBody?.contactTestBitMask = kFoodCategory
skewer.physicsBody?.collisionBitMask = kSceneEdgeCategory
addChild(skewer)
}
and physics for the falling Sprites are here
func random() ->CGFloat
{
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat,max: CGFloat) -> CGFloat
{
return random() * (max - min) + min
}
func addFood()
{
let randomCatchIndex = Int(arc4random_uniform(UInt32(foods.count)))
let food = SKSpriteNode(imageNamed: foods[randomCatchIndex])
let actualX = random(min: food.size.width/2, max: size.width - food.size.width/2)
let actualDuration = random(min: CGFloat(1.5), max: CGFloat(8.0))
let actionMove = SKAction.moveTo(CGPoint(x: actualX, y: -food.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
food.physicsBody = SKPhysicsBody(rectangleOfSize: food.size)
food.position = CGPoint(x: actualX, y: size.height + food.size.height/2)
food.physicsBody?.categoryBitMask = kFoodCategory
food.physicsBody?.contactTestBitMask = kSkewerCategory
food.physicsBody?.collisionBitMask = 0x0
food.physicsBody?.dynamic = true
food.runAction(SKAction.sequence([actionMove, actionMoveDone]))
addChild(food)
}
Set the skewer to not have dynamic physics. What you have currently is it not being affected by gravity, and as soon as it locks onto the food (which is traveling down and has momentum), the skewer moves with it.
In the creation of the skewer, run the following line:
skewer.physicsBody?.dynamic = false
You can also now ignore the affectedByGravity as that is something that only affects dynamic objects.

How can I make the player not able to go through certain boundaries? (off-screen)

Hey so I was watching Ray Wenderlich's tutorial on How to Make a Game Like Mega Jump! and tried to create a similar project on my own. So during the tutorial you set the player to be able to go off the screen whenever you tilt the device.
override func didSimulatePhysics() {
player.physicsBody?.velocity = CGVector(dx: xAcceleration * 400.0, dy: player.physicsBody!.velocity.dy)
if player.position.x < -20.0 {
player.position = CGPoint(x: self.size.width + 20.0, y: player.position.y)
} else if (player.position.x > self.size.width + 20.0) {
player.position = CGPoint(x: -20.0, y: player.position.y)
}
return;
}
This is the code I have which makes the player go off the screen. How can I do this but for the player NOT being able to go off the screen? For example if you tilt your device all the way to the right make the player to stay on the right side until you move it the other way! Thank you in advance.
Currently you check if the player is out of screen (more then 20px) and then set his position to the other side, 20px out of the screen.
But you want to stop the player at 0px and your screen width. You can do it like this:
if player.position.x <= 0.0 {
player.position = CGPoint(x: 0.0, y: player.position.y)
} else if (player.position.x >= self.size.width) {
player.position = CGPoint(x: self.size.width, y: player.position.y)
}
This just set's the position of the player to 0.0 if he's moving out to the left, or your screen width, if he's moving out to the right.
The current code "loops around" so moving all the way to the left means you end up on the right.
Just change the coordinates in the if blocks to pin to the edges. Something like this:
if player.position.x < -20.0 {
player.position = CGPoint(x: -20.0, y: player.position.y)
} else if (player.position.x > self.size.width + 20.0) {
player.position = CGPoint(x: self.size.width + 20.0, y: player.position.y)
}
Notice that if the player is beyond the left edge (x < -20) then you just leave the x-position at -20. Similar thing on the right edge.
You'll have to try this out to see how it works with the physics and acceleration but that should be the general idea.