How to call a UITextField inside of SpriteKit? - ios

As the question states, I am attempting to call on a UITextField inside of SpriteKit to accept a user input for a name. I've looked at many posts on here but none of them seem to apply to the issue I am facing. The first thing I am doing is declaring the UITextField as a class variable, so I can remove it from view in a separate function later by doing the following:
let nameEntry = UITextField(frame: CGRect(origin: CGPoint(x: 800, y: 875), size: CGSize(width: 600, height: 200)))
then I go on to add the UITextField by stating:
self.view?.addSubview(nameEntry)
However, the text box does not show up in my scene. I've looked at it in hierarchy view and it is simply not there. I am fairly inexperienced at using SceneKit so I'm curious to see what I'm doing wrong. Thanks in advance for anyone who attempts to help!
for a better look at how I am going about this:
class Tutorial : SKScene{
let nameEntry = UITextField(frame: CGRect(origin: CGPoint(x: 800, y: 875), size: CGSize(width: 600, height: 200)))
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//insert many lines of code here
self.view?.addSubview(nameEntry)
}
}
EDIT: Here's all of the code leading up to this point.
class Tutorial : SKScene{
var chatBoxInt : Int = 0
let chatBoxLabel : SKLabelNode = SKLabelNode(text: "")
let arrow = SKSpriteNode(imageNamed: "Arrow")
let HappinessIcon = SKSpriteNode(imageNamed: "Happiness Icon")
let IntelligenceIcon = SKSpriteNode(imageNamed: "IntelligenceIcon")
let HealthIcon = SKSpriteNode(imageNamed: "HealthIcon")
let chatBox = SKSpriteNode(imageNamed: "ChatBox")
let maleButtonBackground = SKSpriteNode(imageNamed: "ButtonBackground")
let femaleButtonBackground = SKSpriteNode(imageNamed: "ButtonBackground")
let selectedGenderButtonBackground = SKSpriteNode(imageNamed: "ButtonBackgroundSelected")
let femaleLabel = SKLabelNode(text: "Female")
let maleLabel = SKLabelNode(text: "Male")
let genderLabel = SKLabelNode(text: "Gender:")
let nameLabel = SKLabelNode(text: "Name:")
let nameEntry = UITextField(frame: CGRect(origin: CGPoint(x: 800, y: 875), size: CGSize(width: 600, height: 200)))
enum genders {
case Male
case Female
}
var genderSelected : genders = .Male
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "Background")
background.size = CGSize(width: self.size.width, height: self.size.height)
background.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
background.zPosition = 0
scene?.addChild(background)
Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(beginTutorial), userInfo: nil, repeats: false)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch : AnyObject in touches{
let pointOfTouch = touch.location(in: self)
if(maleButtonBackground.contains(pointOfTouch) || femaleButtonBackground.contains(pointOfTouch)){
if(maleButtonBackground.contains(pointOfTouch)){
swapGenderButton(ImageToSwap: maleButtonBackground)
genderSelected = .Male
}
if(femaleButtonBackground.contains(pointOfTouch)){
swapGenderButton(ImageToSwap: femaleButtonBackground)
genderSelected = .Female
}
}else{
chatBoxInt += 1
let fadeInAnimation = SKAction.fadeIn(withDuration: 1)
let fadeOutAnimation = SKAction.fadeOut(withDuration: 1)
let removeAction = SKAction.removeFromParent()
let fadeOutSequence = SKAction.sequence([fadeOutAnimation, removeAction])
switch chatBoxInt {
case 1:
ChangeText(LabelText: "Best Life is the place for you to make the life you've always dreamed of!")
case 2:
ChangeText(LabelText: "In Best Life you can be who you want to be, do what you want to do... as long as you don't die.")
case 3:
ChangeText(LabelText: "The goal of Best Life is simple, make the best life possible for yourself before your time runs out.")
case 4:
ChangeText(LabelText: "Let's go over some of the basics of best life:")
case 5:
ChangeText(LabelText: "When you get into your new life, you'll have a HUD at the bottom of your screen at all times. This HUD will act as your guide as you go throughout Best Life. Let's get familiar with the icons and what they mean.")
case 6:
ChangeText(LabelText: "This is your happiness indicator, it will show you how much you are enjoying your life, try and keep this high at all times... too little happiness can result in your alter ego committing suicide.")
HappinessIcon.position = CGPoint(x: self.size.width/2, y: (self.size.height/2) - 100)
HappinessIcon.size = CGSize(width: 200, height: 200)
HappinessIcon.zPosition = 2
HappinessIcon.alpha = 0
self.addChild(HappinessIcon)
HappinessIcon.run(fadeInAnimation)
case 7:
HappinessIcon.run(fadeOutSequence)
ChangeText(LabelText: "This is your intelligence indicator... it shows... well.... intelligence... what else would it do...? Too little of this and you may not get good jobs; leading to lower income.")
IntelligenceIcon.position = CGPoint(x: self.size.width/2, y: (self.size.height/2) - 100)
IntelligenceIcon.size = CGSize(width: 200, height: 200)
IntelligenceIcon.zPosition = 2
IntelligenceIcon.alpha = 0
self.addChild(IntelligenceIcon)
IntelligenceIcon.run(fadeInAnimation)
case 8:
IntelligenceIcon.run(fadeOutSequence)
ChangeText(LabelText: "This is your health icon. It shows your current overall health level. If this gets too low it can contribute to life threatning illnesses which can ultimately lead to your demise.")
HealthIcon.position = CGPoint(x: self.size.width/2, y: (self.size.height/2) - 100)
HealthIcon.size = CGSize(width: 200, height: 200)
HealthIcon.zPosition = 2
HealthIcon.alpha = 0
self.addChild(HealthIcon)
HealthIcon.run(fadeInAnimation)
case 9:
HealthIcon.run(fadeOutSequence)
ChangeText(LabelText: "The last thing that you need to know is that one day in your world will pass one year in your alter ego's life. Make sure you are making the most out of every day to make the best life possible for your alter ego!")
case 10:
ChangeText(LabelText: "Alright, that pretty much sums it up. The world is yours for the taking, go seize it! Have fun in your new Best Life!")
case 11:
ChangeText(LabelText: "First, we'll need to we'll need to set up your new alter ego. Some of these options can only be accessed by purchasing them, but since this is your first go around I'll cover this one for you.")
chatBoxLabel.run(SKAction.moveTo(y: 1750, duration: 1))
let changeSize = SKAction.scale(to: CGSize(width: 800, height: 1600), duration: 1)
chatBox.run(changeSize)
arrow.run(fadeOutSequence)
genderLabel.position = CGPoint(x: 500, y: 1300)
genderLabel.fontColor = .black
genderLabel.zPosition = 2
genderLabel.fontSize = 45
genderLabel.alpha = 0
self.addChild(genderLabel)
genderLabel.run(fadeInAnimation)
maleButtonBackground.position = CGPoint(x: 575, y: 1175)
maleButtonBackground.zPosition = 2
maleButtonBackground.setScale(0.3)
maleButtonBackground.alpha = 0
self.addChild(maleButtonBackground)
maleButtonBackground.run(fadeInAnimation)
femaleButtonBackground.position = CGPoint(x: 955, y: 1175)
femaleButtonBackground.zPosition = 2
femaleButtonBackground.setScale(0.3)
femaleButtonBackground.alpha = 0
self.addChild(femaleButtonBackground)
femaleButtonBackground.run(fadeInAnimation)
maleLabel.fontSize = 45
maleLabel.fontColor = .white
maleLabel.zPosition = 4
maleLabel.alpha = 0
maleLabel.position = CGPoint(x: 575, y: 1162)
self.addChild(maleLabel)
maleLabel.run(fadeInAnimation)
femaleLabel.fontSize = 45
femaleLabel.fontColor = .white
femaleLabel.zPosition = 4
femaleLabel.alpha = 0
femaleLabel.position = CGPoint(x: 955, y: 1162)
self.addChild(femaleLabel)
femaleLabel.run(fadeInAnimation)
nameLabel.position = CGPoint(x: 500, y: 1000)
nameLabel.fontSize = 45
nameLabel.fontColor = .black
nameLabel.alpha = 0
nameLabel.zPosition = 3
self.addChild(nameLabel)
nameLabel.run(fadeInAnimation)
nameEntry.backgroundColor = .white
self.view?.addSubview(nameEntry)
default:
return
}
}
}
}

