Create rectangle with contents for AR SpriteKit application, programatically - ios

I created a new augmented reality app project in Xcode 9 with the content technology set to SpriteKit.
This generated a template project which places this little guy in the centre of your current view when you tap on the screen.
I wish to create something which looks a little like this instead of the placeholder emoji:
The idea behind this is to be able to set the Name, Type and Rating String values in code and create an SKNode object from that, to place in the centre of the screen.
I understand how to create a rectangle:
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? {
// Create and configure a node for the anchor added to the view's session.
//more of a square than a rectangle..
var rect = SKShapeNode(rectOf: CGSize(width: 10, height: 10))
rect.fillColor = SKColor.white
rect.position = CGPoint(x: 0.5, y: 0.5)
return rect;
}
^ which gives me a white square on the screen
But how can I place text on top of that, as part of the same 'object' as it were? Is that possible? I've not been able to come across a solution to this anywhere and I'm not sure how to implement it.
EDIT:
Here is what I created based on a suggestion in the comments:
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? {
// Create and configure a node for the anchor added to the view's session.
let rect = SKShapeNode(rectOf: CGSize(width: 100, height: 50))
rect.fillColor = SKColor.white
rect.position = CGPoint(x: 0.5, y: 0.5)
let nameNode = SKLabelNode(text: "Name")
nameNode.fontSize = 10
nameNode.fontColor = UIColor.black
let typeNode = SKLabelNode(text: "Type")
typeNode.fontSize = 10
typeNode.fontColor = UIColor.black
let ratingNode = SKLabelNode(text: "Rating")
ratingNode.fontSize = 10
ratingNode.fontColor = UIColor.black
rect.addChild(nameNode)
rect.addChild(typeNode)
rect.addChild(ratingNode)
return rect;
}
The text is all bundled together in the centre of the rectangle however. How can I align the labels like in my concept image above?
Plus the text seems to be very pixelated, any suggestions on particular settings I need to apply to combat this?

Related

How to translate ARAnchor size to SpriteKit equivalent

Question:
How do I create a new SKSpriteNode that is the same height and width of the ARObjectAnchor.referenceObject.
Context:
I'm currently fiddling with ARKit's new object detection feature and have working code to detect an object that I scanned in. When ARKit detects the object it provides a new ARAnchor of type ARObjectAnchor.
I know ARSCNView provides a projectPoint method, but I can't find any equivalent function for ARSKView. How can I map the ARObjectAnchor dimensions to the new Sprite?
Here's how I'm processing the detected object:
func view(_ view: ARSKView, didAdd node: SKNode, for anchor: ARAnchor) {
if let objectAnchor = anchor as? ARObjectAnchor {
let width = objectAnchor.referenceObject.extent.x
let height = objectAnchor.referenceObject.extent.y
// How to translate above height/width to the below size?
let box = SKSpriteNode(color: .white, size: ???)
node.addChild(box)
}
}
...
let size = CGSize(width: width, height: height)
let box = SKSpriteNode(color: .white, size: size)
...
When I add a SKScene to my SCNPlane in my image recognition app, I do a * 10000 of the size of my SCNPlane (don't ask me why it's 10000 i have no idea) as follow
let toto = SCNNode(geometry: SCNPlane(width: 0.3, height: 0.1))
let sk = SKScene(size: CGSize(width: 3000, height: 1000))
and for me it's the same size, try this out and tell me if it works for you !

ARKit: How can I add a UIView to ARKit Scene?

