Lesson 21: Advanced Controller Input

Lesson 7 introduced you to the STICK and STRIG statements to interact with joystick and game pad devices. These statements were introduced with the original QuickBasic language when joysticks and game pads were much simpler devices. Although these statements have been improved by QB64 to take advantage of new controller features they still leave much of the discovery process about connected controllers up to the programmer. How many total buttons does a joystick or game pad have? This question can only be answered using STRIG and getting the user involved to press buttons and having your code detect which button was pressed. How many total axes are there? Again, you'll need to use STICK in the same manner to find that answer. How do I use wheel inputs? This can't be done with STRIG or STICK unfortunately as wheel inputs (such as found on a mouse) were not a thing yet.

Fortunately the QB64 developers have taken this into account and created a whole new suite of controller statements to better work with today's modern controllers.  Furthermore, the new controller statements include the keyboard and mouse as valid controller devices and allow you to interact with them just as you would a joystick or game pad device. Let's get started.

The _DEVICES Statement

The _DEVICES statement is used to get the number of input devices connected to the computer. Input devices include the keyboard, the mouse, and all joystick and game pad controllers currently connected. Other types of controllers found could be gas/brake pedals, rudder pedals, and flight simulator throttle quadrants.

IMPORTANT NOTE: The _DEVICES statement must be called first and at least one time to activate the new QB64 controller statements. After _DEVICES has been used all the other new controller statements can be used throughout your code.

DeviceCount% = _DEVICES ' get total number of controllers connect to computer

DeviceCount% will now hold the total number of controllers found connected to the computer. Remember, besides the total number of joysticks and game pads found, the count will also include the keyboard and mouse if they are present.

The _DEVICE$ Statement

Once the total number of input devices have been determined by _DEVICES you can use _DEVICE$ to get descriptive information about each device found. The _DEVICE$ statement returns a string value holding information about the controller type, the descriptive name, and the input types (buttons, axes, wheels) available on the controller.

DeviceInfo$ = _DEVICE$(DeviceNumber%)

DeviceNumber% can be any number in the range from 1 to the total number of devices reported by _DEVICES. Type in the following example.

( This code can be found at .\tutorial\Lesson21\DevicesDemo.bas )

DIM DeviceCount AS INTEGER ' total number of controller device found
DIM Dcount AS INTEGER '      device loop counter

' DEVELOPER NOTE: Remember that _DEVICES must be called first and at least once before the other QB64
'                 controller related commands will function properly.

DeviceCount = _DEVICES '                                             get number of input devices
Dcount = 0 '                                                         reset loop counter
PRINT '                                                              print header
PRINT TAB(15); "Input devices currently connected to this computer"
PRINT TAB(15); "--------------------------------------------------"
PRINT
DO '                                                                 begin device loop
    Dcount = Dcount + 1 '                                            increment loop counter
   PRINT " Device"; STR$(Dcount); " - "; _DEVICE$(Dcount) '          print input device description
LOOP UNTIL Dcount = DeviceCount '                                    leave when all devices polled

Figure 1: Input Controllers Found

The information produced by _DEVICE$ is separated by square brackets [  ] and can either be parsed out or searched using the INSTR statement. One method I like to use is to parse the information into a user defined type contained within an array. Type in the following example.

( This code can be found at .\tutorial\Lesson21\DeviceInfoDemo.bas )

CONST FALSE = 0, TRUE = NOT FALSE '      truth detectors
CONST KBD_CONTROLLER = 1 '               controller is a keyboard
CONST MOUSE_CONTROLLER = 2 '             controller is a mouse
CONST JPAD_CONTROLLER = 3 '              controller is a joystick/game pad

TYPE TYPE_CONTROLLER '                   CONTROLLER PROPERTIES
    id AS INTEGER '                      device id number of controller (1 to _DEVICES)
    Kind AS INTEGER '                    the type of controller         (1, 2, or 3)
    Buttons AS INTEGER '                 controller buttons
    Axis AS INTEGER '                    controller Axis
    Wheels AS INTEGER '                  controller wheels
    Description AS STRING * 20 '         description of controller
END TYPE

REDIM Controller(0) AS TYPE_CONTROLLER ' controller information array
DIM DeviceCount AS INTEGER '             number of devices connected to computer (_DEVICES)
DIM Device AS STRING '                   device description                      (_DEVICE$)
DIM Dcount AS INTEGER '                  device loop counter
DIM Start AS INTEGER '                   start position of controller description
DIM Finish AS INTEGER '                  end position of controller description
DIM Format AS STRING '                   format of single chart line
DIM a AS STRING '                        axis "YES" or "NO"
DIM b AS STRING '                        button "YES" or "NO"
DIM w AS STRING '                        wheel "YES" or "NO"

Format = " |      ##       | \                             \ |   \ \   |  \ \ |   \ \  |" ' chart line format
DeviceCount = _DEVICES '                                                           get number of controllers
Dcount = 0 '                                                                       reset device loop counter
PRINT
PRINT
" +---------------------------------------------------------------------------+" ' set up chart
PRINT " |               Controllers found connected to your computer.               |"
PRINT " |---------------+---------------------------------+---------+------+--------|"
PRINT " | CONTROLLER ID |         CONTROLLER NAME         | BUTTONS | AXES | WHEELS |"
DO '                                                                               begin device search
    Dcount = Dcount + 1 '                                                          increment device loop counter
   REDIM _PRESERVE Controller(Dcount) AS TYPE_CONTROLLER '                         increase size of array by 1
    Device = _DEVICE$(Dcount) '                                                    get controller information
    Controller(Dcount).id = Dcount '                                               record device id number
   IF INSTR(Device, "[KEYBOARD]") THEN '                                           is this a keyboard?
        Controller(Dcount).Description = "Keyboard" '                              yes, save description
        Controller(Dcount).Kind = KBD_CONTROLLER '                                 record device type
   ELSEIF INSTR(Device, "[MOUSE]") THEN '                                          no, is this a mouse?
        Controller(Dcount).Description = "Mouse" '                                 yes, save description
        Controller(Dcount).Kind = MOUSE_CONTROLLER '                               record device type
   ELSEIF INSTR(Device, "[CONTROLLER]") THEN '                                     no, is this a controller?
       IF INSTR(Device, "[NAME]") THEN '                                           yes, with descriptive name?
            Start = INSTR(Device, "[NAME]") + 7 '                                  yes, get start of description
            Finish = INSTR(Start, Device, "]") '                                   get end of description
            Controller(Dcount).Description = MID$(Device, Start, Finish - Start) ' parse out descriptive name
            Controller(Dcount).Kind = JPAD_CONTROLLER '                            record device type
       END IF
   END IF
   IF INSTR(Device, "[BUTTON]") THEN Controller(Dcount).Buttons = TRUE '          record controller has buttons
   IF INSTR(Device, "[AXIS]") THEN Controller(Dcount).Axis = TRUE '               record controller has axes
   IF INSTR(Device, "[WHEEL]") THEN Controller(Dcount).Wheels = TRUE '            record controller has wheels
   IF Controller(Dcount).Buttons THEN b = "YES" ELSE b = "NO"
   IF Controller(Dcount).Axis THEN a = "YES" ELSE a = "NO" '                      print current chart line
   IF Controller(Dcount).Wheels THEN w = "YES" ELSE w = "NO"
   PRINT " |---------------+---------------------------------+---------+------+--------|"
   PRINT USING Format; Controller(Dcount).id; Controller(Dcount).Description; b; a; w
