How does one initialize/move a camera in a SpriteKit program using Swift? - ios

I have a map and it's bigger than the device's screen size, so I want the user to be able to navigate around the map by dragging the screen, similar to Clash of Clans.
How can I create a camera view so I can drag the camera around the map?
Also, how can I customize where the camera's center begins as the scene switches to the map?

theres no camera in swift. the idea is you add your background to a "world node"
// set up stuff you want to add to world node
let bg = SKSpriteNode(... initializer
// make a "worldNode" that you move around
self.worldNode = SKNode()
// add your bg to the worldNode
worldNode.addChild(bg)
// add worldNode to the scene
self.addChild(worldNode)
ok so now you have that.. now you make some method to move the world around depending on a position
func centerViewOn(centerOn: CGPoint) {
self.worldNode.position = CGPoint(x: -x, y: -y)
}
You pass in the players position, and the worldNode will move. That's the general idea.
You probably need to add some conditional stuff in here so that the camera doesn't move too far. With this code, the camera has no limit, and can look past the edge of your game world. You'd want to limit it so it stops following the player when they reach the edge.

Related

How to make SKEmitter particles stay relative to the background when I move my SKEmitterNode around the screen?

I feel like this should be a very simple question, but I looked around and it seems to just be working for everybody automatically. But for some reason, when I move the particle emitter around the screen, the particles move with it, and there is no trail behind the emitter node of particles.
I am making a SpriteKit game of a character running around on the bottom of the screen, and he has a boost ability which releases small bubble particles behind him when he boosts because he is underwater. But the particles aren't staying relative to the screen, they are relative to the player and move with him.
Here's some code to add the boost bubbles:
func addBoostBubbles(){
boostBubbles = SKEmitterNode(fileNamed: "bubbleBoost")!
boostBubbles.particlePositionRange = CGVector(dx: player.frame.size.width, dy: player.frame.size.height)
boostBubbles.position = player.position
boostBubbles.zPosition = 0
self.addChild(boostBubbles)
}
I call this func to create the emitter node and set it to the player's position.
then to move the position of the bubbles in the didSimulatePhysics func I have this:
boostBubbles.position.x += xAcceleration
I see the bubbles on the screen and the emitter node is moving to the right places, but I want them relative to the background so the bubbles just slowly float up behind the player.
Any help would be greatly appreciated!!!!
Set the targetNode property of your emitter to be the background node. In addBoostBubbles, try adding the line:
boostBubbles.targetNode = <background node name>
https://developer.apple.com/documentation/spritekit/skemitternode
targetNode - The target node that renders the emitter’s particles.
This causes the particles to render as though they are children of the background.
If you don't have a background node then you'll need to add one.

SceneKit - positioning SCNCamera and permitting object rotation

I am close to completing my first project in SceneKit but I'm struggling with the last few steps. It is probably easiest to explain my progress by sharing a short screen capture video of the Xcode Simulator displaying my current scene.
As you can see by the screen capture my project is composed of three elements (this is all done in code, I do not import any external assets):
outside box (defined via six SCNBox objects per corner)
inside sun (defined via a SCNTube object for the circle and UIBezierPath objects per "ray")
position of camera
Based on feedback I have committed the code to GitHub.
Right now the camera is allowed to rotate as seen in the screen capture but the centre of rotation of the camera and of the objects doesn't align so it appears to spin off-axis.
Here's where I want to get to:
correct camera position so that the combined box & sun is positioned directly in front of the camera, filling the screen
maintain the sun's position as being fixed (already done I guess)
allow the box to rotate freely in x, y & z around the sun based on touch input - so the user can "flick" the box and watch it flip and spin around the sun
The code structure is straight forward:
class GameViewController: UIViewController {
var gameView: SCNView!
var gameScene: SCNScene!
var cameraNode: SCNNode!
var targetCreationTime: TimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
initView()
initScene() // createSun() and createCube() called here
initCamera()
}
And with respect to the camera position:
func initCamera() {
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
cameraNode.rotation = SCNVector4Make(1, 0, 0, .pi/2)
}
But what I've found is that despite playing around with the random cameraNode.position and cameraNode.rotation values the camera view doesn't seem to change.
My questions - any help will be greatly appreciated:
advice on repositioning the camera (what am I doing wrong?!) - once it's in the right place I can easily set "gameView.allowsCameraControl = false"
advice on how to enable the box to spin about its axis around the sun (while the sun remains fixed)
stretch goal! Any kind of general "check out this tutorial" type info on materials and lighting, and embedding this view into a SwiftUI view
Thanks!
I decided to stop fighting the point of rotation and instead reposition the elements around this.
One interesting thing, which I’ve mentioned at the start of the createBox() func.
// originally debugCube & debugNode were used for debugging the pivot point of the box
// but I found have this large node helped to balance out the centre of mass
// set to fully transparent and added to boxNode as final step after all other transformations
If you comment out the lines 19-26 plus 117 you will completely remove debugNode. And funnily enough when you do that the box stops spinning correctly. But you add it back in and everything is fixed. I’m guessing it’s adding “mass” to the overall node and helping lock the point of rotation to the correct position. So in the end I just made it transparent!
The final (version 1.0) code is posted on GitHub at github.com/LedenMcLeden/logo
Use this answer in post for your camera: 57586437, remove camera rotation and take camera control off. Rotate your box with a simple (I'd do an x,y,z independent spin just to verify it) spin so that you'll know if your pivot point is correct. It should be ok by default and spin in place right in front of the camera, but depends on how you built your cube.
If you added the sun and stuff as a subnode of your box, then you're probably in decent shape and the pieces will rotate together.
If you want to do camera rotations similar to cameraControl, then you'll need to add a gesture recognizer and then you can start experimenting with it.
Hope that helps!

How do I convert coordinates in SpriteKit?

I'm trying to grasp the conversion thing in SpriteKit but despite having read the documentation and several posts on SO I can't seem to get it right. As far as I understand there are two coordinate systems that work independently of one another, one for the scene and one for the view, which is why I simply can't use the things like UIScreen.main.bounds.maxX to determine screen corners that the node can relate to. Am I getting this right?
Anyway, here's my attempt at converting coordinates:
let mySquare = SKShapeNode(rectOf: CGSize(width: 50, height: 50))
mySquare.fillColor = SKColor.blue
mySquare.lineWidth = 1
let myPoint = CGPoint(x: 200, y: 0)
let newPosition = mySquare.convert(myPoint, from: self)
mySquare.position = newPosition
print(newPosition)
self.addChild(mySquare)
The print returns the exact same position as went in so obviously I'm not doing this right, but I have tried a number of different constellations but with pretty much no result; the coordinates remain the same. I have also tried let myPoint = CGPoint(x: UIScreen.main.bounds.maxX, y: UIScreen.main.bounds.maxY) but same there; no conversion.
What am I missing? In my head I read the conversion above as "convert myPoint from the view coordinate system and use it for my node mySquare.
There are lots of coordinate systems floating around, and so lots of potential sources of confusion:
Scene coordinates: that's your game's world, and what you usually think about when imagining coordinates and how to position things overall.
Node: Nodes have their own coordinate system. Once you start building a hierarchy, that matters. Imagine, e.g., an on-screen joystick that has a background showing a graphic of movement directions and a central "knob" that the player can manipulate. You might represent the joystick as a node with two children. One child is a sprite for the background, and the other is a sprite for the knob. The background sprite would naturally be at position (0,0), meaning the center of the overall joystick. The knob would move around, with (0,0) meaning centered and maybe (0,100) meaning up a bit. The overall joystick might sit at (200,300) in the scene. Then the background sprite would show up at (200,300) in the scene and the knob, when up, would be at (200,300)+(0,100) = (200,400) in the scene. The convert(from:) and convert(to:) are for converting within the node hierarchy. You could ask where the knob is in the overall scene's coordinates by knob.convert(.zero, to: scene) or joystick.convert(knob.position, to: scene). You very rarely need to do that sort of conversion.
View coordinates: The view is a window on the scene, i.e., what's actually being shown. If you've got a full screen game, the view is basically determined by the screen size in points. How view coordinates map to scene coordinates determines what part of the scene you actually see. If you need to go between view coordinates and scene coordinates, you use the scene's convertPoint(fromView:) and convertPoint(toView:) methods.
If you don't do anything special and have the scene size the same as the view size, then the scene-view mapping will have (0,0) in the scene at the lower left corner of the view. Another common convention is to have (0,0) in the scene at the center of the screen by setting the scene's anchorPoint to (0.5,0.5). Or perhaps you've designed the scene so that the world is 2000x2000 in size and there will be a nontrivial scaling and possible letter-boxing or cropping involved (depending on the setting of the scene's scaleMode). Or if your game has a camera node and, e.g., the camera is set to follow the player around, then the view-to-scene mapping will be changing as the player moves.
In your code, calling mySquare.convert(from:) doesn't really even make sense since the square hasn't been added to the scene at the time you're doing the "conversion".
Anyway, if you really want to do something like "put a square in the top-left corner of the screen", then you can take the point in the view's frame and convert it to scene coordinates and set the square's position to that.
override func didMove(to view: SKView) {
...
mySquare.position = convertPoint(fromView: CGPoint(x: view.frame.minX, y: view.frame.minY))
addChild(mySquare)
...
}
Edit: I would encourage you though to think mostly in terms of the overall scene, after some initial consideration of how the game should map to devices with screens of different sizes and aspect ratios. Once you're thinking in terms of the scene, then the scene's frame (rather than the view's frame) becomes the most natural reference when you're imagining "at the left edge" or "near the bottom right".

How to make a view scrollable to view the full scene with sprites with Swift and SpriteKit?

I have a Scene which now fills in the view:
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
I fill in the scene with sprites from bottom to top and in my up they need to go further up - outside the view.
So my scene should have the same width as a view but height should be... around 3000 fixed.
Sprite will fill in the screen and i want to scroll up to be able to view the whole scene.
Would you please give me an advice on how to "construct" that?
Here is a quick road map:
In your game scene "didMoveToView" you want to make a "world" node. Make sure the "world" node is a global variable by declaring it outside of the class. Make that "world" node size as large as the full size needed (e.g., 3000).
Also in "didMoveToView" add a pinch gesture that will eventually allow you to "zoom" that world, using UIPinchGestureRecognizer
Make the function to handle the pinch. Ultimately, the "recognizer.scale" should influence the "world.scale"
All your sprites should be added to the "world" node
You should create a node that acts as the World for the scene in your project and simply shift the y coordinate downwards to give the illusion of being able to see the scene that is further up.
For some sample code, you can look here

how to Move world view instead of camera view

I have a viewport3d with a camera and some blocks inside of it. Currently using the keyboard to move the camera up/down/left/right/rotate ect.
but instead of the camera i want to move the world view. So when a user presses the W key to move up, its not just moving the camera in a +x position. As the user maybe at a 20 degree view.
Create a virtual Camera Boom.
Create an invisible object like a TDummy, in the same location as the object of interest or the center of the scene.
Create the TCamera as a child of the object. Set its position the desired distance away on one of the axes.
you can now rotate the camera around the object simply by changing the rotationAngle of the dummy object. The camera will maintain the exact distance and automatically points directly toward the center.
Also by adding light as a child of the camera it too will follow as the camera moves. Hope this helps someone.

Resources