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.

Gus' Text Input Module

Gust

Member

Gus' Text Input Module Version: 1.00
By: Gustavo Bicalho (Gu574v0; Gust)


Introduction

Gus' Text Input Module (GTI) is an input module to complement the old ones. It provides a way to capture key presses like a text editor would: with special signs, diacritical marks and respecting the keyboard layout. The old input modules can't do that, so they are not appropriate for text input (like allowing the player to write a name or a memo).
On the other side, this input module doesn't track key-ups and downs, so it's not really appropriate for normal game play because you can't get the current state of a key, you can only know which characters were typed since the last update; and it will only pick key presses that create characters.
But anyway, GTI won't break any input system, so it can be used together with them.

That said, my advice: use a good old input module (or just the default one) for general gameplay, and activate GTI whenever you need text input.

I don't know exactly how many people out there wanted to let the player type something and couldn't, but I hope this can be used to make user interface friendlier.

Features
  • Characters typed are read correctly, accordingly to the keyboard layout.
  • Won't break the old input modules (unless they have a DLL that also messes with the window procedures, but I don't think any of them does that. Anyway, if that is the case, placing GTI under those scripts should solve the problem.)
  • The module is updated automatically by the Input module.

Script
Download of the DLL
DLL Source Code (if you want to check it out)

Code:
 

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

# ** GTI - Gus' Text Input                  for RPG Maker XP/VX

#   (CC-BY) Gustavo Bicalho

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

# * Version 1.00

#     - First release

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

# Gus' Text Input (GTI) is an input module to complement the old

# ones. It provides a way to capture key presses like a text

# editor would: with special signs, diacritical marks and

# respecting the keyboard layout. The old input modules can't

# do that, so they are not appropriate for text input.

#

# On the other side, this input module doesn't track key-ups and

# downs, so it's not appropriate for normal game play because

# you can't get the current state of a key, you can only know

# which characters were typed since the last update.

#

# Anyway, GTI won't break any input system, so it can be used

# together with them.

#

# That said, my advice: use a good old input module (or just

# the default one) for general gameplay, and activate GTI

# whenever you need text input.

#

# * PLACE THIS SCRIPT ABOVE MAIN *

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

# Some Info:

#

# 1) GTI is updated automatically by the Input module update.

# So all you need to do is iterate through the events list

# (GTI.events) and handle each event. If you call the update

# method directly, you'll lose the events cought in the previous

# update. The events list is cleared when the module updates,

# so don't bother doing that.

#

# 2) Each event is an array where the first element is a string

# containing the character typed (this string can be several

# bytes long, because of the UTF-8 encoding. But it's one

# character per event).

# The second element in the array is the repeat count: when

# the user holds a key, Windows sometimes will send one event

# for several "repeats", with the repeat count indicating how

# many repeats that event represents. Most of the time, however,

# Windows will send one event per repeat. So you could ignore

# the repeat count without losing any important info.

#

# 3) A few constants are provided which represent common

# special keys. You should probably make a few other tests to

# check if a character is printable before writing them to the

# screen or whatever.

#

# 4) GTI is NOT thread-safe. I know almost nobody uses threads

# in RGSS, but I thought I should warn you anyway.

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

