Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

RMXP Gamepad Input Support

Hey everyone, I ran into a bit of a problem with my RMXP project, and I'm looking for some help or advice. I'm using an Input module for custom key controls. I've tried a couple, but currently settled on using Blizz's input module for now.

Code:
# This work is protected by the following license:

#----------------------------------------------------------------------------

#

# Creative Commons - Attribution-NonCommercial-ShareAlike 3.0 Unported

# ( [url=http://creativecommons.org/licenses/by-nc-sa/3.0/]http://creativecommons.org/licenses/by-nc-sa/3.0/[/url] )

#

# You are free:

# to Share - to copy, distribute and transmit the work

# to Remix - to adapt the work

#

# Under the following conditions:

#

# Attribution. You must attribute the work in the manner specified by the

# author or licensor (but not in any way that suggests that they endorse you

# or your use of the work).

#

# Noncommercial. You may not use this work for commercial purposes.

#

# Share alike. If you alter, transform, or build upon this work, you may

# distribute the resulting work only under the same or similar license to

# this one.

#

# - For any reuse or distribution, you must make clear to others the license

#   terms of this work. The best way to do this is with a link to this web

#   page.

#

# - Any of the above conditions can be waived if you get permission from the

#   copyright holder.

#

# - Nothing in this license impairs or restricts the author's moral rights.

#

#----------------------------------------------------------------------------

#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

# Custom Game Controls by Blizzard

# Modified by kellessdee

#   - added Input.keys?

#    - returns array of last triggered key values

#   - added Input.key?

#    - optimized version of Input.keys?; returns first "found" triggered key value

#    - NOTE: Some keys (such as Shift) will usually return 2 values (Shit and Left Shift or Right Shift)

#                  and it may NOT necessarily be the value you intended. Any values that do not exist in the

#                  key map WILL not be returned, i.e. removing "Shift" from the map will force only "Left Shift" or

#                  "Right Shift" to be returned.

# Version: 3.0

# Date: 19.4.2007

# Date v1.2: 20.10.2007

# Date v2.0b: 3.4.2008

# Date v2.1b: 25.10.2008

# Date v2.2b: 11.6.2009

# Date v3.0: 20.7.2009

#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

#

# VERY IMPORTANT NOTE:

#

#   DO NOT USE THIS ADD-ON IF YOU ARE USING BLIZZ-ABS!!!

#

#

# Compatiblity:

#

#   99% compatible with SDK 1.x, 90% compatible with SDK 2.x.

#

#

# Note:

#

#   Why is this input module better than others? I has far less code and it

#   can handle keyboard language layout.

#

#

# Explanation & Configuration:

#

#   This Add-on will allow you to specify your own game controls. Just below

#   is a list of possible keys, below that is the configuration. The default

#   configuration is RMXP's real game control configuration. You can add any

#   key specification into a key array and separate them with commas. Example:

#  

#   RIGHT = [Key['Arrow Right'], Key[','], Key['F'], Key['Ctrl'], Key['3'],

#           Key['NumberPad 6'], Key['F3'], Key['\''], Key['\\']]

#  

#   This example would assign for the RIGHT button the following keys:

#   - directional right (right arrow key)

#   - comma

#   - letter key F

#   - Control key (CTRL)

#   - Number Key 3 (on top over the letter keys)

#   - Numberpad Key 6 (number 6 on the numberpad on the right)

#   - Functional Key 3 (F3)

#   - apostrophe (')

#   - backslash (\)

#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

#==============================================================================

# module Input

#==============================================================================

 