LOOP UNTIL Dcount = DeviceCount '                                                  leave when all devices polled
PRINT " +---------------+---------------------------------+---------+------+--------+"

Figure 2: Controller Information Saved to an Array

The controllers and related information are now stored in an array that can be called upon when needed. However, simply knowing if a controller has buttons, axes, or wheels is not very useful. It would be better if the number of each could be stored instead. Let's explore those options now and return to this code with highly useful modifications.

The _LASTBUTTON Statement

The _LASTBUTTON statement returns the number of buttons a controller contains.

Buttons% = _LASTBUTTON(DeviceNumber%)

DeviceNumber% can be any number in the range from 1 to the total number of devices reported by _DEVICES.

Buttons% will contain the number of buttons that the controller reports to the operating system. It's important to note however that the actual number of buttons on the controller may differ from the number that the controller reports.

The "USB Gamepad" seen listed in figures 1 and 2 above reports that it has 10 buttons but in actuality only 4 buttons exist on the controller.  To make things even more confusing the buttons are listed as buttons 2, 3, 9, and 10. This game pad is a cheap Nintendo USB clone that can be purchased on Amazon for $8 a pair. All of my more expensive name brand controllers, such as the Saitek ST290 Pro joystick and others, report the exact amount of buttons they contain. Keep this in mind when writing software; your game players may not have expensive name brand controllers at their disposal. A little interaction from your players may be necessary to identify their controller's button arrangement.

The _LASTAXIS Statement

The _LASTAXIS statement returns the number of axes a controller contains.

Axes% = _LASTAXIS(DeviceNumber%)

DeviceNumber% can be any number in the range from 1 to the total number of devices reported by _DEVICES.

Axes% will contain the number of axes that the controller reports to the operating system. Just like with _LASTBUTTON, the actual number of axes on the controller may differ from the number that the controller reports.

None of the controllers I own, not even the cheap USB game pad, mis-report their number of axes. I've tried steering wheels, flight yokes, rudder pedals, gas/brake pedals, game pads, joysticks, and throttle quadrants. All have correctly reported their number of axes. However, this may not always be the case.

The _LASTWHEEL Statement

The _LASTWHEEL statement returns the number of wheels a controller contains.

Wheels% = _LASTWHEEL(DeviceNumber%)

DeviceNumber% can be any number in the range from 1 to the total number of devices reported by _DEVICES.

Wheels% will contain the number of wheels that a controller reports to the operating system. Again, the actual number of wheels on the controller may differ from the number that the controller reports.

The only controller I have with a wheel is a mouse. None of the other controllers I have report any of their input methods as wheels. As far as I know wheels are only included with mice, but that may not be the case. If you happen to have a game controller, other than a mouse, that reports one or more of its input methods as being a wheel please contact me so I can purchase that device for testing.

Ok, now that we know the methods for identifying the number of buttons, axes, and wheels, let's revisit the previous code and modify it to take advantage of these new statements. Type in the following code.

( This code can be found at .\tutorial\Lesson21\DeviceInfoDemo2.bas )

CONST FALSE = 0, TRUE = NOT FALSE '      truth detectors
CONST KBD_CONTROLLER = 1 '               controller is a keyboard
CONST MOUSE_CONTROLLER = 2 '             controller is a mouse
CONST JPAD_CONTROLLER = 3 '              controller is a joystick/game pad

TYPE TYPE_CONTROLLER '                   CONTROLLER PROPERTIES
    id AS INTEGER '                      device id number of controller (1 to _DEVICES)
    Kind AS INTEGER '                    the type of controller         (1, 2, or 3)
    Buttons AS INTEGER '                 controller buttons
    Axis AS INTEGER '                    controller Axis
    Wheels AS INTEGER '                  controller wheels
    Description AS STRING * 20 '         description of controller
END TYPE

REDIM Controller(0) AS TYPE_CONTROLLER ' controller information array
DIM DeviceCount AS INTEGER '             number of devices connected to computer (_DEVICES)
DIM Device AS STRING '                   device description                      (_DEVICE$)
DIM Dcount AS INTEGER '                  device loop counter
DIM Start AS INTEGER '                   start position of controller description
DIM Finish AS INTEGER '                  end position of controller description
DIM Format AS STRING '                   format of single chart line

Format = " |      ##       | \                             \ |   ###   |  ##  |   ##   |" ' chart line format
DeviceCount = _DEVICES '                                                           get number of controllers
Dcount = 0 '                                                                       reset device loop counter
PRINT
PRINT
" +---------------------------------------------------------------------------+" ' set up chart
PRINT " |               Controllers found connected to your computer.               |"
PRINT " |---------------+---------------------------------+---------+------+--------|"
PRINT " | CONTROLLER ID |         CONTROLLER NAME         | BUTTONS | AXES | WHEELS |"
DO '                                                                               begin device search
    Dcount = Dcount + 1 '                                                          increment device loop counter
   REDIM _PRESERVE Controller(Dcount) AS TYPE_CONTROLLER '                         increase size of array by 1
    Device = _DEVICE$(Dcount) '                                                    get controller information
    Controller(Dcount).id = Dcount '                                               record device id number
   IF INSTR(Device, "[KEYBOARD]") THEN '                                           is this a keyboard?
        Controller(Dcount).Description = "Keyboard" '                              yes, save description
        Controller(Dcount).Kind = KBD_CONTROLLER '                                 record device type
   ELSEIF INSTR(Device, "[MOUSE]") THEN '                                          no, is this a mouse?
        Controller(Dcount).Description = "Mouse" '                                 yes, save description
        Controller(Dcount).Kind = MOUSE_CONTROLLER '                               record device type
   ELSEIF INSTR(Device, "[CONTROLLER]") THEN '                                     no, is this a controller?
       IF INSTR(Device, "[NAME]") THEN '                                           yes, with descriptive name?
            Start = INSTR(Device, "[NAME]") + 7 '                                  yes, get start of description
            Finish = INSTR(Start, Device, "]") '                                   get end of description
            Controller(Dcount).Description = MID$(Device, Start, Finish - Start) ' parse out descriptive name
            Controller(Dcount).Kind = JPAD_CONTROLLER '                            record device type
       END IF
   END IF
   IF INSTR(Device, "[BUTTON]") THEN Controller(Dcount).Buttons = _LASTBUTTON(Dcount) ' number of buttons
   IF INSTR(Device, "[AXIS]") THEN Controller(Dcount).Axis = _LASTAXIS(Dcount) '        number of axes
   IF INSTR(Device, "[WHEEL]") THEN Controller(Dcount).Wheels = _LASTWHEEL(Dcount) '    number of wheels
   PRINT " |---------------+---------------------------------+---------+------+--------|"
   PRINT USING Format; Controller(Dcount).id; Controller(Dcount).Description; controller(dcount).buttons;_
                        Controller(Dcount).Axis; Controller(Dcount).Wheels
LOOP UNTIL Dcount = DeviceCount '                                                  leave when all devices polled
PRINT " +---------------+---------------------------------+---------+------+--------+"

Figure 3: Number of buttons, axes, and wheels now reported

You're probably looking at the chart in figure 3 with a bit of confusion. A 512 key keyboard? A 3 wheeled mouse? While they are rare you can purchase a 512 key keyboard and 3 wheeled mouse from Amazon at fairly reasonable prices. I highly recommend using them! It's enabled me to program so much faster having a macro key for every single QB64 statement.