module GTI

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

  # * Special key strings

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

  BACKSPACE = "\010"

  ESCAPE = "\e"

  RETURN = "\r"

  

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

  # * WinApi and GTI DLL constants and callers.

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

  DLL_NAME = 'GusTextInput'

  HookTextInput = Win32API.new(DLL_NAME,'HookTextInput','L','L')

  UnhookTextInput = Win32API.new(DLL_NAME,'UnhookTextInput','','L')

  PopEvent = Win32API.new(DLL_NAME,'PopEvent','P','I')

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

  

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

  # * GTI.running?

  #   Returns true if GTI is active, false otherwise.

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

  @running = false

  def self.running?

    return @running

  end

  

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

  # * GTI.events

  #   Returns the event list, where the events catch at the

  #   last update are stored for handling.

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

  @events = []

  def self.events

    return @events

  end

  

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

  # * GTI.start

  #   Activates the text input.

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

  def self.start

    return if @running

    # Call the DLL method that activates the text input on

    # the game window

    err = HookTextInput.call(GetActiveWindow.call)

    # err stores the error code. If it's 0, it's ok.

    # If it's different from 0, raises an exception.

    # This will never happen unless you're using another thing

    # that also messes with the window procs, so you don't

    # need to worry.

    raise "Couldn't hook the text input. Error #{err}" if (err != 0)

    @running = true; # GTI is now running

  end

  

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

  # * GTI.stop

  #   Deactivates the text input.

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

  def self.stop

    return if !@running

    # Call the DLL method that deactivates the text input

    err = UnhookTextInput.call

    # err stores the error code. If it's 0, it's ok.

    # If it's different from 0, raises an exception.

    # This will never happen unless you're using another thing

    # that also messes with the window procs, so you don't

    # need to worry.

    raise "Couldn't unhook the text input. Error #{err}" if (err != 0)

    @running = false # GTI is not running anymore

  end

 

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

  # * GTI.update

  #   Updates the module, popping the events from the DLL event

  #   stack and placing them at the event list.

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

  def self.update

    return if !@running # Stop if GTI is not running

    @events.clear; # Clear the event list

    # While there are events on the event stack, pop one

    # and add it to the event list.

    while ((c = GTI.pop) != nil)

      @events.push(c)

    end

  end

 

private

 

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

  # * GTI.pop

  #   Pops an event from the DLL event stack. Users shouldn't

  #   call this method. Use the event list instead.

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

  def self.pop

    # Allocate space so the DLL can put its data there

    # The DLL needs a 8-byte string.

    s = ' '*8

    # Call the PopEvent method from the DLL. It puts the data

    # in the string bytes, and returns the number of bytes

    # used to store the character. This number is up to 4.

    # If it returns 0, there were no events to pop, so

    # return nil.

    return nil if ((ret = PopEvent.call(s)) == 0)

    # Copy the part of s that store the character

    n = s[0,ret]

    # Unpack the string to get its bytes' values. We'll

    # only need the 5th byte (that's the first C), that stores

    # the repeat count for the event.

    s = s.unpack("LCCCC")

    return [n,s[1]] # Array with character and repeat count

  end

  

end

 

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

# ** Input

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

# Changes the Input.update method so it calls GTI.update.

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

module Input

  class << self

    unless method_defined?(:gus_gti_input_update)

      alias :gus_gti_input_update :update

  

      def update

        gus_gti_input_update

        GTI.update

      end

    end

  end

end

 

Instructions

Place this script above Main, and below the default scripts (and below any scripts with custom DLLs that mess with the window). Place the GusTextInput.dll in the same directory as Game.exe.
Whenever you need text input, call GTI.start to start the event processing. You can just leave it running through the whole game, or activate it only in Scenes where you need it and then call GTI.stop to stop it. The latter is probably the best if you need performance.
Anyway, once you activate it, you can get the event list (an array) from GTI.events at each update. The list is cleared when the module is updated, so each frame you get only the event of that frame. You can then iterate through the list, processing each event (the first of the list happened first and therefore should be processed first. Just use an each statement).

Below is an example of how Scene_Name (on RMXP) could be implemented using GTI:
Code:
class Scene_Name

  

  def main

    @actor = $game_actors[$game_temp.name_actor_id]

    @edit_window = Window_NameEdit.new(@actor, $game_temp.name_max_char)

    Graphics.transition

    GTI.start # Start Text Input

 

    loop do

      Graphics.update

      Input.update

      update

 

      if $scene != self

        break

      end

    end

 

    GTI.stop # Stop Text Input

    Graphics.freeze

    @edit_window.dispose

  end

  

  #this handles the events as they come

  def handle_input(c)

    if c[0] == GTI::BACKSPACE #if a backspace is received, go back

      @edit_window.back

      return

    elsif c[0] == GTI::RETURN # a return, confirm

      if @edit_window.name == ""     # if the box is empty

        @edit_window.restore_default # restore the default name

        $game_system.se_play($data_system.buzzer_se)

        return

      end

      # Set the actor name and go back to the map

      @actor.name = @edit_window.name

      $game_system.se_play($data_system.decision_se)

      $scene = Scene_Map.new

      return

    elsif c[0] == GTI::ESCAPE # escape pressed, back to the map

      $scene = Scene_Map.new

      return

    else # any other key pressed is added to the name

      if @edit_window.index == $game_temp.name_max_char

        $game_system.se_play($data_system.buzzer_se)

        return

      end

      @edit_window.add(c[0]*c[1])

      return

    end

  end

  

  def update

    @edit_window.update

    # Iterate the event list, calling the handler method

    GTI.events.each { |c| handle_input(c) }

  end