module Input

 

  #----------------------------------------------------------------------------

  # Simple ASCII table

  #----------------------------------------------------------------------------

  Key = {'A' => 65, 'B' => 66, 'C' => 67, 'D' => 68, 'E' => 69, 'F' => 70,

         'G' => 71, 'H' => 72, 'I' => 73, 'J' => 74, 'K' => 75, 'L' => 76,

         'M' => 77, 'N' => 78, 'O' => 79, 'P' => 80, 'Q' => 81, 'R' => 82,

         'S' => 83, 'T' => 84, 'U' => 85, 'V' => 86, 'W' => 87, 'X' => 88,

         'Y' => 89, 'Z' => 90,

         '0' => 48, '1' => 49, '2' => 50, '3' => 51, '4' => 52, '5' => 53,

         '6' => 54, '7' => 55, '8' => 56, '9' => 57,

         'NumberPad 0' => 45, 'NumberPad 1' => 35, 'NumberPad 2' => 40,

         'NumberPad 3' => 34, 'NumberPad 4' => 37, 'NumberPad 5' => 12,

         'NumberPad 6' => 39, 'NumberPad 7' => 36, 'NumberPad 8' => 38,

         'NumberPad 9' => 33,

         'F1' => 112, 'F2' => 113, 'F3' => 114, 'F4' => 115, 'F5' => 116,

         'F6' => 117, 'F7' => 118, 'F8' => 119, 'F9' => 120, 'F10' => 121,

         'F11' => 122, 'F12' => 123,

         ';' => 186, '=' => 187, ',' => 188, '-' => 189, '.' => 190, '/' => 220,

         '\\' => 191, '\'' => 222, '[' => 219, ']' => 221, '`' => 192,

         'Backspace' => 8, 'Tab' => 9, 'Enter' => 13, 'Shift' => 16,

         'Left Shift' => 160, 'Right Shift' => 161, 'Left Ctrl' => 162,

         'Right Ctrl' => 163, 'Left Alt' => 164, 'Right Alt' => 165,

         'Ctrl' => 17, 'Alt' => 18, 'Esc' => 27, 'Space' => 32, 'Page Up' => 33,

         'Page Down' => 34, 'End' => 35, 'Home' => 36, 'Insert' => 45,

         'Delete' => 46, 'Arrow Left' => 37, 'Arrow Up' => 38,

         'Arrow Right' => 39, 'Arrow Down' => 40,

         'Mouse Left' => 1, 'Mouse Right' => 2, 'Mouse Middle' => 4,

         'Mouse 4' => 5, 'Mouse 5' => 6}

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

# START Configuration

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  UP = [Key['Arrow Up'], Key['W']]

  LEFT = [Key['Arrow Left'], Key['A']]

  DOWN = [Key['Arrow Down'], Key['S']]

  RIGHT = [Key['Arrow Right'], Key['D']]

  A = [Key['Shift']]

  B = [Key['Esc'], Key['X']]

  C = [Key['Space'], Key['Enter'], Key['C']]

  X = [Key['Q']]#[Key['A']]

  Y = [Key['E']]#[Key['S']]

  Z = [Key['R']]#[Key['D']]

  L = [Key['Right Alt'], Key['Left Ctrl'] ]#[Key['Q'], Key['Page Down']]

  R = [Key['Left Alt'], Key['Right Ctrl'] ]#[Key['W'], Key['Page Up']]

  F5 = [Key['F5']]

  F6 = [Key['F6']]

  F7 = [Key['F7']]

  F8 = [Key['F8']]

  F9 = [Key['F9']]

  SHIFT = [Key['Shift']]

  CTRL = [Key['Ctrl']]

  ALT = [Key['Alt']]

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

