How do I get access to a variable's property to my class if it's in another class? - swift

Basically, I have an SKScene class called GameScene. In GameScene, I have a variable called playableArea that is calculated depending on the size of the user's phone.
I have an enemy class that needs to know the width of the playableArea, and then use that value to move to to the x position by an SKAction depending on the width of playableArea.
This is my class (simplified version):
class PacuPiranha: Fish {
static var s = SKSpriteNode(imageNamed: "PacuPiranha1")
var width: CGFloat = 0
struct SharedAssets {
static var move: SKAction!
static var token: dispatch_once_t = 0
}
init(position: CGPoint, width: CGFloat) {
self.width = width
}
override class func preloadAssets() -> SKTexture? {
dispatch_once(&SharedAssets.token, {
SharedAssets.move = SKAction.sequence([SKAction.moveToX(width + s.size.width, duration: 2.5), SKAction.removeFromParent()])
return SharedAssets.texture
}
}
}
The enemy fish needs to move to the other size of the screen, but the size of the screen is determined by the size of the device the user is using, so I need to pass the width of the screen to the moveToX action which occurs in the init call. But I get the error "Instance member 'width' cannot be used on type 'PacuPiranha'."
How do I get the width of a variable that is from the GameScene class, which is where the new object of the enemy is being created?
let fish = PacuPiranha(position: CGPoint(...), width: playableArea.size.width)
I have also tried making a new function in the enemy class like this:
func getWidth() -> CGFloat {
let view = scene as! GameScene
view.playableArea.size.width
}
And then using getWidth() inside the moveToX, but that didn't work either since it's asking for an argument of type self: PacuPiranha.

Retrieving the Screen Size
You can get the screen size with this code
let screenSize = UIScreen.mainScreen().bounds.size
My point of view
I think you are overcomplicating the code.
Wouldn't it be easier if PacuPiranha was a subclass of SKSpriteNode added to GameScene?
And finally, do you really need all that code to preload the resources? SpriteKit is pretty good at managing the resources, you should let it do its work unless your game specifically needs a custom approach.
Update
IF PacuPiranha was a SKSpriteNode subclass (as it should be) added to the the SKScene you would be able to use this code to get the size of the current view.
class PacuPiranha: SKSpriteNode {
func foo() {
if let viewSize = self.scene?.view?.frame {
print(viewSize)
}
}
}

UIScreen.mainScreen().bounds.size.width * UIScreen.mainScreen().scale Will give you the screens width in pixels.

Related

Use multiple classes to control a single SKScene [duplicate]

I'm trying to learn how to make a GameManager type class, and making individual classes for each of my GameScenes... probably the wrong thing to do, but for the sake of this question, please accept this as the way to do things.
My GameManager looks like this, having a reference to each of the scenes, that's static:
import SpriteKit
class GM {
static let scene2 = SecondScene()
static let scene3 = ThirdScene()
static let home = SKScene(fileNamed: "GameScene")
}
How do I create a SKScene programmatically, without size info, since they're in a subclass of SKScene and don't have any idea what the view size is, and I don't want them to need worry about this:
I'm doing this, but getting a EXC_BAD_Access at convenience override init()
class SecondScene: SKScene {
override init(size: CGSize){
super.init(size: size)
}
convenience override init(){
self.init()
self.backgroundColor = SKColor.red
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
}
As I mentioned your question is a bit vague but lets do some examples of what a GameManager class can be.
Before I start lets differentiate between calling this
let scene = StartScene(size: ...)
and this
let scene = SKScene(fileNamed: "StartScene")
The 1st method, with size, is when you create your scenes all in code and you are not using the xCode visual level editor.
The 2nd method is when you are using the Xcode level editor, so you would need to create a StartScene.sks file. Its that .sks file that it looks for in fileNamed.
Now for some game manager example, lets first imagine we have 3 SKScenes.
class StartScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameOverScene: SKScene {
override func didMove(to view: SKView) { ... }
}
Lets say you want to transition from StartScene to GameScene, you would add this code in your StartScene at the correct spot e.g when the play button is pressed. Thats the simplest way to move from one SKScene to the next, directly from the SKScene itself.
// Code only, no xCode level editor
let gameScene = GameScene(size: CGSize(...))
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
// With xCode level editor (returns an optional so needs if let
// This will need the GameScene.sks file with the correct custom class set up in the inspector
// Returns optional
if let gameScene = SKScene(fileNamed: "GameScene") {
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
}
Now for some actual examples of GameManagers, Im sure you know about some of them already.
EXAMPLE 1
Lets say we want a scene loading manager. You approach with static methods will not work because a new instance of SKScene needs be created when you transition to one, otherwise stuff like enemies etc will not reset. Your approach with static methods means you would use the same instance every time and that is no good.
I personally use a protocol extension for this.
Create a new .swift file and call it SceneLoaderManager or something and add this code
enum SceneIdentifier: String {
case start = "StartScene"
case game = "GameScene"
case gameOver = "GameOverScene"
}
private let sceneSize = CGSize(width: ..., height: ...)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
// No xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
let scene: SKScene
switch identifier {
case .start:
scene = StartScene(size: sceneSize)
case .game:
scene = GameScene(size: sceneSize)
case .gameOver:
scene = GameOverScene(size: sceneSize)
}
let transition = SKTransition...\
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
// With xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
scene.scaleMode = .aspectFill
let transition = SKTransition...
view?.presentScene(scene, transition: transition)
}
}
Now in the 3 scenes conform to the protocol
class StartScene: SKScene, SceneManager { ... }
and call the load method like so, using 1 of the 3 enum cases as the scene identifier.
loadScene(withIdentifier: .game)
EXAMPLE 2
Lets make a game manager class for game data using the Singleton approach.
class GameData {
static let shared = GameData()
private init() { } // Private singleton init
var highscore = 0
func updateHighscore(forScore score: Int) {
guard score > highscore else { return }
highscore = score
save()
}
func save() {
// Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
}
}
Now anywhere in your project you can say
GameData.shared.updateHighscore(forScore: SOMESCORE)
You tend to use Singleton for things where you only need 1 instance of the class. A good usage example for Singleton classes would be things such as helper classes for Game Center, InAppPurchases, GameData etc
EXAMPLE 3
Generic helper for storing some values you might need across all scenes. This uses static method approach similar to what you were trying to do. I like to use this for things such as game settings, to have them in a nice centralised spot.
class GameHelper {
static let enemySpawnTime: TimeInterval = 5
static let enemyBossHealth = 5
static let playerSpeed = ...
}
Use them like so in your scenes
... = GameHelper.playerSpeed
EXAMPLE 4
A class to manage SKSpriteNodes e.g enemies
class Enemy: SKSpriteNode {
var health = 5
init(imageNamed: String) {
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
func reduceHealth(by amount: Int) {
health -= amount
}
}
Than in your scene you can create enemies using this helper class and call the methods and properties on it. This way you can add 10 enemies easily and individually manage their health etc. e.g
let enemy1 = Enemy(imageNamed: "Enemy1")
let enemy2 = Enemy(imageNamed: "Enemy2")
enemy1.reduceHealth(by: 3)
enemy2.reduceHealth(by: 1)
Its a massive answer but I hope this helps.

How to set the window size and position programmatically for a SpriteKit/GameScene app on OSX

I have a bare-bones project created in Xcode as a SpriteKit/GameScene app. I want to set the window size programmatically. I've read a lot of answers here, and several tutorials elsewhere, but none of the things I've read have helped.
This answer talks about overriding WindowController.windowDidLoad, but GameScene doesn't give me a WindowController. It does give me a ViewController. This tutorial says you can call self.view.window?.setFrame() from ViewController.viewDidLoad(), but my window stubbornly remains the same size. A number of the answers I've found on SO talk about auto-layout. I don't have anything to lay out. It's just a SpriteKit starter project.
This answer says you can set preferredContentSize in ViewController.viewWillAppear(), and that does in fact let me set the window size, but if I make the window too big (because I had to guess at a legitimate size), it's not draggable or resizable. This answer says you can get the correct size from view.bounds.size, but that says 800 x 600, which is nowhere near the size of my screen. This answer says you can get the bounds from UIScreen.mainScreen().bounds.size, but my GameScene/SpriteKit starter project seems not to have any kind of UIScreen in it.
How can I get the screen size, and how can I set the window size and position programmatically?
Also, one of the other answers, which I can't seem to find any more, says you should delete GameScene.sks, which I did, and everything seems fine still, except for the size.
Updated for Swift 5
Window Size
Remember to add NSWindowDelegate to your NSViewController class if you wish to implement it there (ie. viewDidLoad(), viewWillAppear(), viewDidAppear(), etc.)
NSView
class ViewController: NSViewController, NSWindowDelegate {
override func viewWillAppear() {
fillWindow()
}
/// Sizes window to fill max screen size
func fillWindow() {
if let screenSize = view.window?.screen?.frame {
view.window!.setFrame(screenSize, display: true)
}
}
}
NSWindow
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
fillWindow()
}
/// Sizes window to fill max screen size
func fillWindow() {
if let screenSize = window?.screen?.frame {
window!.setFrame(screenSize, display: true)
}
}
}
Print to Debugger Console
print(screenSize) // embed within if let screenSize { ... }
Window Position
See the full answer, with implemented code, here.
extension NSWindow {
/// Positions the `NSWindow` at the horizontal-vertical center of the `visibleFrame` (takes Status Bar and Dock sizes into account)
public func positionCenter() {
if let screenSize = screen?.visibleFrame.size {
self.setFrameOrigin(NSPoint(x: (screenSize.width-frame.size.width)/2, y: (screenSize.height-frame.size.height)/2))
}
}
/// Centers the window within the `visibleFrame`, and sizes it with the width-by-height dimensions provided.
public func setCenterFrame(width: Int, height: Int) {
if let screenSize = screen?.visibleFrame.size {
let x = (screenSize.width-frame.size.width)/2
let y = (screenSize.height-frame.size.height)/2
self.setFrame(NSRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height)), display: true)
}
}
/// Returns the center x-point of the `screen.visibleFrame` (the frame between the Status Bar and Dock).
/// Falls back on `screen.frame` when `.visibleFrame` is unavailable (includes Status Bar and Dock).
public func xCenter() -> CGFloat {
if let screenSize = screen?.visibleFrame.size { return (screenSize.width-frame.size.width)/2 }
if let screenSize = screen?.frame.size { return (screenSize.width-frame.size.width)/2 }
return CGFloat(0)
}
/// Returns the center y-point of the `screen.visibleFrame` (the frame between the Status Bar and Dock).
/// Falls back on `screen.frame` when `.visibleFrame` is unavailable (includes Status Bar and Dock).
public func yCenter() -> CGFloat {
if let screenSize = screen?.visibleFrame.size { return (screenSize.height-frame.size.height)/2 }
if let screenSize = screen?.frame.size { return (screenSize.height-frame.size.height)/2 }
return CGFloat(0)
}
}
Usage
NSWindow
Positions the existing window to the center of visibleFrame.
window!.positionCenter()
Sets a new window frame, at the center of visibleFrame, with dimensions
window!.setCenterFrame(width: 900, height: 600)
NSView
Using xCenter() and yCenter() to get the central x-y points of the visibleFrame.
let x = self.view.window?.xCenter() ?? CGFloat(0)
let y = self.view.window?.yCenter() ?? CGFloat(0)
self.view.window?.setFrame(NSRect(x: x, y: y, width: CGFloat(900), height: CGFloat(600)), display: true)
It would be awesome if some UI guru could comment on whether this is the "right" answer, but here's what I did as a wild guess based on "El Tomato's" rather cryptic comments. First, add the following to ViewController.swift:
class WildGuessWindowController: NSWindowController {
override func windowDidLoad() {
if let screenSize = window?.screen?.frame {
window!.setFrame(screenSize, display: true)
print(screenSize)
}
super.windowDidLoad()
}
}
Next, open Main.storyboard in the project navigator. You might see something like the following:
Click on the box that says "Window Controller", and open the right-hand side panel. You should see something like this.
Notice the box next to the arrow, that has a grayed-out class name in it. (Also notice that you might have to click the "Identity inspector" button, the blue one above where it says "Custom Class".) Now click that drop-down, and you should see WildGuessWindowController. Select that, and you're set. Build and run. The framework will instantiate your class, and run windowDidLoad().