I am working on an AR project using ARKit.
I want to add a UIView to ARKit Scene. When I tap on an object, I want to get information as a "pop-up" next to the object. This information is in a UIView.
Is it possible to add this UIView to ARKit Scene?
I set up this UIView as a scene and what can I do then?
Can I give it a node and then add it to the ARKit Scene? If so, how it works?
Or is there another way?
Thank you!
EDIT: Code of my SecondViewController
class InformationViewController: UIViewController {
#IBOutlet weak var secondView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view = secondView
}
}
EDIT 2: Code in firstViewController
guard let secondViewController = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else {
print ("No secondController")
return
}
let plane = SCNPlane(width: CGFloat(0.1), height: CGFloat(0.1))
plane.firstMaterial?.diffuse.contents = secondViewController.view
let node = SCNNode(geometry: plane)
I only get a white screen of a plane, not the view.
The simplest (although undocumented) way to achieve that is to set a UIView backed by a view controller as diffuse contents of a material on a SCNPlane (or any other geometry really, but it works best with planes for obvious reasons).
let plane = SCNPlane()
plane.firstMaterial?.diffuse.contents = someViewController.view
let planeNode = SCNNode(geometry: plane)
You will have to persist the view controller somewhere otherwise it's going to be released and plane will not be visible. Using just a UIView without any UIViewController will throw an error.
The best thing about it is that it keeps all of the gestures and practically works just as a simple view. For example, if you use UITableViewController's view you will be able to scroll it right inside a scene.
I haven't tested it on iOS 10 and lower, but it's been working on iOS 11 so far. Works both in plain SceneKit scenes and with ARKit.
I cannot provide you code now but this is how to do it.
Create a SCNPlane.
Create your UIView with all elements you need.
Create image context from UIView.
Use this image as material for SCNPlane.
Or even easier make SKScene with label and add it as material for SCNPlane.
Example: https://stackoverflow.com/a/74380559/294884
To place text in a label in the world you draw it into an image and then attach that image to a SCNNode.
For example:
let text = "Hello, Stack Overflow."
let font = UIFont(name: "Arial", size: CGFloat(size))
let width = 128
let height = 128
let fontAttrs: [NSAttributedStringKey: Any] =
[NSAttributedStringKey.font: font as UIFont]
let stringSize = self.text.size(withAttributes: fontAttrs)
let rect = CGRect(x: CGFloat((width / 2.0) - (stringSize.width/2.0)),
y: CGFloat((height / 2.0) - (stringSize.height/2.0)),
width: CGFloat(stringSize.width),
height: CGFloat(stringSize.height))
let renderer = UIGraphicsImageRenderer(size: CGSize(width: CGFloat(width), height: CGFloat(height)))
let image = renderer.image { context in
let color = UIColor.blue.withAlphaComponent(CGFloat(0.5))
color.setFill()
context.fill(rect)
text.draw(with: rect, options: .usesLineFragmentOrigin, attributes: fontAttrs, context: nil)
}
let plane = SCNPlane(width: CGFloat(0.1), height: CGFloat(0.1))
plane.firstMaterial?.diffuse.contents = image
let node = SCNNode(geometry: plane)
EDIT:
I added these lines:
let color = UIColor.blue.withAlphaComponent(CGFloat(0.5))
color.setFill()
context.fill(rect)
This lets you set the background color and the opacity. There are other ways of doing this - which also let you draw complex shapes - but this is the easiest for basic color.
EDIT 2: Added reference to stringSize and rect

How to create a physics body for an SKSpriteNode with an interior in Sprite Kit (Swift)

I am currently making an iOS game in which a ball must bounce within a frame defined by the below image:
The game relies on the below method to automatically create and assign a physics body to the frame surrounding the game.
frame.physicsBody = SKPhysicsBody(texture: frameTexture, size: frame.size)
This code, however, assigns a body that encompasses the outside of the image, rather than the interior, and thus treats the blank interior as a solid object. This, in turn, means that the ball cannot be inside the frame and is forced off the screen immediately after being spawned.
Is there any way to give a physics body an interior?
Another post here: SpriteKit SKPhysicsBody with Inner Edges aims to do something similar, but does not involve automatic wrapping around a texture (hence the new question)
You can use init(edgeLoopFrom:) initializer to achieve inner edge friction. you can use this code below:
override func didMove(to view: SKView) {
//Setup scene's physics body (setup the walls)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
let back = SKSpriteNode(imageNamed: "1")
back.position = CGPoint(x: frame.midX, y: frame.midY)
back.size = CGSize(width: 500, height: 600)
back.zPosition = -3
let rect = CGRect(origin: CGPoint(x: -back.size.width/2, y: -back.size.height/2), size: back.size)
back.physicsBody = SKPhysicsBody(edgeLoopFrom: rect)
addChild(back)
//Add Red ball "inside" the back sprite
let red = SKShapeNode(circleOfRadius: 20)
red.fillColor = .red
red.strokeColor = .clear
red.position = back.position
red.physicsBody = SKPhysicsBody(circleOfRadius: 20)
red.physicsBody?.restitution = 1
red.physicsBody?.friction = 0
red.zPosition = 1
red.physicsBody?.affectedByGravity = false
addChild(red)
red.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 15))
}
please follow this Question and have a look to the answer given below.
i am providing a screenshot of my project

