I am trying to use GameplayKit to implement all the entities/components of a 2D game made with SpriteKit. The first component that I have created is the SpriteComponent:
class SpriteComponent:GKComponent
{
let node:SKSpriteNode
init(texture:SKTexture) {
node = SKSpriteNode(texture: texture, color: SKColor.clear, size: texture.size())
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Which is meant to be used by all the components that needs to be added to the scene. Next I wanted to add a component to fire bullets, which I called FiringComponent. Since a bullet is a graphical item, it could be an entity implementing a SpriteComponent, so I wrote this code:
class Bullet:GKEntity
{
var velocity:CGPoint
var node:SKSpriteNode
{
return self.component(ofType: SpriteComponent.self)!.node
}
init(withVelocity velocity:CGPoint)
{
self.velocity = velocity
super.init()
let spriteComponent = SpriteComponent(texture: SKTexture(imageNamed: "Bullet"))
addComponent(spriteComponent)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func update(deltaTime seconds: TimeInterval)
{
self.node.position += self.velocity * CGFloat(seconds)
// I have overridden the CGPoint += and * operators
}
}
Next comes the doubt: should the FiringComponent add directly the bullet entity to the scene? a possible implementation of the FiringComponent could be of adding a Bullet entity that should be added to the game hierarchy and updated at each frame. But I think that this logic is flawed, because this way the component needs to have a variable that points not only to the game scene, but also to the object that holds all the entities and updates them periodically. This way I have to write contorted code just to fire a bullet. I am searching for a neat and clean way to implement that: any suggestion?
Related
I'm trying to make my app more accessible for Voice Over users. I have a slider that has numbers 1-100. If a user with Voice Over turned on swipes up or down to change the value, several numbers are being skipped. This means that an exact number is not able to be set. I'm following the suggestion from this site on subclassing UISlider and overriding accessibilityIncrement() and accessibilityDecrement() but they do not get called when the slider value changes. Below is my subclassed slider. Any idea why the methods are not getting called?
class FontSizeSlider: UISlider {
required init?(coder: NSCoder) {
super.init(coder: coder)
self.isAccessibilityElement = true
self.accessibilityTraits.insert(.adjustable)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func accessibilityIncrement() {
self.value += 1
self.sendActions(for: .valueChanged)
}
override func accessibilityDecrement() {
self.value -= 1
self.sendActions(for: .valueChanged)
}
}
This is something I need to know for work, so this was a fantastic exercise for me. Thank you for posting the question. Anyway, I got it to work after taking a peek at this page on Apple's website.
I could not get the increment/decrement methods to be called, either. I suspect they're stepper-specific. The value property, OTOH, gets called.
Here's the code I came up with to get it to work:
class FontSizeSlider: UISlider {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isAccessibilityElement = true
accessibilityLabel = "Font Size Slider"
accessibilityIdentifier = "fontSizeSlider"
// accessibilityIdentifier = AccessibilityConstants.fontSizeSlider.rawValue
minimumValue = 0
maximumValue = 100
isContinuous = true
}
override var accessibilityValue: String? {
get {
return sliderValueString
}
set {
super.accessibilityValue = sliderValueString
}
}
override var accessibilityTraits: UIAccessibilityTraits {
get {
return .adjustable
}
set {
super.accessibilityTraits = newValue
}
}
// Nobody needs to know about this outside the class, so marked it private
private var sliderValueString: String {
let stringValue = String(Int(value))
return "The font size is \(stringValue)"
}
}
You'll notice I used the setup() method, which does the same stuff for both initializers. You can tweak your values as you see fit for the min/max values.
You'll note I added accessibilityLabel, so it doesn't read off that it's a generic slider. I added the accessibilityIdentifier in there, too. That's something that can be used for UI tests so the element can be identified.
You'll probably want to put the accessibilityIdentifier somewhere where "everyone" can see it. Perhaps an enum. Here's what the enum implementation would look like:
enum AccessibilityConstants: String {
case fontSizeSlider
}
// Usage
accessibilityIdentifier = AccessibilityConstants.fontSizeSlider.rawValue
I overrode the accessibilityValue with a custom setter and getter. Additionally, I created a computed var for the string that's read off when the accessibilityValue is updated. Here's the code for that portion of it. Note I made it private because nobody outside the class needs to know about it:
// I adapted this from Apple's accessibility page that I posted above
override var accessibilityValue: String? {
get {
return sliderValueString
}
set {
super.accessibilityValue = sliderValueString
}
}
private var sliderValueString: String {
let stringValue = String(Int(value))
return "The font size is \(stringValue)"
}
One last thing...you don't need self everywhere unless you're accessing a property of your custom UISlider inside a closure like an animation block or a completion block.
Update
Deleted...
Update 2
So let's say you're on your viewController, you could add a target action to the slider, like so:
slider.addTarget(self, action: #selector(doSomething), for: .valueChanged)
#objc func doSomething() {
print("How much wood could a wood chuck chuck if a wood chuck could chuck wood")
}
Whenever value changes, your selector will get called.
I am getting a couple of errors like this in a few of my programs and been trying to fix it. Example: When I put size instead of coder it will tell me its incorrect and change it back to coder.
func reset() {
self.removeAllChildren()
var viewSize = view?.bounds.size
backgrounMusicPlayer.stop()
replayButton.isHidden = true
let gameScene = GameScene(coder: viewSize) //where error is
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
gameScene.scaleMode = SKSceneScaleMode.aspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
The problem is in your initialization of the GameScene object ... as the error implies.
let gameScene = GameScene(coder: viewSize) //where error is
You are passing in a CGSize object, when this initializer expects an NSCoder object. I'm assuming that GameScene is a subclass of SKScene, you should be initializing with
let gameScene = GameScene(size: viewSize)
To avoid to write wrong syntax you could use the XCode autocompletion.
Control + SpaceBar triggers XCode’s autocomplete functionality, also when you want to know the available init methods about a generic class like SKScene you simply write:
Doing this you can immediatly see input properties and the required types.
About your case you have a class named GameScene that subclass SKScene and you try to make:
SKScene.init(coder: <#T##NSCoder#>)
Obviusly you cannot pass viewSize as the input parameter for this init method. You should use init(size: CGSize) method
Details:
Now we speak about the init(coder: NSCoder).
The procedure to save the state of your view to disk is known as serialization. The reverse is deserialization - restoring the state of the object from disk.
override func encode(with aCoder: NSCoder) {
// Serialize your object here
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
// Deserialize your object here
fatalError("init(coder:) has not been implemented")
}
These methods are needed in your custom class to speak with Interface Builder, for example objects to the storyboard, that serialize the state of that object on to disk and deserialize it when you use the storyboard but if you don't add any new properties to your subclass you can also avoid to write your own serialization and deserialization code for the subclass.
Assuming your GameScene class is a subclass of SKScene, it doesn't have an initializer with a coder label.
Just change GameScene(coder: viewSize) to GameScene(size: viewSize)
If this is a SpriteKit Project, the GameScene class does not need the size and only requires the name of the .sks file which represent the scene. (the size would be set as property on that file)
Try this, it would reset and represent the game scene again
if let scene = GameScene(fileNamed:"GAME_SCENE_FILE_NAME") {
scene.replayButton.isHidden = true
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
scene.scaleMode = .aspectFill
self.view!.presentScene(scene, transition: transition)
}
I have this line in my GameViewController.swift. It was trying to make me "correct" my code to use "coder" instead of "size" and NScoder was the type.:
let scene = GameScene(size: view.bounds.size)
I commented out this part of my code in my GameScene.Swift. That fixed my problem.
/*
required init?(coder aDecoder: NSCoder) {
// theSelf = self
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder) //VL:4/1/2015 had to comment out for some reason
}
*/
Intuition helped me figure it out. I did a search for NSCoder in my project and found this. Which essentially was changing how the GameScene inits. This project started at least as far back as xcode 6, which is probably why.
I started following Ray Wenderlich's 'Space Invaders' tutorial, but have diverged considerably. I now have 3 SKScenes - my title screen, my main game screen and my end level/game over screen. The title screen and the end game scene I added and these both have .sks files; the main game screen does not and all elements (SKSpriteNodes etc) are placed programatically. The flow of my program is as follows:
I now would actually like to place some events of the main game screen via the scene editor, so I created a .sks file for it and tried to change my titleScene.swift as follows:
from:
let gameScene = GameScene(size:CGSize(width: 1536, height: 2048))
to:
let gameScene = SKScene(fileNamed: "GameScene.sks") as! GameScene!
However, this then gives:
I tried to remove the required init(coder aDecoder: NSCoder) but Xcode then complains that
required init(coder: must be supplied by subclass of SKScene
However my titleScene and gameOverScene are also sub-classes of SKScene and they don't have init(coder:)
I really can't see the difference in what I'm doing to display my titleScreen and my gameOverScene via (fileNames:) and their .sks file and trying to do the same for my gameScene.
The reason why you are getting the required is because you have variables that are not optional or not initialized before init takes place.
If you have variables that need to be assigned inside of an init function, then you would do:
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
But then you will ask me: Mr Knight0fDragon, it is telling me to replace fileNamed with coder, and it is not compiling when I switch it.
Well this is because init(fileNamed:) is a convenience init, not a designated init. In order to be able to subclass a class and get all of it's convenience inits, you need to override all of it's designated inits.
Now with SKScene, you have 3, and you already know about 1.
Let's override the other 2:
override init() {
super.init()
}
override init(size: CGSize) {
super.init(size: size)
}
Alright, now this puppy should be ready to compile, we just need to get the variables assigned.
Well what I like to do is create a setup method for any variable that has to be assigned in any version of initialization after the super is called.
Unfortunately we can't do this for constants before super is called, so those we would need to set up in each method. The reason being is that self does not fully exist yet.
This would end up looking like this:
let constant : String
required init?(coder aDecoder: NSCoder)
{
constant = "hi"
super.init(coder: aDecoder)
setup()
}
override init() {
constant = "hi"
super.init()
setup()
}
override init(size: CGSize) {
constant = "hi"
super.init(size: size)
setup()
}
So, as a swift novice, I'm noodling around and learning as I go. Generally a little google foo or a tutorial will help me, but now I'm stumped. So if anyone could explain this to me I would be very happy.
So I'm trying to draw a circle on screen, well, a few of them actually. I found this code online;
(http://www.ioscreator.com/tutorials/drawing-circles-uitouch-ios8-swift)
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
var context = UIGraphicsGetCurrentContext();
//... various stuff to draw a circle ..//
}
}
it's a class that draws a circle when initialised like this
currentCircle = CircleView(frame: CGRectMake(0,0,100,100 ) )
I have a few questions though.
Why does it actually draw a circle? the drawRect function never gets called directly. I guess it's because we're overriding a function in UIView, and I don't understand those concepts yet.
How can a pass variables to the initialisation of that function? Say, I want to draw circles of different thickness and I want to pass an extra variable like so:
currentCircle = CircleView(frame: CGRectMake(0,0,100,100 ), thickness:10 )
How would I modify the init to accept this? adding it like this:
override init(frame: CGRec, thickness: Int) {
super.init(frame: frame)
}
triggers an error (initialiser does override a designated initialiser from its superclass)
And why all the overrides? I've tried making it a class and using the code to draw. However,
CGContextAddArc
triggers a compiler error saying the context isn't valid, so I suppose
UIGraphicsGetCurrentContext()
isn't returning anything useful.
or if anyone knows a useful resource where I can learn a bit more about overriding and initialising classes, that would be welcome.
Why does it actually draw a circle?
You're correct that you never directly call drawRect:. It's called by the system when the view needs to be redrawn. This happens when, for example, the bounds of the view change, or you call setNeedsDisplay()
How would I modify the init…
Your init should look like this:
convenience init(frame: CGRec, thickness: Int) {
self.thickness = thickness
self.init(frame: frame)
}
You can only override methods from your class's superclasses, init(frame: CGRec, thickness: Int) isn't one of them.
You should take time to read Apple's Swift documentation before embarking on tutorials. All you need to know about the language is in there.
For more info on drawing, see Defining a Custom View, here.
An elegant approach is to turn your view into a class with full Interface Builder support. That way you can directly add and configure instances of your view in Interface Builder.
To use your custom view:
Select and insert a view
In the Identity Inspector view, set the Custom Class property to your class name (CircleView)
In the Attributes Inspector view, set the thickness
There are slightly modifications to your code needed, in particular the annotations #IBDesignable and #IBInspectable:
import UIKit
#IBDesignable class CircleView: UIView {
#IBInspectable var thickness: CGFloat = 1
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
var context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, thickness)
var r = self.bounds
var ellipseRect = CGRectMake(r.origin.x + self.thickness / 2, r.origin.y + self.thickness / 2,
r.size.width - thickness, r.size.height - thickness)
CGContextStrokeEllipseInRect(context, ellipseRect)
}
}
And yes, you're right. You never call 'drawRect:' yourself. You add instances of your view to your screens and iOS will call 'drawRect:' when it needs to paint your view.
It's the overall patterns of an graphical user interfaces system: you mainly react to events such as clicks from the user or repaint events.
I am implementing a Circle class (subclass of UIView) in Swift that sets its radius in its initializer according to the frame that is passed in init(frame: CGRect) like so:
override init(frame: CGRect)
{
radius = frame.width/2.0
super.init(frame: frame)
}
I also want to ensure for the case when the circle is instantiated from Interface Builder, so I also implement 'required init(coder aDecoder: NSCoder)` (which I am forced to do by Xcode anyway).
How can I retrieve the frame property of the view that is somehow contained in aDecoder. What I want to achieve basically would look like this:
required init(coder aDecoder: NSCoder)
{
var theFrame = aDecoder.someHowRetrieveTheFramePropertyOfTheView // how can I achieve this?
radius = theFrame.width/2.0
super.init(coder: aDecoder)
}
You could compute the radius after the frame has been set by super.init():
required init(coder aDecoder: NSCoder)
{
radius = 0 // Must be initialized before calling super.init()
super.init(coder: aDecoder)
radius = frame.width/2.0
}
Martin's answer is the correct one. (Voted). You might be able to find the way that the base class encodes the frame value and extract it, but that is fragile. (It relies on private details of the implementation of the base class, which might change and break your app in the future.) Don't develop code that depends on non-public implementation details of another class, or of your base class. That's a future bug just waiting to happen.
The pattern in initWithCoder is to first call super to get the values for the ancestor class, then extract the values for your custom class.
When you do it that way, the ancestor class will have already set up your view's frame for you, and you can just use that.