I am porting a showroom application from as3/starling to a native swift iPad app.
I have 2 questions:
How can I fade a video over my spritekit content (from alpha 0 to 1).
How to control the iPad volume with an individual UI element without showing the iOS onscreen-volume notification graphic.
My first answer answers your initial question, and this answer is tailored to the subsequent questions that were posted in the comments. I feel that both answers are useful to the community, instead of combining into just one which I think would be confusing.
Here I implement a volume slider and a scrubbing slider that controls the VideoNode's AVPlayer object.. you get the full power of the AVPlayer but with the convenience of the SKVideoNode. Also, the volume for playing video is controlled without having to adjust the system volume and subsequent gray sound box pop-up:
import SpriteKit
import MediaPlayer
class GameScene: SKScene {
let seekSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
let volSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
// Because it takes time for us to load the video, we don't know it's duration,
// hence we don't know what the `.maximumValue` should be for the seekSlider
let defaultMax = Float(0.123456789)
var player: AVPlayer!
var videoNode: SKVideoNode!
func seekSliderChangedValue(sender: UISlider) {
// Wait for file to finish loading so we can get accurate end duration:
if sender.maximumValue == defaultMax {
if CMTimeGetSeconds(player.currentItem!.duration).isNaN { return }
else { sender.maximumValue = Float(CMTimeGetSeconds(player.currentItem!.duration)) }
}
let value = CMTimeMake(Int64(seekSlider.value), 1)
player.seek(to: value)
player.play()
}
func volSliderChangedValue(sender: UISlider) {
player.volume = sender.value
}
override func didMove(to view: SKView) {
removeAllChildren() // Delete this in your actual project.
// Add some labels:
let seekLabel = SKLabelNode(text: "Seek")
seekLabel.setScale(3)
seekLabel.verticalAlignmentMode = .center
seekLabel.position = CGPoint(x: frame.minX + seekLabel.frame.width/2,
y: frame.maxY - seekLabel.frame.height)
let volLabel = SKLabelNode(text: "Volume")
volLabel.setScale(3)
volLabel.verticalAlignmentMode = .center
volLabel.position = CGPoint(x: frame.minX + volLabel.frame.width/2,
y: frame.minY + volLabel.frame.height + volSlider.frame.height)
// Make player and node:
let url = Bundle.main.url(forResource: "sample", withExtension: "mp4")!
player = AVPlayer(url: url)
videoNode = SKVideoNode(avPlayer: player!)
//Configure seek slider:
seekSlider.addTarget(self, action: #selector(seekSliderChangedValue), for: UIControlEvents.valueChanged)
seekSlider.maximumValue = defaultMax
let seekOrigin = convertPoint(toView: CGPoint(x: seekLabel.frame.minX, y: seekLabel.frame.minY))
seekSlider.frame = CGRect(origin: seekOrigin, size: CGSize(width: 200, height: 15))
//Configure vol slider:
volSlider.addTarget(self, action: #selector(volSliderChangedValue), for: UIControlEvents.valueChanged)
volSlider.value = 1
let volOrigin = convertPoint(toView: CGPoint(x: volLabel.frame.minX, y: volLabel.frame.minY))
volSlider.frame = CGRect(origin: volOrigin, size: CGSize(width: 200, height: 15))
//Scene stuff:
view.addSubview(seekSlider)
view.addSubview(volSlider)
addChild(seekLabel)
addChild(volLabel)
addChild(videoNode)
// Start video and animation:
videoNode.alpha = 0
videoNode.play()
videoNode.run(.fadeIn(withDuration: 5))
}
}
Related
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)
}
I'm trying to add a function that will show the close button in my custom alert view.
When I call it for the first time, only an empty area appears that does not contain a button (no color, no text and no pressing). When you call again - everything works as it should.
It does not depend on the parameter animated, in both cases it works the same.
My dialogView is a my custom UIView. I don't use UIAlertController
What could be the problem?
initial view after the first attempt second attempt
GIF call func by click
func showCloseButton(text: String, animated: Bool){
let dialogViewHeight = self.dialogView.frame.height + 50 + 1
let button = UIButton(type: .roundedRect)
button.backgroundColor = .red
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight)
button.frame.size = CGSize(width: self.dialogView.frame.width, height: 50)
button.setTitle(text, for: .normal)
button.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
let separatorLineView = UIView()
separatorLineView.frame.origin = CGPoint(x: 0, y: self.dialogView.frame.height)
separatorLineView.frame.size = CGSize(width: dialogView.frame.width, height: 1)
separatorLineView.backgroundColor = UIColor.groupTableViewBackground
dialogView.addSubview(separatorLineView)
if animated {
animateAdjustDialogView(height: dialogViewHeight){
Logger.Log("completion did")
self.dialogView.addSubview(button)
}
} else {
Logger.Log("button.frame.origin = \(button.frame.origin)")
Logger.Log("button.frame.size = \(button.frame.size)")
Logger.Log("button.title = \(button.titleLabel)")
self.dialogView.frame.size = CGSize(width: self.dialogView.frame.width, height: dialogViewHeight)
self.dialogView.addSubview(button)
}
}
private func animateAdjustDialogView(height: CGFloat, completion: (()->Void)?){
UIView.animate(withDuration: 1.0, animations: {
self.dialogView.frame.size = CGSize(width: self.dialogView.frame.width, height: height)
self.layoutIfNeeded()
}) { (finished) in
Logger.Log("finished = \(finished)")
if finished {
Logger.Log("completion run")
completion?()
}
}
}
Button frame is causing the issue -
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight)
Where
let dialogViewHeight = self.dialogView.frame.height + 50 + 1
That means, button goes beyond dialogView frame.
So replace
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight)
With
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight - 50) // Height of the Button.
Let me know if you are still having any issue.
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
When the user enters the view controller which contains the image below, i would like the blue color to be animated based on the value which in this case is 70. This means, the color starts loading up from 0px in the view and stops at the amount of whatever the value is. Also, i would like the number to be counting from 0 to the end value at the same time the color is loading. Are there any tips/ links or some direction you could point me into in order to achieve this? Thanks in advance!
Create progressView with the desired backgroundColor
let progressView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 50))
progressView.backgroundColor = .blue
self.view.addSubview(progressView)
Call the following function to animate its width proportional to progress
func setProgress(_ progress: CGFloat) {
let fullWidth: CGFloat = 200
let newWidth = progress/100*fullWidth
UIView.animate(withDuration: 1.5) {
self.progressView.frame.size = CGSize(width: newWidth, height: self.progressView.frame.height)
}
}
Create a progressLabel and a currentProgress variable
let progressLabel = UILabel(frame: CGRect(x: 0, y: 100, width: 50, height: 50))
var currentProgress: CGFloat = 0
label.text = String(currentProgress)
self.view.addSubview(progressLabel)
Call the following function to update its progress in a loop with delay, example: setLabelProgress(initialValue: self.currentProgress, targetValue: 70)
func setLabelProgress(initialValue: CGFloat, targetValue: CGFloat) {
guard currentProgress != targetValue else { return }
let range = targetValue - initialValue
let increment = range/CGFloat(abs(range))
let duration: TimeInterval = 1.5
let delay = duration/TimeInterval(range)
currentProgress += increment
progressLabel.text = String(describing: currentProgress)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay) {
self.setLabelProgress(initialValue: initialValue, targetValue: targetValue)
}
}
For the second label call the same function but swapping initialValue and targetValue
hello thereI'm working in my Game but I face a problem when I'm trying to change the scene. I sit up a button and it's work but when the transfer happened I can't see my Background this is my code
This is the code for my Game scene. the back ground ok
import SpriteKit
class GameScene: SKScene {
// Next scene button
var nextButton: UIButton!
let background = SKSpriteNode(imageNamed: "BG")
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
// Background Image
background.position = CGPoint(x: size.width / 2, y: size.height / 2)
background.size = CGSize(width: 2100, height: 1200)
background.zPosition = -3
addChild(background)
// Next button Action
nextButton = UIButton(frame: CGRect(x: 0, y: 0, width: frame.size.width / 2, height:300))
nextButton.center = CGPoint(x: view.frame.size.width / 7.3, y: view.frame.size.width / 3)
nextButton.setTitle("Next Button", forState: UIControlState.Normal)
nextButton.setTitleColor(UIColor.redColor(), forState: UIControlState.Normal)
nextButton.addTarget(self, action: Selector("Next"), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(nextButton)
}
func Next() {
self.view?.presentScene(Play(), transition: SKTransition.crossFadeWithDuration(1.0))
nextButton.removeFromSuperview()
background.removeFromParent()
}
}
this is the code for my Play scene. the back ground is not appearing i don't know why
import Foundation
import SpriteKit
class Play: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
// Background Image
let background = SKSpriteNode(imageNamed: "BG")
background.position = CGPoint(x: size.width / 2, y: size.height / 2)
//background.size = CGSize(width: 2100, height: 1200)
background.zPosition = -3
addChild(background)
}
}
Are you checking that the play scene is actually being transitioned too?.
You should also init the new scene with a size if you dont use .sks files.
func Next() {
nextButton.removeFromSuperview()
let scene = Play(size: self.size) // get size of current scene
self.view?.presentScene(scene, transition: SKTransition.crossFadeWithDuration(1.0))
}
If you do use .sks files (xCode visual editor) than you would need to create a new .sks file called Play and load the scene like so
func Next() {
if let scene = Play(fileNamed: "Play") {
nextButton.removeFromSuperview()
self.view?.presentScene(scene, transition: SKTransition.crossFadeWithDuration(1.0))
}
}
As a tip you dont have to remove SKSpriteNodes after changing scenes. You are already adding them to the scene (addChild) and they will get removed for you automatically.
You still have to remove UIKit stuff tho, such as your UIButton, because they get added to your view and not the SKScene.