How to make objects move?

This is my code I wrote in Xcode using Sprite Kit, Swift. I'm trying to make a game like ComboQuest. The game consists of a moving bar that moves left and right trying to hit objects. As I'm trying to recreate that left and right movement here is my code:
func rightSprite(){
let actionR = SKAction.moveByX(0.001, y: 0, duration: 0.01)
Sprite.runAction(SKAction.repeatActionForever(actionR))
}
func leftSprite(){
let actionR = SKAction.moveByX(-0.001, y: 0, duration: 0.01)
Sprite.runAction(SKAction.repeatActionForever(actionR))
}
These two functions are then activate in the TouchesBegan override func, but there is a small flaw. When you touch the screen to change the direction there is not a complete reaction.
How would you guys code this?
First off, make sure you have a valid instance of a SKSpriteNode. It would be helpful to see more of your code to see where you are creating the bar sprite you would like to move. For example, below I created a red rectangle sprite.
let bar = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 10))
As far as your actions, you need to call them on your instance of a sprite. I used the same bar sprite from the example above. Also, since you are setting an action to repeat forever, you will need to remove that action to get it to stop. It appears you want the bar to move left and right. You can either remove all actions as I have done, or you could name your actions, and just remove a certain one. For simplicity, I just removed all actions.
func rightSprite(){
let actionR = SKAction.moveByX(0.001, y: 0, duration: 0.01)
bar.removeAllActions()
bar.runAction(SKAction.repeatActionForever(actionR))
}
func leftSprite(){
let actionL = SKAction.moveByX(-0.001, y: 0, duration: 0.01)
bar.removeAllActions()
bar.runAction(SKAction.repeatActionForever(actionL))
}
Take a look a this :
override func didMoveToView(view: SKView) {
let sprite = SKSpriteNode(color: UIColor.purpleColor(), size: CGSize(width: 20, height:50))
var startPoint:CGPoint = CGPoint(x: 100, y: CGRectGetMidY(frame))
var endPoint:CGPoint = CGPoint(x: 300, y: CGRectGetMidY(frame))
let moveLeft = SKAction.moveTo(endPoint, duration:3)
let moveRight = SKAction.moveTo(startPoint, duration:3)
let sequence = SKAction.sequence([moveLeft,moveRight])
sprite.position = startPoint
addChild(sprite)
sprite.runAction(SKAction.repeatActionForever(sequence), withKey:"moving")
}
I assume that your scene and view are initialized and sized correctly (eg that you done something like this scene.size = skView.bounds.size ) so when you try this example, sprite will show up on screen (instead of possible off-screen because you probably loading a scene from a .sks file).
IMO this is the natural way when using SKAction to move a sprite from point A to point B continuously (like from a Combo Quest) and that is:
Create starting and ending point
Make separate actions for moving sprite left and right
Make a sequence to run those two actions one after another
Repeat that action forever
Run that action with key to give your self a way to stop an action
When you want to stop action, you should do something like this:
if(sprite.actionForKey("moving") != nil){
sprite.removeActionForKey("moving")
}

SKEffectNode, SpriteKit invert color in Swift

I want to invert the color from the part of the HelloLable which is outside of the boxSprite. I know that it is possible with SKEffectNode but I don't know how.
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let box = SKSpriteNode()
box.position = CGPoint(x: size.width/2-100, y: size.height/2)
box.color = UIColor.blackColor()
box.size = CGSize(width: 200, height: 200)
addChild(box)
let HelloLable = SKLabelNode()
HelloLable.text = "Hello"
HelloLable.position = CGPoint(x: size.width/2, y: size.height/2)
HelloLable.fontColor = UIColor.whiteColor()
HelloLable.fontSize = 50
addChild(HelloLable)
}
If I understand you correctly, you'd like to make something like this:
I don't think there is a standard solution to do this (maybe you could try blending modes or shaders).
Maybe the easiest solution could be to make 2 labels, one white in the box, and one black under the box, with similar properties, and clip the white one with the box.

Resources