SpriteKit's SKSKScene exists inside of UIKit's SKView. ie your whole Spritekit scene is basically the backing layer for a single view in your app. If you want to do things in the UIKit layer it makes more sense to do them at the level of your view controller and delegate the responsibility back. So, for instance if you want a textField you can set it up in interface builder and make it hidden, then show it only when your scene requests it.
If you don't want to do it this way, you can still reach up into your view as you are doing and add subviews from your SKScene (its bad OOP to reach up into and alter your parent, but nothing in UIKit prevents you from doing it). Most likely the issue you are having is that the coordinate systems in SpriteKit and UIKit are different and they are actually upside down. So you cannot just use your SKScene coordinates for UIViews. You need to convert. SKView has a family of methods that will do the math for you: SKView.convert(:to:) SkView.convert(:from:). Note that that only gets you as far as your SKView. You then need to convert to your viewController.view coordinates using UIKit's convert methods.
I think its far less confusing if you leave the UIKit stuff in the UIKit layer and just have the viewController and scene pass the relevant information back and forth.
EDIT: here is an example
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
private lazy var textField: UITextField = {
let textField = UITextField()
textField.frame.size = CGSize(width: 100, height: 30)
textField.backgroundColor = .cyan
return textField
}()
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let view = view,
let point = touches.first?.location(in: self) else {
return
}
if textField.superview != nil {
textField.removeFromSuperview()
}
textField.center = view.convert(point, from: self)
view.addSubview(textField)
textField.becomeFirstResponder()
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView

Related

How to generate a random SKSpriteNode based on four possible values

To generate an image I have written the following function. However, I want that function to able to generate one of four possible images, instead of the same image every time. Right now it generates the "Top Side" image every time, but I want it to randomly choose one of the four images I have to generate.
func spawnBrick() {
let Brick = SKSpriteNode(imageNamed: "Top Side")
Brick.size = CGSize(width: 130, height: 100)
Brick.position = CGPoint(x: frame.midX, y: frame.maxY - Brick.size.width)
addChild(Brick)
}
enum brickType: UInt {
case brickTop = 1
case brickLeft = 2
case brickRight = 3
case brickBottom = 4
}
let brickTop = SKSpriteNode(imageNamed: "Top Side")
let brickLeft = SKSpriteNode(imageNamed: "Left Side")
let brickRight = SKSpriteNode(imageNamed: "Right Side")
let brickBottom = SKSpriteNode(imageNamed: "Bottom Side")
Swift array has a method called randomElement that will give you an optional element from the array (not its optional because the array could be empty)
https://developer.apple.com/documentation/swift/array/2994747-randomelement
func spawnBrick() {
let Brick = SKSpriteNode(imageNamed: ["Top Side","Left Side","Right Side","Bottom Side"].randomElement()!)
Brick.size = CGSize(width: 130, height: 100)
Brick.position = CGPoint(x: frame.midX, y: frame.maxY - Brick.size.width)
addChild(Brick)
}

SKSpriteNode fall off the screen

Can some body help me, I created a SpriteKit ​node called(player) and once I set its physics it fall off the screen​:
let player = SKSpriteNode(imageNamed: "car")
player.position.x = -300
player.zPosition = 1
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody.categoryBitMask = 1
addChild(player)
I already tried adding
affectedByGravity
but the same result for the player and the enemy.
If I got it, you want your car not to be effected by gravity.
If it is so you might want to setup the physics of your "world", like:
func setWorld() {
self.backgroundColor = UIColor.white
//This should do the trick!
physicsWorld.gravity = CGVector(dx:0, dy:0)
//This is useful if you want to create wall along the borders
let frame = CGRect(x: self.frame.minX, y: self.frame.minY, width: self.frame.width, height: self.frame.height)
//here you set the borders as walls
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
physicsBody?.friction = 0
physicsBody?.restitution = 1
physicsBody?.linearDamping = 0
}
and call this function in didMove