How to activate This Class to another

I have a question about using another class in my main GameplayScene. What I am trying to do is make the motions of the X-axis of the phone move the character left and right. Here is what I have in my MotionClass.swift
import SpriteKit
import CoreMotion
class MotionClass: SKScene {
var player: Player?
var motionManager = CMMotionManager()
var destX: CGFloat = 0.0
override func sceneDidLoad() {
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data, error) in
if let myData = data {
let currentX = self.player?.position.x
if myData.acceleration.x > 0.2 {
self.destX = currentX! + CGFloat(myData.acceleration.x * 100)
print("Tilted Right")
} else {
if myData.acceleration.x < -0.2 {
self.destX = currentX! + CGFloat(myData.acceleration.x * 100)
print("Tilted Left")
}
}
}
}
}
override func update(_ currentTime: TimeInterval) {
let action = SKAction.moveTo(x: destX, duration: 1)
self.player?.run(action)
}
}
Now I'm trying to call this class in my GameplayScene.swift in the motionBegan function, but I don't know how to go about doing that. I have the variable 'grapple' as MotionClass? but I don't know where to go from there. Could anyone give a good an example on doing this?
I think you may be confused about the purpose of an SKScene subclass, which is what your MotionClass currently is. (The main idea) is to only use one SKScene at a time: if you need stuff from MotionClass then you should just make it a plain class, not an SKScene subclass.
I think you may also need to familiarize yourself a bit more with OOP as well... Outside of static properties / functions, you don't "call" a class, you instantiate it ( you make an object :] )
So, if you have goodies in MotionClass that you want to access in GamePlayClass, you need a reference to a MotionClass object
This can be done with a simple global variable... I suggest putting it into your GameViewController.swift:
// Here is a global reference to an 'empty' motion class object..
var global_motionClassObject = MotionClass()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ...
if let view = self.view as! SKView? else {
let scene = MotionClass(size: view.frame.size)
// Assign our global to the new scene just made:
global_motionClassObject = scene
scene.scaleMode = .aspectFit
view.presentScene(scene)
}
// ...
}
Now inside of your GamePlayClass, or wherever else, you can access MotionClass by calling global_motionClassObject
However, this may not give the desired results because I'm worried you may need to restructure your MotionClass into something other than an SKScene :)