Kidding, LOL

The _LASTBUTTON statement will always report 512 buttons (also known as keys) and a mouse will always report 3 wheels (or 2 wheels if no actual wheel is present). The 512 reported buttons for a keyboard is due to scan codes that will be used (more on that later). The reason a mouse will report three wheels is because you're given the ability to use the mouse axes as wheels if you wish. Have you ever pressed your mouse's center button (or wheel) to switch the mouse into scroll mode? When in scroll mode the mouse's vertical axis is used like a scroll wheel instead (again, more on this later).

The _DEVICEINPUT Statement

The _DEVICEINPUT statement returns the controller device number when an event such as a button, axis, or wheel has been interacted with. _DEVICEINPUT can be used in two different ways:

Device% = _DEVICEINPUT '                return the controller number that has been interacted with
Active% = _DEVICEINPUT(DeviceNumber%) ' check a specific controller for interaction

DeviceNumber% can be any number in the range from 1 to the total number of devices reported by _DEVICES.

The first method above will return a number that correlates to any input controller that has been interacted with. From there you can use the second method to interact with that specific controller and gather information from it. Type the following example. This code assumes that you have a mouse and keyboard connected to your computer.

( This code can be found at .\tutorial\Lesson21\DeviceInputDemo.bas )

DIM DeviceCount AS INTEGER ' number of controllers (simply used to activate controller statements)
DIM DeviceInput AS INTEGER ' indicates a controller has been interacted with

PRINT
PRINT
" Interact with an input controller and it will highlight."
PRINT
PRINT
" Press ESC to exit program."
PRINT
DeviceCount = _DEVICES '                                         activate controller statements
DO '                                                             begin controller interaction loop
   _LIMIT 10 '                                                   slow things down a bit for viewing
   LOCATE 6, 1 '                                                 position cursor
   COLOR 7, 0 '                                                  gray on black text
   PRINT " Keyboard" '                                           list controllers
   PRINT " Mouse"
   PRINT " Controller 1"
   PRINT " Controller 2"
   PRINT " Controller 3"
   PRINT " Controller 4"
    DeviceInput = _DEVICEINPUT '                                 get any controller interaction
   IF DeviceInput THEN '                                         was a controller interacted with?
       WHILE _DEVICEINPUT(DeviceInput): WEND '                   yes, get the latest controller information
       COLOR 14, 1 '                                             yellow on blue text
       IF DeviceInput = 1 THEN '                                 was the keyboard interacted with?
           LOCATE 6, 1 '                                         yes, position cursor
           PRINT " Keyboard" '                                   highlight controller
       ELSEIF DeviceInput = 2 THEN '                             no, was the mouse interacted with?
           LOCATE 7, 1 '                                         yes, position cursor
           PRINT " Mouse" '                                      highlight controller
       ELSE '                                                    no, a joystick/game pad interacted with
           LOCATE 5 + DeviceInput, 1 '                           position cursor
           PRINT " Controller "; _TRIM$(STR$(DeviceInput - 2)) ' highlight controller
       END IF
   END IF
LOOP UNTIL _KEYDOWN(27) '                                        leave when ESC key pressed
SYSTEM '                                                         return to OS

Figure 4: Controller Interaction

As you interact with the various controllers attached to your computer the controller names will highlight. Line 20 of the code uses _DEVICEINPUT to scan all controller devices for interaction and will return a value of -1 if interaction is detected. Once interaction has been detected line 22 of the code clears all previous interactions of the specific controller that was interacted with leaving the latest and most up to date interaction.

In Lesson 7 you learned how the mouse has an input buffer that saves all movements and clicks. You can then either read in every mouse event one at time or clear the mouse buffer to get the latest mouse information. The same this is happening here with _DEVICEINPUT. When using the second method of _DEVICEINPUT one interaction at a time is read from the controller buffer. Line 22 clears that controller buffer leaving the last interaction to be read. If you REMark line 22 and run the code again you'll see this in action (you may also want to change the _LIMIT 10 in line 11 to _LIMIT 30 to speed things up a bit). Most of the time it's only desirable to get the latest information from a controller. If you don't clear the controller buffer you'll need to make sure your code handles and processes this extra input properly.

You may have noticed that one or more of your game controllers, or the mouse if you let it hover over the window, randomly gets activated. With game controllers this is typically caused by jitter created in the potentiometers that read joystick axes. I'm not sure why the mouse does it but my guess is that either QB64 or the operating system (or both) polls it for occasional information. Either way, this jitter and constant mouse polling can add up in the controller buffers quickly. Keep this in mind.

Now that we can detect interaction with devices let's investigate the statements that pinpoint which inputs are being interacted with.

The _BUTTON and _BUTTONCHANGE Statements

In Lesson 7 you learned that the STRIG statement worked in numbered pairs when detecting button presses. The first number in the pair is used to record a button event while the second number is used to get instant feedback on a button being either up or down. QB64's controller statements have broken this down into two separate statements; _BUTTONCHANGE to record a button event and _BUTTON to get instant feedback on a button's status.

Status% = _BUTTONCHANGE(ButtonNumber%) ' -1 for a press, 1 for a release, 0 for no interaction
Press% = _BUTTON(ButtonNumber%) '        -1 if pressed, 0 if not

ButtonNumber% can be any number in the range from 1 to _LASTBUTTON of the controller being checked. ButtonNumber% can't exceed the _LASTBUTTON value or an error will occur.

Status% will contain the value of -1 if a button was pressed since the last _BUTTONCHANGE, a value of 1 if a button was released since the last _BUTTONCHANGE, or a value of 0 if no interaction occurred.

Press% will contain a value of -1 if a button is currently pressed or a value of 0 if it is not.

Notice that both of these statements have no way of identifying which controller you want them to check. This is determined by the _DEVICINPUT statement previously discussed. The last controller referenced by _DEVICEINPUT is the current active controller used by _BUTTON and _BUTTONCHANGE.

Type the following code. This code assumes you have a keyboard, mouse, and at least one joystick or game pad controller connected.

( This code can be found at .\tutorial\Lesson21\ButtonDemo.bas )

CONST KBKEY_UP = 329 '       keyboard controller up    arrow key (button 1 in demo)
CONST KBKEY_LEFT = 332 '     keyboard controller left  arrow key (button 2 in demo)
CONST KBKEY_DOWN = 337 '     keyboard controller down  arrow key (button 3 in demo)
CONST KBKEY_RIGHT = 334 '    keyboard controller right arrow key (button 4 in demo)

DIM DeviceCount AS INTEGER ' number of controllers
DIM Button(_DEVICES, 10) '   create 10 buttons for each device
DIM Device AS INTEGER '      indicates a controller has been interacted with
DIM Format AS STRING '       chart line format
DIM Count AS INTEGER '       generic counter

