UI in Action
Feb 17, 2015
Another week and another stunning professionally produced gameplay video, this one of the first pass at the verb UI.
My goal last week was not only to get the UI working, but also the hooks from Squirrel into the engine so when verbs and objects were clicked on, everything flows correctly and wasn't just kludged up.
Astute readers will note the interface works more like Monkey Island than Maniac Mansion, and that is a good thing. Maniac Mansion was a first pass at the verb-based point & click UI and it was clunky. Monkey Island introduced (or maybe it was Last Crusade) scanning around the screen and the "sentence line" autoupdating. In Maniac Mansion, you had to select a "what is" verb. It also required a double-click to execute sentences. What were we thinking?
On the programming side, I was having some issues with how Squirrel handled classes, so I ditched the notion of rooms being classes and just made them simple tables/dictionaries. It works a lot better since I don't really need full-on classes for the rooms. There will only be one of each and they never get destroyed once created.
It also allowed me to get rid of the object { } table that enclosed all the room's objects. It was an unnecessary scope due the c code's inability to cleanly iterate through Squirrel classes. There was probably a way around it, but less is more and I like this better and it's all about me.
It could be argued you might want to create a base room class and derive from that, especially in the case of generic rooms like the forest in Monkey Island and the hotel in Thimbleweed Park, but endless class deriving and polymorphism is a pit of pain and eye poking. Object oriented programing (or OOP we pros call it) can make you feel very clever, but sometimes it feels like it's being used just because it makes you feel very clever.
I'm sure this will all change dramatically over the next few months. Stay tuned!
Here is the scripting code the runs the video above
{
background = "TestStreet"
enter = function()
{
print("Entering TestStreet")
}
exit = function()
{
}
TestStreetToBankDoor =
{
name = "Bank Door"
walkTo = function()
{
enterRoomFromDoor(Bank.bankToMainStreetDoor)
}
}
}
{
background = "Bank"
function threadTest()
{
while(1) {
print("loop")
breakhere(100)
}
}
// Called when the room is entered, after objects are inited.
enter = function()
{
print("Entering Bank")
// This thread needs to be killed when leaving the room. Need to figure out a way to make that happen automatically.
startthread(threadTest)
}
// Called when exiting, before new room enter code
exit = function()
{
}
bankPainting =
{
look_count = 0
name = "Cheap Painting"
lookAt = function()
{
if (look_count == 0) { currentActor.say("You'd think a bank could afford a better painting.") }
if (look_count == 1) { currentActor.say("This looks like a knockoff.") }
if (look_count == 2) { currentActor.say("I think I can see the numbers behind the paint.") }
if (look_count >= 3) { currentActor.say("I'm done looking at this painting.") }
look_count++
}
}
bankClock =
{
name = "Clock"
lookAt = function()
{
currentActor.say("It's who cares'o clock.")
}
}
bankToMainStreetDoor =
{
name = "Door"
quickExit = true // Actor exits before fully reaching the door
walkTo = function()
{
enterRoomFromDoor(TestStreet.TestStreetToBankDoor)
}
}
}
defineRoom(Bank);
defineRoom(TestStreet);
// Create detective
detective <- Actor("DetectiveFemaleWalk")
detective.talkColors(0x45a3ff, 0x173450) // text + outline colors
// Make her the selected character
selectActor(detective)
// Put her inside the Bank at the front door
enterRoomFromDoor(Bank.bankToMainStreetDoor)
One issue I have yet to solve is a simple and slick way to do different dialog for different characters.
if (look_count == 1) { currentActor.say("This looks like a knockoff.") }
if (look_count == 2) { currentActor.say("I think I can see the numbers behind the paint.") }
if (look_count >= 3) { currentActor.say("I'm done looking at this painting.") }
For a lot of lines, everyone can say the same thing, but we're going to want enough variation to give the characters personality. In reality (pro tip), you don't need that many character specific lines. The characters in Maniac Mansion basically react the same to 95% of everything the player does, but it's that 5% that stands out. That 5% feels like 75%. It's about picking and choose the custom replies and making them count.
I could so this...
if (look_count == 0) { currentActor.say("You'd think a bank could afford a better painting.") }
if (look_count == 1) { currentActor.say("This looks like a knockoff.") }
if (look_count == 2) { currentActor.say("I think I can see the numbers behind the paint.") }
if (look_count >= 3) { currentActor.say("I'm done looking at this painting.") }
}
if (selectedActor == Ransome) {
// etc
}
But that's going to get real tedious real fast. I need to figure something out.
The other issue I need to deal with is fonts. Right now the C64 font is a truetype font and truetype fonts want to render smooth and antialiased. It works most of the time, but with certain sizes it looks odd. I may end up doing a true bitmap texture atlas based font once all the sizes have settled down, but that's months down the road.
And in conclusion, I leave you with a time lapse of getting the UI working...
- Ron
Pure gold! :D
Yes, you can do it, but I have found it to just keep the text with the logic.
I also find writing in databases or spreadsheets to completely kill my creativity. It's a silly practice that seems to be accepted as the norm these days by writers. If you're a writer, rebel against this. It's often programers who want data in clean formats forcing creative people to adjust.
Although writing code like...
if (look_count == 0) { currentActor.say(TEXT_ENUM_MAGIC.BankPaintingLook1) }
...is no fun at all.
For reference, here is the Qt API docs for their version: http://doc-snapshot.qt-project.org/qt5-5.4/qtranslator.html
I still say that magic function could treat different characters as different localizations. Dunno if that's a good way to do it, but that's what I'd try. :)
Maybe you could use explicit IDs as well as the english text? Something like currentActor.say("bankpainting_1", "This looks like a knockoff.") ?
ransome.say(12478, "My makeup is starting to itch!")
Remember, there is a program that goes though in insets the 12478, so a person doesn't have to type that or keep track.
There is a lot more detail to this, like how to handle lines said by any playable character, so I should just do a full blog post about it in the future.
ransome.say("My makeup is starting to itch!")
Turns into what you said:
ransome.say(12478, "My makeup is starting to itch!")
I was wondering what would happen if you inserted a new line before that later on, but if I understand correctly, you'd simply keep the ID on the line from earlier, so it'd look like this:
ransome.say("Storing those fleas in my makeup container might have been short-sighted.")
ransome.say(12478, "My makeup is starting to itch!")
And you'd run your program again and it'd only touch lines that don't already have an ID:
ransome.say(12694, "Storing those fleas in my makeup container might have been short-sighted.")
ransome.say(12478, "My makeup is starting to itch!")
That's clever :)
And yes, I'd love more detailed blog post about this. Well, about everything. Thank you :)
But that's not a problem since the game code isn't hard coded in the engine but in a scripting language. After the game is complete (or nearing completion) it would be easy to run a script that extracts all unique strings and puts them in a data file (urgh!) for localization and another to "patch" the game code on run time with the translated replacements. For that to be efficient you could add a special prefix character in all strings that should be marked for localization like this:
if (look_count == 0) { currentActor.say("$You'd think a bank could afford a better painting.") }
This way it would be easier for a script to extract all strings starting with "$" (how doesn't love $?) while your "say" routine would simply ignore it.
Keep up the great work!
32 year old me: Wow! How on earth do they make these games...
Your April Fools update should be MMO support.
In C/C++ you don't have interfaces like in java and C#, that would be my first approach.
In C++ you can create the same funcionality extending your class (since it supports multiple inheritance).
What I am suggestion is creating a class with actions, like this (please ignore the sintax, it's only illustrative):
class DetectiveComments extends ActorComments {
int count = 0;
Room currentRoom;
// constructor
DetectiveComments (room) { currentRoom = room; }
// overrides base method
override Say() {
// calls logic for count and current room
}
}
When you enter a room, you create a new Comments class for your room and actor:
ActorComments comments = new DetectiveComments(currentRoom);
Ok, now the dumb designer with barely Javascript knowledge makes the dumb question:
Why the multiple "IFs"?
Isn't there a "Switch" statement you can use?
(Please, don't kill me!) :P
Thank you for the demonstration! I'll have to reduce the speed of the dialog text but I've greatly enjoyed watching this!
Today was the first time I wanted to post: When I saw the replies to "Look at painting" I instantly though of your earlier scripts and wanted to suggest different answers for different characters. But scrolling further down I saw that OF COURSE you have though of that - stupid me :-) It's great to see that after all those years (after all I was a small boy when I first played Maniac Mansion on a friend's NES) you haven't lost your edge and it's a great pleasure to see you at work.
Thanks Ron & Gary!
Four arrows.
I loved that. Of course it's not that important but that cursor, whenever I see it, somehow moves me ;). And on the commodore they had this cute snail mouse cursor when the game was still loading in the background! Awesome!
You could also give the resource file the extension .d64, just for fun.
Check out C64 quiz on iOS (which features a few lucasfilm games) for those who don't know what sound we're talking about...
...unless you think that encourages or condones the practice of software piracy.
But my preferred one was the "snail cursor" that appeared when you paused the game by pressing space-bar. :)
( also I loved the load/save background images )
Honestly I know there is still a long way to go but it's already looking like I hoped for, and the comments about the painting were pure comedy gold.
I have two comments about the video:
1. The room scrolling: Why not just constant-speed scrolling with an abrupt halt at the end? The constant acceleration and deceleration draws more attention than it probably should. I guess it's one of the points where you have to make a decision about whether you want it really old school or a tad more modern.
2. A darker border color for text lines would make the lines easier to distinguish from the colorful background.
Here's another thing. Yeah, it's about the seckrit of Monkey Island. I'm sorry if this bothers you and I guess people must be asking about that all the time. You got us hooked real good! There's still lots of speculation regarding the final scenes of Le Chuck's Revenge and what would have happened afterwards.
Looking at Thimbleweed Park and your post from 2013 (http://grumpygamer.com/if_i_made_another_monkeyisland) I think it's safe to say there are some obvious connections here, like Thimbleweed Park is that dream come true after a couple of months, just with another story (I noticed how the Maniac Mansion screenshots have a Disney watermark on them, which is kind of unsettling). Anyway, please keep going in that direction, it's good! Where was I? Oh yeah, please make sure you've written down the Secret of Monkey Island _somewhere_. Put it in a time capsule maybe? Some day, someone will be happy to hear the end of the story.
should work in a texas-like landscape - alright alright alright
http://vignette1.wikia.nocookie.net/uncyclopedia/images/e/e6/Maniacmansion.jpg/revision/latest?cb=20051206011333
When executing an action, say "look at painting", the engine would first ask the room, then other actors, then then player and finally the direct object, in this case the painting. Technically, you ask each of those how they react to the attempt to look at the painting. Each may handle the action or not, allow the action to succeed or not and stop processing or pass it on to the next one in line.
This may sound overly complex but allows to put responses where they logically belong. For "look at painting" you could put the default responses with the painting and potential overrides with the individual characters or even allow other characters to respond to your looking at the painting. If an action should have a special effect on a room, put the code with room. If an NPC intervenes if he sees you trying to x the y, put the code with the NPC, etc. You can probably find descriptions of the mechanism on the internet or rather likely in Graham Nelson's documentation of the Inform programming language.
Doors also had distinct opening and closing sounds even if mostly very simple.
The best part about doors is, when they're not "just" doors. Maybe there is something to read on them. Or a mail slot. Or they creak. Or you enter a doom and you'll see something that was hidden by an open door, and you can only see it if you close the door behind you.
Good times.
"Walk to door"
"Open door"
"Walk to door"
Next screen
"Close door"
You mentioned sometime on your blog that opening and closing doors is no good game design. I think it's awesome!
Doors however can not only be part of a puzzle, but also add a feeling of a real place. NPCs can use them, which is great too.
I loved how lively Melee Island felt with all the pirates walking through the town, opening and closing doors.
For me, it's an important adventure game design element, as it is to just pick up everything that is not nailed. Opening doors feels a bit like "breaking in". You're not sure, if you're allowed to do that. But hey, it's an exploration game… oh man, sound pretty stupid, huh?
The UbiArt framework used LUA for its data (at least at the time of the Rayman games), since LUA is essentially one giant key-value table, like JSON. (Note that this was only for data, not scripting, and production-time: data was entirely binarized in the final game).
BUT, it meant we could still execute the LUA (it was not so much parsed as evaluated), and do crazy things like importing pieces of tables from various different files, or "inherit" from a base file while overriding some values, etc. So basically we had uber-polymorphism without the clutter of classes. A kind of power I've yet to find in a prefab system like Unity.
Finally, everything was unrolled and arrived "flat" (well it's still a tree, but a POD tree, not a dynamic self-aware monster), so if you had trouble with your includes etc it was only a parsing issue, not an runtime issue. Overall a surprising simple & efficient way to approach data.
And during loading include mini games like arkanoid clones.
Anyway, I hope you keep the 'default' verb control mechanism, where hovering over a clickable area highlights one of the verbs (say 'open' for a door) and lets you right-click to execute it. That was really handy.
Or in other words, a 3-dimensional array that contains all the comments made by the characters:
- First dimension is the name of the character
- Second dimension is the object they are looking at
- Third dimension is the number of times the have looked at it.
When a character looks at an object, there would be this call:...
retrieve_text(currentActor, element, look_count)
...which will retrieve the sentence to be said.
Going one step further, the 3-d array could store the cursor for each Actor, so that you don't need to count the times it has been looked at, and the call would be like this:
retrieve_next_text(currentActor, element)
When using a different language, set a global variable that would choose if the 3-d array used is the English one, or the German one, or the Chinese one. Translators would translate the box.
How does it sound?
keys could be { item, actor, action, count, line } // hmmm it already feels clunky
{
{ bankpainting, detective, lookat, 1, "You'd think a bank could afford a better painting."}
{ bankpainting, detective, lookat, 2, "This looks like a knockoff." }
{ bankpainting, detective, lookat, 3, "I think I can see the numbers behind the paint."}
{ bankpainting, detective, lookat, 4, "I'm done looking at this painting."}
{ bankpainting, child, lookat, 1, "Ooh pretty colors!"}
{ bankpainting, child, lookat, 2, "My friend Denise made one just like this."}
{ bankpainting, child, lookat, 3, "My friend Denise made one just like this."}
}
with different actions instead of lookat, you could combine all dialog for the game in one place for easy editing... but tbh I think this has a lot of potential for becoming a giant cluttered mess...
Write a function "who" that takes an actor as parameter and returns said actor if it is the current one and a "dummy" actor if it's not:
function who(IsCurrentActor) {
return(IsCurrentActor == selectedActor?currentActor:dummyActor)
}
Then make all (well only the approriate ones) methods of an actor check if they are executed on the dummy actor or not:
function say(Text) {
if ( this==dummyActor) return;
[...]
}
Then you can use something like this:
who(Ransome).say("I'm starting to get angry at that paintin");
who(DetectiveRay).say("PAINTING!");
who(PurpleTentacle).say("I feel like I could....");
Of course this method could work for other stuff besides talking...
who(Ransome).pickup(this);
who(Dolores).say("No way I'm touching this!");
And then you could enhance your say method to take more than one text as parameter and acces the look_counter with a variable that is called "selectedObject" or something like this:
function say( args[] ) {
if ( this==dummyActor) return;
if ( args.length>1) {
if ( selectedObject.look_counter < args.length-1) selectedObject.look_counter++;
}
textToSay = args[selectedObject.look_counter]
[...old code...]
}
Then you could write something like this:
who(Ransome).say("I hate paintings",
"Makes me want to shoot it...",
"If only this wasn't a bank.")
Which is kind of the shortest way to do it, no?
Of course the parameter array would only work for unconditional multi-answers. But if you are planning to have a lot of them, a system like this might shorten the code.
function say(...) {
[...code...]
return(this)
}
function pickup(...) {
[...code...]
return(this)
}
who(Ransome).pickup(this).say("Yup, I got it alright").delay(100).facePlayer().delay(100).say("Why the hell did I do that? That will NEVER be useful.");
https://www.youtube.com/watch?v=37caBIZkDhI
Academy Award Nominee "Wild Tales" theme:
https://www.youtube.com/watch?v=_ueDO0HpCQw
woow....
http://assemblyrequired.crashworks.org/gdc2012-dynamic-dialog/
(It seems likely you would have seen this, but on the chance you only ever tagged it for later reading....)
I was trying to think of a way to combine the approaches there and maybe nest the dialogue in a JSON structure similar to your rooms. I think it would simplify the multiple character issue, and fits with your need to have them organized and easily-changeable (and translatable), but it gets tricky trying to marry unique numeric IDs and variable conditions more complicated than just look_count....
I just caught up with this blog and I have a question regarding the dependency charts. This may be stupid, but I'm trying to understand exactly what sort of information it is trying to convey. When a node has, say, two connections coming from above, does it mean that there are two paths to arrive at that node, or that in order to arrive at that node you must complete two other paths?
I know you've mentioned before that it's a "dependency" chart, not a "flow" chart; but I'm having a hard time making a distinction between them. Anyway, thanks for sharing your fantastic work--I look forward with enthusiasm to playing the game.
-dZ.
On a flow chart you can follow a path and you can also arrive at a node from different paths.
On a dependency chart you don't follow a path. A node is dependent from all its incoming paths and all of them need to be fulfilled.
Hope I could help.