I've added an SKView as a subview in my Sprite Kit game to display the game nodes and content.
let skView = SKView()
class GameViewController: UIViewController, CLLocationManagerDelegate {
self.view.addSubview(skView)
The SKView is added as a subview instead of the default way suggested in Sprite Kit projects (the commented code below), because I also have an MKMapView that is displayed behind the mostly transparent SKView, and otherwise the MapView would obscure the game scene.
//let skView = self.view as! SKView
The Game Scene is then presented on the SKView:
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
skView.allowsTransparency = true
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.backgroundColor = .clearColor()
skView.presentScene(scene)
}
In the main Game Scene, I have a main worldnode, to which I add a bunch of character nodes in a separate function, which is called when the scene is presented (code below). This function also applies rotate and move actions to the character nodes. If I set up the SKView that presents the scene using the default Sprite Kit method (let skView = self.view as! SKView), this works perfectly, but if I add the presenting scene to the view controller as a subview (let skView = SKView() and self.view.addSubview(skView)), then the actions do not work properly - the character nodes twitch instead of rotating properly, and do not move at all.
Can anyone help me understand why the actions seem not to be functioning when the scene is presented on an SKView that is a subview of the view controller? (But function fine when the SKView is set as self.view as! SKView)? I've checked the nodes with hasActions() (they do), and everything seems to be in the proper view hierarchy, so I'm a bit stumped.
I'm running the latest iOS 9.0 beta, and Xcode 7.0 beta.
Thanks.
func moveCharacters() {
if characterDic.count > 0 {
for i in 1...characterDic.count {
if let characterKey = characterDic["P" + "\(i)"] {
let characterStatus = Int(characterKey["active"] as! NSNumber)
if characterStatus == 1 || characterStatus == 2 {
if worldNode.childNodeWithName("P" + "\(i)") != nil {
worldNode.childNodeWithName("P" + "\(i)")?.removeFromParent()
}
let characterXPos = CGFloat(characterDic["P" + "\(i)"]!["curX"] as! Double)
let characterYPos = CGFloat(characterDic["P" + "\(i)"]!["curY"] as! Double)
let characterTexture = SKTexture(imageNamed: "character1.png")
let character = SKSpriteNode(texture: characterTexture)
character.position = CGPoint(x: characterXPos, y: characterYPos)
character.xScale = 0.2
character.yScale = 0.2
worldNode.addChild(character)
/////////
let characterXDest = CGFloat(characterDic["P" + "\(i)"]!["destX"] as! Double)
let characterYDest = CGFloat(characterDic["P" + "\(i)"]!["destY"] as! Double)
let destLoc = CGPoint(x: characterXDest, y: characterYDest)
let distToGo = sqrt(pow((characterXDest - characterXPos),2) + pow((characterYDest - characterYPos),2))
let moveNode = SKAction.moveTo(destLoc, duration: Double(distToGo/50))
let angle = atan2(((destLoc.y - (character.position.y))), ((destLoc.x - (character.position.x))))
let Pi = CGFloat(M_PI)
let DegreesToRadians = Pi / 180
let rotate = SKAction.rotateToAngle(angle - 90 * DegreesToRadians, duration: 0)
character.runAction(rotate)
character.runAction(moveNode)
}
}
}
}
}
Related
So I writing a program in swift, and am having trouble understanding the proper communication between GameScene and GameViewController:
Basically I have 'savefiles' which are plists, when loaded using a function "self.loadFromPlist(fileName: )" are loaded by clearing the previous array of nodes, and drawings a set of SKShapeNodes based on attributes from the dictionary elements of the loaded Plist
I needed a file browser, which I've implemented from a Cocoapod (FileBrowser), that must be called in the "GameViewController"
My Problem is this:
When calling my 'loadFromPlist' function from inside 'viewDidLoad' like this (to initialize the project) - the function works and the gamescene is drawn:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
let gameScene = scene as! GameScene
gameScene.loadFromPlist(scoreFileName: gameScene.defaultScore)
gameScene.gameViewControllerDelegate = self
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
When I try to do same thing in my own function in the GameViewController (called view a GameviewDelete in GamScene), the code executes the line and I can follow it with the debugger into the 'loadFromPlist' function in the gamescene, it executes the for loop drawing shapes etc, but it just does nothing.
func callloadplist() {
var loadedfilename2:String!
let file = FileBrowser()
present(file, animated: true, completion: nil)
if let scene = GameScene(size: view.frame.size) as? GameScene {
var strippedfilename: String?
file.didSelectFile = { (file: FBFile) -> Void in
let loadedfilename = file.displayName
scene.filebrowserselectedfilename = loadedfilename
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let myskscene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
let gameScene = myskscene as! GameScene
let strippedfilename = String(loadedfilename.dropLast(6))
print(strippedfilename)
gameScene.loadFromPlist(scoreFileName: "output")
}
}
}
}
}
So the function call working inside of 'viewDidLoad()' and also as 'self.loadFromPlist(fileName: )' in GameScene.swift, but cannot be called from a custom method/ function I've made in GameViewControllers -- Any Insights?
Thanks
So file.didSelectFile is setting up a closure to respond to the event of the file being selected. This was then running the function called on a different instance of a 'GameScene' Object.
I was able to refine my question and answer it here:
Swift - SKSprite Kit - Change Gamescene Variables from FileBrowser Closure
I am trying to use a SKView in my login UI for some animations. The SKView is added as a subview to a LoginContainerView as well as the view containing the UITextFields and the sign in button. All of the subviews are positioned with autolayout and the FireScene attached to the SKView has it's scaleMode set to .resizeFill.
The issue I'm having is very clear to observe from this clip: https://streamable.com/5it17 .
Clip explanation: Whenever I double tap any of the UITextFields (to trigger the
UIMenuController / Paste button) the FireScene freezes for a brief
amount of time (yet it's very noticeable) and the FPS drops down to around 30 for 1-2 seconds.
The only node that the FireScene has added as its child node is a modified fireflies SKEmitterNode template. I'm fairly certain that this has nothing to do with the actual code (as it is very straightforward and concise) and is probably a UIKit mixed with SpriteKit bug.
Relevant code:
class FireScene: SKScene {
// MARK: - Properties
var fireSparkEmitter: SKEmitterNode? = nil
// MARK: - Inherited
override func didChangeSize(_ oldSize: CGSize) {
// position the emitter to scene's center
fireSparkEmitter?.position = CGPoint(x: size.width/2, y: size.height/2)
}
override func didMove(to view: SKView) {
if let emitter = fireSparkEmitter {
emitter.resetSimulation()
} else {
guard let emitter = SKEmitterNode(fileNamed: "FireSparks.sks") else { return }
addChild(emitter)
fireSparkEmitter = emitter
}
}
}
class LoginContainerView: UIView {
/// ...
let fireBackgroundScene: FireScene = {
let scene = FireScene(size: .zero)
scene.scaleMode = .resizeFill
return scene
}()
/// ...
// MARK: - Inherited
// called after view initialization
func setupSubviews() {
let skView = SKView(frame: .zero)
skView.translatesAutoresizingMaskIntoConstraints = false
addSubview(skView)
skView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
skView.topAnchor.constraint(equalTo: topAnchor).isActive = true
skView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
skView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
skView.showsFPS = true
skView.presentScene(fireBackgroundScene)
}
}
PS: I've tried to profile the app with Time Profiler and it seems that the bottleneck is the UITextField gesture handling by UIKit itself.
Your issue seems related to a different layout syncronization between SKView and the other UIKit objects.
SKView have a property called isAsynchronous (default = true) that indicates
whether the content is rendered asynchronously.
You can try to set:
skView.isAsynchronous = false
to syncronize your SKView with the core animation updates.
This could be not enough because you'll syncronize SKView with the Core Animations of your project but there are always the UIKit-style animations that could be out of sync.
If this property don't solve your issue you can change also the preferredFramesPerSecond property. Remember that when you try to mix two frameworks (sprite-kit and UIKit) you will inevitably incur to unpleasant hitches.
I'm making a game using spritekit. I'm using a joystick in my game which is declared in a separate SKNode class called 'joystick'.
In this class, I add a UIGesturerecongiser to the view from the class. In the constructor method, one of the parameters is a SKView which is passed from the game scene.
Constructor Method in Joystick class:
init(colour: UIColor, position: CGPoint, skView: SKView) {
//constructor method
self.colour = colour;
self.parentposition = position;
self.view = skView;
super.init();
//setup properties
//user interaction is needed to allow touches to be detected
self.isUserInteractionEnabled = true;
//setup
setup();
}
In the games scene, I initialise the class like this:
class GameScene: SKScene {
func setupJoyStick() {
let joystick1 = Joystick(colour: UIColor.red, position: CGPoint(x: screenwidth / 3, y: screenwidth / 10 * 1.5), skView: self.view!)
self.addChild(joystick1)
}
}
Error:
When I run my app, I get an error because the 'self.view' return nil and because it is forced to unwrap, it causes a fatal error.
Where View is defined:
if let scene = GKScene(fileNamed: "GameScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! GameScene? {
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
sceneNode.size = view.bounds.size
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
Additional Info:
I'm using:
Xcode 9.2
Swift 4
Sprite Kit
I'm testing it on:
Iphone 6s
Latest version of IOS (non-beta), latest public release.
Can someone please explain why this is happening and how to fix this problem.
Thanks in advance, any help would be appreciated.
Looks like you're trying to get view property of SKScene but it's nil. It's because you didn't presented SKScene.
Unfortunately I haven't worked with SpriteKit but you can find info about view property here and about SKScene here.
I have created a UI elements on main.storyboard which i require to be hidden until the game is over and the once the player tap the screen to dismiss. Main.storyboard is linked to GameViewController therefor all my IBOutlets and IBActions are in there and all my game code is in GameScene. How can i link the view controller to the scene for that the popup image and buttons only appear when it is game over. Would greatly appreciate some help, I have been stuck on this for quite some time now.
This seems to be quite a common problem people have with SpriteKit games so lets go through the difference between SpriteKit games and UIKit apps.
When you make a regular UIKit app, e.g. YouTube, Facebook, you would use ViewControllers, CollectionViews, Views etc for each screen/menu that you see (Home screen, Channel screen, Subscription channel screen etc). So you would use UIKit APIs for this such as UIButtons, UIImageViews, UILabels, UIViews, UICollectionViews etc. To do this visually we would use storyboards.
In SpriteKit games on the other hand it works differently. You work with SKScenes for each screen that you see (MenuScene, SettingsScene, GameScene, GameOverScene etc) and only have 1 ViewController (GameViewController). That GameViewController, which has a SKView in it, will present all your SKScenes.
So we should add our UI directly in the relevant SKScenes using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes, SKNodes etc. To do this visually we would use the SpriteKit scene level editor and not storyboards.
So the general logic would be to load your 1st SKScene as usual from the GameViewController and than do the rest from within the relevant SKScenes. Your GameViewController should basically have next to no code in it beyond the default code. You can also transition from 1 scene to another scene very easily (GameScene -> GameOverScene).
If you use GameViewController for your UI it will get messy really quickly if you have multiple SKScenes because UI will be added to GameViewController and therefore all SKScenes. So you would have to remove/show UI when you transition between scenes and it would be madness.
To add a label in SpriteKit it would be something like this
class GameScene: SKScene {
lazy var scoreLabel: SKLabelNode = {
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.text = "SomeText"
label.fontSize = 22
label.fontColor = .yellow
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return label
}()
override func didMove(to view: SKView) {
addChild(scoreLabel)
}
}
To make buttons you essentially create a SKSpriteNode and give it a name and then look for it in touchesBegan or touchesEnded and run an SKAction on it for animation and some code after.
enum ButtonName: String {
case play
case share
}
class GameScene: SKScene {
lazy var shareButton: SKSpriteNode = {
let button = SKSpriteNode(imageNamed: "ShareButton")
button.name = ButtonName.share.rawValue
button.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return button
}()
override func didMove(to view: SKView) {
addChild(shareButton)
}
/// Touches began
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
if let nodeName = node.name {
switch nodeName {
case ButtonName.play.rawValue:
// run some SKAction animation and some code
case ButtonName.share.rawValue:
let action1 = SKAction.scale(to: 0.9, duration: 0.2)
let action2 = SKAction.scale(to: 1, duration: 0.2)
let action3 = SKAction.run { [weak self] in
self?.openShareMenu(value: "\(self!.score)", image: nil) // image is nil in this example, if you use a image just create a UIImage and pass it into the method
}
let sequence = SKAction.sequence([action1, action2, action3])
node.run(sequence)
default:
break
}
}
}
}
}
To make this even easier I would create a button helper class, for a simple example have a look at this
https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/
You can also check out Apple's sample game DemoBots for a more feature rich example.
This way you can have things such as animations etc in the helper class and don't have to repeat code for each button.
For sharing, I would actually use UIActivityController instead of those older Social APIs which might become deprecated soon. This also allows you to share to multiple services via 1 UI and you will also only need 1 share button in your app. It could be a simple function like this in the SKScene you are calling it from.
func openShareMenu(value: String, image: UIImage?) {
guard let view = view else { return }
// Activity items
var activityItems = [AnyObject]()
// Text
let text = "Can you beat my score " + value
activityItems.append(text as AnyObject)
// Add image if valid
if let image = image {
activityItems.append(image)
}
// Activity controller
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
// iPad settings
if Device.isPad {
activityController.popoverPresentationController?.sourceView = view
activityController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
activityController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
}
// Excluded activity types
activityController.excludedActivityTypes = [
UIActivityType.airDrop,
UIActivityType.print,
UIActivityType.assignToContact,
UIActivityType.addToReadingList,
]
// Present
view.window?.rootViewController?.present(activityController, animated: true)
}
and then call it like so when the correct button was pressed (see above example)
openShareMenu(value: "\(self.score)", image: SOMEUIIMAGE)
Hope this helps
create reference of GameViewController in GameScene class like this way
class GameScene: SKScene, SKPhysicsContactDelegate {
var referenceOfGameViewController : GameViewController!
}
in GameViewController pass the reference like this way
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.referenceOfGameViewController = self
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
by using this line you can pass reference to GameScene class
scene.referenceOfGameViewController = self
Now In GameScene class you can access all the variable of GameViewController like this way
referenceOfGameViewController.fbButton.hidden = false
referenceOfGameViewController.gameOverPopUP.hidden = false
This question already has an answer here:
Applying simple Physics to a .scn object in SceneKit XCODE SWIFT
(1 answer)
Closed 6 years ago.
Hey I have a scene kit scene and my goal is just to get the guy/character on the scene to fall do to gravity and then hit the floor. Thats it. the "Guy" is simple a node .scn node. and the ground is a also a .scn scene as shown in the code below. Ive gotten as far as the skills I have to try and add simple physics to the scene. I know how to add physics perfectly in SPRITEKIT but the .scn nodes don't let me attach the same variable that would use give the character physics. There are no errors in this code But the character is not affect by gravity when ran Thanks
Code:
// Collision bit masks
let BitmaskCollision = 1 << 2
let BitmaskCollectable = 1 << 3
let BitmaskEnemy = 1 << 4
let BitmaskFriend = 1 << 5
let BitmaskOutofBounds = 1 << 6
class GameViewController: UIViewController, ADBannerViewDelegate, SKPhysicsContactDelegate, SKSceneDelegate, SCNSceneRendererDelegate, SCNPhysicsContactDelegate{
// The character
let character = Character()
// The Playing Fields
let FieldScene = SCNScene(named: "art.scnassets/TesingCampusField.dae")!
override func viewDidLoad() {
super.viewDidLoad()
let scnView = self.view as! SCNView
scnView.scene = FieldScene
scnView.playing = true
scnView.delegate = self
scnView.scene!.physicsWorld.contactDelegate = self
//-----Adding Gravity----------------------------------------------
scnView.scene!.physicsWorld.gravity = SCNVector3(x: 0, y: 0, z: -9.8)
//------Add-the-Character-to-Scene--------------------
scnView.scene!.rootNode.addChildNode(character.node)
}
End Of code for this Controller
The Character is simply being set up from a file called "Character"
class Character {
let node = SCNNode()
init() {
let GuyScene = SCNScene(named: "art.scnassets/Guy.scn")
let characterTopLevelNode: SCNNode = GuyScene!.rootNode.childNodeWithName("Guy", recursively: true)!
node.addChildNode(characterTopLevelNode)
characterTopLevelNode.physicsBody?.contactTestBitMask = BitmaskEnemy
characterTopLevelNode.physicsBody?.collisionBitMask = BitmaskCollision
characterTopLevelNode.physicsBody?.categoryBitMask = BitmaskCollectable
characterTopLevelNode.physicsBody?.affectedByGravity = true
characterTopLevelNode.physicsBody?.friction = 0
characterTopLevelNode.physicsBody?.restitution = 0
characterTopLevelNode.physicsBody?.angularDamping = 0
// Below is the Creation of the Physics body of the character
let (min, max) = node.boundingBox
let collisionCapsuleRadius = CGFloat(max.x - min.x) * 0.4
let collisionCapsuleHeight = CGFloat(max.y - min.y)
let characterCollisionNode = SCNNode()
characterCollisionNode.name = "collider"
// position light above the floor so you dont hit it and cause a contact
characterCollisionNode.position = SCNVector3(0.0, collisionCapsuleHeight * 0.51, 0.0)
characterCollisionNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius, height: collisionCapsuleHeight), options:nil))
// Adding the Pysics body of the character called "characterCollisionNode" to the actually character
node.addChildNode(characterCollisionNode)
}
}
This seems to be an earlier version of this question Applying simple Physics to a .scn object in SceneKit XCODE SWIFT
And the answer is probably similar, you don't appear to be adding the node as a child of the scene.