Format = "       |     #      | ## | ## | ## | ## | ## | ## | ## | ## | ## | ##  |" ' chart line format
DeviceCount = _DEVICES '                                                              get number of controllers
DO '                                                                                  begin button search loop
   _LIMIT 30 '                                                                        don't hog the CPU
   LOCATE 2, 1 '                                                                      set up chart
   PRINT
   PRINT "                             Controller Button Test"
   PRINT "                        Test only for down (-1) or up (0)"
   PRINT
   PRINT "       +------------+----+----+----+----+----+----+----+----+----+-----+"
   PRINT "       | CONTROLLER | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 |"
   PRINT "       +------------+----+----+----+----+----+----+----+----+----+-----+"
    Device = _DEVICEINPUT '                                                           get controller interaction
   IF Device THEN '                                                                   controller interaction?
       WHILE _DEVICEINPUT(Device): WEND '                                             yes, get latest buffer info
       IF Device = 1 THEN '                                                           keyboard interaction?
            Button(1, 1) = _BUTTON(KBKEY_UP) '                                        yes, record buttons pressed
            Button(1, 2) = _BUTTON(KBKEY_LEFT)
            Button(1, 3) = _BUTTON(KBKEY_DOWN)
            Button(1, 4) = _BUTTON(KBKEY_RIGHT)
       ELSE '                                                                         no, mouse or controller
            Count = 0 '                                                               reset counter
           DO '                                                                       begin button query
                Count = Count + 1 '                                                   increment counter
                Button(Device, Count) = _BUTTON(Count) '                              save button status
           LOOP UNTIL Count = _LASTBUTTON(Device) OR Count = 10 '                     leave when buttons saved
       END IF
   END IF
   LOCATE 9, 1 '                                                                      position cursor
    Count = 0 '                                                                       reset counter
   DO '                                                                               begin button print loop
        Count = Count + 1 '                                                           increment counter
       PRINT USING Format; Count; Button(Count, 1); Button(Count, 2); Button(Count, 3); Button(Count, 4);_
                                  Button(Count, 5); Button(Count, 6); Button(Count, 7); button(Count, 8);_
                                  Button(Count, 9); Button(Count, 10) '               print buttons
   LOOP UNTIL Count = DeviceCount '                                                   leave when all printed
   PRINT "       +------------+----+----+----+----+----+----+----+----+----+-----+" ' finish chart
   PRINT
   PRINT "         The keyboard ARROW KEYS will register as buttons 1 through 4."
   PRINT "       Mouse pointer needs to hover over window for buttons to register."
   PRINT
   PRINT "                              Press ESC to exit"
LOOP UNTIL _KEYDOWN(27) '                                                             leave when ESC pressed
SYSTEM '                                                                              return to OS

Figure 5: Controller Button Test

This code produces the same results as the STRIG example in Lesson 7 with a few important differences. First, buttons are referenced by a logical order instead of the odd/even pairing that must be deciphered using STRIG. Second, the number of buttons for each controller is known because of the use of _LASTBUTTON. Third, unlike STRIG, _BUTTON will produce an error if a button number is used that does not exist on a controller.

Line 36 in the code ensures this does not happen. The loop will exit when the number of buttons on the controller is reached or 10 buttons have been polled, whichever is smaller.

As you can see in lines 27 through 30 of the code, keyboard "buttons" must be referenced by a scan code number. The scan code numbers for the four keyboard arrow keys are provided as CONSTants in lines 1 through 4 of the code. We'll talk more about the use of keyboards and their scan codes in a bit.

Change all the _BUTTON statements in the code to _BUTTONCHANGE statements and save the code as "ButtonChangeDemo.BAS" when finished. This code can also be found in your .\Tutorial\Lesson21\ subdirectory. Once you've made the changes or loaded the code execute it to see the difference between the _BUTTON and _BUTTONCHANGE statements.

The _AXIS Statement

The _AXIS statement returns the position of a specified axis contained in a controller. The use of _AXIS is much more straightforward that the STICK statement from Lesson 7. If a controller has, for instance, four axes then you simply identify them by an axis number rather than having to identify the controller and desired axis together as you do with the STICK statement.

Position! = _AXIS(AxisNumber%)

AxisNumber% can be any number in the range from 1 to the total number of axes reported by _LASTAXIS.

Position! will be a value expressed between -1 and 1 depending on the direction of axis deflection. For horizontal axes -1 is typically a full deflection the left and 1 a full deflection the the right. For vertical axes -1 is typically a full deflection upward and 1 a full deflection downward. A value of 0 for both axis orientations means the axis is centered. Controller axes come in many forms; joystick handles, sliders (throttles), top hats, gas/brake pedals, rudder pedals, game pad "plus" inputs, and joystick handle twists, etc..

Type in the following code.

( This code can be found at .\tutorial\Lesson21\AxisDemo.bas )

DIM DeviceCount AS INTEGER '      number of controllers
DIM Axis(_DEVICES, 8) AS SINGLE ' create 8 axes for each device
DIM Device AS INTEGER '           controller being checked
DIM Format AS STRING '            chart line format
DIM Count AS INTEGER '            generic counter

Format = "|  #  |##.#### |##.#### |##.#### |##.#### |##.#### |##.#### |##.#### |##.#### |" ' chart line format
DeviceCount = _DEVICES '                                                            get number of controllers
DO '                                                                                begin button search loop
   _LIMIT 30 '                                                                      don't hog the CPU
   LOCATE 2, 1 '                                                                    set up chart
   PRINT "                             Controller Axis Test"
   PRINT
   PRINT "+-----+--------+--------+--------+--------+--------+--------+--------+--------+"
   PRINT "| CON | 1 (1X) | 2 (1Y) | 3 (2X) | 4 (2Y) | 5 (3X) | 6 (3Y) | 7 (4X) | 8 (4Y) |"
   PRINT "+-----+--------+--------+--------+--------+--------+--------+--------+--------+"
    Device = 1 '                                                                    reset device counter
   DO '                                                                             begin axes search
        Device = Device + 1 '                                                       increment device counter
       WHILE _DEVICEINPUT(Device): WEND '                                           get latest update
        Count = 0 '                                                                 reset axis counter
       DO '                                                                         begin axis query
            Count = Count + 1 '                                                     increment axis counter
            Axis(Device, Count) = _AXIS(Count) '                                    save axis value
       LOOP UNTIL Count = _LASTAXIS(Device) OR Count = 8 '                          leave when all axes saved
   LOOP UNTIL Device = DeviceCount '                                                leave when all devices polled
    Device = 1 '                                                                    reset counter
   DO '                                                                             begin axis print loop
        Device = Device + 1 '                                                       increment counter
       PRINT USING Format; Device; Axis(Device, 1); Axis(Device, 2); Axis(Device, 3); Axis(Device, 4);_
                                    Axis(Device, 5); Axis(Device, 6); Axis(Device, 7); Axis(Device, 8)
   LOOP UNTIL Device = DeviceCount '                                                leave when all printed
   PRINT "+-----+--------+--------+--------+--------+--------+--------+--------+--------+" ' finish chart
   PRINT
   PRINT "        Mouse pointer needs to hover over window for axes to register."
   PRINT "                              Press ESC to exit"
LOOP UNTIL _KEYDOWN(27) '                                                            leave when ESC pressed
SYSTEM '                                                                             return to OS

Figure 6: Getting Axis Values

Analog axis inputs such as joystick handles and  throttle sliders will smoothly change from values between -1 and 1 depending on their current position with a value of 0 being the center point. Push button axis inputs such as top hats and Dpads or the "plus" style inputs found on game pads will only give three values, -1 for up or left, 0 for center or no button pressed, and 1 for down or right.

Note in Figure 6 above the first two values seen for controller 4. Axis 1 (1X) and axis 2 (1Y) are the Dpad ("plus" buttons) inputs on the cheap USB game pad I have. They are both showing a value of -0.0078. This is considered 0 or centered for these two axis inputs. It may be necessary at times to adjust the resolution of values you are seeking. Later in the lesson you'll be shown how to remap axis values for better resolution instead of relying on the values of -1 to 1 for input.