How to initialize a variable using another variable in an SKScene

I want to be able to make two variables available to the entire SKScene, and all functions inside of it. One of these variable using the other one to create its value. I understand why I cannot do this, but I don't know a fix for it. I have this code:
class GameScene: SKScene {
let num : CGFloat = 1.25
let reciprocal = 1 / num // <— This Line
override func sceneDidLoad() {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
But I am obviously getting an error the line 4.
Cannot use instance member 'num' within property initializer; property
initializers run before 'self' is available
This means that I cannot use the variable because it is connected to the skscene, and the scene hasn't been implemented fully yet. Is there a way to declare this variable without throwing an error and making it assessable everywhere within this class?
Since reciprocal depends directly upon num, it could make sense to let the prior be a computed property based on the latter
class GameScene: SKScene {
let num: CGFloat = 1.5
var reciprocal: CGFloat { return 1/self.num }
// ...
}
Since num is an immutable property and will never change at runtime, another alternative is to let reciprocal be a lazy variable, computed upon its first use
class GameScene: SKScene {
let num: CGFloat = 1.5
lazy var reciprocal: CGFloat = { return 1/self.num }()
// ...
}
(Or, implement your own custom initializer for the GameScene, where you can initialize num and reciprocal to e.g. a given value and its reciprocal, respectively).

Creating and Using Classes

If I create an instance of the class below and call the spawn function from my controller, the sprite will appear but I won't be able to change any of its properties.
class Hero: SKSpriteNode
{
var hero = SKSpriteNode(imageNamed: "hero3")
func spawn(parentNode: SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50))
{
hero.size = size
hero.position = position
hero.physicsBody = SKPhysicsBody(circleOfRadius: 25)
hero.physicsBody?.allowsRotation = false
hero.zPosition = 10
parentNode.addChild(hero)
}
}
If I get rid of the hero property and change everything to self, it works fine.
class Hero: SKSpriteNode
{
func spawn(parentNode: SKNode, position: CGPoint, size:CGSize = CGSize(width: 50, height: 50))
{
self.size = size
self.position = position
self.texture = SKTexture(imageNamed: "hero3")
self.physicsBody = SKPhysicsBody(circleOfRadius: 25)
self.physicsBody?.allowsRotation = false
self.zPosition = 10
parentNode.addChild(self)
}
}
I'm sure this is swift 101, but can someone please explain why the first version doesn't work as expected?
In your first example, you created a var (basically a SpriteNode inside the SpriteNode).
When you instantiate the class like let hero = Hero(....) you now have a SpriteNode called hero, with a property called hero. You can change either. Calling hero.size would change the base hero, and hero.hero.size would change the inside SpriteNode....this is probably not the behavior you were looking for.
The second class looks correct, if you are just trying to create a SpriteNode and modify it. The class is a subclass of SpritNode, so it's already a SpriteNode - no need to create one inside it like the first one.
Hope this helps!
Your Hero class inherits from SKSpriteNode, so it is essentially an SKSpriteNode already, which means you don't need to create a hero SKSpriteNode variable.
In your first class, when you use hero.size = size, you're accessing the properties of the variable within your class instead of on the class itself. Then you add the hero SKSpriteNode as a child along with all its properties, but your class SKSpriteNode, which holds the variable doesn't have set properties size or a physics body. You can think of the class as a container that's holding the variable SKSpriteNode.
In your second function, when you use self.size = size, you're accessing the class's properties and giving the class SKSpriteNode all the properties you need to use it in other classes.