I am trying to create a turn-based game which progresses until 13 rounds are over. However, after I created an infinite while loop which would only break after 13 rounds, the scene never loaded. Why is it that the code in didMoveToView is executed before the scene shows? Is there a way to fix this or am I possibly doing something wrong?
override func didMoveToView(view: SKView) {
/* Setup your scene here */
view.ignoresSiblingOrder = true
roundFirstCard = Card(key: "2c")
var actions = [SKAction]()
let cards = makeDeck()
for c in cards {
let card = Card(key: c)
card.name = card.key
card.position = CGPointMake(500, 400)
addChild(card)
giveCardToCorrectPlayer(cards.indexOf(c)!, c: card)
actions.append(SKAction.runAction(SKAction.moveTo(getTargetLocationForCard(cards.indexOf(c)!), duration: 1.0), onChildWithName: card.name!))
actions.append(SKAction.waitForDuration(0.05))
}
actions.append(SKAction.waitForDuration(1.0))
playerCardSets = [player1Cards, player2Cards, player3Cards, player4Cards]
self.runAction(SKAction.sequence(actions), completion: {self.spreadCards()})
currentPlayer = 1
while true {}
}
You don't need a while loop, especially if your scene is a round.
So I suppose this scene you've write in your question is a typical round of your game.
You can save the rounds number in NSUserDefaults for example in AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
NSUserDefaults.standardUserDefaults().setInteger(0, forKey: "roundCounter")
NSUserDefaults.standardUserDefaults().synchronize()
return true
}
So in your scene you know what is your round:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var roundCounter = NSUserDefaults.standardUserDefaults().integerForKey("roundCounter")
roundCounter += 1 //Start new round
guard let r = roundCounter where r > 13 {
gameOver() //launch gameOver function
return
}
// do your stuff
// save the actual round number
NSUserDefaults.standardUserDefaults().setInteger(roundCounter, forKey: "roundCounter")
NSUserDefaults.standardUserDefaults().synchronize()
}
func gameOver() {
// reset to zero your round counter
NSUserDefaults.standardUserDefaults().setInteger(0, forKey: "roundCounter")
NSUserDefaults.standardUserDefaults().synchronize()
//do your stuff
}
Related
I am wondering how I could detect key presses in Xcode - my goal is to be able to detect when keys are pressed, specifically the space, escape, enter and WASD keys.
What I have tried
I have tried pressesBegan, pressesEnded and its other 'variants', and I have also tried keyDown. After looking for 2 hours, the only ways I could find to detect key presses were from the two methods listed above, which don't seem to be working for me.
When using pressesBegan, I tried putting the following code in both the GameViewController.swift file and the AppDelegate.swift file, however it doesn't seem to work:
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
print(key.keyCode)
}
The code shows no errors, and even when I put a print file at the very start of the function to see if it is being called it does not give any output.
After this, I tried a keyDown function (in the GameViewController.swift file), however it threw the error "Cannot find type NSEvent in scope". I fixed this error by adding ", NSObject" to the top of the file in the line "class GameViewController: UIViewController {" but that threw the error "Multiple inheritance from classes 'UIViewController' and 'NSObject'". I have looked for about an hour but there are no solutions which match my issue/have not worked for me.
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "SplashScreen") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return true
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
case .keyboardR:
print("Roll dice")
case .keyboardH:
print("Show help")
default:
super.pressesBegan(presses, with: event)
}
}
override func keyDown(with event: NSEvent) {
print("a")
}
}
If you need any additional things I'll try to get back to you as quick as possible. I'm really sorry if I've missed something extremely obvious like putting it in a different file.
Thank you!
the pressesEnd() method works in the same way; Try to import UIKit first:
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
case .keyboardSpacebar:
print("Continue the quiz…")
default:
super.pressesEnded(presses, with: event)
}
}
More can be found at:
https://www.hackingwithswift.com/example-code/uikit/how-to-detect-keyboard-input-using-pressesbegan-and-pressesended
So I'm making a game for my computer science class and I made a class named Bomb for the sprites in the game. In the Bomb class, I have a function named blowUp() that is called when a countdown timer for the bomb goes off. When time runs out, the bomb's texture changes among other things, but it should also trigger a game over.
I have a gameOver() function in my GameScene that I want to call up in the blowUp() method but when I do that I get the error message "Instance member 'gameOver' cannot be used on type 'GameScene'; did you mean to use a value of this type instead?"
Is there any way around this? Thanks in advance
class Bomb{
var sprite = SKSpriteNode()
var timer = Timer()
var secondsLeft = 20
func countDown(){
secondsLeft -= 1
if secondsLeft == 0{
blowUp()
}
}
init {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in self.countDown()})
}
func blowUp(){
self.timer.invalidate()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.sprite.removeFromParent()
}
// gameOver() gives me an error
}
}
Here are the changes I would make to your code.
Extend SKSpriteNode and remove the sprite variable, because you want your bomb to be an actual sprite.
class Bomb : SKSpriteNode{
var sprite = SKSpriteNode()
Eliminate your Timer. Timer goes off of real world, not in game world, so if any external event happens (like a phone call) your time will be off. Use SKActions instead.
var timer = Timer()
var secondsLeft = SKAction.wait(forDuration:20)
Remove your countdown function, blowup function and your init, and replace it with an ignite function
func countDown(){
secondsLeft -= 1
if secondsLeft == 0{
blowUp()
}
}
init {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in self.countDown()})
}
func blowUp(){
self.timer.invalidate()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.sprite.removeFromParent()
}
// gameOver() gives me an error
}
func ignite() {
var explosionAnimation = SKAction.animate(with:arrayOfExplosionTextures,timePerFrame:0.1666)
var blowup = SKAction.run{
[unowned self] in
(self.scene as GameScene).gameOver()
}
//var seq = SKAction.sequence([secondsLeft,explosionAnimation,blowup,SKAction.removeFromParent])
var seq = SKAction.sequence([secondsLeft,blowup,SKAction.removeFromParent])
}
What ignite does is start your bomb when you are ready to start it, waits 30 seconds, then explodes. This allows you to place bombs on the screen that are delayed or duds.
Here is what your class should look like:
class Bomb : SKSpriteNode{
var secondsLeft = SKAction.wait(forDuration:20)
func ignite() {
//var explosionAnimation = SKAction.animate(with:arrayOfExplosionTextures,timePerFrame:0.1666)
var blowup = SKAction.run{
[unowned self] in
(self.scene as GameScene).gameOver()
}
//var seq = SKAction.sequence([secondsLeft,explosionAnimation,blowup,SKAction.removeFromParent])
var seq = SKAction.sequence([secondsLeft,blowup,SKAction.removeFromParent])
}
}
Here is how you use it in game scene:
func placeBomb(x:CGFloat,y:CGFloat){
let bomb = Bomb(imageNamed:"bomb")
addChild(bomb)
bomb.position = CGPoint(x:x,y:y)
bomb.ignite()
}
I'm trying to use AudioKit to playback a sound on each beat of a measure(s). Although I've implemented the code from this similar question regarding callbacks via AudioKit, I can't seem to get the sequencer to update changes and playback properly. It will play once accurately, however after rewinding and changing the values it will only use the initial values (or not playback at all).
My intent is to create a struct of measures with beat values for each measure, then use MIDI and the callback to play different sounds dependent on how many measures/beats there are. Thanks!
import UIKit
import AudioKit
class ViewController: UIViewController {
let sequencer = AKSequencer()
let click = AKSynthSnare()
let callbackInst = AKCallbackInstrument()
// Create the struct that defines each line
struct Line {
var name: String
var measures: Int
var beatsPerMeasure: Int
func totalBeats() -> Int {
return (measures * beatsPerMeasure)
}
}
// Initialize intro line
var intro = Line(name: "Intro", measures: 0, beatsPerMeasure: 0)
// A function to create/update/playback the sequence on button press
func playBack() {
let metronomeTrack = sequencer.newTrack()
metronomeTrack?.setMIDIOutput(click.midiIn)
let callbackTrack = sequencer.newTrack()
callbackTrack?.setMIDIOutput(callbackInst.midiIn)
for steps in 0 ... Int(measuresRowOneValue) {
// this will trigger the sampler on the four down beats
metronomeTrack?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
// set the midiNote number to the current beat number
callbackTrack?.add(noteNumber: MIDINoteNumber(steps), velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
// set the callback
callbackInst.callback = {status, noteNumber, velocity in
guard status == .noteOn else { return }
print("beat number: \(noteNumber + 1)")
}
}
}
#IBOutlet weak var rowOneLocationOne: UIImageView!
// Listener for UI display values
var measuresRowOneValue: Int = 0 {
didSet {
intro.measures = measuresRowOneValue
}
}
#IBAction func rowOnePlusButton(_ sender: UIButton) {
measuresRowOneValue += 1
}
#IBAction func rowOneMinusButton(_ sender: UIButton) {
measuresRowOneValue -= 1
}
#IBAction func playbackStart(_ sender: UIButton) {
playBack()
sequencer.play()
}
#IBAction func playbackStop(_ sender: UIButton) {
sequencer.stop()
}
#IBAction func playbackRestart(_ sender: UIButton) {
sequencer.rewind()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
AudioKit.output = click
try!AudioKit.start()
}
}
There are a few confusing things in your code, so I'm not sure if this is your only issue, but minimally, every time you change the length of your sequence, you will need to call setLength() followed by enableLooping. Basically, by default (i.e., unless you explicitly set the length) the length of the sequence will be the length of the longest track in the sequence. In your 'playback' method you are adding track on top of track without removing the old ones so it has no way of knowing how long you intend the sequence to be.
Your 'playback' method is doing two distinct things (neither of which involves playback). You might want to break it up. You could have a setup() to do the things that only ever need to be done once (create the tracks, set their outputs, set up the callback) and a rewriteSequence() methods that gets called when you want to re-write the tracks. This way you can reuse your existing tracks rather than continuously creating new ones.
var metronomeTrack: AKMusicTrack!
var callbackTrack: AKMusicTrack!
// call this just once at the start
func setup() {
metronomeTrack = sequencer.newTrack()
metronomeTrack?.setMIDIOutput(click.midiIn)
callbackTrack = sequencer.newTrack()
callbackTrack?.setMIDIOutput(callbackInst.midiIn)
callbackInst.callback = {status, noteNumber, velocity in
guard status == .noteOn else { return }
print("beat number: \(noteNumber + 1)")
}
}
// call this each time you want to change the sequence
func rewriteSequence() {
metronomeTrack?.clear()
callbackTrack?.clear()
for steps in 0 ... Int(measuresRowOneValue) {
metronomeTrack?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
callbackTrack?.add(noteNumber: MIDINoteNumber(steps), velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
}
// This will make sure it loops correctly:
sequencer.setLength(AKDuration(beats: Double(measuresRowOneValue)))
sequencer.enableLooping()
}
I hope this helps.
I have the game in which the new scene is called by pressing buttons. When you click in a new scene sent the corresponding pictures. When you return back to the game map memory always increases by 30 MB. I do not understand where the strongest link. Instruments can not detect leaks. Sorry my english. Help me please.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self)
for i in 0...3 {
if childNode(withName: "button\(i)")!.isHidden == false && childNode(withName: "button\(i)")!.contains(location) {
buttonOfBattlefield = childNode(withName: "button\(i)")
}
}
switch buttonOfBattlefield?.name {
case "button0"?:
battlefieldName = "A"
case "button1"?:
battlefieldName = "B"
case "button2"?:
battlefieldName = "C"
case "button3"?:
battlefieldName = "D"
default:
break
}
if battlefieldName != nil {
let myScene = GameScene(size: self.size , battlefield: battlefieldName!)
myScene.scaleMode = self.scaleMode
let reveal = SKTransition.fade(withDuration: 2.0)
self.view?.presentScene(myScene, transition: reveal)
}
}
}
Essentially there could be many factors that cause the increase of memory in your game.
I try to help you with some useful correction.
Speaking about custom protocols, you could break the strong references by adding class at the end of the line, and declare weak var to the delegate:
protocol ResumeBtnSelectorDelegate: class {
func didPressResumeBtn(resumeBtn:SKSpriteNode)
}
weak var resumeBtnDelegate:ResumeBtnSelectorDelegate?
...
Speaking about completion could be a strong reference to self so you can do like this example:
self.launchVideo(completion: {
[weak self] success in
guard let strongSelf = self else { return }
//self.showMyVideo()
strongSelf.showMyVideo()
}
same thing to run actions blocks:
let exp = SKAction.run {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.getExplosion(percentageDimension: 15,type: 0,position: enemy.position)
}
If you use third part library in objective C , you may need to remove the strong references also there:
__weak __typeof__(self) weakSelf = self;
SKAction *sequence = [SKAction sequence:#[[SKAction followPath:_ascentPath duration:1.5], [SKAction runBlock:^(void){[weakSelf explode];}]]];
[self runAction:sequence];
}
If you have some observer try to remove it, samething for NSTimers to the willMoveFromView methods.
override func willMove(from view: SKView) {
//remove UIKit objects, observers, timers..
}
The solution found. The new Game Scene need to use the method.
self.enumerateChildNodes(withName: "//*"){ (node, stop) -> Void in
node.removeAllActions()
node.removeAllChildren()
node.removeFromParent()
}
Thank you all for your help. I'm happy!
I want to be able to toggle my SKEmitterNode (rain particles) off and on based on the score. But my update function gets called constantly, i.e. I end up with millions of particles on the screen with my current code below...how can I structure my code so that the rain particles will only get called once when a score is achieved?
class GameScene: SKScene, SKPhysicsContactDelegate {
func setUpRain() {
if let rainParticle = SKEmitterNode(fileNamed: "Rain") {
rainParticle.position = CGPointMake(frame.size.width, frame.size.height)
rainParticle.name = "rainParticle"
rainParticle.zPosition = Layer.Flash.rawValue
worldNode.addChild(rainParticle)
}
}
func makeItRain() {
let startRaining = SKAction.runBlock {
self.setUpRain()
}
runAction(startRaining, withKey: "Rain")
}
func stopRaining() {
removeActionForKey("Rain")
worldNode.enumerateChildNodesWithName("rainParticle", usingBlock: { node, stop in
node.removeFromParent()
})
}
}
class PlayingState: GKState {
unowned let scene: GameScene //used to gain access to our scene
override func updateWithDeltaTime(seconds: NSTimeInterval) {
scene.updateForegroundAndBackground()
scene.updateScore()
if scene.score > 2 {
scene.makeItRain()
}
if scene.score > 4 {
scene.stopRaining()
}
}
There's a few ways you can do this, but the simplest of these is to only call makeItRain() or stopRaining() once per toggle. What I mean by this is once makeItRain is called, it cannot be called again until stopRaining is called. This can be done with a boolean like so:
var rainToggle: Bool = false; //True = Raining
override func updateWithDeltaTime(seconds: NSTimeInterval) {
scene.updateForegroundAndBackground()
scene.updateScore()
if (scene.score > 4){
scene.stopRaining()
rainToggle = false;
}
else if (scene.score > 2 && !rainToggle) {
scene.makeItRain()
rainToggle = true;
}
}
This is only slightly inefficient since you are calling stopRaining() every frame for no reason, however it gets the job done and is easy to understand. Note also that I had to flip the order in which your if statements came (otherwise it wouldn't work).