Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
Hey there. Just felt like coding up something quick for fun. Tried to sort of speed through it while still not making too much of a mess of it. Thus it's not elegantly written, but it's also not completely horrendous to look at :p

Well, anyway, I made a quick little Pong game with SpriteKit targeting the Mac. You can get the code and a binary with the links at the bottom, here's a short video demonstration; It's nothing super special :p




If you download the game, here are some instructions. - Press space to start the countdown for a round. Arrows up and down control the right paddle, while w and s control the left, designed for multiplayer on the same machine for now. Might add game controller and online play if I get bored again, haha.

If you want to configure it a bit you can change the paddle's movement speed with the variable in the Paddle class and you can change the ball speed with the multiplier in the start game that makes up dx and dy.

GitHub:

App Bundle

Note that the app bundle is only signed for local execution, so your Mac might complain about developer signing certificates
 
  • Like
Reactions: velocityg4

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
Nice simple example and very kind of you to post it.

Might I suggest a mild change to reduce the dependency upon magic numbers in event handling code.

NOTE:
This may only apply to the ANSI keyboard layout, but I don't think so, and can't remember for certian and don't have any other keyboards to try against.

Code:
import Carbon.HIToolbox

...

override func keyDown(with event: NSEvent) {
    switch Int(event.keyCode) {
    case kVK_Space:
        startCountdown()
    case kVK_UpArrow:
        rightPaddle.currentMovement = .Up
    case kVK_DownArrow:
        rightPaddle.currentMovement = .Down
    case  kVK_ANSI_W:
        leftPaddle.currentMovement = .Up
    case kVK_ANSI_S:
        leftPaddle.currentMovement = .Down
    default:
        print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")
    }
}

override func keyUp(with event: NSEvent) {
    switch Int(event.keyCode) {
    case kVK_UpArrow, kVK_DownArrow:
        rightPaddle.currentMovement = .None
    case kVK_ANSI_W, kVK_ANSI_S:
        leftPaddle.currentMovement = .None
    default:
        return
    }
}
 
Last edited:

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
Nice simple example and very kind of you to post it.

Might I suggest a mild change to reduce the dependency upon magic numbers in event handling code.

NOTE:
This may only apply to the ANSI keyboard layout, but I don't think so, and can't remember for certian and don't have any other keyboards to try against.

Code:
import Carbon.HIToolbox

...

override func keyDown(with event: NSEvent) {
    switch Int(event.keyCode) {
    case kVK_Space:
        startCountdown()
    case kVK_UpArrow:
        rightPaddle.currentMovement = .Up
    case kVK_DownArrow:
        rightPaddle.currentMovement = .Down
    case  kVK_ANSI_W:
        leftPaddle.currentMovement = .Up
    case kVK_ANSI_S:
        leftPaddle.currentMovement = .Down
    default:
        print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")
    }
}

override func keyUp(with event: NSEvent) {
    switch Int(event.keyCode) {
    case kVK_UpArrow, kVK_DownArrow:
        rightPaddle.currentMovement = .None
    case kVK_ANSI_W, kVK_ANSI_S:
        leftPaddle.currentMovement = .None
    default:
        return
    }
}

Ey thanks for the comment :) - Really there are a lot of things I'd change if I were to do it more cleanly, but this is absolutely one of them. Though I believe there should be named constants for keys that doesn't rely on Carbon

Interestingly though, Apple's SpriteKit "template" uses numerical key codes and doesn't even have the comments I added saying what each magical number is.

I think if I couldn't find a predefined set of key code constants that doesn't rely on Carbon, I'd just set up an Int based enum instead for it or something, but yeah definitely better your way there than just having it say "case 13" or whatever.

I doubt I'll look much more at fun little hobby-projects like this until after my exams, but if I do I was going to look into Apples game controller APIs and that might require rewriting input code anyway, so we'll see where that goes. Never supported controllers before anyway.

SpriteKit is super fun and easy to quickly prototype things with. :D

Apple really should fix up the template though. The first 30 minutes of starting a new SpriteKit project is deleting **** from the template and making it actually a usable starting point. I should just do It once and save my own template, but I'm always dumb enough to never save myself long term time and instead just jump the fence for short term benefits :p
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
What can you do do if Apple only supplies what they supply?
You mean for the key codes?
Well, for one of my other SpriteKit projects (very early stages, bigger project) I'm doing this
1623954947794.png