Trying to delay spawn nodes using waitForDuration in didMoveToView with SpriteKit Scene (SWIFT) but not working

I'm trying to stop my nodes from falling just for a second or two at the start of my game. So my problem is when I push start the nodes are already halfway down the screen. I also tried changing how high the nodes start but it seems like a costly solution since I want to be careful not to let my FPS get too low. In my code I am trying to do this in the didMoveToView and I am using waitForDuration but it doesn't work.
Example Image of Nodes Falling Down
Any SpriteKit masters know what I should do? I'm using Swift.
Here is my code:
override func didMoveToView(view: SKView) {
let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
self.spawnNumbers()
}
numContainer.runAction(SKAction.sequence([wait, run]))
}
func spawnNumbers() {
let minValue = self.size.width / 8
let maxValue = self.size.width - 36
let spawnPoint = CGFloat(arc4random_uniform(UInt32(maxValue - minValue)))
let action = SKAction.moveToY(-300, duration: 2)
numContainer = SKSpriteNode(imageNamed: "Circle")
numContainer.name = "Circle"
numContainer.size = CGSize(width: 72, height: 72)
numContainer.anchorPoint = CGPointMake(0, 0)
numContainer.position = CGPoint(x: spawnPoint, y: self.size.height)
numContainer.runAction(SKAction.repeatActionForever(action))
numContainer.zPosition = 2
let numberLabel = SKLabelNode(fontNamed: "AvenirNext-Bold")
numberLabel.text = "\(numToTouch)"
numberLabel.name = "Label"
numberLabel.zPosition = -1
numberLabel.position = CGPointMake(CGRectGetMidX(numContainer.centerRect) + 36, CGRectGetMidY(numContainer.centerRect) + 36)
numberLabel.horizontalAlignmentMode = .Center
numberLabel.verticalAlignmentMode = .Center
numberLabel.fontColor = UIColor.whiteColor()
numberLabel.fontSize = 28
addChild(numContainer)
numContainer.addChild(numberLabel)
numContainerArray.append(numContainer)
numToTouch += 1
}
I think you have to cast the 2.5 seconds to an NSTimeInterval:
let wait = SKAction.waitForDuration(NSTimeInterval(2.5))
You could also try putting the actions in the init function for your scene:
override init (size: CGSize) {
super.init(size: size)
//your code from before
}
Also, not sure if it matters, but this is what I normally do and it works,
let wait = SKAction.waitForDuration(NSTimeInterval(2.5))
let action = SKAction.runBlock({() in self.spawnNumbers()})
let actionThenWait = SKAction.sequence([wait, action])
self.runAction(actionThenWait)

UICollisions in Swift

