Handling mouse events
Categories: Adventure Game
In this series of posts I describe the process I've followed, as an amateur game designer, for developing an adventure game written in Swift using the SpriteKit framework for iOS.
Introduction
Last week I’ve implemented the callbacks for handling touch events on iOS and tvOS.
This week I’m implementing a callback for handling mouse events on macOS.
Moving the cursor (on macOS)
The only method that I need to override for now is mouseMoved
. It informs the
receiver that the mouse has moved. Each function call will update the position
of the cursor node with the relative position of the mouse in the game scene:
1
2
3
4
5
6
7
8
9
10
#if os(OSX)
// Mouse-based event handling
extension GameScene {
override func mouseMoved(with event: NSEvent) {
cursor.position = event.location(in: self)
}
}
#endif
The code is pretty straight forward, but soon as I build the app for the macOS target, I get this compilation error:
1
GameScene.swift: Use of unresolved identifier 'nextPosition'
This error happens because in the touchHandler
method I’ve defined
nextPosition
only if either os(iOS)
or os(tvOS)
is true, but then I’m
using it even when the target is macOS. In order to fix this issue, because
touchHandler
makes sense only for touch devices, I can just wrap the whole
method definition in a #if os(iOS) || os(tvOS)
block to conditionally compile
that portion of code only when the target is either iOS or tvOS:
1
2
3
4
5
#if os(iOS) || os(tvOS)
private func touchHandler(_ location: CGPoint) {
// ...
}
#endif
Fixed this error, the app builds successfully. The app starts, a window appears on the screen, I move the mouse and… nothing happens :(
The cursor is still at the center of the game scene.
After a small search, I find the solution: I’m missing a tracking area!
A tracking area is:
A region of a view that generates mouse-tracking and cursor-update events when the pointer is over that region.
The game’s tracking area covers the whole game scene and has the scene as its owner.
I’ve put this code in setUpScene
and wrapped it in a conditional compilation
block that checks if os(macOS)
is true:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#if os(OSX)
// Create a tracking area object with self as the owner
// (i.e., the recipient of mouse-tracking messages)
if let frame = view?.frame {
let trackingArea = NSTrackingArea(
rect: frame,
options: [.activeInKeyWindow, .mouseMoved],
owner: self,
userInfo: nil
)
// Add the tracking area to the view
view?.addTrackingArea(trackingArea)
}
#endif
This is the final result:
Hiding the default cursor
In the video above you’ll notice that both cursors, the default mac OS cursor, and the game cursor, are visible when the mouse is in the game window.
I would like to hide the default cursor as soon as the mouse enters the game’s tracking area, and unhide it when the mouse exits the tracking area.
To hide/unhide the cursor one can call NSCursor.hide()
/NSCursor.unhide()
, so
the first thing that I tried was to implement two more NSResponder
event
callbacks mouseEntered
/mouseExited
:
1
2
3
4
5
6
7
8
9
override func mouseEntered(with event: NSEvent) {
// Hide the default cursor
NSCursor.hide()
}
override func mouseExited(with event: NSEvent) {
// Show the default cursor
NSCursor.unhide()
}
For some reason these callbacks never run.
I tried to search for a solution, but I couldn’t find anything that doesn’t
involve subclassing the main game view (SKView
), so I’ve decided to just do
nothing for now. I guess I will come back and fix this issue later :)
Conclusion
Now the app can react to mouse events by moving the cursor on the screen.
There is still the issue that both the default cursor and the game cursor are visible in the game tracking area, but I will fix this issue later.
In the next post I’ll create the first sprite node for displaying the scene background.