Adding keys to it as I need them. Then in the switch itself I just do like

Code:
case ButtonMap.ESC: characterSelection.close()

or whatever.

My hesitance with Carbon is that it's considered a legacy, deprecated framework
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
1623955299527.png


Though I can't find specific mention of the HIToolbox on developer.apple.com, Carbon as a whole s considered legacy at least. So I'd prefer if a Cocoa/AppKit replacement existed but I know of no key code map like that. Though for most cases one could also use the character options
 

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
My comment was meant as a slight provocation for conversation and/or action; but not out of complete helplessness, just slight frustration.

The main problem, as I see it, is lack of clear documentation on a replacement method that works across ALL Mac based systems of which all will pretty much have a keyboard as the most likely input device if nothing else.

And there have been so many of those over the years with different layouts and scan-codes.

One solution, never standardized, was to have the user choose the key for each necessary game function as part of installation and setup.

My first commercial Mac game was released in '87.
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
My comment was meant as a slight provocation for conversation and/or action; but not out of complete helplessness, just slight frustration.

The main problem, as I see it, is lack of clear documentation on a replacement method that works across ALL Mac based systems of which all will pretty much have a keyboard as the most likely input device if nothing else.

And there have been so many of those over the years with different layouts and scan-codes.

One solution, never standardized, was to have the user choose the key for each necessary game function as part of installation and setup.

My first commercial Mac game was released in '87.

Ah I see. Misunderstood your last comment then :)

But yeah no I get your point. I think the problem with an install time choice is that unless there's sensible defaults you can just click past anyway people will just get annoyed with it. Similar how UAC prompts have just become "Get work done, click yes" buttons, instead of added security. Just want to get started.

But I'm honestly not sure what would be the best way to do it if Apple were to define a replacement to the carbon key code constants stuff - I mean, let's say someone is using Dvorak. The most natural thing would probably be for A to map to A even if it's somewhere else on the keyboard. But in a game situation it might make more sense to have the placement of the qwerty wasd keys be mapped to whatever is place there on whatever keyboard layout the user is using.

What games have you worked on? :) - If you released something all the way back in 87 I assume that was programmed through assembly with just memory mapped I/O to some frame buffer or something?
 

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
The first Mac based game was something I ported for Sierra On-Line while at Synergistic Software as they moved to a being a contract programming house.

Yes is was in assembly, frame-buffer based and my first exposure to Mac programming.

Was also simultaneously ported to the Amiga, by me, as well, both under a short time wise, contract.

Lots of firsts for me and quite the learning experience on my part.

Maybe you've heard of the Game Arts arcade style game on the NEC PC-8801 in 1985 called Thexder.
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
The first Mac based game was something I ported for Sierra On-Line while at Synergistic Software as they moved to a being a contract programming house.

Yes is was in assembly, frame-buffer based and my first exposure to Mac programming.

Was also simultaneously ported to the Amiga, by me, as well, both under a short time wise, contract.

Lots of firsts for me and quite the learning experience on my part.

Maybe you've heard of the Game Arts arcade style game on the NEC PC-8801 in 1985 called Thexder.

Ah that's really cool :) I've watched quite a bit of the YouTube channel Coding Secret which digs into the old game dev strategies for making Sonic and Toy Story and stuff on old consoles.

Unfortunately I don't know Thexder. I had a super quick Google and it looks like a sci-fi aesthetic, slightly more complex Pitfall or something like that.