end

 

Terms and Conditions

  • You can redistribute and change this script and the DLL as you wish, as long as you give credit to me (Gustavo Bicalho) as the original creator.
 
Well, I thought JEM by Dervv could do it, but I guess it's never too late to get another input system for free he he he.
 

Gust

Member

kyonides":36g9b06w said:
Well, I thought JEM by Dervv could do it, but I guess it's never too late to get another input system for free he he he.

Does it? =O
I've never seen that one. Do you have a link?
Anyway, it was worth the fun of programming XD
----------- Edit --------------
This one? viewtopic.php?f=156&t=26736
It depends on the Keyboard layout and doesn't deal correctly with diacritical marks. Also, you can't get the secondary characters of some keys, like ¬ or º or ³. Mine works for all those.
 
First I thought "just another Input script" but the event handling, the fact that most pressed buttons are returned as characters and not some random numbers and your own library makes it a bit different.
You should try if it is faster than AWorks, which would probably be a reason for some people to use this one instead.

Anyway, even if it is not, thanks for the source :)
 

Gust

Member

Neo-Bahamut":37cqh1jn said:
First I thought "just another Input script" but the event handling, the fact that most pressed buttons are returned as characters and not some random numbers and your own library makes it a bit different.
You should try if it is faster than AWorks, which would probably be a reason for some people to use this one instead.

Anyway, even if it is not, thanks for the source :)

I guess it's probably slower than AWorks if you try to use it to just check the state of a key. The event list structure is useful when you need to know the order in which the keys were pressed, but for game play it's waste of time. And this gets only keys that translate into characters, no [Home] [End] nor the arrows.
That's why I said one should use this for text input and use other Keyboard module for the gameplay.

About the source: I hope you can learn something from it =] Dealing with the Windows Api to make this was really fun XD
 
How thoroughly did you test the hook? If it was made improperly, it could permanently slow down a system. This involves things such as the application not closing the hook on its own, as well as various other issues. I'm asking because hooks are a legitimate safety concern, and I'd rather be certain that something is safe before distributing it to people for use.

Edit: By the way, I thought you might like to know that the script crashes the game when the player presses F12, to reset. It's because of how RMXP and RMVX work. It reloads the scripts without unloading the currently loaded set, resulting in a double alias, an infinite loop, and a stack level too deep error.
 

Gust

Member

Glitchfinder":ae4t22xr said:
How thoroughly did you test the hook? If it was made improperly, it could permanently slow down a system. This involves things such as the application not closing the hook on its own, as well as various other issues. I'm asking because hooks are a legitimate safety concern, and I'd rather be certain that something is safe before distributing it to people for use.

Edit: By the way, I thought you might like to know that the script crashes the game when the player presses F12, to reset. It's because of how RMXP and RMVX work. It reloads the scripts without unloading the currently loaded set, resulting in a double alias, an infinite loop, and a stack level too deep error.

Well, I called it "hook" because it works like one, since it enters between two parts of the program code, but actually it's not really a "hook". I just replace the function that handles the events sent to the game window. The new function handles the events and calls the old function, no big deal. It's no security concern. You can check the DLL code yourself.

Thanks about the the bug report. Any hint on how to solve it? =P It's solved. I guess there probably is a better way to do that but... whatever.
Btw, glitch, i'm waiting for your "coding style comments" XD
 
Gu574v0":3qilxn4p said:
Well, I called it "hook" because it works like one, since it enters between two parts of the program code, but actually it's not really a "hook". I just replace the function that handles the events sent to the game window. The new function handles the events and calls the old function, no big deal. It's no security concern. You can check the DLL code yourself.

Thanks about the the bug report. Any hint on how to solve it? =P
Btw, glitch, i'm waiting for your "coding style comments" XD

Well, it's good to know it isn't a legitimate hook. As for the way to solve the aliasing issue, all you have to do is stick the alias in an if statement that checks to see if the new method (the one created by the alias) already exists.