The _WHEEL Statement

The _WHEEL statement is used to read the current state of wheel inputs on controllers. The most common wheel of course will be the one included with a mouse.

Direction% = _WHEEL(WheelNumber%)

WheelNumber% can be any number in the range from 1 to the total number of wheels reported by _LASTWHEEL.

Direction% will contain a value of -1 when scrolling up, a value of 0 when no movement is detected, and a value of -1 when scrolling down. It's best to read _WHEEL values within a loop that scans the entire input buffer for wheel changes. Type in the following example code.

( This code can be found at .\tutorial\Lesson21\WheelDemo.bas )

DIM DeviceCount AS INTEGER ' number of controller devices
DIM x AS INTEGER '           x position of solid character
DIM Oldx AS INTEGER '        previous x position of solid character

DeviceCount = _DEVICES '                            activate controllers
x = 40 '                                            center solid character
DO '                                                begin wheel loop
   _LIMIT 30 '                                      don't hog the CPU
   CLS
   COLOR 7, 0 '                                     gray on black text
   LOCATE 2, 32 '                                   position cursor
   PRINT "Mouse Wheel Demo"
   LOCATE 4, 1
   PRINT "   Use mouse scroll wheel to change position of solid character on bar below."
   DO WHILE _DEVICEINPUT(2) '                       is mouse being interacted with?

        '+------------------------------------------------------+
        '| Here we need to scan entire buffer for wheel changes |
        '+------------------------------------------------------+

        x = x + _WHEEL(3) '                         yes, add scroll wheel value to x
   LOOP '                                           leave when no more interaction
   IF x < 1 THEN x = 80 ELSE IF x > 80 THEN x = 1 ' keep solid character on screen
   LOCATE 6, 1
   PRINT STRING$(80, 176) '                         print bar for solid character to slide on
   LOCATE 6, x
   COLOR 14, 0
   PRINT CHR$(219); '                               print solid character
   LOCATE 8, 33
   IF Oldx < x THEN PRINT "Scrolling DOWN" '        report direction of mouse wheel spin
   IF Oldx > x THEN PRINT " Scrolling UP"
    Oldx = x '                                      save character position as old
   LOCATE 10, 31
   COLOR 7, 0
   PRINT "Press ESC to exit."
LOOP UNTIL _KEYDOWN(27) '                           leave when ESC pressed
SYSTEM '                                            return to OS

Figure 7: Pulling a Wheelie With The Mouse

The code is very straightforward but lines 15 through 22 need examining. You'll see that _WHEEL values were gathered within a loop that reads each value contained in the input buffer. Once all values have been read the loop ends. If you try to read _WHEEL values after emptying the buffer, i.e WHILE _DEVICEINPUT(2): WEND, you'll almost never get accurate _WHEEL values if any. This is because of that periodic mouse polling pointed out earlier and the fact that the mouse is moved around a lot by the user which in many cases causes the last mouse input in the buffer to be a movement value. Keep this in mind when working with the mouse wheel.

IMPORTANT NOTE: The mouse supports the use of _WHEEL(1) and _WHEEL(2) to utilize mouse movements as wheel inputs. However, there seems to be an issue right now with that function not working properly in QB64. When this gets sorted out an update on the main page will posted and this section will be updated to show _WHEEL(1) and _WHEEL(2) use.

Example: Using the Keyboard as a Controller

The keyboard can be used as a game controller that supports the _BUTTON and _BUTTONCHANGE statements. Each key on the keyboard is assigned a scan code to be used with both statements. It's important to note that these scan codes are not the same as used with the _KEYHIT and _KEYDOWN statements. Figure 8 below is a copy of the scan code values for use with _BUTTON and _BUTTONCHANGE taken from the QB64PE wiki here.

Figure 8: Keyboard Button Scan Codes From the Wiki

Included in your .\Tutorial\Lesson21\ subdirectory is an include file you can use called "KeyboardConstants.BI" that contains all of the scan codes listed in figure 8 above associated with the name of the keys. Simply add the statement:

'$INCLUDE:'KeyboardConstants.BI'

or

'$INCLUDE:'.\Tutorial\Lesson21\KeyboardConstants.BI'

depending on where your source code is located to the top of your code and you'll be able to reference scan codes by name.

( This code can be found at .\tutorial\Lesson21\KeyboardConstants.BI )

