Handling Text Input or Accepting Character/Player Names

Your game would be more interesting if players can enter their name. It would make them feel like they really are part of the game. For instance, you can make certain dialog text address the player's name so it would seem like the game is "talking" to the player. It would also allow the player to associate his name with his score as proof of his victory and bragging rights.

What's the fuzz all about? You can always use a TextBox, right? No, my young padawan. Well, you could probably get away with it. But there's a couple of reasons why you should avoid using a TextBox in a fullscreen game. In most phones, you're game is hidden while the TextBox is displayed. It is usually drawn using the phones U.I. theme and would not look like it's part of the game. Once again, it's all about aesthetics.

We will be using the code from the tutorial Using Custom Fonts or Bitmap Fonts to display the characters on the screen. So head over there if you haven't already.

If you prefer, you can still use the normal Graphics.drawChar() method in place of the clsFont.drawChar() method. But you at least need to have the project code which you can download from : Clean MIDP 2.0 Game Templates.


Lack of Buttons
In this tutorial, we're going to mimic the text input used in arcade consoles of old. Those coin/token-eating machines only had a 4 way joystick, 6 function buttons, a start button, and a reset button. But these were sufficient enough to let the player input names or initials for the scoreboard. While a typical mobile phone has more buttons for us to play with, not all of them map to the same key codes on different phones.

Fortunately, the GameCanvas class gives us a few standard keys which more or less works on most phones.
Standard Keys:
  • 8 key or UP Button
  • 2 key or DOWN Button
  • 4 key or LEFT Button
  • 6 key or RIGHT Button
  • 5 key or FIRE Button

We're going to assign functions to these keys as follows:
  • LEFT - select the character to the left of the current selected character in the string.
  • RIGHT - select the character to the right of the current selected character in the string.
  • UP - Scroll through the characters upwards.
  • DOWN - Scroll through the characters downwards.
  • FIRE - commit changes


The Variables
Let's begin by opening the project from NetBeans and navigate your way to the clsCanvas code. Add the following global variables before the clsCanvas constructor:


private clsFont myFont;

// this will hold the resulting text
private String prText = "AAAAAA";

// the currently selected character
private int prSelected = 0;

// width and height of the characters
private int prWidth = 9;
private int prHeight = 10;

// character spacing - includes width of character
private int prSpacing = 12;

// vars for timing the blinking cursor
private long prStart = 0;
private long prDelay = 100;
private boolean prShow = true;