# END Configuration

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  # All keys

  ALL_KEYS = (0...256).to_a

  # Win32 API calls

  GetKeyboardState = Win32API.new('user32','GetKeyboardState', 'P', 'I')

  GetKeyboardLayout = Win32API.new('user32', 'GetKeyboardLayout','L', 'L')

  MapVirtualKeyEx = Win32API.new('user32', 'MapVirtualKeyEx', 'IIL', 'I')

  ToUnicodeEx = Win32API.new('user32', 'ToUnicodeEx', 'LLPPILL', 'L')

  # some other constants

  DOWN_STATE_MASK = 0x80

  DEAD_KEY_MASK = 0x80000000

  # data

  @state = "\0" * 256

  @triggered = Array.new(256, false)

  @pressed = Array.new(256, false)

  @released = Array.new(256, false)

  @repeated = Array.new(256, 0)

  #----------------------------------------------------------------------------

  # update

  #  Updates input.

  #----------------------------------------------------------------------------

  def self.update

    # prevents usage with Blizz-ABS

    if $BlizzABS

      # error message

      raise 'Blizz-ABS was detected! Please turn off Custom Controls in Tons of Add-ons!'

    end

    # get current language layout

    @language_layout = GetKeyboardLayout.call(0)

    # get new keyboard state

    GetKeyboardState.call(@state)

    # for each key

    ALL_KEYS.each {|key|

        # if pressed state

        if @state[key] & DOWN_STATE_MASK == DOWN_STATE_MASK

          # not released anymore

          @released[key] = false

          # if not pressed yet

          if !@pressed[key]

            # pressed and triggered

            @pressed[key] = true

            @triggered[key] = true

          else

            # not triggered anymore

            @triggered[key] = false

          end

          # update of repeat counter

          @repeated[key] < 17 ? @repeated[key] += 1 : @repeated[key] = 15

        # not released yet

        elsif !@released[key]

          # if still pressed

          if @pressed[key]

            # not triggered, pressed or repeated, but released

            @triggered[key] = false

            @pressed[key] = false

            @repeated[key] = 0

            @released[key] = true

          end

        else

          # not released anymore

          @released[key] = false

        end}

  end

  #----------------------------------------------------------------------------

  # dir4

  #  4 direction check.

  #----------------------------------------------------------------------------

  def self.dir4

    return 2 if self.press?(DOWN)

    return 4 if self.press?(LEFT)

    return 6 if self.press?(RIGHT)

    return 8 if self.press?(UP)

    return 0

  end

  #----------------------------------------------------------------------------

  # dir8

  #  8 direction check.

  #----------------------------------------------------------------------------

  def self.dir8

    down = self.press?(DOWN)

    left = self.press?(LEFT)

    return 1 if down && left

    right = self.press?(RIGHT)

    return 3 if down && right

    up = self.press?(UP)

    return 7 if up && left

    return 9 if up && right

    return 2 if down

    return 4 if left

    return 6 if right

    return 8 if up

    return 0

  end

  #----------------------------------------------------------------------------

  # trigger?

  #  Test if key was triggered once.

  #----------------------------------------------------------------------------

  def self.trigger?(keys)

    keys = [keys] unless keys.is_a?(Array)

    return keys.any? {|key| @triggered[key]}

  end

  #----------------------------------------------------------------------------

  # press?

  #  Test if key is being pressed.

  #----------------------------------------------------------------------------

  def self.press?(keys)

    keys = [keys] unless keys.is_a?(Array)

    return keys.any? {|key| @pressed[key]}

  end

  #----------------------------------------------------------------------------

  # repeat?

  #  Test if key is being pressed for repeating.

  #----------------------------------------------------------------------------

  def self.repeat?(keys)

    keys = [keys] unless keys.is_a?(Array)

    return keys.any? {|key| @repeated[key] == 1 || @repeated[key] == 16}

  end

  #----------------------------------------------------------------------------

  # release?

  #  Test if key was released.

  #----------------------------------------------------------------------------

  def self.release?(keys)

    keys = [keys] unless keys.is_a?(Array)

    return keys.any? {|key| @released[key]}

  end

  #----------------------------------------------------------------------------

  # get_character

  #  vk - virtual key

  #  Gets the character from keyboard input using the input locale identifier

  #  (formerly called keyboard layout handles).

  #----------------------------------------------------------------------------

  def self.get_character(vk)

    # get corresponding character from virtual key

    c = MapVirtualKeyEx.call(vk, 2, @language_layout)

    # stop if character is non-printable and not a dead key

    return '' if c < 32 && (c & DEAD_KEY_MASK != DEAD_KEY_MASK)

    # get scan code

    vsc = MapVirtualKeyEx.call(vk, 0, @language_layout)

    # result string is never longer than 2 bytes (Unicode)

    result = "\0" * 2

    # get input string from Win32 API

    length = ToUnicodeEx.call(vk, vsc, @state, result, 2, 0, @language_layout)

    return (length == 0 ? '' : result)

  end

  #----------------------------------------------------------------------------

  # get_input_string

  #  Gets the string that was entered using the keyboard over the input locale

  #  identifier (formerly called keyboard layout handles).

  #----------------------------------------------------------------------------

  def self.get_input_string

    result = ''

    # check every key

    ALL_KEYS.each {|key|

        # if repeated

        if self.repeat?(key)

          # get character from keyboard state

          c = self.get_character(key)

          # add character if there is a character

          result += c if c != ''

        end}

    # empty if result is empty

    return '' if result == ''

    # convert string from Unicode to UTF-8

    return self.unicode_to_utf8(result)

  end

  #----------------------------------------------------------------------------

  # unicode_to_utf8

  #  string - string in Unicode format

  #  Converts a string from Unicode format to UTF-8 format as RGSS does not

  #  support Unicode.

  #----------------------------------------------------------------------------

  def self.unicode_to_utf8(string)

    result = ''

    string.unpack('S*').each {|c|

        # characters under 0x80 are 1 byte characters

        if c < 0x0080

          result += c.chr

        # other characters under 0x800 are 2 byte characters

        elsif c < 0x0800

          result += (0xC0 | (c >> 6)).chr

          result += (0x80 | (c & 0x3F)).chr

        # the rest are 3 byte characters

        else

          result += (0xE0 | (c >> 12)).chr

          result += (0x80 | ((c >> 12) & 0x3F)).chr

          result += (0x80 | (c & 0x3F)).chr

        end}

    return result

  end

 

  # Which keys were triggered?

  # @author kellessdee

  # @return [Integer] Key value(s) (ascii) which were triggered

  def self.keys?

    input = []

    keys = Key.invert

    @triggered.each_index { |i|

      if @triggered[i] && keys[i]

        input << i

      end

    }

    return input

  end

 

  # Which key was triggered?

  # @author kellessdee

  # returns Integer key value (ascii) which was triggered

  def self.key?

    keys = Key.invert

    @triggered.each_index { |i|

      if @triggered[i] && key[i]

        return i

      end

    }

  end

 