' __________________________________________________________________________________________________________________________________________________
'/                                                                                                                   KEYBOARD KEY _BUTTON CONSTANTS \
CONST KBKEY_ESC = 2 '                                                                                                                               |
CONST KBKEY_F1 = 60 '              FUNCTION KEY ROW _BUTTON CONSTANTS                                                                               |
CONST KBKEY_F2 = 61 '               _____     _____ _____ _____ _____    _____ _____ _____ _____    _____ _____ _____ _____                         |
CONST KBKEY_F3 = 62 '              ||ESC||   ||F1 |||F2 |||F3 |||F4 ||  ||F5 |||F6 |||F7 |||F8 ||  ||F9 |||N/A|||F11|||F12||                        |
CONST KBKEY_F4 = 63 '              ||___||   ||___|||___|||___|||___||  ||___|||___|||___|||___||  ||___|||___|||___|||___||                        |
CONST KBKEY_F5 = 64 '              |/___\|   |/___\|/___\|/___\|/___\|  |/___\|/___\|/___\|/___\|  |/___\|/___\|/___\|/___\|                        |
CONST KBKEY_F6 = 65 '                                                                                                                               |
CONST KBKEY_F7 = 66 '              NOTE: F10 does not register as a _BUTTON. I know, strange but true.                                              |
CONST KBKEY_F8 = 67 '                    These _BUTTON constants were provided by gx.bi in dbox's Game Engine: https://github.com/boxgaming/gx      |
CONST KBKEY_F9 = 68 '                                                                                                                               |
CONST KBKEY_F11 = 88 '                                                                                                                              |
CONST KBKEY_F12 = 89 '                                                                                                                              |
CONST KBKEY_BACKQUOTE = 42 '       -----------------------------------------------------------------------------------------                        |
CONST KBKEY_1 = 3 '                FIRST KEY ROW _BUTTON CONSTANTS                                                                                  |
CONST KBKEY_2 = 4 '                                                                                                                                 |
CONST KBKEY_3 = 5 '                 _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _________                         |
CONST KBKEY_4 = 6 '                ||`~ |||1! |||2@ |||3# |||4$ |||5% |||6^ |||7& |||8* |||9( |||0) |||-_ |||=+ |||BACKSP ||                        |
CONST KBKEY_5 = 7 '                ||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||_______||                        |
CONST KBKEY_6 = 8 '                |/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/_______\|                        |
CONST KBKEY_7 = 9 '                                                                                                                                 |
CONST KBKEY_8 = 10 '                                                                                                                                |
CONST KBKEY_9 = 11 '                                                                                                                                |
CONST KBKEY_0 = 12 '                                                                                                                                |
CONST KBKEY_MINUS = 13 '                                                                                                                            |
CONST KBKEY_EQUALS = 14 '                                                                                                                           |
CONST KBKEY_BACKSPACE = 15 '                                                                                                                        |
CONST KBKEY_TAB = 16 '             -----------------------------------------------------------------------------------------                        |
CONST KBKEY_Q = 17 '               SECOND KEY ROW _BUTTON CONSTANTS                                                                                 |
CONST KBKEY_W = 18 '                                                                                                                                |
CONST KBKEY_E = 19 '                _______ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _______                         |
CONST KBKEY_R = 20 '               ||TAB  |||Q  |||W  |||E  |||R  |||T  |||Y  |||U  |||I  |||O  |||P  |||[{ |||]} |||\|   ||                        |
CONST KBKEY_T = 21 '               ||_____|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||_____||                        |
CONST KBKEY_Y = 22 '               |/_____\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/_____\|                        |
CONST KBKEY_U = 23 '                                                                                                                                |
CONST KBKEY_I = 24 '                                                                                                                                |
CONST KBKEY_O = 25 '                                                                                                                                |
CONST KBKEY_P = 26 '                                                                                                                                |
CONST KBKEY_LBRACKET = 27 '                                                                                                                         |
CONST KBKEY_RBRACKET = 28 '                                                                                                                         |
CONST KBKEY_BACKSLASH = 44 '                                                                                                                        |
CONST KBKEY_CAPSLOCK = 59 '        -----------------------------------------------------------------------------------------                        |
CONST KBKEY_A = 31 '               THIRD KEY ROW _BUTTON CONSTANTS                                                                                  |
CONST KBKEY_S = 32 '                                                                                                                                |
CONST KBKEY_D = 33 '                ________ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ ____________                         |
CONST KBKEY_F = 34 '               ||CAPS  |||A  |||S  |||D  |||F  |||G  |||H  |||J  |||K  |||L  |||;: |||'" |||ENTER     ||                        |
CONST KBKEY_G = 35 '               ||______|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||__________||                        |
CONST KBKEY_H = 36 '               |/______\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/__________\|                        |
CONST KBKEY_J = 37 '                                                                                                                                |
CONST KBKEY_K = 38 '                                                                                                                                |
CONST KBKEY_L = 39 '                                                                                                                                |
CONST KBKEY_SEMICOLON = 40 '                                                                                                                        |
CONST KBKEY_QUOTE = 41 '                                                                                                                            |
CONST KBKEY_ENTER = 29 '                                                                                                                            |
CONST KBKEY_LSHIFT = 43 '          -----------------------------------------------------------------------------------------                        |
CONST KBKEY_Z = 45 '               FOURTH KEY ROW _BUTTON CONSTANTS                                                                                 |
CONST KBKEY_X = 46 '                                                                                                                                |
CONST KBKEY_C = 47 '                _____________ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____________                         |
CONST KBKEY_V = 48 '               ||LEFT SHIFT |||Z  |||X  |||C  |||V  |||B  |||N  |||M  |||,< |||.> |||/? |||RIGHT SHIFT||                        |
CONST KBKEY_B = 49 '               ||___________|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___________||                        |
CONST KBKEY_N = 50 '               |/___________\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___________\|                        |
CONST KBKEY_M = 51 '                                                                                                                                |
CONST KBKEY_COMMA = 52 '                                                                                                                            |
CONST KBKEY_PERIOD = 53 '                                                                                                                           |
CONST KBKEY_SLASH = 54 '                                                                                                                            |
CONST KBKEY_RSHIFT = 55 '                                                                                                                           |
CONST KBKEY_LCTRL = 30 '           -----------------------------------------------------------------------------------------                        |
CONST KBKEY_LWIN = 348 '           FIFTH KEY ROW _BUTTON CONSTANTS                                                                                  |
CONST KBKEY_SPACEBAR = 58 '                                                                                                                         |
CONST KBKEY_RWIN = 349 '            ______ ______ ______ _____________________________________________ ______ ______ ______                         |
CONST KBKEY_RCTRL = 286 '          ||LCTL|||LWIN|||MENU|||SPACEBAR                                   |||N/A |||RWIN|||RCTL||                        |
CONST KBKEY_MENU = 350 '           ||____|||____|||____|||___________________________________________|||____|||____|||____||                        |
'                                  |/____\|/____\|/____\|/___________________________________________\|/____\|/____\|/____\|                        |
'                                                                                                                                                   |
CONST KBKEY_NUMLOCK = 326 '        -----------------------------------------------------------------------------------------                        |
CONST KBKEY_NUMPAD_DIVIDE = 310 '  NUMBER PAD _BUTTON CONSTANTS                                                                                     |
CONST KBKEY_NUMPAD_MULTIPLY = 56 '                                                                                                                  |
CONST KBKEY_NUMPAD_MINUS = 75 '     _____ _____ _____ _____                                                                                         |
ONST KBKEY_NUMPAD_7 = 72 '         ||NUM|||/  |||*  |||-  ||                                                                                        |
CONST KBKEY_NUMPAD_8 = 73 '        ||___|||___|||___|||___||                                                                                        |
CONST KBKEY_NUMPAD_9 = 74 '        |/___\|/___\|/___\|/___\|                                                                                        |
CONST KBKEY_NUMPAD_PLUS = 79 '      _____ _____ _____ _____                                                                                         |
CONST KBKEY_NUMPAD_4 = 76 '        ||7  |||8^ |||9  |||+  ||                                                                                        |
CONST KBKEY_NUMPAD_5 = 77 '        ||___|||___|||___|||   ||                                                                                        |
CONST KBKEY_NUMPAD_6 = 78 '        |/___\|/___\|/___\||   ||                                                                                        |
CONST KBKEY_NUMPAD_1 = 80 '         _____ _____ _____||   ||                                                                                        |
CONST KBKEY_NUMPAD_2 = 81 '        ||4< |||5  |||6> |||   ||                                                                                        |
CONST KBKEY_NUMPAD_3 = 82 '        ||___|||___|||___|||___||                                                                                        |
CONST KBKEY_NUMPAD_ENTER = 285 '   |/___\|/___\|/___\|/___\|                                                                                        |
CONST KBKEY_NUMPAD_0 = 83 '         _____ _____ _____ _____                                                                                         |
CONST KBKEY_NUMPAD_PERIOD = 84 '   ||1  |||2v |||3  |||E  ||                                                                                        |
'                                  ||___|||___|||___|||N  ||                                                                                        |
'                                  |/___\|/___\|/___\||T  ||                                                                                        |
'                                   ___________ _____||E  ||                                                                                        |
'                                  ||0        |||.  |||R  ||                                                                                        |
'                                  ||_________|||___|||___||                                                                                        |
'                                  |/_________\|/___\|/___\|                                                                                        |
'                                                                                                                                                   |
CONST KBKEY_UP = 329 '             -----------------------------------------------------------------------------------------                        |
CONST KBKEY_LEFT = 332 '           ARROW KEY _BUTTON CONSTANTS                                                                                      |
CONST KBKEY_DOWN = 337 '                                                                                                                            |
CONST KBKEY_RIGHT = 334 '                 _____                                                                                                     |
'                                        || ^ ||                                                                                                    |
'                                        ||___||                                                                                                    |
'                                        |/___\|                                                                                                    |
'                                   _____ _____ _____                                                                                               |
'                                  || < ||| v ||| > ||                                                                                              |
'                                  ||___|||___|||___||                                                                                              |
'                                  |/___\|/___\|/___\|                                                                                              |
'                                                                                                                                                   |
CONST KBKEY_SCRLK = 71 '           -----------------------------------------------------------------------------------------                        |
CONST KBKEY_PAUSE = 70 '           POSITION KEY _BUTTON CONSTANTS                                                                                   |
CONST KBKEY_INSERT = 339 '                                                                                                                          |
CONST KBKEY_HOME = 328 '            _____ _____ _____                                                                                               |
CONST KBKEY_PAGEUP = 330 '         ||N/A|||SCR|||PAU||                                                                                              |
CONST KBKEY_DELETE = 340 '         ||___|||___|||___||               NOTE: Pause not working on my system?                                          |
CONST KBKEY_END = 336 '            |/___\|/___\|/___\|                                                                                              |
CONST KBKEY_PAGEDOWN = 338 '        _____ _____ _____|                                                                                              |
'                                  ||INS|||HOM|||PUP||                                                                                              |
'                                  ||___|||___|||___||                                                                                              |
'                                  |/___\|/___\|/___\|                                                                                              |
'                                   _____ _____ _____                                                                                               |
'                                  ||DEL|||END|||PDN||                                                                                              |
'                                  ||___|||___|||___||                                                                                              |
'                                  |/___\|/___\|/___\|                                                                                              |
'                                                                                                                                                   |
'\__________________________________________________________________________________________________________________________________________________/

