Mouse
One of the most well-known non-controller peripherals is the mouse. While not supported by many games, if you're making one that needs accurate aiming or pointing (especially if fast paced) then adding support for it can be great, especially since the mouse is reasonably easy to acquire.
The US version has two more buttons, while the Japanese and European versions can be used like a handheld trackball (otherwise, they work the same way).
- Setting up
- Reading the mouse
- Moving the cursor around
- Click and double click
- Left-handed mouse
- Mega Mouse vs Sega Mouse
Setting up
If you try to detect it using the peripheral
ID, the mouse has an ID of 3 (%0011
).
Setting up the mouse is similar to setting up
the controllers, except that instead of writing $40
to
the control and data ports, you write $60
.
; Assuming 2P port
move.b #$60, (IoCtrl2)
move.b #$60, (IoData2)
The addresses come from the I/O ports article.
Reading the mouse
First, read back from the data port and check if bits 3-0 are
0000
. If they aren't, assume no mouse is here.
Now, every time you read the mouse (once per frame, like the controllers), it returns the data in a packet, which consists of nine nibbles. The idea goes like this (all this on the data port):
- Write
$20
, wait until bit 4 = 1, read nibble from bits 3-0 - Write
$00
, wait until bit 4 = 0, read nibble from bits 3-0 - Write
$20
, wait until bit 4 = 1, read nibble from bits 3-0 - Write
$00
, wait until bit 4 = 0, read nibble from bits 3-0 - Write
$20
, wait until bit 4 = 1, read nibble from bits 3-0 - Write
$00
, wait until bit 4 = 0, read nibble from bits 3-0 - Write
$20
, wait until bit 4 = 1, read nibble from bits 3-0 - Write
$00
, wait until bit 4 = 0, read nibble from bits 3-0 - Write
$20
, wait until bit 4 = 1, read nibble from bits 3-0 - Write
$60
to finish
You may want to put a timeout, i.e. if it takes too long for bit 4 to set to the correct value, bail out. This can happen if the mouse gets unplugged in the middle of reading (your game would hang up without the timeout!).
Some sample code (it reads the nibbles into a buffer):
; Reads a mouse packet
; d0.w = 0 on success
; -1 on failure
ReadMouse:
; I/O data port to read from
; Use IoData2 for player 2
lea (IoData1), a0
; Buffer to store the nibbles
; (we use up one byte per nibble)
lea (MouseBuffer), a1
; Look-up table, see below
lea (@Table), a2
; Now loop through all nibbles
; (the -1 is because of DBF)
moveq #9-1, d0
@Loop:
; Tell mouse to send next nibble
; and wait for it to be ready (if
; it takes too long we bail out)
move.b (a2)+, (a0)
move.w #$FF, d7
@Wait:
moveq #$10, d6 ; Mask out bit 4 and
and.b (a0), d6 ; check if it matches
cmp.b (a2), d6 ; what we want
beq.s @GotIt
dbf d7, @Wait ; Keep waiting if not,
bra @Error ; or throw error if we
; waited way too long
; Got the nibble, store it into a
; buffer and move onto the next one
@GotIt:
moveq #$0F, d6 ; Mask out the nibble
and.b (a0), d6 ; and store it into
move.b d6, (a1)+ ; the buffer
lea 1(a2), a2 ; Advance look-up table
dbf d0, @Loop ; Keep looping
moveq #0, d0 ; Success!
rts ; End of subroutine
@Error:
moveq #-1, d0 ; Failure...
rts ; End of subroutine
; Look-up table used in the loop above
; 1st byte is what to write, 2nd byte is
; what to wait for (in bit 4)
@Table:
dc.b $20,$10 ; 1st nibble
dc.b $00,$00 ; 2nd nibble
dc.b $20,$10 ; 3rd nibble
dc.b $00,$00 ; 4th nibble
dc.b $20,$10 ; 5th nibble
dc.b $00,$00 ; 6th nibble
dc.b $20,$10 ; 7th nibble
dc.b $00,$00 ; 8th nibble
dc.b $20,$10 ; 9th nibble
OK, now onto the nibbles:
Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
---|---|---|---|---|
1st nibble ($20 )
| 1
| 0
| 1
| 1
|
2nd nibble ($00 )
| 1
| 1
| 1
| 1
|
3rd nibble ($20 )
| 1
| 1
| 1
| 1
|
4th nibble ($00 )
| Yo
| Xo
| Ys
| Xs
|
5th nibble ($20 )
| C
| M
| R
| L
|
6th nibble ($00 )
| X7
| X6
| X5
| X4
|
7th nibble ($20 )
| X3
| X2
| X1
| X0
|
8th nibble ($00 )
| Y7
| Y6
| Y5
| Y4
|
9th nibble ($20 )
| Y3
| Y2
| Y1
| Y0
|
First two things to watch out:
-
The first three nibbles must match
%1011 %1111 %1111
, this is the mouse signature. If they're something else then whatever is plugged in isn't a mouse. -
The
Xo
andYo
bits tell you if the motion in either axis overflowed (doesn't fit). If either bit is set, ignore the whole packet, since that means the value can't be trusted. Just pretend that nothing happened (i.e. mouse is plugged, but did not move).
The mouse motion is given by X7-X0
and Y7-Y0
.
The Xs
and Ys
flags are the sign and act as if
they were an extra bit: they indicate if you should sign extend with
zeroes or ones. Make sure to not sign extend 0 or you'll get
-256 instead. X increments to the right, Y increments upwards
(you may want to flip its sign).
Sample code to do the sign extension:
; d0.w = X coordinate (6th-7th nibbles)
; d1.w = Y coordinate (8th-9th nibbles)
; d2.b = flags (4th nibble)
; If you didn't already, make sure
; the coordinates are word-sized
and.w #$FF, d0
and.w #$FF, d1
; The code below uses a double NEG
; to ensure only values that aren't
; 0 are sign extended (when it's 0
; then it leaves it as 0)
; Check if X is negative
btst #0, d2
beq.s @PositiveX
neg.b d0
neg.w d0
@PositiveX:
; Check if Y is negative
btst #1, d2
beq.s @PositiveY
neg.b d1
neg.w d1
@PositiveY:
The bits in the 5th nibble tell you the state of each button
(1
= pressed, 0
= released):
L
: left buttonR
: right buttonM
: middle buttonC
: Start button
Moving the cursor around
The mouse tells you how much it moved, but it has no idea what's going on screen. If your game is using the mouse to move a cursor then it's up to you to keep track of it.
Moving the cursor is a matter of adding the motion to its current position:
cursor_x = cursor_x + mouse_x
cursor_y = cursor_y + mouse_y
After that you also have to take care that the cursor doesn't go beyond the screen boundaries (or whatever rectangle you want to restrict it to). After applying the motion make sure to limit it to the boundaries:
if cursor_x < min_x then cursor_x = min_x
if cursor_x > max_x then cursor_x = max_x
if cursor_y < min_y then cursor_y = min_y
if cursor_y > max_y then cursor_y = max_y
Click and double click
Detecting a "click" is pretty much the same thing as checking when a button just goes down in a controller, but with the mouse buttons.
To check for a double click, you need to set up a timer when the first click happens. If another click comes in within certain threshold (e.g. within 30 frames or so), you can treat it as a double click. So something like this:
if clicked == true
if click_timer > 0
double_clicked = true
click_timer = 0
else
double_clicked = false
click_timer = click_threshold
end
else
if click_timer > 0
click_timer = click_timer - 1
end
end
Further improvements:
- You may want to make the threshold configurable (some people struggle to double click in time, while some other people may accidentally double click when they wanted another single click)
- You may want to also check if the cursor moved too much from is location from the first click (maybe 3px or 4px?), this will help reduce spurious double clicks if the mouse was moving.
Left-handed mouse
The mouse has no idea of whether it's right-handed or left-handed, this is up to you. Generally, this means swapping the bits for the left and right buttons while configured for left-handed use. How you do this is up to you.
If you're writing in assembly there's a handy trick with the extend flag:
roxr.b #1, d0 ; Push L into extend
ror.b #1, d0 ; Push R into bit 7
roxl.b #2, d0 ; Pull both back
Mega Mouse vs Sega Mouse
There are two variants of the mouse: Mega Mouse is the American version, while Sega Mouse is the Japanese and European version (yes, they got the names backwards for the mouse). While the protocol is the same, there are some important differences regarding the buttons:
- Sega Mouse only has two buttons (left and right). This means you can't rely on the middle or Start buttons if you want to support this variant (albeit they can still be convenient if used for optional shortcuts).
- Sega Mouse can be flipped upside down to be used like a trackball, and pressing the ball acts like a left button click (this is so you can use the mouse without leaving it on a surface, your index finger rests on the right button).