end

The problem I'm running into is that all the scripts I've found completely rewrite the input module, and therefore don't allow for gamepad use that vanilla RMXP projects recognize by default. Is it possible to have gamepad inputs included, or at least restore the gamepad default functionality? I know there's other ways around this, but I'm trying to avoid making the player use an outside program like joy2key to map the controls to keyboard functions if they'd like to use a gamepad.

While on the topic, would there be any possibility of testing if a certain gamepad was plugged in? I'd love to have a function be able to tell which type of gamepad is plugged in, so I can show the correct controls accordingly ex (Xbox One controller prompts would vary from the PS4 controller prompts), but this might be biting off entirely more than I can chew for now. Any feedback or info would be appreciated, thanks!
 
Solution
These custom input scripts use the Win32API, so you are likely to lose gamepad support entirely.

I would say the best solution would be to write your own gamepad DLL that added functionality for all the gamepads you want to support. You could try attacking the problem purely in RGSS via xinput, but not every machine has xinput installed and you're going to be writing a lot of RGSS for something that can be done quicker and more efficiently in C/C++.

EDIT: As a hint, you'd use Xinput for XBone/360 gamepad (and anything masquerading as xinput) and standard gamepad for Dualshock 3/4 and any other gamepads.
Detecting between xbox/ps is harder. Best bet is to try and detect vendor ID, but you almost certainly should have the option...
These custom input scripts use the Win32API, so you are likely to lose gamepad support entirely.

I would say the best solution would be to write your own gamepad DLL that added functionality for all the gamepads you want to support. You could try attacking the problem purely in RGSS via xinput, but not every machine has xinput installed and you're going to be writing a lot of RGSS for something that can be done quicker and more efficiently in C/C++.

EDIT: As a hint, you'd use Xinput for XBone/360 gamepad (and anything masquerading as xinput) and standard gamepad for Dualshock 3/4 and any other gamepads.
Detecting between xbox/ps is harder. Best bet is to try and detect vendor ID, but you almost certainly should have the option to switch between xbox/ps button layouts (and even generic button 0, 1, 2, etc layout) as you can never know how users are connecting their gamepads (masquerading as xinput seems to be a common one).
 
Solution
Thanks for the suggestions Xilef! After some more digging, I found Glitchfinder's Gamepad Input Module, which comes with a downloadable .DLL It also states it doesn't interfere w/ the default Input commands, perhaps I can get the two scripts running together to get access to both custom keyboard and gamepad inputs.

Good advice on the reading the gamepads. You're right, I'll have to include options to switch between keyboard, ps and xbox controller layouts. The unfortunate things is, it'll be impossible to account for every type of gamepad, but at least I can provide a couple of more popular options. In a perfect world, I'd like to have it so the player can map their own custom controls, but that's going down a completely different rabbit hole. As a one man developer, I don't want to spend that much time and resources on what ultimately going to be a free game that only lasts a couple of hours.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top