To enter braille characters electronically, we use 6-key entry, where we can press combinations of s-d-f and j-k-l at the same time to correspond with combinations of braille dots. Can this be programmed in Unity?
bool keysRelased = true;
void Update()
{
if (Input.GetKey(KeyCode.A) && Input.GetKey(KeyCode.S) && Input.GetKey(KeyCode.D))
{
if (keysRelased)
{
Debug.Log(“ASD”);
keysRelased = false;
}
}
if (!Input.GetKey(KeyCode.A) && !Input.GetKey(KeyCode.S) && !Input.GetKey(KeyCode.D))
{
keysRelased = true;
}
}
Before really getting into the details of “how” to implement multi-key functionality, it’s important to look at what is necessary to make it function in an intuitive way.
In addition to having virtually no personal experience with Braille myself, there are a few elements to consider in terms of feedback. Let’s look at them in terms of specific scenarios when typing:
Hold two keys, release one, then press another in its place.
Should this result in a key press, or is it an incomplete action/correction? Since input should logically conclude when the last of the six keys has been released, should changing the currently-held keys mean that only the final state before releasing them is important?
Hold three keys, release one, wait, then release the other two.
Should there be a timeout if a key is released alone, to revise the current multi-key state?
Hold one key, then tap another one repeatedly.
Is there any situation where a repeated, partial input is an intended/expected action?
With no keys previously held, tap a single one rapidly.
While it’s definitely normal behavior on a keyboard to be able to type the same letter in rapid succession, one of the only standard modifiers is the shift key. Additionally, standard typing input is registered on [key down], where multi-key input of this nature would only logically be able to register [key up] completion. Should each key simply be considered both a key and a modifier, able to be typed rapidly, or should there be any minimum period required to signal “intent” in the process to ignore “accidental” inputs?
Hold a relevant key (S), then press an irrelevant one (U).
Should this interfere with the input scheme in any way? Should it cancel the current Braille input? Alternatively, if the second key IS relevant yet disconnected, such as spacebar, should that cancel the current input, or just add a space without interrupting the resulting Braille character?
With those points in mind (if not others I've overlooked), there will be plenty of this that I don't have a direct solution for. Additionally, some things may just have to be empirically derived through personal testing (such as any aforementioned timeouts). So, on that note, let's start with some basic implementation logic to get things started:
// Note: This is not optimal, thorough, nor master-planned
// ... nor has it been tested.
//
// This also doesn't include:
// -Releasing a key and pressing another clearing currently-not-pressed keys
// -Accommodating irrelevant key presses
// -Repeated taps (if special behavior is desired)
public class KeyState
{
// The key to be tracked
public KeyCode key;
// Keeps track of whether the key...
// hasn't been pressed [inactive]
// is currently held [held]
// or was released, but is within timeout [released]
public enum State { INACTIVE, HELD, RELEASED }
public State state = State.INACTIVE;
// The timestamp to be set when [released] status begins
public float timestamp = float.MinValue;
// Constructor, to immediately set a key
public KeyState(KeyCode key)
{
this.key = key;
}
// Returns -1 when [held]
// Returns 1 when changing from [held] to [released]
// Returns 2 when remaining in [released] state
// Otherwise, returns 0 (when [inactive] or [released])
public int UpdateState(bool pressed, float time, float timeout)
{
if(pressed)
{
state = State.HELD;
return -1;
}
else
{
if(state == State.HELD)
{
state = State.RELEASED;
timestamp = time;
return 1;
}
else if(state == State.RELEASED)
{
if(time - timestamp >= timeout)
{
state = State.INACTIVE;
}
else
{
return 2;
}
}
}
return 0;
}
public void Reset()
{
state = State.INACTIVE;
timestamp = float.MinValue;
}
}
public class BrailleKeyTracker : MonoBehaviour
{
// Maintain only a single timeout value
public float timeout = 0.5f; // Likely needs empirical tuning
public KeyState[] keys = new KeyState[6]
{
new KeyState(KeyCode.F),
new KeyState(KeyCode.D),
new KeyState(KeyCode.S),
new KeyState(KeyCode.J),
new KeyState(KeyCode.K),
new KeyState(KeyCode.L)
};
// Based on some reading, this might be readily convertible
// into an applicable final character's data type
sbyte combinedInput;
void Update()
{
int justReleasedCount = 0;
combinedInput = 0;
// Reverse for loop is slightly faster than counting up
// and order isn't important
for(int i = keys.Length - 1; i >= 0; i--)
{
int inputState = keys_.UpdateState(Input.GetKey(keys*.key), Time.time, timeout);*_
* // Add to combinedInput sbyte using bitwise operator*
* if(inputState == -1)*
* {*
* combinedInput |= unchecked((sbyte)(1 << 7)); // 10000000, or negative bit*
* }*
* else if(inputState == 1)*
* {*
* justReleasedCount++;*
* combinedInput |= (sbyte)(1 << i);*
* }*
* else if(inputState == 2)*
* {*
* combinedInput |= (sbyte)(1 << i);*
* }*
* }*
* if(justReleasedCount > 0)*
* {*
* combinedInput |= (1 << 6); // 01000000, spare bit*
* }*
* // Maximum possible Braille combinations, 6-bit limit*
* if(combinedInput >= 64) // Is top, non-sign bit set?*
* {*
* // Last key was just released. Finalize input*
* for(int i = keys.Length - 1; i >= 0; i–)*
* {*
_ keys*.Reset();
}*_
* combinedInput &= 63; // Clear top two bits to ensure 0-63 range*
* // Convert from 0-63 range input to Braille char*
* // I don’t know for certain how it “should” be handled,*
* // so you’d need to make this function yourself if there’s*
* // not already an established approach*
* ConvertToBraille(combinedInput);*
* }*
* }*
}
This is very, very helpful. Thank you so much! We’ll put the puzzle pieces together over time and I’ll come back to confirm how things go and for more clarity, if needed.