As for the coding style comments, that's generally BlueScope's domain. I just chime in when it takes him too long to reply to something.

Also, I'd like you to know that you've sort of forced my hand. I had an update to my key input module sitting on my hard drive, waiting for me to get around to updating the header and releasing it. Well, now I pretty much have to. The only thing that's changed since the date on the version history is the header. While the character generation isn't quite as accurate as yours (it has a bit of trouble with shift-sensitive dead keys, and occasionally, it gives the wrong character. The only instance of the second that I've found so far is the Euro key on a German keyboard, but there could be others)

Also, I had another question. Can your script handle IME input?
 

Gust

Member

Glitchfinder":1gp3vun9 said:
Well, it's good to know it isn't a legitimate hook. As for the way to solve the aliasing issue, all you have to do is stick the alias in an if statement that checks to see if the new method (the one created by the alias) already exists.

As for the coding style comments, that's generally BlueScope's domain. I just chime in when it takes him too long to reply to something.

Also, I'd like you to know that you've sort of forced my hand. I had an update to my key input module sitting on my hard drive, waiting for me to get around to updating the header and releasing it. Well, now I pretty much have to. The only thing that's changed since the date on the version history is the header. While the character generation isn't quite as accurate as yours (it has a bit of trouble with shift-sensitive dead keys, and occasionally, it gives the wrong character. The only instance of the second that I've found so far is the Euro key on a German keyboard, but there could be others)

Also, I had another question. Can your script handle IME input?

Sorry, I don't understand what you mean by "force your hand" (i'm not a native english speaker) =/

About the IME input, I'm not sure. It handles the messages that Windows send that represent key presses. IME input on Windows would probably send the same kind of messages, otherwise it wouldn't work with any program unless they have specific code for IME. So I guess it would work, but I can't be 100% sure.
 
Btw, glitch, i'm waiting for your "coding style comments" XD
If this is what you want, I can provide some of those too :P

Never use semicolons in Ruby.
Class variables should only be used when really needed. They often show some weird behavior.
Ruby doesn't need type declaration as you did in GTI.pop.
Instead of using methods.include?("gus_gti_input_update") you should use method_defined?:)gus_gti_input_update).
 

Gust

Member

Neo-Bahamut":25mfap8a said:
Btw, glitch, i'm waiting for your "coding style comments" XD
If this is what you want, I can provide some of those too :P

Never use semicolons in Ruby.
Class variables should only be used when really needed. They often show some weird behavior.
Ruby doesn't need type declaration as you did in GTI.pop.
Instead of using methods.include?("gus_gti_input_update") you should use method_defined?:)gus_gti_input_update).
Thank you :biggrin:

1- That's my Java-programmer side in action XD Old habits don't go away easily. I'll remove them now.
2- They were needed. Is there any other way to store info relative to the module itself?
3- I know that "private" doesn't do anything in RGSS, but since it is a part of actual Ruby, I thought I should put it there to remember "DO NOT USE THIS METHOD" XD
4- Thanks, I didn't know that. I'm not really Ruby-fluent, I don't know much about these dynamic-language-tricks XD
 
Well, for the class variables, you can change them to a more appropriate type by leaving off one of the @ symbols, so that they are @variablename, as opposed to @@variablename. As for the private method, it actually is fully functional in RGSS.
 

Gust

Member

Glitchfinder":1weqrc33 said:
Well, for the class variables, you can change them to a more appropriate type by leaving off one of the @ symbols, so that they are @variablename, as opposed to @@variablename. As for the private method, it actually is fully functional in RGSS.

Private doesn't seem to have any effect here. I can call GTI.pop from an event without getting any error.

About variables: The @variables are instance variables, right? So I thought they wouldn't exist in the context of the class methods of the module (def self.etc). But now I tested and even if I make GTI a class, a self.etc method can acess a @variable. So I guess I don't really know what @ and @@ variables mean =P
 
The bigger or larger a variable scope is the less you need to use them. That means that most of the time you'd only depend the most from local variables and @instance variables.
 

Gust

Member

