|  
                               DirectXInput:
                              Action Mapping 
                              Author: 
                                Jack Hoxley 
                                Written: 20th June 2002 
                                Contact: [EMail] 
                                Download: IN3_ActMap.Zip
                              (15kb) 
                               
                              Contents 
                                of this lesson 
                                1. Introduction 
                                2. About Genre's 
                              3. Initialization and Configuration 
                              4. Interpreting and understanding  
                              5. Conclusion 
                               
                              1. Introduction 
                              Welcome to the third tutorial in the
                              DirectXInput series. DirectInput is one of the
                              simplest parts of the DirectX API - most people
                              will be content with using just the keyboard mouse
                              and joystick to control their applications. Once
                              these three have been learnt there's not much else
                              to do! 
                              However, there is one last thing - Action
                              Mapping, a new addition in DirectX8, and probably
                              about as advanced and interesting as
                              input-programming gets. I'm surprised its taken
                              this long to work its way into the DirectX API
                              really; DirectInput has always been about
                              abstracting the input interfaces - you access the
                              mouse and DI will sort out if its a 1-button/10
                              button, 2 axis/3 axis mouse and give you the data
                              you want. Action Mapping goes one step beyond this
                              - you tell it what data you want and it'll do the
                              rest - whatever device it comes from. 
                              For example, in the sample that this tutorial
                              covers (a simple racing game) the input can come
                              from the keyboard, mouse or joystick - and your
                              application doesn't need to differentiate between
                              them. When using action mapping an 'Accelerate' or
                              'Turn Left' message is just that, an 'Accelerate'
                              message from the joystick is not different from an
                              'Accelerate' message from the keyboard. 
                               
                              2. About Genre's 
                              Action Mapping is built upon the principle of
                              Genre's - any game that you write should be able
                              to pick its input to match with that typical of
                              one of the genre's. This doesn't mean that if
                              you're writing a racing game you HAVE to use the
                              racing-game genre's - if the flight-sim genre
                              matches better you can use that. Also, if a
                              particular genre doesn't have all the controls
                              that you need you can always add additional
                              control handles (as we'll see later). 
                              The following tree shows all the genre's
                              defined in DirectInput8: 
                              
                              • Action Genres 
                                   • Hand-to-Hand (
                              _FIGHTINGH_ , DIVIRTUAL_FIGHTING_HAND2HAND) 
                                   • Shooting (
                              _FPS_ , DIVIRTUAL_FIGHTING_FPS) 
                                   • Third Person Action (
                              _TPS_ , DIVIRTUAL_FIGHTING_THIRDPERSON) 
                              
                              • Arcade Genres 
                                   • Platform (
                              _ARCADEP_ , DIVIRTUAL_ARCADE_PLATFORM) 
                                   • Side-to-Side
                              ( _ARCADES_ , DIVIRTUAL_ARCADE_SIDE2SIDE) 
                              
                              • CAD Genres 
                                   • 2D Object (
                              _2DCONTROL_ , DIVIRTUAL_CAD_2DCONTROL) 
                                   • 3D Model (
                              _CADM_ , DIVIRTUAL_CAD_MODEL) 
                                   • 3D Navigation (
                              _CADF_ , DIVIRTUAL_CAD_FLYBY) 
                                   • 3D Object (
                              _3DCONTROL_ , DIVIRTUAL_CAD_3DCONTROL) 
                              
                              • Control Genres 
                                   • Browser (
                              _BROWSER_ , DIVIRTUAL_BROWSER_CONTROL) 
                                   • Remote Control (
                              _REMOTE_ , DIVIRTUAL_REMOTE_CONTROL) 
                              
                              • Driving Genres 
                                   • Combat Racing (
                              _DRIVINGC_ , DIVIRTUAL_DRIVING_COMBAT) 
                                   • Mechanical Fighting (
                              _MECHA_ , DIVIRTUAL_DRIVING_MECHA) 
                                   • Racing (
                              _DRIVINGR_ , DIVIRTUAL_DRIVING_RACE) 
                                   • Tank (
                              _DRIVINGT_ , DIVIRTUAL_DRIVING_TANK) 
                              
                              • Flight Genres 
                                   • Air Combat (
                              _FLYINGM_ , DIVIRTUAL_FLYING_MILITARY) 
                                   • Civilian Flight (
                              _FLYINGC_ , DIVIRTUAL_FLYING_CIVILIAN) 
                                   • Helicopter Flight (
                              _FLYINGH_ , DIVIRTUAL_FLYING_HELICOPTER) 
                                   • Space Combat
                              ( _SPACESIM_ , DIVIRTUAL_SPACESIM) 
                              
                              • Sports Genres 
                                   • Baseball Batting (
                              _BASEBALLB_ , DIVIRTUAL_SPORTS_BASEBALL_BAT) 
                                   • Baseball Fielding (
                              _BASEBALLF_ , DIVIRTUAL_SPORTS_BASEBALL_FIELD) 
                                   • Baseball Pitching (
                              _BASEBALLP_ , DIVIRTUAL_SPORTS_BASEBALL_PITCH) 
                                   • Basketball Defense (
                              _BBALLD_ , DIVIRTUAL_SPORTS_BASKETBALL_DEFENSE) 
                                   • Basketball Offense (
                              _BBALLO_ , DIVIRTUAL_SPORTS_BASKETBALL_OFFENSE) 
                                   • Fishing (
                              _FISHING_ , DIVIRTUAL_SPORTS_FISHING) 
                                   • Football Defense (
                              _FOOTBALLD_ , DIVIRTUAL_SPORTS_FOOTBALL_DEFENSE) 
                                   • Football Offense (
                              _FOOTBALLO_ , DIVIRTUAL_SPORTS_FOOTBALL_OFFENSE) 
                                   • Football Play (
                              _FOOTBALLP_ , DIVIRTUAL_SPORTS_FOOTBALL_FIELD) 
                                   • Football Quarterback (
                              _FOOTBALLQ_ , DIVIRTUAL_SPORTS_FOOTBALL_QBCK) 
                                   • Golf (
                              _GOLF_ , DIVIRTUAL_SPORTS_GOLF) 
                                   • Hockey Defense (
                              _HOCKEYD_ , DIVIRTUAL_SPORTS_HOCKEY_DEFENSE) 
                                   • Hockey Goalie (
                              _HOCKEYG_ , DIVIRTUAL_SPORTS_HOCKEY_GOALIE) 
                                   • Hockey Offense (
                              _HOCKEYO_ , DIVIRTUAL_SPORTS_HOCKEY_OFFENSE) 
                                   • Hunting (
                              _HUNTING_ , DIVIRTUAL_SPORTS_HUNTING) 
                                   • Mountain Biking (
                              _BIKINGM_ , DIVIRTUAL_SPORTS_BIKING_MOUNTAIN) 
                                   • Racquet (
                              _RACQUET_ , DIVIRTUAL_SPORTS_RACQUET) 
                                   • Skiing (
                              _SKIING_ , DIVIRTUAL_SPORTS_SKIING) 
                                   • Soccer Defense (
                              _SOCCERD_ , DIVIRTUAL_SPORTS_SOCCER_DEFENSE) 
                                   • Soccer Offense (
                              _SOCCERO_ , DIVIRTUAL_SPORTS_SOCCER_OFFENSE) 
                              
                              • Strategy Genres 
                                   • Role Playing (
                              _STRATEGYR_ , DIVIRTUAL_STRATEGY_ROLEPLAYING) 
                                   • Turn Based (
                              _STRATEGYT_ , DIVIRTUAL_STRATEGY_TURN) 
                              A fairly long
                              list! The two values in the parenthesis: first one
                              represents a string you can use to search the SDK
                              help files and/or VB object browser for a complete
                              list of the controls for that genre, second one is
                              the genre's 'name' - the use for which you'll see
                              in a little while. 
                               
                              3.  Initialization and Configuration 
                              90%
                              of the work to get action mapping is done when the
                              application starts or when the end-user decides to
                              alter their control settings. 
                              The
                              first step is to construct a default set of
                              controls for the application - you assign one of
                              the controls from a genre listed above to a
                              particular internal constant and give it a name.
                              Secondly we query the system about the proposed
                              action map, it will then return a list of devices
                              attached to the system that are
                              compatible/necessary for the given action map. We
                              then create an instance of each device that the
                              system "recommended", configuring them
                              as we go. 
                              Before
                              we actually execute any code we need  a list
                              of constants - some are just for convenience,
                              others are requirements. It is necessary to have a
                              list of internal constants representing each
                              control - later on we'll send these values to the
                              action map, and DirectInput will use these as the
                              basis for returning information back to us. 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        '//PROGRAM CONSTANTS AND VARIABLES 
                                                          'controls whether the app. is running or not 
                                                          Private bRunning
                                                           As Boolean 
                                                          'we need a GUID to make sure DI8 responds properly (Use DX.CreateNewGUID() to make your own) 
                                                          Private Const AppGUID
                                                           As String = "{506BD635-CEAF-494D-B3D2-4F0E1F1469FC}" 
                                                          'in a game this would probably be the players name / handle 
                                                          Private Const AppUserName
                                                           As String
                                                           = "DirectX4VB.Com Sample User" 
                                                          'there are a list of these in the SDK and Object Browser (F2)
                                                          - also, see list in
                                                          tutorial. 
                                                          Private Const AppGenre
                                                           As Long = DIVIRTUAL_DRIVING_RACE 
                                                           
                                                          '//DEFINE COMMAND CONSTANTS 
                                                          'these are the values that we'll receive back from DI8 
                                                          Const
                                                           CAR_ACCELERATE
                                                           As Long = 1 
                                                          Const
                                                           CAR_BRAKE
                                                           As Long = 2 
                                                          Const
                                                           CAR_STEER
                                                           As Long = 3 
                                                          Const
                                                           CAR_NITRO
                                                           As Long = 4 
                                                          Const
                                                           CAR_STEER_LEFT
                                                           As Long
                                                           = 5 
                                                          Const
                                                           CAR_STEER_RIGHT
                                                           As Long = 6 
                                                          Const
                                                           CAR_ACCEL_OR_BRAKE
                                                           As Long
                                                           = 7 
                                                          'these are general commands 
                                                          Const
                                                           DISPLAY_OPTIONS
                                                           As Long = 8 
                                                          Const
                                                           EXIT_PROGRAM
                                                           As Long = 9 | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              Above
                              is all fairly straightforward, pay particular
                              attention to AppGUID and AppUserName - these
                              should be changed if you use Action Mapping in
                              your own programs. AppGUID is a unique value
                              generated by DX.CreateNewGUID( ) - you should
                              create a new one for each application you make.
                              The reason for this being that DirectInput is
                              clever and saves action maps to the hard drive for
                              future sessions, and it uses this GUID (and the
                              user name) to differentiate between multiple maps.
                              If, for example, you used the same GUID for all
                              your games and one person installed more than one
                              game on their system you'd start to get conflicts
                              (one game would overwrite another's settings). 
                              Now
                              we move onto the actual inialisation routine,
                              aptly named InitDirectInput( ). Because this
                              function can be called multiple times (it's called
                              each time the user changes the control settings)
                              we need to start off by erasing any history of
                              existing action maps and/or devices: 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        '//Clear Any Existing data / format memory for new data 
    lActionCount = 0 
                                                              ReDim DIActionFmt.ActionArray(0)
                                                           As DIACTION 
                                                           
                                                              If lDeviceCount > 0
                                                           Then 
                                                              For I = 0
                                                           To lDeviceCount 
                                                                 
                                                          If Not DevList(I)
                                                           Is Nothing Then 
                                                                     
                                                          'we have a device allocated here. kill it!! 
                                                                     
                                                          DevList(I).Unacquire         'shut it down 
                                                                     
                                                          Set DevList(I) =
                                                           Nothing
                                                              'delete it 
                                                                 
                                                          End If 
                                                              Next I 
                                                              End If | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              Next
                              we're going to setup the default action map. The
                              actions you specify now will be the only ones
                              visible to the end-user - if you didn't set a
                              default for DIAXIS_DRIVINGR_STEER then it
                              wouldn't automatically appear, and the user would
                              not be allowed to use axis-steering. 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        '//Add all of the actions to the list 
    'there aren't any set patterns for the _DRIVINGR_ type constants 
    'but DIKEYBOARD_ and DIMOUSE_ DIJOFS_ constants can only be mapped 
    'to their respective device (when the user looks at the settings window) 
    AddAction CAR_STEER, DIAXIS_DRIVINGR_STEER, 0, "Steer Vehicle" 
    AddAction CAR_ACCEL_OR_BRAKE, DIAXIS_DRIVINGR_ACCELERATE, 0, "Accelerate" 
    AddAction CAR_ACCEL_OR_BRAKE, DIAXIS_DRIVINGR_BRAKE, 0, "Brake" 
    AddAction CAR_ACCELERATE, DIBUTTON_DRIVINGR_ACCELERATE_LINK, 0, "Accelerate" 
    AddAction CAR_BRAKE, DIBUTTON_DRIVINGR_BRAKE, 0, "Brake" 
    AddAction CAR_NITRO, DIBUTTON_DRIVINGR_BOOST, 0, "Turbo Speed" 
    AddAction DISPLAY_OPTIONS, DIKEYBOARD_D, 0, "Display Options Window" 
    AddAction EXIT_PROGRAM, DIKEYBOARD_ESCAPE, 0, "Exit Sample" 
    AddAction CAR_STEER_LEFT, DIKEYBOARD_LEFT, 0, "Steer Left" 
    AddAction CAR_STEER_RIGHT, DIKEYBOARD_RIGHT, 0, "Steer Right" 
                                                           
                                                           
                                                          '//Finally, Configure the final map 
                                                              DIActionFmt.guidActionMap = AppGUID 
    DIActionFmt.lGenre = AppGenre 
    DIActionFmt.lBufferSize = 10  '10 input events can be stored 
    DIActionFmt.lAxisMax = 100 
    DIActionFmt.lAxisMin = -100 
    DIActionFmt.ActionMapName = "DirectX4VB.Com Sample Action Mapper" 
    DIActionFmt.lActionCount = lActionCount 
                                                           
                                                           
                                                           
                                                           
                                                          '//A
                                                          custom procedure that
                                                          helps with creating
                                                          the default action
                                                          set. 
                                                          Private Sub
                                                           AddAction(UserActionName As
                                                          Long, _ 
                                                                                          
                                                          ActualActionName  As
                                                          Long, _ 
                                                                                          
                                                          flags  As
                                                          Long, _ 
                                                                                          
                                                          FriendlyName  As
                                                          String) 
                                                           
                                                          'resize the array appropriately 
                                                          ReDim Preserve DIActionFmt.ActionArray(lActionCount)
                                                           As DIACTION 
                                                           
                                                          'fill in the new entry 
                                                          With DIActionFmt.ActionArray(lActionCount) 
                                                              .lAppData = UserActionName 
                                                              .lSemantic = ActualActionName 
                                                              .lFlags = flags 
                                                              .ActionName = FriendlyName 
                                                          End With 
                                                           
                                                          'increment the counter 
                                                          lActionCount = lActionCount + 1 
                                                           
                                                          End Sub | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              once
                              we've configured our default control setup we can
                              show it to the user and allow them to further
                              customize it to their style. The sample does this
                              now, but a real-world application would probably
                              want to hide this next part in an options screen. 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        'This next part displays the DirectInput dialog allowing 
    'the user to configure the controls... 
                                                              Dim
                                                           Params
                                                           As
                                                           DICONFIGUREDEVICESPARAMS 
                                                              ReDim
                                                           Params.ActionFormats(0) 
                                                              ReDim
                                                           Params.UserNames(0) 
                                                           
    Params.ActionFormats(0) = DIActionFmt 
    Params.FormatCount = 1 
    Params.UserCount = 1 
    Params.UserNames(0) = AppUserName 
                                                           
    DI.ConfigureDevices 0, Params, DICD_EDIT 
                                                           
                                                          'we call this so that our internal data reflects any changes 
    'made by the end user while the dialog box was displayed. 
    DIActionFmt = Params.ActionFormats(0) | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              An
                              important note here: ConfigureDevices( ) actually
                              displays a default DirectInput dialog box to the
                              end user. Whilst it is a very nice, functional,
                              window I highly doubt it will fit into the
                              majority of real-world game environments. The
                              following image is the window: 
                              
                              A
                              very tasteful black-and-green :) 
                              The
                              three tabs along the top of the screen represent
                              the (valid) devices currently attached to the
                              system - in my case a cheap gamepad, a keyboard
                              and a mouse. If multiple players are
                              active/playing then they will appear in the
                              "Player" drop-down list box. 
                              Once
                              the end user has configured the devices, we can
                              move on to creating the devices. By using the
                              GetDevicesBySemantics( ) function we can retrieve
                              the list of devices that match the current genre
                              and/or are going to be used by the system. With
                              this list we need to go through and create each
                              device and store a pointer to it. 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        Set DIEnum = DI.GetDevicesBySemantics(AppUserName, _ 
                                                                                                                     
                                                          DIActionFmt, _ 
                                                                                                                                                               DIEDBSFL_ATTACHEDONLY) 
                                                           
                                                              For I = 1
                                                           To DIEnum.GetCount 
                                                             
                                                          'retrieve the device 
                                                              Set
                                                           DevInst =
                                                           Nothing
                                                           'clear any existing 
                                                              Set
                                                           DevInst = DIEnum.GetItem(I) 
                                                           
                                                             
                                                          'resize the arrays 
                                                              lDeviceCount = lDeviceCount + 1 
                                                             
                                                          ReDim Preserve  DevList(lDeviceCount)
                                                           As
                                                           DirectInputDevice8 
                                                             
                                                          ReDim Preserve  DevType(lDeviceCount)
                                                           As Long 
                                                           
                                                             
                                                          'create the device 
                                                             
                                                          Set  DevList(lDeviceCount) = DI.CreateDevice(DevInst.GetGuidInstance) 
                                                             
                                                          DevType(lDeviceCount) = DevInst.GetDevType 
                                                             
                                                          Debug.Print "DEVICE #" & I & " created: " & DevInst.GetInstanceName 
                                                           
                                                              DevList(lDeviceCount).BuildActionMap DIActionFmt, _ 
                                                                                                                                          AppUserName, DIDBAM_DEFAULT 
                                                              DevList(lDeviceCount).SetActionMap DIActionFmt, _ 
                                                                                             
                                                          AppUserName, DIDSAM_DEFAULT 
                                                              DevList(lDeviceCount).SetCooperativeLevel frmMain.hWnd, _ 
                                                                                             
                                                          DISCL_EXCLUSIVE Or DISCL_FOREGROUND 
                                                           
                                                              Next
                                                           I | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              that's
                              initialization completed now. Assuming the above
                              code executes successfully you are ready to
                              properly make use of action mapping. The last
                              thing I want to cover in this section is
                              termination. It's always a good idea to properly
                              terminate any DirectX interfaces that you use -
                              particularly so with DirectInput. The following
                              code is executed just before the application
                              closes: 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        '//CLEAN UP AFTER WE'RE FINISHED 
                                                              Debug.Print "Terminating DirectInput Sample..." 
                                                              If lDeviceCount > 0
                                                           Then 
                                                              For I = 0
                                                           To lDeviceCount 
                                                                 
                                                          If Not  DevList(I)
                                                           Is Nothing Then 
                                                                     
                                                          'we have a device allocated here. kill it!! 
                                                                     
                                                          DevList(I).Unacquire         'shut it down 
                                                                     
                                                          Set DevList(I) =
                                                           Nothing    'delete it 
                                                                 
                                                          End If 
                                                              Next
                                                           I 
                                                              End If | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                                
                               
                              
                              4. Interpreting and understanding  
                              Now
                              that DirectInput is configured and ready to send
                              us input data we need to be ready to receive and
                              process it. When using action mapping we have to
                              use a method similar to polling - whilst for
                              keyboard/button input it works just the same as
                              event-based (which is best) it is necessary for
                              axis-based devices to be polled. For a further
                              discussion of event-based vs polling see the first
                              lesson - keyboard access. 
                              Exactly
                              what you do once you've received input from
                              attached devices is entirely dependent on you.
                              This sample code does no more than output a list
                              to the screen of the input received, a real-world
                              application would then want to apply these
                              controls to the current character (for example). 
                              As
                              mentioned, we're going to use a polling method -
                              so the basic code structure looks like this: 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        bRunning = InitDirectInput() 
                                                           
                                                              '//EXECUTE THE MAIN LOOP 
    'this loop would be the same loop as used in almost all games 
                                                              Do While bRunning 
                                                              bRunning = UpdateUserInput() 
                                                              DoEvents 
                                                              Loop | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              This loop would,
                              in a real-world application, have a rendering
                              function, AI, physics, logic etc... system
                              attached. You've already seen the InitDirectInput(
                              ) function (section #3 above). I'm now going to be
                              focusing on the UpdateUserInput( ) function. 
                              The basic idea
                              behind the UpdateUserInput( ) process is to: 
                              1. Loop through all created devices 
                              2. receive any new input from the device 
                              3. Process this input. 
                              the first part,
                              and the general function outline is as simple as
                              this: 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        Private Function
                                                           UpdateUserInput()
                                                           As Boolean 
                                                          On Error GoTo
                                                           BailOut: 
                                                           
                                                             
                                                          '//INTERNAL VARIABLES 
                                                              Dim DevData(20) As
                                                           DIDEVICEOBJECTDATA 
                                                              Dim I
                                                           As
                                                          Long, J
                                                           As Long 
                                                              Dim nData
                                                           As Long 
                                                           
                                                             
                                                          'a scalar value between 0.0 and 1.0 (0.0=no saturation) 
                                                              Const
                                                           JOYSTICK_SATURATION
                                                           As Single
                                                           = 0.4 
                                                           
                                                             
                                                          '//SCAN DEVICES FOR INPUT 
                                                              For
                                                           I = 1
                                                           To
                                                           lDeviceCount 
                                                                
                                                          '//ADDITIONAL CODE
                                                          FITS IN HERE. 
                                                              Next I 
                                                           
                                                             
                                                          '//This next line is
                                                          not important for DI8,
                                                          but just for the
                                                          samples
                                                          user-interface     
                                                              txtOutput.SelStart = Len(txtOutput.Text) 
                                                           
                                                           
                                                              UpdateUserInput =
                                                           True 
                                                              Exit Function 
                                                          BailOut: 
                                                              Debug.Print "Unable to update user input.", Err.Number, Err.Description 
                                                              UpdateUserInput =
                                                           False 
                                                          End Function | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              the DevData( )
                              array is very important, this is refreshed for
                              each device and gives us a list of up to 20 events
                              that have occurred since we last checked the
                              device (last frame). JOYSTICK_SATURATION is
                              important when we deal with the joystick a bit
                              later on. 
                              Now that we have
                              the main loop setup we can treat each input
                              generically - such as the following code to
                              collect input from the devices: 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        'aquire and poll the devices as necessary 
                                                                  On Error Resume Next 
                                                           
                                                                  If
                                                          DevList(I)
                                                           Is Nothing Then
                                                          Debug.Print "Device #" & I & " does not exist" 
                                                           
        DevList(I).Acquire 
                                                                  If
                                                           Err.Number
                                                           Then
                                                          GoTo SkipThisDev: 
                                                           
        DevList(I).Poll 
                                                                  If
                                                           Err.Number Then
                                                          GoTo SkipThisDev: 
                                                           
                                                                  On Error GoTo
                                                           BailOut: 
                                                           
                                                                  'extract the data 
        nData = DevList(I).GetDeviceData(DevData, DIGDD_DEFAULT) | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              The first three
                              parts are very important - firstly we should check
                              to make sure the pointer is still valid - it is
                              possible that we could loose a device (another
                              application can steal it for example), in which
                              case the DevList(I) will be a null pointer. Next
                              we must attempt to aquire the device - should it
                              have been somehow unaquired (similar to being
                              lost, but not as bad). Next we have to poll the
                              device - basically tell it to collect the current
                              state of the hardware (it'll go and check the axis
                              positions on the joypad for example). Lastly we
                              use GetDeviceData( ) to get DirectInput to return
                              us a formatted list of events. 
                              We can then loop
                              through this formatted input (it'll be formatted
                              based on our action map) as shown in the following
                              piece of code: 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        For J = 0
                                                           To nData - 1 
                                                              With DevData(J) 
                                                             
                                                          'it isn't too easy to see with this sample, but there will 
                                                              'be two messages generated - key down and key up - for the keyboard 
                                                              'the latter is signified by lData=0; which can easily get lost in 
                                                              'some logic systems. 
                                                                 
                                                          Select Case .lUserData 
                                                                     
                                                          Case CAR_ACCELERATE 
                                                                     
                                                          Case CAR_BRAKE 
                                                                     
                                                          Case CAR_ACCEL_OR_BRAKE 
                                                                     
                                                          Case CAR_STEER 
                                                                     
                                                          Case CAR_STEER_LEFT 
                                                                     
                                                          Case CAR_STEER_RIGHT 
                                                                     
                                                          Case CAR_NITRO 
                                                                     
                                                          Case DISPLAY_OPTIONS 
                                                                     
                                                          Case EXIT_PROGRAM 
                                                                 
                                                          End Select 
                                                              End With 
        Next  J | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              With this piece
                              of code we will cycle through each event from the
                              device (there will usually only be one or two). We
                              then split apart incoming message using a Select
                              Case tree. Remember the constants we defined at
                              the beginning of this tutorial? well they appear
                              back here again - the wonders of action mapping
                              has meant that regardless of the raw control data
                              all we get back is a generic, custom value. 
                              What you actually
                              do within each branch of the logic tree is
                              completely up to you. However, I shall demonstrate
                              two useful pieces of code for processing input. 
                               
                                
                                  
                                     
                                     
                                       
                                        
                                           
                                           
                                             
                                              
                                                 
                                                 
                                                   
                                                    
                                                       
                                                       
                                                        '//PROCESSING
                                                          AXIS-BASED INPUT. 
                                                          If .lData > DIActionFmt.lAxisMax * JOYSTICK_SATURATION
                                                           Then 
                                                              txtOutput.Text = txtOutput.Text & "'CAR_BRAKE' MESSAGE RECIEVED: BRAKE!" & vbCrLf 
                                                                                  ElseIf .lData < DIActionFmt.lAxisMin * JOYSTICK_SATURATION
                                                           Then 
                                                              txtOutput.Text = txtOutput.Text & "'CAR_BRAKE' MESSAGE RECIEVED: ACCELERATE!" & vbCrLf 
                                                                                  End If 
                                                           
                                                          '//PROCESSING
                                                          BUTTON-BASED INPUT: 
                                                          If .lData = 128
                                                           Then  
                                                              txtOutput.Text = txtOutput.Text & "{KEY_DOWN} RECEIVED 'CAR_BRAKE' MESSAGE!!" & vbCrLf 
                                                          End If 
                                                                                  If .lData = 0
                                                           Then  
                                                              txtOutput.Text = txtOutput.Text & "{KEY_UP} RECEIVED 'CAR_BRAKE' MESSAGE!!" & vbCrLf 
                                                          End
                                                          If | 
                                                       
                                                       
                                                     
                                                   | 
                                                 
                                                 
                                               
                                             | 
                                           
                                           
                                         
                                       | 
                                     
                                     
                                   
                                
                               
                              You can see a
                              more complete set of examples if you download the
                              source code for this tutorial. But in general they
                              all follow this pattern. 
                              for axis-based
                              input the .lData value will contain the current
                              value on the device's axis scaled according to the
                              values in .lAxisMax and .lAxisMin. Take a joystick
                              for example, each axis goes from -ve to +ve
                              (left->right or top->bottom). Therefore if .lData
                              is a negative number it is somewhere towards the
                              left or top (depending on which axis the user
                              selected, either way does not matter to us). If .lData
                              is a positive number then it lies somewhere
                              towards the right or bottom. Fairly simple really.
                              I've made it more complicated based on some
                              testing and general knowledge that I have of
                              joysticks... even if you push them all the way to
                              the left you will rarely get the maximum -ve
                              number, and if you don't touch the joystick at all
                              then it rarely stays at 0. This may be different
                              with digital controllers and/or more elegant
                              designs (my joystick is laughable at best!). What
                              I did to combat this was to implement a saturation
                              value: in order for the new data to be considered
                              a significant change it MUST be greater than a
                              certain value. .lAxisMax and .lAxisMin determine
                              the max/min possible values, and
                              JOYSTICK_SATURATION is a scalar multiplier. 
                              As seen above, I
                              set JOYSTICK_SATURATION to be 0.4; given that the
                              range defined earlier is [-100,+100] this logic
                              implies that the new value must be less than -40
                              or greater than +40 to be considered a change. You
                              could allow your users to specify how sensitive
                              they want their controls using this method. 
                               
                              5.
                              Conclusion 
                              That
                              is all there is to a basic DirectInput8-Action
                              Mapping sample. Obviously, you can make it more
                              complicated if necessary - but to get a basic
                              generic input based system working this is all you
                              need. One useful thing to note is that there are
                              an additional set of control constants you can use
                              for DirectPlay Voice Communications and also a set
                              of generic non-controller specific constants
                              (search for _ANY_ ). 
                              The
                              source code for this tutorial can be download here,
                              or from the top of the page.
                               
                               
                             |