In this installment, we'll have a look at the displays, interfaces, and controls of the CRPG.
It may come as a surprise to some of you, but displays and interfaces are some of the most complicated, messy, time-consuming and memory-consumptive pieces of code to write. You thought the map processing algorithm was nasty? Ha! Displays and controls make that seem like a cake-walk.
The main problem is that for the computer to display information in a human-readable form is not a trivial task. Every display, while seemingly simple, is very complicated to put together. I had somewhat anticipated this when allocating memory blocks in my designs, but it was still a shock to see it playing out.
Let's look at a small example, a very simple task that's done frequently: displaying a word variable as an integer on-screen for human viewing:
* Integer to string function. Uses ADDNUM as a subroutine. Returns 0 to 65535. * Pre-byte length string in NUMWRK array * R3 - Value to calculate DNUM DATA VWS,DNUM1 DNUM1 MOV @>0006(R13),R3 LI R0,NUMWRK+1 CLR R1 MOV R3,R3 JEQ DNEND DN0 CLR R2 CLR R5 LI R6,10000 DN1 DIV R6,R2 DIV @W10,R5 MOV R5,R6 CLR R5 MOV R2,R2 JNE DN2 MOV R1,R1 JNE DN2 CI R6,1 JEQ DNEND JMP DN1 DN2 BL @ADDNUM CI R6,1 JNE DN1 DNEND MOV R3,R2 BL @ADDNUM SLA R1,8 MOVB R1,@NUMWRK RTWP * Companion function for DNUM, adds number to string ADDNUM SLA R2,8 AB @OFFSET,R2 MOVB R2,*R0+ INC R1 CLR R2 RTQuite a bit of code just to print a single 16-bit value on screen! I use a BLWP to access it because the amount of computations and register usage is high enough I don't want to try and tailor it so it doesn't destroy important values in the calling routine. I use my video register workspace for it since there's no video access occuring during the number-crunching.
100 REM PNAME$ contains player name 101 REM PCLASS$ contains player class 102 REM PLV contains player level 200 C=INT((28-LEN(PNAME$))/2):: DISPLAY AT(1,C):PNAME$ 210 S$="LEVEL "&STR$(PLV)&" "&PCLASS$ :: C=INT((28-LEN(S$))/2):: DISPLAY AT(2,C):S$Not that bad. Now let's look at the assembly equivalent:
* Draw header information of stat screens * @PACT - Player number HEADER MOV R11,*R10+ MOV @STACHR,R4 SLA R4,5 LI R0,PNAMES A R4,R0 LI R1,WORK LI R2,32 BLWP @VMBR * Write name MOV @ACTSCR,R0 LI R1,WORK+1 MOVB @WORK,R2 SRL R2,8 LI R3,32 S R2,R3 SRL R3,1 A R3,R0 BLWP @VMBW MOVB @HIBUFF,R3 SRL R3,8 BLWP @DNUM MOVB @B6,@STRING LI R3,UITXT+6 LI R4,STRING+1 LI R5,5 BL @CPYSTR MOVB @SPACE,*R4+ AB @NUMWRK,@STRING AB @B1,@STRING LI R3,NUMWRK+1 MOVB @NUMWRK,R5 SRL R5,8 BL @CPYSTR MOVB @SPACE,*R4+ AB @WORK+16,@STRING LI R3,WORK+17 MOVB @WORK+16,R5 SRL R5,8 BL @CPYSTR MOV @ACTSCR,R0 AI R0,32 LI R1,STRING+1 MOVB @STRING,R2 SRL R2,8 LI R3,32 S R2,R3 SRL R3,1 A R3,R0 BLWP @VMBW B @SUBRET * String copy subroutine * R3 - Source * R4 - Destination * R5 - Length CPYSTR MOVB *R3+,*R4+ DEC R5 JNE CPYSTR RTYikes, that's a lot more work! It's over 50 lines of code, and around 200 bytes of codespace. And that's just to write a little header! We can't afford a lot of code like this, not at all. We can also see why interpretive languages have their definite uses.
STAORD BYTE 1,2,3,4,9,58,48,49,51,57 STAPOS DATA 167,182,199,214,294,373,428,460,522,585 DATA 262,277,358 DATA 309,438,470,534,598 * Stat screen #00 - Player Statistics SSCR00 MOV @STACHR,R8 SLA R8,5 CLR R4 CLR R5 * Write 'direct' values to screen S00A MOVB @STAORD(R4),R1 SRL R1,8 MOVB @HIBUFF(R1),R3 SRL R3,8 BLWP @DNUM MOV @ACTSCR,R0 A @STAPOS(R5),R0 LI R1,NUMWRK+1 MOVB @NUMWRK,R2 SRL R2,8 BLWP @VMBW INCT R5 INC R4 CI R4,9 JLE S00AThe HIBUFF array contains player data, which the STAPOS array contains pointers to various player data elements. Each value is extracted, converted to a displayable integer, and then output into a position on screen as dictated by the placement in the array.
So now you may ask, what about the little graphic headers? Well, I initially was going to draw them on screen, but this, as you can imagine, consumed even more code-space for a trivial task. So then I stored it in a 64-byte data array in CPU memory to just write out in one pass.
Then I got really clever, and decided, why not just template as much as possible into memory images that I can just read directly into a video screen buffer?
There's two things to consider before using screen images for this purpose:
One small regret I have is how frugal I have to be with text in-game. The fact is, every scrap of text takes up room, and feedback to the player will be very minimalist. I've noticed that the Ultima games, for example, seem to have quite a bit of feedback in their little text window when you're playing. I wish I could be that wordy, but I'd rather have the game features over excessive prose.
I have to avoid excessively-sized words and phrases like "%NAME% strikes the foul creature for %VALUE% points of damage!" in lieu of "%NAME% hits for %VALUE% damage". I'll have to rely on the icon system to tell the player what happened, rather than say "%NAME% is poisoned!". I'll also use the demo as a means to determine, from play-testers, where further feedback is absolutely critical.
So, some of my UI elements are:
Key control just makes sense... there is not that many input devices for the TI besides the keyboard, and it's a stock part of the console. Joysticks really don't fit a CRPG, being more for action games. And a mouse is an uncommon to rare item on a TI, and requires a completely different approach to the interface. Not to mention that I don't even have the source code for the driver!
On a side-note, here's the easy way to deal with the caps issue in assembly: Just insert a bit of code like this:
B97 BYTE 97 B32 BYTE 32 * * Key's been pressed and in @KEYVAL CB @B97,@KEYVAL JGT KEY1 SB @B32,@KEYVAL * Key actions KEY1 NOPNot coincidentally, the upper and lower-case alphabets are separated by exactly 32 characters in the ASCII table. So all you need to do is check if a character is equal or above 97 ('a'), and if so, subtract 32 from the value, and then you need only check one set of letters. The one flaw with this technique is that it will make the characters from 123+ coorespond to different codes... only a concern if you really have to have brackets or control characters.
The turn-based nature of the game is also inherent in the UI design. The process is pretty simple:
The input functionality is something I eliminated early. A bit of background story...
When I was in college, I had a Java course over the summer. Ten weeks was crammed into six weeks, and just to make things interesting, our teacher was not familiar with Java at all, having been handed a book a week or so prior. He was game to learn, though, and threw out a number of minor programming ideas to try and do in Java.
A rather funny one was to write a simple "wait until a key is pressed, then continue" function. This is easily doable in BASIC on any platform, and in C/C++ using the getc() function. To our surprise, though, Java simply didn't have the low-level access necessary to do this; the best we could do was have a blinking cursor that would advance if you pressed any character and THEN pressed enter.
A particular one that was gruesome, though, was an input style command in the Java command window. I don't think the Java devs put much effort into command-window controls, and as such, there's no key-driven input class (or wasn't when I was working with it) native to the platform. Our textbooks usually supplied these, using various KeyInputStream and Buffer commands, and were fairly complex for a novice Java programmer. Eventually our instructor decided it was too difficult to ask us to write one of our own.
An input function is really a challenge to implement in assembly. You have to have a buffer for the input, implement a blinking cursor, track the cursor position, allowing you to move back and forth, consider every keystroke to see if it's a "viable" character or not, and detect when the ENTER key is pressed so you know the user is finished.
The worst part of an input function, though, is interpreting the string entered by the user. Giving the user that level of control means he could literally enter ANYTHING. You'll have to do string comparisons to see if the user entered this or that. It's a huge amount of work, and for what? How many times does the user need that kind of flexibility in the game?
Consider Tunnels of Doom. Selecting a character involves typing out part of their name. If you have two characters with similar names, you have to type out far enough that it knows which one you mean. It has to do a string comparison on all four names to find the right player. I wonder why Kevin Kinney didn't just do "Select player (1-#)" and assign numbers to the characters? Maybe GPL makes string comparisons easier, I don't know.
The only area I see an input function being used in the game is when you're entering a character's name at character creation time. And this isn't part of the game engine, it's a seperate program. So I don't need to write one for the game itself.
Probably one of the worst things a CRPG's UI can do is make ordinary tasks a trial. It's up there with online games that have horrible installation and setup processes. There's no quicker way to drive off a potential player.
I think the nastiest example I can think of is the buying interface in Final Fantasy on the NES console. It did not allow the player to specify a quantity of items; everything had to be purchased one at a time. With items like potions, it was a nightmare of pressing buttons and moving the controller over and over and over... You could get Carpal Tunnel from doing it too long. It didn't help that the game guides all advised you to buy "99 of everything". Argh!
This is an extreme case, though. There are less onerous cases that can be equally bad.
When I was writing the travel portion of the engine, I had planned to have "portrait" screens, which would be used to give a more graphical look at objects, and a variety of options to interact with them.
For example, a fountain would have a nice graphic representation of a flowing fountain (using the animated water tiles and other bits) and an invitation for the party to drink, along with the results of it. Another is a door, which will offer options for the player to interact with it.
However, in light of my recent discoveries of how much codespace having that level of interactivity takes, I've been asking myself if maybe a simpler approach is needed. What about if when you move into a door, the following things are done:
Next time, we'll take an in-depth look at the combat engine, which I'm working on now. Should be a fun one!