I've never done anything with graphics in assembly though I just today finished up my bachelor project of making an OS (just a very simple one of course). Mostly C but a fair bit of x86/x86-64 assembly in there as well and instructions you normally wouldn't (and can't) touch from user space.

While something like SpriteKit is super fun for quickly throwing things together and trying out things, and bigger tools like Unity and such are wonderful, I do really like the super hardware-near bare metal way of programming. It's not super practical for modern systems if you want to tap into GPUs and use newer rendering tactics and do more complex things and such, but it's super fun I think to do everything on a chip-native instruction level. - And I have so much respect for the tricks and hardware optimisations that were invented for machines back then. So much clever stuff to get more colours on screen than the colour palette would normally allow, or add some transparency or whatnot! :D
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
I am absolutely terrible at this game, haha. But cool. I like the whole flying around stuff when you hit the down arrow. Though yeah, I'm bloody terrible at it, haha
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
The first Mac based game was something I ported for Sierra On-Line while at Synergistic Software as they moved to a being a contract programming house.

Yes is was in assembly, frame-buffer based and my first exposure to Mac programming.

Was also simultaneously ported to the Amiga, by me, as well, both under a short time wise, contract.

Lots of firsts for me and quite the learning experience on my part.

Maybe you've heard of the Game Arts arcade style game on the NEC PC-8801 in 1985 called Thexder.
Update! It's not live to git yet, but while experimenting with a PS5 controller I discovered the GameController framework has replacements for the key codes stuff. They have a "GCKeyCode" enum, so it's basically the same thing I had started doing myself for the other project I sent a screenshot off.

1624059501993.png

(Experimenting with different structuring right now, so it's not using the NSEvent keydown method and keyUp here. Instead it's polling every frame to make the keyboard and controller code paths the same. Though I haven't decided how to do this yet. I'm mostly just playing around to learn the Game Controller framework right now :p - But it apparently is designed to work for all game inputs not just controllers, so there is a GCKeyboard so your game input code can all look the same)
 

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
Thanks Casperes1996,

Good catch.

I'd noticed an announcement a while back but lost track of both its name and framework membership and was thus unable to come up with a search keyword to rediscover it thinking perhaps it'd all been a dream.
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
To some extend it sometimes almost feels like the history of computer science, is the history of reinventing the wheel, and telling ourselves the new wheels are better even though they do exactly the same thing effectively.
At least it sometimes feels that way to me when my code gets little "deprecated" warnings and I have to use another API that effectively does the same thing just differently ¯\_o_/¯

We like to minimise code duplication as programmers, but it still sometimes feels like we're running in circles and rewriting things perpetually.
I don't know, maybe just a jaded late night thought, haha, it's almost 2 am now, so I may feel different in the morning :p
 

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
To some extend it sometimes almost feels like the history of computer science, is the history of reinventing the wheel, and telling ourselves the new wheels are better even though they do exactly the same thing effectively.
At least it sometimes feels that way to me when my code gets little "deprecated" warnings and I have to use another API that effectively does the same thing just differently ¯\_o_/¯

We like to minimise code duplication as programmers, but it still sometimes feels like we're running in circles and rewriting things perpetually.
I don't know, maybe just a jaded late night thought, haha, it's almost 2 am now, so I may feel different in the morning :p

Started getting paid to program in mid '81 and I well know what you mean.
Where's the next hoop and how high do I ..., you want to what?!
 

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
Anyway ...

A quick once over and I'm not yet convinced the GameController.framework provides a complete solution.

Some further combing of the web led to this page with a solution similar to what I think you're trying also leads me to believe there is at present no complete solution offered - but I admit I only scanned it quickly and briefly.
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
Anyway ...

A quick once over and I'm not yet convinced the GameController.framework provides a complete solution.

Some further combing of the web led to this page with a solution similar to what I think you're trying also leads me to believe there is at present no complete solution offered - but I admit I only scanned it quickly and briefly.

The workaround for the Beep/alert sound is funnily enough the exact same conclusion I arrived at myself within 5 seconds of trying the Game Controller framework for this, though I thought to myself "This is a bit of a hack, must exist a better solution; I'll look into that later" haha.

Though on all the other points they raise it seems to either be specific to Catalyst; I.e. UIKit not he Mac which I don't personally care about, or actually be the behaviour I want for a Game Controller centric implementation of keyboard input events rather than just a general UI event system
 

casperes1996

macrumors 604
Original poster
Jan 26, 2014
7,485
5,650
Horsens, Denmark
So is it also your opinion that a real solution is still missing?
I would say I haven't looked into it far enough to conclude that yet. If there isn't a way to tell the system "This program takes key inputs its own way, don't beep" without just implementing dummy event methods that's a bit *****. - But I need to look more into options there.

UIKit and Catalyst I can't speak to at all here. I've only focused on Cocoa/AppKit+SpriteKit

Bit I feel like the GameController framework does offer a pretty good solution here, if the beeping can easily be circumvented, maybe just through a plist entry or some first responder shenanigans in the root view controller or something.

That is, a good solution for games where things like left and right shift being different keys can be desired behaviour. For event apps I would probably not even switch on key codes in the first place, but instead on the NSEvent's character member.

So in short, I feel like, if the beeping can be dealt with better, we have a good solution here actually. Though the Game Controller framework is one of those things that could use a bit better documentation - I started by watching one of the WWDC videos from just this year. Copied a code snipped directly from that to try out... And it was wrong. In a minor way, but still. And it wasn't even clean either. What they did was effectively this

controller.physicalInputProfile[ButtonWeWantToLookAt]

... They're just getting a dictionary of every single input the controller supports and indexing into it. Now they were using framework defined constants to resolve the string keys into the dictionary, but there exists a better solution, at least for the inputs they wanted. because you can actually just do this, I found out

controller.extendedGamepad?.buttonA

Now this is specifically for extended gamepads; it doesn't actually need to be for buttonA you can do the same thing for the non-extended profile. But you don't need to have a key to index the dictionary of all controller inputs like in Apple's own video. You can just ask for the button you want as a member variable directly. Much nicer IMO.

Anyways, small gripe but you'd want Apple's WWDC videos to show best practices, and I don't feel like they did in that one, though they might disagree.

But yeah for keyboard I do feel the solutions Game Controller and NSEvents offer are fine if some things apply that I still haven't looked into enough
 

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
We're pretty much in agreement but I'm still disappointed with what there is to work with.

I still prefer the old 'void GetKeys(KeyMap& theKeys)' which admittedly had the very same problems as today with different keyboards, but at least it could handle multiple simultaneous keypresses.

Have a pleasant evening.
 
Last edited:

lloyddean

macrumors 65816
May 10, 2009
1,047
19
Des Moines, WA
Well an example of the supported version of a GetKeys polling replacement.

Code:
//
//  GameScene.swift
//

import SpriteKit

class GameScene: SKScene {
 
    private var label : SKLabelNode?
    private var spinnyNode : SKShapeNode?
 
    override func didMove(to view: SKView) {
  
        self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
        if let label = self.label {
            label.alpha = 0.0
            label.run(SKAction.fadeIn(withDuration: 2.0))
        }
  
        // Create shape node to use during mouse interaction
        let sceneSize = self.size
        print("GameScene size: \(sceneSize)")

        let length = (sceneSize.width + sceneSize.height) * 0.05
        self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: length, height: length), cornerRadius: length * 0.3)
  
        if let spinnyNode = self.spinnyNode {
            spinnyNode.lineWidth = 2.5
      
            spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
            spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
                                              SKAction.fadeOut(withDuration: 0.5),
                                              SKAction.removeFromParent()]))
        }
    }
}