kyonides":1rv44w5e said:
The bigger or larger a variable scope is the less you need to use them.
I know that. It's just I'm not really a Ruby-expert, I'm used to Java and some C++. I thought @@ were just like Java
static
variables and @ were like normal member variables. But the behavior I see isn't what I expected (a class method like GTI.update shouldn't be able to access an instance variable, since there's no instance).
I'll try to learn about that and when I understand it I'll change it (or not) =P
 
Well, instance variables CAN be accessed within any method in your class and from another class by calling variable_name_that_stands_for_a_class.method_name if there's some attr_reader or attr_accessor with that method declared in the class or the method def method_name exist. It's pretty much part of the Ruby standard behavior so to say.
 
Glitchfinder":2c06bxiw said:
As for the coding style comments, that's generally BlueScope's domain. I just chime in when it takes him too long to reply to something.
Heh, interesting... I didn't know I had an official assistant :thumb:

However as far as coding style goes, it looks not too bad overall, other than the stuff already mentioned (especially the class variables are heavily redundant). You do have a lot of blank lines in there, which aren't looking good to me... the only time I use empty lines is to seperate classes or modules from each other.
You also have the alias naming issue of putting your name in there. I know you don't think it's bad, I know noone else does, and I know you've seen it in one of the other scripts. Still, I'm not giving up telling people they shouldn't put redundant information in alias names... NEVER! :fap:

On a side note: Semicolons do have their uses, for example to make giant case lists neater. Just like this:
Code:
case @variable

when 0; print 'Hey yo!'

when 1; print 'This is the one!'

when 2; print 'I know you don't care...'

end
But yeah, they're not needed to simply end a line, and as it provides zero to none assistance for reading the script (such as 'return' does), I wouldn't suggest using it.


So yeah, that being said, let's talk about general functionality... as I'm pretty much aware of what Glitch's script does, since I was more or less his testing bitch for the last few weeks (this might sound negative at first... but it's actually very negative :blank: ), I'm not having the hardest time to compare the two. I was also briefly testing your script, however I had some issues getting a simple test scene up and running in RMVX, as you didn't provide a demo or anything, or a Name Scene for RMVX.
That being said, I tested with a German keyboard and German keymapping, and everything from special characters (which isn't that uncommon) to dead key functionality (which is more uncommon :p ) worked like a charm. There's no issues with fast input or anything of the sort either, so in general, it works nicely. When testing (I modified the default Scene_Name of RMVX according to your provided sample), I had the issue that I couldn't input anything when I'd completely erase the actor's name, aka have all blank characters. That isn't only strange, but almost made me destroy my keyboard, my relationship and my uncle's boat on Hawaii... meaning you're better not responsible for that error :smile:

Regardless of my personal preferance for Glitch's script for several reasons, my "professional" (non-personal, if you want) opinion is that an Input script should advance the default capabilities of the module by the most keys as possible. As no non-character keys are supported, it seems incomplete to me, but that might be because I'm not really into making games that require lots of text input ^^ If someone needs exactly that solely, then I guess it's a good alternative, but I definately wouldn't suggest combining input scripts... that is just deemed to fail sooner, later, or totally unnoticed.

Still, you did a good job, and I'm especially surprised to hear that you seem to be an RGSS starter ^^
Keep up the good work!
 
2- They were needed. Is there any other way to store info relative to the module itself?
An instance variable, as Glitchfinder said.
The difference is, that you can not access those instance variables in an instance of GTI.
Code:
p GTI.instance_variable_get(:events) # should print something

p GTI.new.instance_variable_get(:events) # should print nil
Class variables are available in a larger scope, for example instances of a class.

The class variables are actually like the static variables in Java.
GTI is an instance of the class "Class". And as it is an instance of something, it can have its own instance variables.

3- I know that "private" doesn't do anything in RGSS, but since it is a part of actual Ruby, I thought I should put it there to remember "DO NOT USE THIS METHOD" XD
This is not what I meant. I was talking about String s = ' '*8.

You can use private, public and that stuff in Ruby, but it is not useful. You can easily call public again to change the access rights.

~edit~

@ BlueScope:
Code:
case @variable

when 0; print 'Hey yo!'

when 1; print 'This is the one!'

when 2; print 'I know you don't care...'

end
Code:
case @variable

when 0 then print 'Hey yo!'

when 1 then print 'This is the one!'

when 2 then print 'I know you don\'t care...'
:P
 

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