I'm trying to add the "virus" as a child of the "cell", however only the cell appears. I tried adding both to the scene and then adding cell to virus, but the program broke. What is the proper way to add a child to an existing SkSpriteNode?
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let virus : VirusNode = VirusNode.create(CGPoint(x: self.frame.maxX / 2, y: self.frame.maxY / 2))
let cell = SKShapeNode(circleOfRadius: CGFloat(10))
cell.fillColor = UIColor.greenColor()
cell.position = CGPoint(x: self.frame.maxX / 2, y: self.frame.maxY / 2)
cell.addChild(virus) //virus is just a SKSpriteNode
self.addChild(cell)
}
Your issue is you need to understand how frame works when dealing with parent child relationships.
The child's frame will always be in the coordinate system of the parent, so when you create your virus. You need to keep this in mind.
As of right now you are setting your virus to the center of the scene. You then set your cell at the center of the scene. When you add the virus to the cell, you are changing the frame to be the location of the parent (Center of the screen) + location of the child (Center of the screen), which puts the virus on the top right of the scene. Which is not what you want.
Instead, set the virus at position (0,0). When you add it to cell, you will find that he is located in the center of your cell.
In regards to the program crashing, if you add an SKNode to another SKNode(or SKScene), you need to remove it from its parent before you can give it to another SKNode. Think of your job like child protective services, you need to remove the child from his original home before you can give the child to his new parents, otherwise you may end up with a wicked confrontation between the two parents that you can't control.
Related
I'm programmatically generating a set of UILabels, attaching them to SCNNodes and then placing them in a scene.
The problem is that the text on some of the labels doesn't appear. This occurs (seemingly) randomly.
Here's the code:
var labels = [SCNNode]()
var index: Int
var x: Float
var y: Float
let N = 3
for i in 0 ... N-1 {
for k in 0 ... N-1 {
let node = label()
labels.append(node)
index = labels.count - 1
x = Float(i) * 0.5 - 0.5
y = Float(k) * 0.5 - 0.5
sceneView.scene.rootNode.addChildNode(labels[index])
labels[index].position = SCNVector3Make(x, y, -1)
}
}
and the method to create the label node:
func label() -> SCNNode {
let node = SCNNode()
let label = UILabel(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(100), height: CGFloat(50)))
let plane = SCNPlane(width: 0.2, height: 0.1)
label.text = "test"
label.adjustsFontSizeToFitWidth = true
plane.firstMaterial?.diffuse.contents = label
node.geometry = plane
return node
}
The labels themselves always appear correctly, it's just that some of them are blank, with no text.
I've tried playing around with the size of the label, the size of the plane it is attached to, the font size etc - nothing seems to work.
I've also tried enclosing the label creation in DispatchQueue.main.async { ... }, which didn't help either.
I'm moderately new to Swift and iOS, so could easily have missed something very obvious.
Thanks in advance!
EDIT:
(1) Setting label.backgroundColor = UIColor.magenta makes it clear that in fact the label is not being created, but the node / plane is.
Some of the labels are left white (i.e only the SCNNode is being rendered), however after a short delay they sometimes then become magenta and the text will appear. Some of the labels will remain missing though.
(2) It further appears that it's related to the position and orientation of the node (label) relative to the camera. I created a large (10x10) grid of labels, then tested placing the camera at different initial positions in the grid. The likelihood that a node appeared seemed directly related to the distance of the node from the initial camera position. Those nodes directly in front of the camera were always rendered, and those far away almost never were.
(3) workaround / hack is to convert the labels to images, and use them instead - code is at https://github.com/Jordan-Campbell/uiimage-arkit-testing if anyone is interested.
If you are labeling things in AR, 99% of the time it is better to do so in "Screen Space" rather than in "Perspective".
Benefits of labels in Screen Space:
ALWAYS readable, regardless of user's distance from the label
You can use regular UILabels, no need to draw them to an image and then map the image to an SCNPlane.
Your app will have a first party feel to it because Apple uses Screen Space for their labels in all of their AR apps (see Measure).
You will be able to use standard animations on your UILabel, animations are much more complex to set up when working with content in Perspective.
If you are sold on Screen Space, let me know and I'll be happy to put up some code showing you the basics.
Use main thread for creating and adding labels to scene. This will make things faster, and avoid coupling this addition to scene with plane detection this really slows down the rendering. this works at times.
DispatchQueue.main.async {
}
Use a UIView SuperView as Parent to your label this would make things smoother.
I have a moving camera in my scene, which always follows my player. But I also have some other content(On screen controls) which I want to stay in a single place on the screen, but when the camera moves, The controls are moving away to. How would I go about doing this. I have searched a lot, and found the SKConstraint, but I couldn't find any tutorials to use it in swift 3.
Should I be using the SKConstraint? If yes, how can I use it, if no, how do I keep the controls at a certain position on the screen at all times.
I also know that I could change the position of the controls in the update method, but I do not want to do that as there are many on screen controls, and I try to refrain from writing code in the update method as much as possible.
Any help would be appreciated, thanks!
You can use an SKConstraint to cause a camera to follow a character.
First, create a camera node and a hero and add them to the scene
let cameraNode = SKCameraNode()
let hero = SKSpriteNode(imageNamed: "Spaceship")
addChild(hero)
camera = cameraNode
addChild(cameraNode)
Next, create a constraint and assign it to the camera node's constraints property
let range = SKRange(constantValue:0)
let constraint = SKConstraint.distance(range, to: hero)
cameraNode.constraints = [constraint]
Lastly, if you have controls or labels that need to be at fixed locations relative to the camera, you can add them to the camera node
let label = SKLabelNode(text: "Score: 123")
// Position the label relative to the camera node
label.position = CGPoint(x: 100, y: 100)
cameraNode.addChild(label)
Context:
there is a cursor (like your mouse) SKSpriteNode
cam is a SKCameraNode and is a child to the cursor (i.e. wherever your cursor goes, so follows the camera).
cam is purposely not centered on the cursor; rather, it is offset so the cursor appears at the top of the view, and there remains empty space below
A simple schematic is given below
Goal:
The goal is two add to sprites to the lower left and lower right corners of the camera's view. The sprites will be children of the camera, so that they always stay in view.
Question
How can I position a sprite in the corner of a camera, especially given that the SKSpriteNode does not have an anchorPoint attribute (as an SKSpriteNode typically has, which let me offset the camera as a child to the cursor)?
Note: One can position the SKSpriteNodes on the GameScene and then call .move(toParent: SKNode), which gets you closers but also messes with the position and scale of the SKSpriteNodes
var cam: SKCameraNode!
let cursor = SKSpriteNode(imageNamed: "cursor")
override func didMove(to view: SKView) {
// Set up the cursor
cursor.setScale(spriteScale)
cursor.position = CGPoint(x: self.frame.midX, y: raisedPositioning)
cursor.anchorPoint = CGPoint(x:0.5, y:0.5)
cursor.zPosition = CGFloat(10)
addChild(cursor)
// Set up the camera
cam = SKCameraNode()
self.camera = cam
cam.setScale(15.0)
// Camera is child of Cursor so that the camera follows the cursor
cam.position = CGPoint(x: cursor.size.width/2, y: -(cursor.size.height * 4))
cursor.addChild(cam)
// Add another sprite here and make it child to cursor
...
the cameraNode has no size, but you can get the current screen size with the frame property
frame.size
then you can position your node accordingly, for example if you want to position the center of yournode in the left corner you set the position as this:
yournode.position.x = 0
yournode.position.y = frame.size.height
This is best solved with a "dummy node" that acts as the camera's screen space coordinates system.
Place this dummy node at the exact centre of the view of the camera, at a zPosition you're happy with, as a child of the camera.
...from SKCameraNode docs page:
The scene is rendered so that the camera node’s origin is placed in
the middle of the scene.
Attach all the HUD elements and other pieces of graphics and objects you want to stay in place, relative to the camera, to this dummy object, in a coordinate system that makes sense relative to the camera's "angle of view", which is its frame of view.
...from a little further down the SKCameraNode docs page:
The camera’s viewport is the same size as the scene’s viewport
(determined by the scene’s size property) and the scene is still
scaled by its scaleMode property when it is rendered into the view.
Whenever the camera moves, it moves the dummy object, and all the children of the dummy object move with the dummy object.
The biggest advantage of this approach is that you can shake or otherwise move the dummy object to create visual effects indicative of motion and explosions. But also a neat system for removal from view, too.
I have a UIView called container that I want to move (offset) using affine transfrom. This view contains UIImageView and is a subview of UICollectionViewCell.
So it should be simple:
container.transform = CGAffineTransformMakeTranslation(100, 200) //render container 100 points right and 200 points down
Instead it is very hard, because theat code does not do anything. The view is rendered excatly on the same place as if I delete that line. So I added 'print' to verify what affine translation was set:
container.transform = CGAffineTransformMakeTranslation(100, 200)
print(container.transform) //prints: CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 100.0, ty: 200.0)
That seems all right. So I tried rotating the container view instead with CGAffineTransformMakeRotation and it rotates the view just not around its center as it should according to documentation. I tried different combinations of translate, rotation and scale transforms just to find that the affine transformation matrixes set are OK, but attributes tx and ty seems to be ignored and a, b, c and d seems to be using different anchor point then the centre of the view (cannot say what that point is).
Any ideas on what can be causing this and how to fix it?
There must be something like auto layout messing things up for you. In the absence of outside influence, setting a view's affine transform to CGAffineTransformMakeTranslation(100, 200) will shift it right 100 points and down 200. I verified this by making a new Single View Project in Xcode and changing the viewDidLoad method in the ViewController.swift class to:
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.blueColor();
let container = UIView(frame: CGRectMake(0,0,100,100));
container.backgroundColor = UIColor.greenColor();
container.transform = CGAffineTransformMakeTranslation(100, 200);
view.addSubview(container);
}
As expected this makes the green container view appear 100 points to the right and 200 points down, even though its frame is (0,0,100,100).
So please check for auto layout and other such things that might influence the placement of this view, and if you can't find anything please post more code. Also, if your container view doesn't have a background color, please give it one so that you can see its position directly, instead of deducing its position by looking at the image view.
n.b. Setting a view's transform doesn't actually move the view itself, it just changes how/where it draws its content.
I'm teaching myself how to do SpriteKit programming by coding up a simple game that requires that I lay out a square "game field" on the left side of a landscape-oriented scene. I'm just using the stock 1024x768 view you get when creating a new SpriteKit "Game" project in XCode - nothing fancy. When I set up the game field in didMoveToView(), however, I'm finding the coordinate system to be a little weird. First of all, I expected I would have to place the board at (0, 0) for it to appear in the lower-left. Not so -- it turns out the game board has to be bumped up about 96 pixels in the y direction to work. So I end up with this weird code:
let gameFieldOrigin = CGPoint(x:0, y:96) // ???
let gameFieldSize = CGSize(width:560, height: 560)
let gameField = CGRect(origin: gameFieldOrigin, size: gameFieldSize)
gameBorder = SKShapeNode(rect: gameField)
gameBorder.strokeColor = UIColor.redColor()
gameBorder.lineWidth = 0.1
self.addChild(gameBorder) // "self" is the SKScene subclass GameScene
Furthermore, when I add a child to it (a ball that bounces inside the field), I assumed I would just use relative coordinates to place it in the center. However, I ended up having to use "absolute" coordinate and I had to offset the y-coordinate by 96 again.
Another thing I noticed is when I called touch.locationInNode(gameBorder), the coordinates were again not relative to the border, and start at (0, 96) at the bottom of the border instead of (0, 0) as I would have guessed.
So what am I missing here? Am I misunderstanding something fundamental about how coordinates work?
[PS: I wanted to add the tag "SpriteKit" to this question, but I don't have enough rep. :/]
You want to reference the whole screen as a coordinate system, but you're actually setting all the things on a scene loading from GameScene.sks. The right way to do is modify one line in your GameViewController.swift in order to set your scene size same as the screen size. Initialize scene size like this instead of unarchiving from .sks file:
let scene = GameScene(size: view.bounds.size)
Don't forget to remove the if-statement as well because we don't need it any more. In this way, the (0, 0) is at the lower-left corner.
To put something, e.g. aNode, in the center of the scene, you can set its position like:
aNode.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));