extension GameScene {

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
    }
}

extension GameScene {

    func throttleEngine(_ speed: Int) { print(#function, speed) }   // Engine Speed
    func aftView()                    { print(#function) }          // Aft View (Attack Computer Off or On)
    func frontView()                  { print(#function) }          // Front View (Attack Computer Off or On)
    func computerTargetting()         { print(#function) }          // Hyper Space Flight Targeting Computer On
    func manualTargetting()           { print(#function) }          // Hyper Space Flight Targeting Computer Off
    func galacticChartView()          { print(#function) }          // Brings up the Galactic Chart
    func hyperWarp()                  { print(#function) }          // Engages Hyperwarp Engine
    func longRangeScanView()          { print(#function) }          // Long Range Sector Scan (Attack Computer Off or On)
    func toggleShields()              { print(#function) }          // Toggles Shields On/Off   //shields.toggle()
    func toggleTrackingMode()         { print(#function) }          // Toggles Tracking Mode
    func togglePauseMode()            { print(#function) }          // Toggles Pause/Resume of game play
}


import GameController.GCKeyCodes

extension GameScene {

    // Keyboard Controls
    //
    // 0..9 Sets the speed of the ship
    //  A/F Sets the current view to aft or forward
    //  C/M Toggles the computer between Attack Computer or Manual targeting
    //    G Displays Galactic Chart
    //    H Engages Hyperwarp
    //    L Engages the Long Range Sector Scan
    //    S Engages shields
    //    T Engages Tracking Mode
    //    P Pauses the game

    override func keyDown(with event: NSEvent) {

        if let keys = GCKeyboard.coalesced?.keyboardInput {

            if keys.button(forKeyCode: .zero   )?.isPressed ?? false { throttleEngine(0) }
            if keys.button(forKeyCode: .keypad0)?.isPressed ?? false { throttleEngine(0) }
            if keys.button(forKeyCode: .one    )?.isPressed ?? false { throttleEngine(1) }
            if keys.button(forKeyCode: .keypad1)?.isPressed ?? false { throttleEngine(1) }
            if keys.button(forKeyCode: .two    )?.isPressed ?? false { throttleEngine(2) }
            if keys.button(forKeyCode: .keypad2)?.isPressed ?? false { throttleEngine(2) }
            if keys.button(forKeyCode: .three  )?.isPressed ?? false { throttleEngine(3) }
            if keys.button(forKeyCode: .keypad3)?.isPressed ?? false { throttleEngine(3) }
            if keys.button(forKeyCode: .four   )?.isPressed ?? false { throttleEngine(4) }
            if keys.button(forKeyCode: .keypad4)?.isPressed ?? false { throttleEngine(4) }
            if keys.button(forKeyCode: .five   )?.isPressed ?? false { throttleEngine(5) }
            if keys.button(forKeyCode: .keypad5)?.isPressed ?? false { throttleEngine(5) }
            if keys.button(forKeyCode: .six    )?.isPressed ?? false { throttleEngine(6) }
            if keys.button(forKeyCode: .keypad6)?.isPressed ?? false { throttleEngine(6) }
            if keys.button(forKeyCode: .seven  )?.isPressed ?? false { throttleEngine(7) }
            if keys.button(forKeyCode: .keypad7)?.isPressed ?? false { throttleEngine(7) }
            if keys.button(forKeyCode: .eight  )?.isPressed ?? false { throttleEngine(8) }
            if keys.button(forKeyCode: .keypad8)?.isPressed ?? false { throttleEngine(8) }
            if keys.button(forKeyCode: .nine   )?.isPressed ?? false { throttleEngine(9) }
            if keys.button(forKeyCode: .keypad9)?.isPressed ?? false { throttleEngine(9) }

            if keys.button(forKeyCode: .keyA   )?.isPressed ?? false { aftView() }
            if keys.button(forKeyCode: .keyF   )?.isPressed ?? false { frontView() }

            if keys.button(forKeyCode: .keyC   )?.isPressed ?? false { computerTargetting() }
            if keys.button(forKeyCode: .keyM   )?.isPressed ?? false { manualTargetting() }

            if keys.button(forKeyCode: .keyG   )?.isPressed ?? false { galacticChartView() }

            if keys.button(forKeyCode: .keyH   )?.isPressed ?? false { hyperWarp() }

            if keys.button(forKeyCode: .keyL   )?.isPressed ?? false { longRangeScanView() }

            if keys.button(forKeyCode: .keyS   )?.isPressed ?? false { toggleShields() }
            if keys.button(forKeyCode: .keyT   )?.isPressed ?? false { toggleTrackingMode() }
            if keys.button(forKeyCode: .keyP   )?.isPressed ?? false { togglePauseMode() }
        }
    }
}


extension GameScene {

    override func mouseDown(with event: NSEvent) {
        print("\(#function) Squeek!")
        self.touchDown(atPoint: event.location(in: self))
    }

    override func mouseDragged(with event: NSEvent) {
        self.touchMoved(toPoint: event.location(in: self))
    }

    override func mouseUp(with event: NSEvent) {
        self.touchUp(atPoint: event.location(in: self))
    }
}

extension GameScene {

    func touchDown(atPoint pos : CGPoint) {
        if let node = self.spinnyNode?.copy() as! SKShapeNode? {
            node.position = pos
            node.strokeColor = SKColor.green
            self.addChild(node)
        }
    }

    func touchMoved(toPoint pos : CGPoint) {
        if let node = self.spinnyNode?.copy() as! SKShapeNode? {
            node.position = pos
            node.strokeColor = SKColor.blue
            self.addChild(node)
        }
    }

    func touchUp(atPoint pos : CGPoint) {
        if let node = self.spinnyNode?.copy() as! SKShapeNode? {
            node.position = pos
            node.strokeColor = SKColor.red
            self.addChild(node)
        }
    }
}

Replaces the GameScene.swift file in an Xcode 14.x macOS Swift Game template based project.

Had Star Raiders on my mind when doing example.
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.