I'm required to make a Breakout app in a Single-View Application in the Swift language. However, I'm having trouble getting the "ball" to respond to hitting the barrier. Additionally, I'm having trouble getting the barrier to disappear after it is hit. Does anyone have the solution to this, or does anyone have an example app I can look off of? This is in Single View Application, not Sprite.
var dynamicAnimatior = UIDynamicAnimator()
override func viewDidLoad() {
super.viewDidLoad()
dynamicAnimatior = UIDynamicAnimator(referenceView: view)
setupViews()
}
func setupViews() {
let blueSquare = UIView(frame: CGRectMake(100, 100, 50, 50))
blueSquare.backgroundColor = UIColor.blueColor()
view.addSubview(blueSquare)
let barrier = UIView(frame: CGRect(x: 0, y: 300, width: 130, height: 20))
barrier.backgroundColor = UIColor.redColor()
view.addSubview(barrier)
addDynamicBehaviors([blueSquare])
}
func addDynamicBehaviors(array: [UIView]) {
let dynamicItemBehavior = UIDynamicItemBehavior(items: array)
dynamicItemBehavior.density = 1.0
dynamicItemBehavior.friction = 0.0
dynamicItemBehavior.resistance = 0.0
dynamicItemBehavior.elasticity = 1.0
dynamicAnimatior.addBehavior(dynamicItemBehavior)
let pushBehavior = UIPushBehavior(items: array, mode: .Instantaneous)
pushBehavior.magnitude = 1.0
pushBehavior.pushDirection = CGVectorMake(0.5, 0.5)
dynamicAnimatior.addBehavior(pushBehavior)
let collisionBehavior = UICollisionBehavior(items: array)
collisionBehavior.translatesReferenceBoundsIntoBoundary = true
collisionBehavior.collisionMode = .Everything
collisionBehavior.collisionDelegate = self
dynamicAnimatior.addBehavior(collisionBehavior)
}
You need to have both the objects in your UICollisionBehavior if you want them to collide. Depending on what you need there is several possibilities. I suppose that you need your ball to bounce on barrier, so create the behavior:
let collisionBehavior = UICollisionBehavior(blueSquare: array)
and then add a rigid boundary that correspond to your barrier:
let edge = CGPointMake(barrier.frame.origin.x + barrier.frame.size.width,
barrier.frame.origin.y + barrier.frame.size.heigth);
collisionBehavior.addBoundaryWithIdentifier("barrier",
fromPoint:barrier.frame.origin,
toPoint:rightEdge];
Now if you want to capture the collision you need to add a delegate to collisionDelegate. This delegate should be able to respond to several methods when a hit occurs, (read documentation about UICollisionBehaviorDelegate.

How to interact with a SKSpriteNode after another one of the same var name has been created?

I appologise for the confusing question title, but I wasn't quite sure how to phrase my question. My ultimate goal is to remove an SKSpriteNode a certain amount of time after it's creation. However, there is a small complication. Using the following code
func remove() {
laser.removeFromParent()
}
runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.runBlock(remove)
])
)
I'm able to remove the SKSpriteNode I named 'laser'. When I call my function once, fireLasers(), everything runs smoothly and the laser disappears after 5 seconds. The problem is when I call it twice within a period of 5 seconds. When this happens, the first laser will stick around indefinitely and the second disappears earlier than intended. I understand why this happens, but would like to know if there's a way around it. Here's the code for the GameScene which creates the world, background image, player Sprite, and defines the fireLasers function. There's a lot of stuff that goes on in the ViewController that works with the velocities and directions of the sprites, but I hope this is sufficient to find a solution.
import SpriteKit
class GameScene: SKScene {
var player = SKSpriteNode()
var world = SKShapeNode()
var worldTexture = SKSpriteNode()
var laser = SKShapeNode()
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPointMake(0.5, 0.5)
self.size = CGSizeMake(view.bounds.size.width, view.bounds.size.height)
// Add world
world = SKShapeNode(rectOfSize: CGSizeMake(7000, 7000))
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path!)
world.fillColor = SKColor.blackColor()
self.addChild(world)
// Add Background Image
worldTexture = SKSpriteNode(imageNamed: "grid")
worldTexture.size = CGSize(width: 7000, height: 7000)
world.addChild(worldTexture)
// Add player
player = SKSpriteNode(imageNamed: "Spaceship")
player.size = CGSize(width: 50, height: 50)
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 50, height: 50))
player.physicsBody?.affectedByGravity = false
player.physicsBody?.dynamic = true
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x
world.position.y = -player.position.y
}
func fireLasers() {
laser = SKShapeNode(rectOfSize: CGSizeMake(10, 50))
laser.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 10, height: 50))
laser.position.x = player.position.x
laser.position.y = player.position.y + CGFloat(50)
laser.fillColor = SKColor.redColor()
laser.physicsBody?.dynamic = true
laser.physicsBody?.affectedByGravity = false
world.addChild(laser)
func remove() {
laser.removeFromParent()
}
runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.runBlock(remove)
])
)
}
}
Instead of keeping a reference to the laser (which will, as you noticed, force you to only have one around at a time), why not run an action on the created laser nodes themselves? This will allow you to use the handy SKAction class method removeFromParent():
let newLaser = makeNewLaser()
let laserAction = SKAction.sequence([SKAction.waitForDuration(5), SKAction.removeFromParent()])
newLaser.runAction(laserAction)

Resources