I need the buttons to be disabled on top of Scene objects. How can i achieve that? The current code i am working is working fine but how can i get a specific child node to be transparent,
extension SCNMaterial {
convenience init(color: UIColor) {
self.init()
diffuse.contents = color
}
convenience init(image: UIImage) {
self.init()
diffuse.contents = image
}
}
let clearMaterial = SCNMaterial(color: .clear)
boxNode.materials = [clearMaterial]
Did you not get any error? SCNGeometry not SCNNode have material. try:
boxNode.geometry?.materials = [clearMaterial]
I tried this but it did not work. Maybe SCNMaterial cant use .clear
I have always used .transparency to hide/unhide node. try this:
func show(){
yourNode.geometry?.firstMaterial?.transparency = 1
}
func hide(){
yourNode.geometry?.firstMaterial?.transparency = 0
}
Related
I created a UIView, called CircleView, that draws a circle. This was added with a click of a button and located with a UITapGestureRecognizer in another view, canvasView, as a subview.
This is the code for circle and it works:
#IBAction func tapCircle(_ sender: UITapGestureRecognizer) {
let tapPoint2 = sender.location(in: canvasView)
let shapeCircle = CircleView(origin: tapPoint2)
canvasView.addSubview(shapeCircle)
shapeCircle.tag = 300
}
#IBAction func circleDraw(_ sender: UIButton) {
canvasView.isUserInteractionEnabled = true
tapCircle.isEnabled = true
tapRect.isEnabled = false
tapGRQ.isEnabled = false
canvasView.setNeedsDisplay()
}
It's all okay but i'm wondering if it is possible to change the color of this shape with a click of a button. Thanks a lot.
Create global variables for arrays of shapes
var circleShapes = [UIView]()
var squareShapes = [UIView]()
...
then append new UIView to certain array after you create it
let shapeCircle = CircleView(origin: tapPoint2)
canvasView.addSubview(shapeCircle)
circleShapes.append(shapeCircle)
...
now just change backgroundColor for each view inside certain array
circleShapes.forEach { $0.backgroundColor = /* UIColor */ }
...
Or if your canvasView contains just shapes subviews, every shape has its own UIView subclass and you want to change colors in the same time, you can change backgroundColor depending on type of shape
canvasView.subviews.forEach {
switch $0 {
case is CircleView:
$0.backgroundColor = /* UIColor */
case is SquareView:
$0.backgroundColor = /* UIColor */
...
default:
continue
}
}
Ok, I need to subclass SCNNode because I have different SCNNodes with different "abilities" in my game (I know people don't usually subclass SCNNode but I need to)
I have followed every other question like Subclassing SCNNode and Creating a subclass of a SCNNode
but continue to get this error:
fatal error: use of unimplemented initializer 'init()' for class 'LittleDude.Dude'
Where Dude is the name of my SCNNode subclass.
Following the second question, because of classing issues this is how I attempt to get the SCNNode from my .dae scene and assign it to my Dude():
var theDude = Dude(geometry: SCNSphere(radius: 0.1)) //random geometry first
var modelScene = SCNScene(named: "art.scnassets/ryderFinal3.dae")!
if let d = modelScene.rootNode.childNodes.first
{
theDude.transform = d.transform
theDude.geometry = d.geometry
theDude.rotation = d.rotation
theDude.position = d.position
theDude.boundingBox = d.boundingBox
theDude.geometry?.firstMaterial = d.geometry?.firstMaterial
}
print("DUDE: ", theDude)
Then in my Dude class:
class Dude: SCNNode {
init(geometry: SCNGeometry) {
super.init()
center(node: self)
self.scale = SCNVector3(x: modifier, y: modifier, z: modifier)
//theDude.transform = SCNMatrix4Mult(theDude.transform, SCNMatrix4MakeRotation(360, 0, 1, 0))
//theDude.worldOrientation = .
//self.theDude.position = SCNVector3Make(0, 0, -1)
for s in animScenes {
if let anim = animationFromSceneNamed(path: s)
{
animations.append(anim)
anim.usesSceneTimeBase = true
anim.repeatCount = Float.infinity
self.addAnimation(anim, forKey: anim.description)
}
}
}
}
/* Xcode required this */
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented222")
}
The error gets drawn on first line of this custom class and happens when I try to clone and add the custom SCNNode to my scene:
func makeDude(hitPosition: SCNVector3) {
//print("DUDE")
let clone = theDude.clone() as? SCNNode
clone?.position = hitPosition
self.sceneView.scene.rootNode.addChildNode(clone!)
}
Even though I cast to SCNNode to try to avoid an error. How can I just clone and use my custom SCNNode in my scene? What is wrong here?
Just to make clear, this answer is hidden on the comments of a previous answer, so to avoid the confusion here is the answer fully spelled out:
class NodeSubClass: SCNNode {
init(geometry: SCNGeometry?){
super.init()
self.geometry = geometry
}
...
}
If you subclass SCNNode and override its initializer init(geometry: SCNGeometry?) then you'll need to call the same initalizer of super during your init. Try changing
super.init()
to
super.init(geometry: geometry)
I am trying to develop a game which need to have a common background across all the scenes using SpriteKit and Swift. Since the background is common and its actions need to be continuous, I created a custom singleton subclass of SKSpriteNode like this:
class BackgroundNode: SKSpriteNode {
static let sharedBackground = BackgroundNode()
private init()
{
let texture = SKTexture(imageNamed: "Background")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
addActors()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func addActors() {
addClouds()
}
private func addClouds() {
addCloud1()
}
private func addCloud1() {
let cloud1 = SKSpriteNode(imageNamed: "Cloud1")
cloud1.size = CGSizeMake(0.41953125*self.size.width, 0.225*self.size.height)
cloud1.position = CGPointMake(-self.size.width/2, 0.7*self.size.height)
self.addChild(cloud1)
let moveAction = SKAction.moveTo(CGPointMake(self.size.width/2, 0.7*self.size.height), duration: 20)
cloud1.runAction(SKAction.repeatActionForever(moveAction))
}
}
And from the GameScene class I am adding this node to the current view like this:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
BackgroundNode.sharedBackground.size = CGSizeMake(self.size.width, self.size.height)
BackgroundNode.sharedBackground.position = CGPointMake(self.size.width/2, self.size.height/2)
addChild(BackgroundNode.sharedBackground)
}
}
The background image is showing up correctly, but the cloud is not getting added. As of the code above The cloud should appear out of the screen and animate into and then again out of the screen through the other edge, but to verify if it is getting added, I even tried adding the cloud to the center of the screen without any animations. Still the cloud didn't show up. What can be the issue here? And how to fix it?
EDIT
I figured out that the child is actually getting added but is getting added and moving through some points far above the screen. I also figured out that it MIGHT have something to do with anchor point of the cloud, but whatever value I set as anchor point, the cloud always remains on the top right corner of the screen. What can I do about the anchor points to make the clouds appear as it should(considering the lower left corner as (0, 0) is what I want)
Solved the issue. The problem was that I had to manually set the anchor points of the Scene and the node. Setting the anchor point of both the Scene and the node to (0, 0) solved the issue. The new code looks as follows:
GameScene
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0, 0) //Added this
BackgroundNode.sharedBackground.size = CGSizeMake(self.size.width, self.size.height)
BackgroundNode.sharedBackground.position = CGPointMake(0, 0)
addChild(BackgroundNode.sharedBackground)
}
BackgroundNode
private init()
{
let texture = SKTexture(imageNamed: "Background")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
anchorPoint = CGPointMake(0, 0) //Added this
addActors()
}
I'm making an a game in XCode 7 using Swift 2. I have a variable that I want to pass from the start screen (which is an UIViewController) to the game scene (which is an SKScene). I want the player to select a character in a UIView and play with it in the SKScene. I also want the score that's in the SKScene to show in the game-over screen that's an UIView. I've seen tutorials for passing data between two UIViewControllers and between two SKScenes, but none of them work for this case.
How can I pass a variable from an UIViewController to a SKScene (and vice versa)?
I ran over this same problem yesterday.
Swift 5.2, xcode 12 and targeting iOS 14. Searched high and low. Eventually found the userData attribute of the SKScene.
Note: The app is based on SwiftUI and the SKScene is inside a UIViewRepresentable:
struct SpriteKitContainer: UIViewRepresentable {
typealias UIViewType = SKView
var skScene: SKScene!
#Binding var timerData : ModelProgressTimer
Not that I am passing a binding into the View - this contains a number of data items I wanted to make available to the scene.
I placed code in two places to populate skScene.userData:
func makeUIView(context: Context) -> SKView {
self.skScene.scaleMode = .resizeFill
self.skScene.backgroundColor = .green
debugPrint("setting user data")
self.skScene.userData = [:]
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["percent"] = timerData.percentComplete
let view = SKView(frame: .zero)
view.preferredFramesPerSecond = 60
// view.showsFPS = true
// view.showsNodeCount = true
view.backgroundColor = .clear
view.allowsTransparency = true
return view
}
func updateUIView(_ view: SKView, context: Context) {
self.skScene.userData = [:]
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["percent"] = timerData.percentComplete
view.presentScene(context.coordinator.scene)
}
Then, inside the skScene object, I am able to retrieve the userData values:
var item: SKShapeNode! = nil
override func sceneDidLoad() {
let scaleUp = SKAction.scale(by: 2.0, duration: 1.0)
let scaleDown = SKAction.scale(by: 0.5, duration: 1.0)
scaleUp.timingMode = .easeInEaseOut
scaleDown.timingMode = .easeInEaseOut
let sequence = SKAction.sequence([scaleUp,scaleDown])
actionRepeat = SKAction.repeatForever(sequence)
item = SKShapeNode(circleOfRadius: radius)
addChild(item)
}
override func didChangeSize(_ oldSize: CGSize) {
item.fillColor = self.userData!["color"] as! UIColor
item.strokeColor = .clear
let meRunning = self.userData!["running"] as! Bool
if meRunning {
item.run(actionRepeat)
} else {
item.removeAllActions()
}
}
This draws a circle that "pulses" if the "running" boolean is set in the userdata (a Bool value).
If you haven't already found the answer, I did find a way to do this. I was doing something very similar.
In my case I have a UIView that gets called as a pause menu from my scene. I have made the class and variable names a little more ambiguous so they can apply to your situation as well.
class MyView: UIView {
var scene: MyScene!
var button: UIButton!
init(frame: CGRect, scene: MyScene){
super.init(frame: frame)
self.scene = scene
//initialize the button
button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "buttonTapped"))
}
func buttonTapped(){
self.removeFromSuperview() // this removes my menu from the scene
scene.doTheThing() // this calls the method on my scene
}
Here are the relevant parts of my scene.
class MyScene: SKScene {
func doTheThing(){
//this is a function on the scene. You can pass any variable you want through the function.
}
}
In your situation, it sounds like the first screen is a UIView and the second screen is the SKScene.
You may want to make your SKScene first, pause it, and then add the UIView in front of the Scene. Once the character is selected, you can remove the UIView and add your character into the scene.
I hope that this answers your question. If it doesn't let me know.
From the level select scene:
let scene = GameScene(fileNamed:"GameScene")
scene?.userData = [:]
scene?.userData!["level"] = 21
self.view?.presentScene(scene!)
From within the game scene:
let level = self.userData!["level"] as! Int
I have a drawing app that uses CALayer sublayers for the actual drawing of an image. I currently have an IBAction that contains code to remove the sublayers from the superlayer. However everytime I run it, I get a BAD ACCESS error that crashes my app. I'm wondering why it won't allow me to remove the sublayers. Also, in theory, this approach would remove all of the layers entirely. I would ideally want it to only undo the last layers that were drawn. Any suggestions on what I should do? Thanks.
var locale: CALayer {
return layerView.layer
}
#IBAction func undoButton(sender: AnyObject) {
var sublayers = self.view.layer.sublayers
for layer in sublayers {
layer.removeFromSuperlayer()
}
}
func setUpLayer() -> CALayer {
locale.contents = image
locale.contentsGravity = kCAGravityCenter
return locale
}
func subLayerDisplay() {
var newLayer = CALayer()
var tempRect = CGRectMake(prevLocation.x, prevLocation.y, 50, 50)
newLayer.frame = tempRect
newLayer.contents = image
self.view.layer.insertSublayer(newLayer, below: locale)
}
To sum up, you can add your layer to a view, and then deal with the view, which will make life much more easier.
When you add the button, you can do
view.tag = 1
self.addSubView(view)
When you want to remove it
let view = self.viewWithTag(1) as! UIView
view.removeFromSuperView()