This code is in an if statement that checks if the user touches a correct button. If not returned true this code below will run. The problem is that when live is removed from parent the line let live = childNodeWithName("liveBall") as! SKSpriteNode returns this error:
fatal error: unexpectedly found nil while unwrapping an Optional
value.
This must be because childNodeWithName("liveBall") does no longer exist.
override func didMoveToView(view: SKView) {
let live = SKSpriteNode(texture: purpleTexture)
live.position = CGPointMake(self.frame.size.width * 0.68, self.frame.size.height * 0.93)
live4.name = "liveBall"
self.addChild(live)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let live = childNodeWithName("liveBall") as! SKSpriteNode
if(intersectsNode(live)){
live.removeFromParent()
}
}
How can I avoid this error?
This means that there is no "liveBall" node as a child. Are you sure it is added? It seems that there is a typo (number 4) in:
live4.name = "liveBall"
You can try to add node like that:
var mySprite: SKSpriteNode = childNodeWithName("mySprite") as SKSpriteNode
To add safety to your function that calls RemoveFromParent(), simply add a new Boolean variable that changes itself to true when RemoveFromParent() is called and use a condition check to prevent it from occuring twice.
Related
Alright, Im weeding thru the provided RealityComposer game at https://developer.apple.com/videos/play/wwdc2019/609/ and am trying to figure out how to have an entity move where the user taps.
I can reference my objects in my .rc scene like this:
struct ARViewContainer: UIViewRepresentable {
let arView = ARView(frame: .zero)
func makeUIView(context: Context) -> ARView {
// arView = ARView(frame: .zero)
// Load the "Box" scene from the "Experience" Reality File
let boxAnchor = try! Experience.loadBox()
//*Notifications
setupNotifications(anchor: boxAnchor)
//*Access vars
if boxAnchor.box1 != nil {
//print(box1.position.x)
/// Set local position
boxAnchor.box1!.position = [1.0, 0.0, 0.5]
/// Set world position
//boxAnchor.box1.setPosition([0.5, 0.2, 1.5], relativeTo: nil)
}
I know this involves some form arhitest, however with the following I get the error:
UIView has no member last?
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Location
guard let currentTouchLocation = touches.first?.location(in: self.arView),
//2. Get The Tapped Node From An SCNHitTest
let hitTestResultNode = self.arView.hitTest(currentTouchLocation, with: nil).last?.node else { return } //error here
//3. Loop Through The ChildNodes
for node in hitTestResultNode.childNodes{
//4. If The Node Has A Name Then Print It Out
if let validName = node.name{
print("Node\(validName) Is A Child Node Of \(hitTestResultNode)")
}
}
}
Im pretty lost as to whether Im going about this at all correctly. Im referencing Detect touch on SCNNode in ARKit but this does not deal with RealityComposer.
How can I do this?
The simplest way to get an Entity to move via touch is to use the built in gestures provides by Apple; which you can read more about here: Apple Documentation
To enable your gesture of choice (in this case translation), first ensure that in RealityComposer that you set the partcipates value to true on every Entity you wish to interact with.
This then adds a Has Collision component to the Entitywhich is simply:
A component that gives an entity the ability to collide with other entities that also have collision components.
Using this you can install built in gestures to do the heavy lifting for you.
Assuming we have the default RealityKit example setup in Xcode, and we have selected participates for the box, its a simple as this to enable the user to pan it using the touch location:
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
//1. Load The Box Anchor
let boxAnchor = try! Experience.loadBox()
//2. Check The SteelBox Has A Collision Component & Add The Desired Gesture
if let hasCollision = boxAnchor.steelBox as? HasCollision {
arView.installGestures(.translation, for: hasCollision)
}
//Add The Box To The Scene Hierachy
arView.scene.anchors.append(boxAnchor)
}
}
Alternatively if you wanted to do some heavy lifting (who doesn't!) then you could do something like this by creating a global variable which will reference the Entity that you have selected (which in this case is called Steel Box):
//Variable To Store Our Currently Selected Entity
var currentEntity: Entity?
Then using touchesBegan and touchesMoved you can something like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
/* Get The Current Touch Location In The ARView
Perform A HitTest For The Nearest Entity
Checks That The Tapped Entity Is The Steel Box
Set It As The Current Entity
*/
guard let touchLocation = touches.first?.location(in: arView),
let tappedEntity = arView.hitTest(touchLocation, query: .nearest, mask: .default).first?.entity,
tappedEntity.name == "Steel Box" else {
return
}
currentEntity = tappedEntity
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
/* Get The Current Touch Location In The ARView
Perform A HitTest For An Existing Plane
Move The Current Entity To The New Transfortm
Set It As The Current Entity
*/
guard let touchLocation = touches.first?.location(in: arView),
let currentEntity = currentEntity else {
return
}
if let transform = arView.hitTest(touchLocation, types: .existingPlaneUsingExtent).first?.worldTransform {
currentEntity.move(to: transform, relativeTo: nil, duration: 0.1)
}
}
Hope it helps point the in the right direction.
The code that I am writing has a problem where whenever I tap somewhere other than a node (ex. background) the app ends up crashing.
I've tried making an if let statement but it says I can't downcast a SKnode to a more optional type SKSpriteNode.
I've also tried if node.contains(position of touch).
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let touchLocation = touch?.location(in: self) {
let selectedNode = nodes(at: touchLocation)[0] as! SKSpriteNode
activeObject = selectedNode
storedData = Array(activeObject.name!)
let platformStoredInt = storedData[2]
storedPlatform = Int(platformStoredInt.unicodeScalars.first!.value - Unicode.Scalar("0")!.value)
}
}
Tapping on anything other than the SKSpriteNodes that are considered objects results in a SIGABRT.
The app crashes because you are force unwrapping a value in this line:
let selectedNode = nodes(at: touchLocation)[0] as! SKSpriteNode
So instead, use:
if let selectedNode = nodes(at: touchLocation)[0] as? SKSpriteNode {
activeObject = selectedNode
storedData = Array(activeObject.name!)
let platformStoredInt = storedData[2]
storedPlatform = Int(platformStoredInt.unicodeScalars.first!.value - Unicode.Scalar("0")!.value)
}
Always try to avoid the force unwrapping (as!). Use Optional Chaining instead.
Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil.
I am very new to swift and am try to refer to my immutable variable let button1 = SKLabelNode(fontNamed: "Bulky Pixels") outside of the function it is declared in.
This is the function that the immutable variable is in:
func runMathsProblem(){
currentGameState = gameSate.mathSlove
if currentGameState == gameSate.mathSlove{
self.speed = 0
}
let button1 = SKLabelNode(fontNamed: "Bulky Pixels")
button1.name = "Button1"
button1.text = "1"
button1.fontSize = 110
button1.fontColor = SKColor.white
button1.zPosition = 120
button1.position = CGPoint(x: self.size.width * 0.3, y: self.size.height * 0.32)
self.addChild(button1)
}
I want to be able to access button1 in the function:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
**//I want to refer to it like this**
if button1.contains(pointOfTouch){
self.speed = 1
}
}
}
Is there a way to refer to it using the name I gave it?
I have tried looking into ways online but I am not sure whether I am looking up the right question.
I hope this is enough for you guys to understand what I am trying to do.
Declare it outside the function like
var button1:SKLabelNode!
then
button1 = SKLabelNode(fontNamed: "Bulky Pixels")
but note you have to call runMathsProblem before any event inside touchesMoved or make it optional like
var button1:SKLabelNode?
You can't do that. Local variables are local, and only exist in the scope in which they are defined. If you define a variable inside a function, it only exists inside that function.
If you want the variable to be visible to other functions in the same object, make it an instance variable:
class MyClass {
//Create an instance variable button1
var button1: SKLabelNode?
func runMathsProblem(){
//Your code removed for brevity
//This function now sets the instance variable rather than a local variable
button1 = SKLabelNode(fontNamed: "Bulky Pixels")
//Your code using Button1 removed for brevity
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
//Now this code works.
if button1?.contains(pointOfTouch){
self.speed = 1
}
}
}
}
Ok, like everyone else I am having trouble dragging/translating an SCNNode in ARKit/world space. Ive looked at Dragging SCNNode in ARKit Using SceneKit and all the popular questions, as well as the code from Apple https://github.com/gao0122/ARKit-Example-by-Apple/blob/master/ARKitExample/VirtualObject.swift
Ive tried to simplify as much as possible and just did what I would in a normal scene kit game - http://dayoftheindie.com/tutorials/3d-games-graphics/t-scenekit-3d-picking-dragging/
I can get the tapped object and store the current finger pos no problem with:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = gameView.hitTest(touch.location(in: gameView), types: [ARHitTestResult.ResultType.featurePoint])
//TAP Test
let hits = gameView.hitTest(touch.location(in: gameView), options: nil)
currentTapPos = getARPos(hitFeature: hitFeature) //TAP POSITION
if let tappedNode = hits.first?.node {
My issue is, however, doing this in update - there is no animation. The object just appears wherever I tap, and overall not working -
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = gameView.hitTest(touch.location(in: gameView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return }
testNode.position = currentTapPos
I convert to SCNVector3 with this func:
func getARPos(hitFeature: ARHitTestResult)->SCNVector3
{
let hitTransform = SCNMatrix4.init(hitFeature.worldTransform)
let hitPosition = SCNVector3Make(hitTransform.m41,
hitTransform.m42,
hitTransform.m43)
return hitPosition
}
I have tried:
-translate by vector
-pan gesture (this screwed up other functions)
-SCNAction.moveTo
What can I do here? Whats wrong?
I agree with #Rickster that the Apple Code provides a much more robust example, however, I have this and it seems to work smoothly (much more so when you are using PlaneDetection (since feature points are much more sporadic):
I don't make use of touchesBegan but simply do the work in touchesMoved instead:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Point
guard let currentTouchPoint = touches.first?.location(in: self.augmentedRealityView),
//2. Get The Next Feature Point Etc
let hitTest = augmentedRealityView.hitTest(currentTouchPoint, types: .existingPlane).first else { return }
//3. Convert To World Coordinates
let worldTransform = hitTest.worldTransform
//4. Set The New Position
let newPosition = SCNVector3(worldTransform.columns.3.x, worldTransform.columns.3.y, worldTransform.columns.3.z)
//5. Apply To The Node
nodeToDrag.simdPosition = float3(newPosition.x, newPosition.y, newPosition.z)
}
I might be going about this completely the wrong way (and if I am I would love to get feedback).
Anyway, I hope it might help... You can see a quick video of it in action here: Dragging SCNNode
I´m working with Sprite Kit. I want to change the button picture of the main class with the settings class. How can I make the variable in the extension (from the setting class) available for the main class?
Here is the extension:
extension ChangingDots {
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
for touch in touches{
let locationUser = touch.location(in: self)
if atPoint(locationUser) == DCButton {
var blackdot = SKSpriteNode(imageNamed: "AppIcon") //<--var I want to use
}
}
}
}
Here is the use in the main class:
blackdot.setScale(0.65)
blackdot.position = CGPoint(x: CGFloat(randomX), y: CGFloat(randomY))
blackdot.zPosition = 1
self.addChild(blackdot)
Does anyone have a better idea of changing button pictures of one class from another?
If you want to use a variable in the main class, it needs to be created in the main class. Extensions are designed to extend functionality, this means functions and computed properties.
To find out more, see this documentation:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
Just to add on here, there are ways to "workaround" adding stored properties in an extension. Here is an article that describes how to do it: https://medium.com/#ttikitu/swift-extensions-can-add-stored-properties-92db66bce6cd
However, if it were me, I would add the property to your main class. Extensions are meant to extend behavior of a class. It doesn't seem to be the right design approach to make a main class DEPENDENT on your extension.
If you want to make it private, you can use fileprivate so your extension can access it but maintain it's "private" access. For example:
fileprivate var value: Object? = nil
Sorry but you make me realize that you can't add stored properties in extensions. You can only add computed properties. You can add your blackdot var as a computed property or declare it in your main class instead of your extension. If you want to try the computed way, use this :
extension ChangingDots {
var blackdot:Type? { // Replace Type with SKSpriteNode type returned
return SKSpriteNode(imageNamed: "AppIcon")
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
for touch in touches {
let locationUser = touch.location(in: self)
if atPoint(locationUser) == DCButton {
blackdot = SKSpriteNode(imageNamed: "AppIcon") //<--var I want to use
}
}
}
}
This way your blackdot var is only gettable and NOT settable. If you want to add the possibility to set it you need to add a setter like this :
var blackdot:Type? { // Replace Type with SKSpriteNode type returned
get {
return SKSpriteNode(imageNamed: "AppIcon")
}
set(newImage) {
...
}
}