I am trying to create game in SpriteKit with parallax effect, but when I implement my method, my hero lags (quickly shakes) from time to time (no pattern observed)...
I wrote this simple code that illustrates the method and also contains the same problem.
The hero should stay in the center of the screen and only his position relative to his parent (foreground) should change. But it lags (quickly shakes for a really short moment) every now and then. I also noticed that when I run the game, the hero sinks down very, very slowly a few pixels (maybe 5-10) before it starts holding its position after ~15 sec.
I can't figure out what is causing this behavior. Any help would be appreciated. I am testing on IPhone 6.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let hero = SKSpriteNode(imageNamed: "hero")
let foreground = SKNode()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(size: CGSize) {
super.init(size: size)
physicsWorld.gravity = CGVector(dx: 0.0, dy: -0.5)
physicsWorld.contactDelegate = self
// anchor point of scene is at (x: 0.5, y: 0.5)
hero.position = CGPoint(x: 0, y: 0)
hero.physicsBody = SKPhysicsBody(rectangleOfSize: hero.size)
hero.physicsBody?.dynamic = true
hero.physicsBody?.allowsRotation = false
addChild(foreground)
foreground.addChild(hero)
}
override func update(currentTime: NSTimeInterval) {
foreground.position = CGPoint(x: 0, y: -(hero.position.y))
}
}
// I need the foreground to move in opposite direction so I can add stars,
// monsters, etc while keeping the player in the view... Update method does
// this: hero is pulled down by gravity so his y position would be -1 pixel
// relative to foreground but I assign foreground the opposite y-position of
// hero (+1) and since hero's position relative to foreground is -1, he should
// be positioned at y-position = 0 which, in this code example, should create
// an illusion that he is positioned at the center and doesn't move
Related
I have a separate class called "Floor" with below.
class Floor: SKNode {
override init() {
super.init()
//let edgeFrame = CGRect(origin: CGPoint(x: 1,y: 1), size: CGSize(width: 1078, height: 1950))
//self.physicsBody = SKPhysicsBody(edgeLoopFrom: edgeFrame)
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
// Apply a physics body to the node
// self.physicsBody?.isDynamic = false
// Set the bit mask properties
self.physicsBody?.categoryBitMask = floorCategory
self.physicsBody?.contactTestBitMask = nailDropCategory
self.physicsBody?.collisionBitMask = balloonCategory
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemted")
}
}
I basically have falling SKSpriteNode's that start at the top of the screen and goes to the bottom where it touches the "Floor" and removes itself then restarts from top again. Issue I'm having is all my SKSpriteNode keeps getting stuck on top and not falling through the border around the frame of the screen. How can I tell my application to ignore those specific nodes and let them in? Appreciate any help!
Here is the object that is moving left and right on the screen but it just falls off the side of the screen without the edgeLoop
if let accelerometerData = motionManager.accelerometerData {
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
balloon.physicsBody?.velocity = CGVector(dx: accelerometerData.acceleration.x * -500.0, dy: 0)
} else {
balloon.physicsBody?.velocity = CGVector(dx: accelerometerData.acceleration.x * 500.0, dy: 0)
}
}
}
What is happening is that your objects are hitting the top boundary of the edge loop and not being able to make it into the scene.
there is several ways you can do you this, if you absolutely needed the edge loop on the sides I would suggest elongating the loop to be higher than the scene and creating the objects inside the loop but above the visible area. However since you haven't given any indication that you actually need the loop on the sides all I would do is get rid of the edge loop detection.
create a box that is the width of the scene and say a 100px high then put a physics body on it of type floorCategory. then put this box 50 px below the bottom of the screen. Assuming that your floor sprite box has an anchorPoint of 0.5, 0.5 this will hide the box below the screen and the top of the box will sit flush with the bottom of the screen.
Now you will be able to detect when your objects hit the bottom of the screen and you will no longer have to worry about them passing through the edge loop at the top.
OR
an example of elongating the loop would be...
You create a rectangle taller than the screen (green border in the image) to apply the edge loop to, not to the screen size itself
I am trying to add sprites to my SKScene, but the coordinate system seems to be weird. The [0, 0] point is in the middle of the screen and not in the lower left corner like it should be. I have no idea how this could have happened as I have tried correcting using a variety of methods I have found in similar questions.
Here is my GameViewController class:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = MenuScene(fileNamed: "MenuScene"){
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
I have tried changed the scaleMode which only changed the scale of sprites and didn't fix the issue. I have also tried changing MenuScene(fileNamed: ) to MenuScene(size: view.bounds.size) which gave me errors. If I am missing any details, please ask.
That is happening because by default, SKScenes have an anchorPoint of (0.5, 0.5). This places the point (0,0) in the centre. If you're looking to change the anchorPoint to the bottom-left, add this to the beginning of didMoveToView:
self.anchorPoint = CGPoint(x: 0, y: 0)
Or initialize the scene with that anchorPoint before presenting it (from GameViewController):
scene.anchorPoint = CGPoint(x: 0, y: 0)
Or change it in the scene editor.
FYI: CGPoint(x: 0, y: 0) is the same as CGPoint.zero
Personally, I find it easier to build the scene outwards from the centre because it makes symmetry easier and laying out HUD easier, instead of from a corner, which results in building the scene from the corner, potentially creating difficulties.
I'm writing an app with a continuously (sdieways) scrolling terrain, which is made up dynamically of images. So I create a sprite, scroll it sideways, & when it's about to show its right edge as it continues to scroll, I add a second sprite to its right, which also scrolls, etc, etc. As they disappear off the left, they're removed.
The problem is that when I add the new sprite, there's a small gap (around 4 px) between it & the previous one, as if in the time it takes to put it on screen, the scroll animation has moved a few pixels.
The scrolling class is this -
import UIKit
import SpriteKit
private let lagOffset: CGFloat = 4.0 // currently, this is the only way I can get rid of the gap between terrain blocks
class GameScene: SKScene
{
private var terrainNumber = 2
private var followingTerrainBlock: TerrainBlock? = .None
override init(size: CGSize)
{
super.init(size: size)
// create the first terrain block
let terrainBlock = TerrainBlock(size: Size.terrainBlock, terrainNumber: 1)
terrainBlock.position = CGPoint(x: size.width / 2.0 + Size.terrainBlock.width, y: size.height / 2.0)
addChild(terrainBlock)
followingTerrainBlock = terrainBlock
moveTerrainBlock(terrainBlock)
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
func moveTerrainBlock(terrainBlock: TerrainBlock)
{
// create the next terrain block on a background thread as it's quite expensive
// it'll be ready in plenty of time for when it's needed
GCD.async { () -> () in
self.followingTerrainBlock = TerrainBlock(size: Size.terrainBlock, terrainNumber: self.terrainNumber)
}
var targetX = size.width / 2.0
let y = terrainBlock.position.y
let move = SKAction.moveTo(CGPoint(x: targetX, y: y), duration: Time.terrainScroll)
// move the terrain block to centre on the screen - at this point we add the following terrain block immediately behind it, off-screen
// the scroll animation lasts 5 seconds before the completion closure runs, by which time self.followingTerrainBlock has been ready for ages
terrainBlock.runAction(move) { [weak self]() -> Void in
if let strongSelf = self
{
strongSelf.terrainNumber++
// we have to put an offset into the position of the new terrain block, or there'll be a gap between the old & new blocks
strongSelf.followingTerrainBlock!.position = CGPoint(x: targetX + Size.terrainBlock.width - lagOffset, y: y)
strongSelf.addChild(strongSelf.followingTerrainBlock!)
// now we tell the new terrain block to follow the existing one
strongSelf.moveTerrainBlock(strongSelf.followingTerrainBlock!)
// then we continue moving the existing terrain block until it's off-screen, then remove the sprite
targetX -= Size.terrainBlock.width - lagOffset
let move = SKAction.moveTo(CGPoint(x: targetX, y: y), duration: Time.terrainScroll)
terrainBlock.runAction(move) { () -> Void in
terrainBlock.removeFromParent()
}
}
}
}
}
What I'd like to know is how to put the following terrain blocks on screen so that they're exactly lined up with the right edge of the previous one. If I stop the animation & add one, the position is correct without the offset, so there's nothing wrong with the position calculation.
As you create each block, create a constraint (with constant = 0) attaching it to previous block...etc for each block there after.
I believe that as each block moves off the view the constraint will dealloc as well, but you might have to consider this part too.
I am creating a Terraria-style game in Swift. I want to have it so the player node is always in the center of the screen, and when you move right the blocks go left like in Terraria.
I am currently trying to figure out how to keep the view centered on the character. Does anyone know of a good way of accomplishing this?
Since iOS 9 / OS X 10.11 / tvOS, SpriteKit includes SKCameraNode, which makes a lot of this easier:
positioning the camera node automatically adjusts the viewport
you can easily rotate/zoom the camera by transform in the camera node
you can fix HUD elements relative to the screen by making them children of the camera node
the scene's position stays fixed, so things like physics joints don't break the way they do when you emulate a camera by moving the world
It gets even better when you combine camera nodes with another new feature, SKConstraint. You can use a constraint to specify that the camera's position is always centered on a character... or add extra constraints to say, for example, that the camera's position must stay within some margin of the edge of the world.
The below will center the camera on a specific node. It can also smoothly transition to the new position over a set time frame.
class CameraScene : SKScene {
// Flag indicating whether we've setup the camera system yet.
var isCreated: Bool = false
// The root node of your game world. Attach game entities
// (player, enemies, &c.) to here.
var world: SKNode?
// The root node of our UI. Attach control buttons & state
// indicators here.
var overlay: SKNode?
// The camera. Move this node to change what parts of the world are visible.
var camera: SKNode?
override func didMoveToView(view: SKView) {
if !isCreated {
isCreated = true
// Camera setup
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.world = SKNode()
self.world?.name = "world"
addChild(self.world)
self.camera = SKNode()
self.camera?.name = "camera"
self.world?.addChild(self.camera)
// UI setup
self.overlay = SKNode()
self.overlay?.zPosition = 10
self.overlay?.name = "overlay"
addChild(self.overlay)
}
}
override func didSimulatePhysics() {
if self.camera != nil {
self.centerOnNode(self.camera!)
}
}
func centerOnNode(node: SKNode) {
let cameraPositionInScene: CGPoint = node.scene.convertPoint(node.position, fromNode: node.parent)
node.parent.position = CGPoint(x:node.parent.position.x - cameraPositionInScene.x, y:node.parent.position.y - cameraPositionInScene.y)
}
}
Change what’s visible in the world by moving the camera:
// Lerp the camera to 100, 50 over the next half-second.
self.camera?.runAction(SKAction.moveTo(CGPointMake(100, 50), duration: 0.5))
Source: swiftalicio - 2D Camera in SpriteKit
For additional information, look at Apple's SpriteKit Programming Guide (Example: Centering the Scene on a Node).
You have to create World node that contains nodes. And you should put anchorPoint for example (0.5,0.5). Center on your player. And then you should move your player.
func centerOnNode(node:SKNode){
let cameraPositionInScene:CGPoint = self.convertPoint(node.position, fromNode: world!)
world!.position = CGPoint(x:world!.position.x - cameraPositionInScene.x, y: world!.position.y - cameraPositionInScene.y)
}
override func didSimulatePhysics() {
self.centerOnNode(player!)
}
I would like to know how to remove my SKNodes when they are off screen to help my game run more smoothly.
How To Do This On Sprite Kit
Thanks So Much
Here is an easy solution in Swift 4:
class GameScene: SKScene {
let s = SKLabelNode(fontNamed: "Chalkduster")
override func didMove(to view: SKView) {
s.text = "test"
s.fontSize = 50
addChild(s)
let moveRight = SKAction.moveBy(x: 40, y: 0, duration: 0.5)
s.run(SKAction.repeatForever(moveRight))
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if ((s.parent != nil) && !intersects(s)) {
s.removeFromParent()
print("Sprite removed.")
}
}
}
You have a sprite (in this case a SKLabelNode but any sprite node will do) that is moving horizontally and you want to delete this sprite once is out of the frame bounds.
You can use the intersects function to check this and then remove that sprite from its parent. I have also checked that the sprite has a parent before removing it (by checking if s.parent is not nil) as we wish to remove the sprite only once.
https://stackoverflow.com/a/24195006/2494064
Here is a link to an answer that removes nodes that go off the top of the screen. You would just have to replicate this to cover the entire border and set all of the walls to have the same contactBitMask values.
Basically the logic is to remove the SKSpriteNodes when they contact physicsbodies that you have resting just outside the visible screen.