Type in the following example code.

( This code can be found at .\tutorial\Lesson21\KeyboardDemo.bas )

'$INCLUDE:'.\Tutorial\Lesson21\KeyboardConstants.BI'

DIM DeviceCount
DIM x AS INTEGER
DIM y AS INTEGER

DeviceCount = _DEVICES '                    activate controller statements
SCREEN _NEWIMAGE(640, 480, 32) '            graphics screen
x = 319 '                                   center circle
y = 239
DO
   _LIMIT 30 '                              don't hog the CPU
   CLS
   LOCATE 2, 12 '                           position cursor
   PRINT "Use the keyboard arrow keys to move circle. ESC to exit."
   WHILE _DEVICEINPUT(1): WEND '            reference the keyboard and get latest update
   IF _BUTTON(KBKEY_UP) THEN y = y - 5 '    move circle upward
   IF _BUTTON(KBKEY_DOWN) THEN y = y + 5 '  move circle downward
   IF _BUTTON(KBKEY_LEFT) THEN x = x - 5 '  move circle to the left
   IF _BUTTON(KBKEY_RIGHT) THEN x = x + 5 ' move circle to the right
   CIRCLE (x, y), 30 '                      draw the circle
   _DISPLAY '                               update screen with changes
LOOP UNTIL _BUTTON(KBKEY_ESC) '             leave when ESC button pressed
SYSTEM '                                    return to OS

As you can see using _BUTTON to reference keyboard keys acts similar to _KEYDOWN. You can hold the keys down for continuous movement and hold multiple keys for diagonal movement. Always remember to reference the keyboard controller first, as was done in line 16 of the code, so the _BUTTON and _BUTTONCHANGE statements will use the keyboard when scanning for button presses.

Example: Using a Joystick Controller

For this section you will need to have an analog joystick connected to your system. Analog inputs on a joystick range smoothly from -1 to 1 in both the vertical and horizontal axes. You can use these values in a number of different ways. By multiplying the _AXIS values by a set speed you can have an object increase or decrease in speed with direct relation to the amount of joystick handle deflection.  Let's explore this method first. Type in the following example code.

( This code can be found at .\tutorial\Lesson21\JoystickDemo1.bas )

CONST JOYSTICK = 3 '         joystick controller id (may need to change to match your joystick id)

DIM DeviceCount AS INTEGER ' number of controller devices (used to activate)
DIM Threshold AS SINGLE '    amount of deflection needed to activate movement
DIM Xaxis AS SINGLE '        value of joystick x axis (horizontal)
DIM Yaxis AS SINGLE '        value of joystick y axis (vertical)
DIM x AS SINGLE '            x location of circle
DIM y AS SINGLE '            y location of circle
DIM Speed AS INTEGER '       axis speed multiplier

DeviceCount = _DEVICES '                                        activate controller statements
Threshold = .05 '                                               set axis threshold
Speed = 10 '                                                    set axis speed multiplier
x = 319 '                                                       center circle
y = 239
SCREEN _NEWIMAGE(640, 480, 32) '                                graphics screen
DO '                                                            begin controller loop
   _LIMIT 60 '                                                  60 frames per second
   CLS '                                                        clear screen
   LOCATE 2, 16 '                                               position cursor
   PRINT "Use joystick handle to move circle. Esc to exit." '   display instructions
   WHILE _DEVICEINPUT(JOYSTICK): WEND '                         reference joystick 1 and get latest update
    Xaxis = _AXIS(1) '                                          get current horizontal value
    Yaxis = _AXIS(2) '                                          get current vertical value
   IF ABS(Xaxis) >= Threshold THEN x = x + Xaxis * Speed '      move circle horizontally if threshold met
   IF ABS(Yaxis) >= Threshold THEN y = y + Yaxis * Speed '      move circle vertically if threshold met
   IF x < 0 THEN x = x + 639 ELSE IF x > 639 THEN x = x - 639 ' keep horizontal movement on screen
   IF y < 0 THEN y = y + 479 ELSE IF y > 479 THEN y = y - 479 ' keep vertical movement on screen
   CIRCLE (x, y), 30 '                                          draw the circle
   _DISPLAY '                                                   update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                       leave when ESC pressed
SYSTEM '                                                        return to OS

NOTE: You may need to change the value in line 1 of the code to match your joystick's controller number.

Small joystick handle movements yield small movements of the circle. The further you deflect the joystick handle the faster the circle moves. Something to take notice of is how the Threshold variable is being used. As mentioned before analog joystick axes are prone to slight jitter when they are centered. To remove the jitter the value in Threshold is used to test each axis for at least that amount of deflection. If you set the Threshold variable in line 12 to 0 you may see this jitter first-hand. Cheap joysticks usually contain quite a bit of jitter while more expensive ones may not contain any at all. Keep in mind that all of your users may not have expensive joysticks at their disposal.

Another way to use the _AXIS values is to treat them as a switch. When a certain threshold of deflection is met the axis is seen as "down" like a button. Type in the following example code.

( This code can be found at .\tutorial\Lesson21\JoystickDemo2.bas )

CONST JOYSTICK = 3 '         joystick controller id (may need to change to match your joystick id)

DIM DeviceCount AS INTEGER ' number of controller devices (used to activate)
DIM Threshold AS SINGLE '    amount of deflection needed to activate movement
DIM Xaxis AS SINGLE '        value of joystick x axis (horizontal)
DIM Yaxis AS SINGLE '        value of joystick y axis (vertical)
DIM x AS SINGLE '            x location of circle
DIM y AS SINGLE '            y location of circle
DIM Speed AS INTEGER '       speed of circle