/** Creates a new instance of clsCanvas */
public clsCanvas(midMain m) {


The String prText will contain the text entered by the player and at the same time provide feed back of the letters the player has chosen. The length of the string assign to prText will also determine the maximum length of the text the player can enter and will also be the first characters displayed on the screen.

The integer prSelected will hold the index of the currently selected character from the string assigned to prText.

The variables prWidth and prHeight holds the width and height of a character. These variables will be passed to the clsFont.drawChar() method for resizing the clipping rectangle.

The integer prSpacing defines the interval or space in pixels between each characters drawing position (X coordinate). The value assigned to prSpacing should be the sum of the width of a character and the space between each character.


Replacing a Single Character
Next, we'll add a new method called replaceCharAt() that will let us change a single character in a given string. Add the new method above the run() method:


public String replaceCharAt(String s, int pos, char c) {
if (pos < (s.length() - 1)){
return s.substring(0, pos) + c + s.substring(pos + 1);
} else {
return s.substring(0, pos) + c;
}
}

public void run() {



The replaceCharAt() method requires 3 parameters:
  • String s - the original string
  • int pos - the position of the character to be replaced
  • Char c - the replacement character


User Controls
We also need a new method for detecting/responding to key presses and toggle the cursors visibility when needed. Add the updatePompt() method above the run() method:


public void updatePrompt(long currTick){
// get text length
int len = prText.length();
// get current selected characters ASCII value
int prOrd = (int)prText.charAt(prSelected);

// is it time to change the cursors visibility
if ((currTick - prStart) >= prDelay){
// update starting time
prStart = currTick;
// toggle cursor visibility
prShow = !prShow;
}

if (isDown[leftKey]){
// if not first character
if (prSelected > 0){
// select previous character in string
prSelected--;
}
} else if (isDown[rightKey]){
// if not last character
if (prSelected < (len - 1)){
// select next character in string
prSelected++;
}
} else if (isDown[upKey]){
if (prOrd == 97){ // small leter a
prOrd = 90; // jump to capital letter Z
} else if (prOrd == 65){ // capital letter A
prOrd = 32; // jump to space character
} else if (prOrd == 32){ // space character
prOrd = 122; // jump to small leter z
} else if ((prOrd > 97) || (prOrd > 65)){
prOrd--; // previous character
}
// replace the selected character with the new character
prText = replaceCharAt(prText, prSelected, (char)prOrd);
} else if (isDown[downKey]){
if (prOrd == 32){ // space character
prOrd = 65; // jump to capilat letter A
} else if (prOrd == 90){ // capital letter Z
prOrd = 97; // jump to small letter a
} else if (prOrd == 122){ // small letter z
prOrd = 32; // jump to space character
} else if ((prOrd < 90) || (prOrd < 122)){
prOrd++; // next character
}
// replace the selected character with the new character
prText = replaceCharAt(prText, prSelected, (char)prOrd);
}
}

public void run() {



The updatePrompt() method takes a single Long parameter currTick which is the current time in milliseconds. The value from the parameter is just used to check if the time that passed between prStart and the currTick is greater than or equal to the delay defined in prDelay. If so, the boolean prShow which controls if the cursor is show or hidden is toggled between true and false.

Notice that the updatePrompt() method allows you to select capital letters, small letters, and the space character. It wouldn't be too hard to add numbers there too or even the entire displayable character set. But you would usually only use capital letters.

You might want to keep the space character though, because the player can use it as placeholders for unused spaces if the length of your prompt is too long. This way you can trim the spaces from the value of prText when the player is finished entering his name.


Displaying the Text
Finally, we'll add a method for drawing the text and the cursor. Again, add the drawPrompt() method above the run() method:


public void drawPrompt(Graphics g, int x, int y){
// get length of the string
int len = prText.length();

// loop through the characters
for (int i = 0; i < len; i++){
// get ASCII value
int cIndex = (int)prText.charAt(i);

// compute next drawing position - X
int cx = x + (i * prSpacing);

// draw the character
myFont.drawChar(g, cIndex, cx, y, prWidth, prHeight);

// compute cusor position - Y
int cy = y + 6;

// if current char is selected
if (i == prSelected){
// if cursor shoud be visible
if (prShow) {
//draw the cursor using char 95 or underscore character
myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
}
} else {
//draw the cursor using char 95 or underscore character
myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
}
}
}

public void run() {



The drawPrompt() method draws each character in the prText string and cursor underneath each character. The cursor under the selected character blinks to let the player know which character he is currently editing. The markers/cursors under the non-selected characters will show the user how many characters he can enter.

The drawPrompt() method takes 3 parameters:
  • Graphics g - the Graphics object used for drawing. It will be passed to the clsFont.drawChar() method
  • int x - the horizontal starting drawing position
  • int y - the vertical drawing position


Putting It All Together
Let's modify the code in our run() method to make use of the new methods we just added. First, add the call to the updatePrompt() method below the call to checkKeys() like so:


checkKeys(iKey, lCurrTick);

updatePrompt(lCurrTick);

if (isDown[fireKey]){



Then, add the following lines above the call to flushGraphics() like so:


g.fillRect(0, 0, screenW, screenH);

myFont.drawString(g, "Enter your name:", 10, 20);

drawPrompt(g, 10, 40);

myFont.drawString(g, "LEFT/RIGHT - move cursor", 10, 80);
myFont.drawString(g, "UP/DOWN - change letter", 10, 100);

myFont.drawString(g, "Hello " + prText.trim() + "!", 10, 140);

flushGraphics();



You can now run the project and should see something like this:

User input sample output.


Here's the completed clsCanvas source code so you can check your work:


package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {
// key repeat rate in milliseconds
public static final int keyDelay = 250;

//key constants
public static final int upKey = 0;
public static final int leftKey = 1;
public static final int downKey = 2;
public static final int rightKey = 3;
public static final int fireKey = 4;

//key states for up, left, down, right, and fire key
private boolean[] isDown = {
false, false, false, false, false
};
//last time the key changed state
private long[] keyTick = {
0, 0, 0, 0, 0
};
//lookup table for key constants :P
private int[] keyValue = {
GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED,
GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED,
GameCanvas.FIRE_PRESSED
};

private boolean isRunning = true;
private Graphics g;
private midMain fParent;

private clsFont myFont;

// this will hold the resulting text
private String prText = "AAAAAA";

// the currently selected character
private int prSelected = 0;

// width and height of the characters
private int prWidth = 9;
private int prHeight = 10;

// character spacing - includes width of character
private int prSpacing = 12;

// vars for timing the blinking cursor
private long prStart = 0;
private long prDelay = 100;
private boolean prShow = true;

/** Creates a new instance of clsCanvas */
public clsCanvas(midMain m) {
super(true);
fParent = m;
setFullScreenMode(true);
}

public void start(){
Thread runner = new Thread(this);
runner.start();
}

public void load(){
try{
// load the images here
}catch(Exception ex){
// exit the app if it fails to load the image
isRunning = false;
return;
}

myFont = new clsFont();
myFont.load("/images/fonts.png");
}

public void unload(){
// make sure the object gets destroyed
myFont.unload();
myFont = null;
}

public void checkKeys(int iKey, long currTick){
long elapsedTick = 0;
//loop through the keys
for (int i = 0; i < 5; i++){
// by default, key not pressed by user
isDown[i] = false;
// is user pressing the key
if ((iKey & keyValue[i]) != 0){
elapsedTick = currTick - keyTick[i];
//is it time to toggle key state?
if (elapsedTick >= keyDelay){
// save the current time
keyTick[i] = currTick;
// toggle the state to down or pressed
isDown[i] = true;
}
}
}
}

public String replaceCharAt(String s, int pos, char c) {
if (pos < (s.length() - 1)){
return s.substring(0, pos) + c + s.substring(pos + 1);
} else {
return s.substring(0, pos) + c;
}
}

public void updatePrompt(long currTick){
// get text length
int len = prText.length();
// get current selected characters ASCII value
int prOrd = (int)prText.charAt(prSelected);

// is it time to change to cursors visibility
if ((currTick - prStart) >= prDelay){
// update starting time
prStart = currTick;
// toggle cursor visibility
prShow = !prShow;
}

if (isDown[leftKey]){
// if not first character
if (prSelected > 0){
// select previous character in string
prSelected--;
}
} else if (isDown[rightKey]){
// if not last character
if (prSelected < (len - 1)){
// select next character in string
prSelected++;
}
} else if (isDown[upKey]){
if (prOrd == 97){ // small leter a
prOrd = 90; // jump to capital letter Z
} else if (prOrd == 65){ // capital letter A
prOrd = 32; // jump to space character
} else if (prOrd == 32){ // space character
prOrd = 122; // jump to small leter z
} else if ((prOrd > 97) || (prOrd > 65)){
prOrd--; // previous character
}
// replace the selected character with the new character
prText = replaceCharAt(prText, prSelected, (char)prOrd);
} else if (isDown[downKey]){
if (prOrd == 32){ // space character
prOrd = 65; // jump to capilat letter A
} else if (prOrd == 90){ // capital letter Z
prOrd = 97; // jump to small letter a
} else if (prOrd == 122){ // small letter z
prOrd = 32; // jump to space character
} else if ((prOrd < 90) || (prOrd < 122)){
prOrd++; // next character
}
// replace the selected character with the new character
prText = replaceCharAt(prText, prSelected, (char)prOrd);
}
}

public void drawPrompt(Graphics g, int x, int y){
// get length of the string
int len = prText.length();

// loop through the characters
for (int i = 0; i < len; i++){
// get ASCII value
int cIndex = (int)prText.charAt(i);

// compute next drawing position - X
int cx = x + (i * prSpacing);

// draw the character
myFont.drawChar(g, cIndex, cx, y, prWidth, prHeight);

// compute cusor position - Y
int cy = y + 6;

// if current char is selected
if (i == prSelected){
// if cursor shoud be visible
if (prShow) {
//draw the cursor using char 95 or underscore character
myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
}
} else {
//draw the cursor using char 95 or underscore character
myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
}
}
}

public void run() {
int iKey = 0;
int screenW = getWidth();
int screenH = getHeight();
long lCurrTick = 0; // current system time in milliseconds;

String sName = "";

load();
g = getGraphics();
while(isRunning){

lCurrTick = System.currentTimeMillis();
iKey = getKeyStates();

checkKeys(iKey, lCurrTick);

updatePrompt(lCurrTick);

if (isDown[fireKey]){
isRunning = false;
}

//restore the clipping rectangle to full screen
g.setClip(0, 0, screenW, screenH);
//set drawing color to black
g.setColor(0x000000);
//fill the screen with blackness
g.fillRect(0, 0, screenW, screenH);

myFont.drawString(g, "Enter your name:", 10, 20);

drawPrompt(g, 10, 40);

myFont.drawString(g, "LEFT/RIGHT - move cursor", 10, 80);
myFont.drawString(g, "UP/DOWN - change letter", 10, 100);

myFont.drawString(g, "Hello " + prText.trim() + "!", 10, 140);

flushGraphics();

try{
Thread.sleep(30);
} catch (Exception ex){

}
}
g = null;
unload();
fParent.destroyApp(false);
fParent = null;
}
}



The code presented in this tutorial show a very simple way for your game to accept text input from the user. Using only directional keys/buttons, the player is able to enter his name which is useful for game customization and recording scores. One thing to remember is that you can adjust the maximum length of text the user can enter simply by assigning a string of the required length to the prText variable. You can even use a bunch of spaces to initialize it.

If you have ever played games on other hand-held devices with a minimal set of controls like the GameBoy® or even console systems like the PlayStation®, you have probably run across other techniques to get text input from the player. Those techniques have been tried and tested so it wouldn't hurt to adapt them for you own use. Just remember to keep it simple.

Was this post helpful? I made a mistake again? The words are to confusing? Please, don't hesitate to post a comment.

4 comments   |   post a comment
said...
Excellent...
Thanks and Keep going..
said...
You're welcome! :)
said...
Very cool! Thanks for sharing!!! :-)
said...
Thanksalot Delvin!
its really great that u shared such eazy way to learn J2Me for beginners.
i want to import my maps on midlet.how i would do that.