DeviceCount = _DEVICES '                                        activate controller statements
Threshold = .5 '                                                set axis threshold
Speed = 5 '                                                     set speed of circle
x = 319 '                                                       center circle
y = 239
SCREEN _NEWIMAGE(640, 480, 32) '                                graphics screen
DO '                                                            begin controller loop
   _LIMIT 60 '                                                  60 frames per second
   CLS '                                                        clear screen
   LOCATE 2, 16 '                                               position cursor
   PRINT "Use joystick handle to move circle. Esc to exit." '   display instructions
   WHILE _DEVICEINPUT(JOYSTICK): WEND '                         reference joystick 1 and get latest update
    Xaxis = _AXIS(1) '                                          get current horizontal value
    Yaxis = _AXIS(2) '                                          get current vertical value
   IF ABS(Xaxis) >= Threshold THEN x = x + SGN(Xaxis) * Speed ' move circle horizontally if threshold met
   IF ABS(Yaxis) >= Threshold THEN y = y + SGN(Yaxis) * Speed ' move circle vertically if threshold met
   IF x < 0 THEN x = x + 639 ELSE IF x > 639 THEN x = x - 639 ' keep horizontal movement on screen
   IF y < 0 THEN y = y + 479 ELSE IF y > 479 THEN y = y - 479 ' keep vertical movement on screen
   CIRCLE (x, y), 30 '                                          draw the circle
   _DISPLAY '                                                   update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                       leave when ESC pressed
SYSTEM '                                                        return to OS

A third method sets the joystick's axes in a direct relationship with the screen coordinates by mapping the values of the axes to a different value range. Type in the following example code.

( This code can be found at .\tutorial\Lesson21\JoystickDemo3.bas )

CONST JOYSTICK = 3 '         joystick controller id (may need to change to match your joystick id)

DIM DeviceCount AS INTEGER ' number of controller devices (used to activate)
DIM Xaxis AS SINGLE '        value of joystick x axis (horizontal)
DIM Yaxis AS SINGLE '        value of joystick y axis (vertical)
DIM x AS SINGLE '            x location of circle
DIM y AS SINGLE '            y location of circle

DeviceCount = _DEVICES '                                      activate controller statements
SCREEN _NEWIMAGE(640, 480, 32) '                              graphics screen
DO '                                                          begin controller loop
   _LIMIT 60 '                                                60 frames per second
   CLS '                                                      clear screen
   LOCATE 2, 16 '                                             position cursor
   PRINT "Use joystick handle to move circle. Esc to exit." ' display instructions
   WHILE _DEVICEINPUT(JOYSTICK): WEND '                       reference joystick 1 and get latest update
    x = Map_Axis(_AXIS(1), 0, 639) '                          map axis 1 to 0 through 639
    y = Map_Axis(_AXIS(2), 0, 479) '                          map axis 2 to 0 through 479
   CIRCLE (x, y), 30 '                                        draw the circle
   _DISPLAY '                                                 update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                     leave when ESC pressed
SYSTEM '                                                      return to OS

' _________________________________________________________________________________________________________
'/                                                                                                         \
FUNCTION Map_Axis (AxisValue AS SINGLE, Lower AS INTEGER, Upper AS INTEGER) '                     Map_Axis |
    ' _____________________________________________________________________________________________________|___
    '/                                                                                                         \
    '| Map the axis value coming in to the new range as set by Lower and Upper                                 |
    '\_________________________________________________________________________________________________________/

    Map_Axis = INT(Lower + (AxisValue + 1) * (Lower - Upper) / -2) ' convert input to adjusted output

END FUNCTION

A function has been created in this code called Map_Axis that takes an axis value and converts it to a new value range. In line 17 the horizontal axis value is converted to 0 through 639 (the width of the screen) and in line 18 the vertical axis in converted to 0 through 479 (the height of the screen). The joystick handle now has a direct relationship to screen. Wherever the joystick handle is pointing the circle will be drawn on the screen in the same position accordingly.

Example: Using a Game Pad Controller

Game pads come in many different flavors, from Nintendo style pads with a simple Dpad ("plus") for axis control, to the more complex with analog top hats that act like joystick handles and top hats that act like Dpad axis inputs. For the analog top hats simply use the joystick examples above to get the effect you desire. Dpad axis inputs however don't offer a full range of motion but instead simply return the values of -1 (up or left), 0 (center), and 1 (down or right). Therefore Dpad axes can be treated as buttons similar to the joystick demo #2 above. Plug in a simple game pad now if you have one. Preferably one with nothing more than a Dpad for axis input (i.e. the original Nintendo style controller). Type in the following example code.

( This code can be found at .\tutorial\Lesson21\GamepadDemo.bas )

CONST GAMEPAD = 3 '          game pad controller id (may need to change to match your game pad id)

DIM DeviceCount AS INTEGER ' number of controller devices (used to activate)
DIM Threshold AS SINGLE '    amount of deflection needed to activate movement
DIM Xaxis AS SINGLE '        value of joystick x axis (horizontal)
DIM Yaxis AS SINGLE '        value of joystick y axis (vertical)
DIM x AS SINGLE '            x location of circle
DIM y AS SINGLE '            y location of circle
DIM Speed AS INTEGER '       speed of circle

DeviceCount = _DEVICES '                                          activate controller statements
Threshold = .9 '                                                  set axis threshold
Speed = 5 '                                                       set speed of circle
x = 319 '                                                         center circle
y = 239
SCREEN _NEWIMAGE(640, 480, 32) '                                  graphics screen
DO '                                                              begin controller loop
   _LIMIT 60 '                                                    60 frames per second
   CLS '                                                          clear screen
   LOCATE 2, 14 '                                                 position cursor
   PRINT "Use game pad direction pad move circle. Esc to exit." ' display instructions
   WHILE _DEVICEINPUT(GAMEPAD): WEND '                            reference game pad 1 and get latest update
    Xaxis = _AXIS(1) '                                            get current horizontal value
    Yaxis = _AXIS(2) '                                            get current vertical value
   IF ABS(Xaxis) >= Threshold THEN x = x + Xaxis * Speed '        move circle horizontally if threshold met
   IF ABS(Yaxis) >= Threshold THEN y = y + Yaxis * Speed '        move circle vertically if threshold met
   IF x < 0 THEN x = x + 639 ELSE IF x > 639 THEN x = x - 639 '   keep horizontal movement on screen
   IF y < 0 THEN y = y + 479 ELSE IF y > 479 THEN y = y - 479 '   keep vertical movement on screen
   CIRCLE (x, y), 30 '                                            draw the circle
   _DISPLAY '                                                     update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                         leave when ESC pressed
SYSTEM '                                                          return to OS

The code in this example is almost identical to the joystick 2 demo above with a notable difference. A threshold value is still being used and set to a very high .9 setting. Even though Dpads and top hats are supposed to return -1 and 1 they sometimes may not. The top hat on my joystick every once in a while will return +/- .99, just shy of +/- 1. By using a high threshold value you're guaranteed to pick up every Dpad or top hat button press.

QB64 Controller Library

I wrote a controller library that puts all of the concepts and ideas in this lesson into an easy to use set of statements. The library is included in your .\Tutorial\Lesson21\ subdirectory as a ZIP file named "ControllerLibV1.10.ZIP". The individual files within the ZIP file have also been extracted into your subdirectory as well. As of this writing the library is on version 1.10. You can find the latest version and more information about the library here at the QB64 Phoenix Edition forum. You can also download the controller library directly here if you prefer.

The controller library will allow you to do the following:

There is a mini-game included with the library (Configure_buttons.BAS) to illustrate how to use the library and how to allow the player to assign their own buttons and axes to movements and actions (see Figures 9 and 10 below). Instructions on how to use the library are included at the top of the CONTROLLER.BI basic include file. See Lesson 20 on how to use and incorporate .BI and .BM library files into your projects.

Figure 9: Mini-game in action

Figure 10: Player configuring controller inputs

Commands and Concepts Learned