Removing mouse modes, walking with arrow keys, adding images to dialogue boxes

Started by LuminescentJester, Wed 01/04/2015 03:46:27

Previous topic - Next topic

LuminescentJester

Quote from: Snarky on Mon 06/04/2015 09:39:46
I'm starting to feel you need to read the manual more thoroughly, and maybe take a step back and think a bit before you post questions. This forum isn't intended to give you step-by-step instructions on every problem you have, but to help you gain understanding to solve things on your own, and help you out when you're really stuck.

So when you're having an issue with the character's speech colors, for example, you should go look in the manual under "Scripting | Character functions and properties". The list is short enough that you can look through it to see if there's anything relevant there. If you do, you'll find an entry for "SpeechColor property." If you read that entry, you'll see that it says "Gets/sets the character's speech text color.

Okay I can't reply to this whole thing right now because this isn't my computer. I will probably give it a better read/reply later tonight but for this part right here.

I did do that though! I had cLuigi's text color set for most of this topic. It was working fine. Then the moment I changed it to sierra style aka portrait that messed it up and that's what I was confused on. I don't understand why changing that would break it.

Reading the manual works fine for me when it comes to basic things but when it comes to scripting I have no idea what I am reading. I tried starting at the basics in manual but it does not seem to start you with the basic of "this is how scripting works." It seems to jump right in and that is really confusing.

Edits start here:

Now that I have my game again, I double checked that Luigi's text color is indeed set to green, which it is, and that it didn't revert to default or something when I switched to sierra style. Then I fiddled with speech style trying to figure out why it overwrites Luigi's text options and realized that I'm using sierra with backgrounds. When I first saw that I thought it defined something about the portraits but apparently that means it overwrites pretty much everything to do with the text boxes. (At the very least it changes the font as well as the background? That is probably clear to everyone but me...)

So I guess the problem of Sierra portraits eating the text settings is now solved. Meaning all my original questions are now solved. Yay.

If for some insane reason you still want to help me with the font I did this in the global script:
Code: ags
void SayFont(this Character*, String message)
{
  int fontNum = this.GetProperty("SpeechFont");
  Game.SpeechFont = fontNum;
  this.Say(message);
} 


And that works now. (Well, it doesn't freeze the game). But I'm still not really clear on how/why it works or how to actually call it in the game. (yes I tried re-reading the help section on that).

You're right, I do need more information on the mechanics of basic scripting. I didn't really realize that when I downloaded this program and the first time I opened the tutorial it didn't seem to cover any of my questions. I guess part of my mistake at this point has been in presuming that the tutorial would be written in a way that would allow me to skip to the parts that are relevant to my current goals, but its more like a novel that needs to be followed from the beginning.

We could call it a day since its just font, and really my characters don't need their own super special font and its probably not worth the headache at this point. It might be better for us to just shelf the remaining problem for now and I can work on those things I do understand for a bit until I encounter another thing that is absolutely necessary, or until I've leveled up my understanding of basic scripting.

I'm really sorry for the headache. Thank you so much for all the help.

Snarky

Glad you've got most of it working!

If you're looking for a basic "this is how scripting works" tutorial, that's what the Scripting Tutorial part of the manual is. It's by no means complete, but it should be enough to get you a basic grasp of the key ideas. If you're still having trouble with basic concepts like functions, variables, structs, headers, etc. having gone through that, you'll have to look elsewhere. These are pretty standard concepts from the C-family of programming languages, so any intro to C tutorial should do the trick.

There ARE certainly aspects of AGS scripting specifically that are not explained (or at least well explained) anywhere (among the more relevant ones: how all the different scripts interact, and how to import and export variables, functions and structs), and it would be good to get some input on trouble spots from a beginner, so if you note down what information seems to be missing, hard to find or difficult to understand, perhaps we could use that feedback to improve the manual.

As for the font... instead of just telling you what to do, I'll step you through how I would go about solving it, step by step. Try to make sure you understand what's going on at each step:

1. First, I would test that the basic approach works. So take somewhere you have "cLuigi.Say("blah blah");", and replace it with:

Code: ags
    Game.SpeechFont = eFontCloisterBlack;
    cLuigi.Say("blah blah");

This should be pretty clear, right?
So, does it now use the right font? If yes, great. If not, we have to figure out why it's not working before we continue. Let's assume it works.

2. Now you might have noticed that after you set the font, all the text uses CloisterBlack for the rest of the game. Let's try to reset it after displaying the line:

Code: ags
  FontType oldSpeechFont = Game.SpeechFont;
  Game.SpeechFont = eFontCloisterBlack;
  cLuigi.Say("blah blah");
  Game.SpeechFont = oldSpeechFont;

You see how we back up the current font, change it, display the message and restore the previous value? This is a pretty common programming pattern. I'm not actually sure whether this will work or not; it depends on whether AGS displays the text right away when we call cLuigi.Say(), before we have time to change the speech font back, or only makes a note to do it later (some game commands aren't executed immediately, but only as soon as the engine can get around to it). If it doesn't, we'll have to figure out some other way to reset it. But let's proceed assuming it does work.

3. If we had to write all of this every time a character says anything, that would be pretty tedious. So let's wrap it up as a function. We'll just use a regular, plain AGS function for now:

Code: ags
void SayFont(Character* cSpeaker, String message, FontType speechFont)
{
  FontType oldSpeechFont = Game.SpeechFont;
  Game.SpeechFont = speechFont;
  cSpeaker.Say(message);
  Game.SpeechFont = oldSpeechFont;  
}

You see how it's just the same code, but we've put all the things that can vary each time you call it (the speaker, the font and the text) as an argument for the callers to fill in?
If you put this function at the top of the global script, you can call it from another function in the global script using this syntax:

Code: ags
  SayFont(cLuigi, "blah blah", eFontCloisterBlack);

Test this to see that it works.

4. If you want to be able to call it from room scripts as well (and you almost certainly do), you need to declare it in the global script header:

Code: ags
import void SayFont(Character* cSpeaker, String message, FontType speechFont);

The idea with headers is that they get added to all the scripts below (so the global script header gets added to all the room scripts), and by putting this definition there, you're letting the scripts know about the function SayFont() they can call. Try calling SayFont() from a room script (the same syntax as in the previous step).

5. It's usually best not to clutter the global script with lots of helper functions like this that aren't specifically about the content of the game. So let's create a new script file and move the code there. I usually keep a script with useful functions in a script called "Convenience." To create a new script, right-click on the script node in the project explorer, and name the script "Convenience" (or whatever else you like). Move the function definition from the global script to the new script (Convenience.asc):

Code: ags
void SayFont(Character* cSpeaker, String message, FontType speechFont)
{
  // The content of the function... 
}

And move the function declaration from the global script header to the new script header (Convenience.ash). At the same time, let's add a bit of documentation:

Code: ags
/// Displays the message as a line spoken by cSpeaker, using speechFont
import void SayFont(Character* cSpeaker, String message, FontType speechFont);

When you have a comment with three slashes on the line above the declaration, AGS will use it as the help-documentation for the function, and it will automatically pop up in the editor the way help for built-in functions does. This can be very helpful once you have a bunch of different functions. (You might have to save and rebuild your game, and sometimes even close and reopen the script files, before the auto-popup starts working.)

Test that calling the function from the global script and the room scripts still works.

6. OK, now let's finally make it an extender function instead of just a plain old function!

This isn't strictly necessary, but it makes the code easier to read, and just... nicer. Writing it as an extender function is essentially saying that this function is an action that happens TO, or is performed BY, one of the arguments (which has to be an instance of one of AGS's built-in struct types: Character*, Object*, DynamicSprite*, etc.). In this case, it's cSpeaker that is Saying something, and cSpeaker is a Character*, so we want the function to be a Character* extender function. We just change it slightly:

Code: ags
void SayFont(this Character*, String message, FontType speechFont)
{
  FontType oldSpeechFont = Game.SpeechFont;
  Game.SpeechFont = speechFont;
  this.Say(message);
  Game.SpeechFont = oldSpeechFont;  
}

Instead of cSpeaker, we declare the first argument as a special variable called "this" (and with the order reversed). That's it! Of course, we also have to change the definition in the header to match:

Code: ags
/// Displays the message as a line spoken by this Character, using speechFont
import void SayFont(this Character*, String message, FontType speechFont);

Now we get to the point of doing this! Having an extender function changes how we call it. Instead of SayFont(cLuigi, "blah blah", eFontCloisterBlack); we now write:

Code: ags
cLuigi.SayFont("blah blah", eFontCloisterBlack);

See how this is looks just like how you'd call the built-in Character.Say() function: "cLuigi.Say("blah blah");"? It's as if there really was a Character.SayFont() function built into AGS. That's the power of extender functions!

7. We might actually be happy with what we have so far and leave it at this point. However, it's a bit annoying that we have to specify the font every time we call the SayFont() function, if for each character it's always (or almost always) going to be the same every time. (Edit: And more importantly, if you change your mind about which font to use, you don't want to have to go back and change a thousand different cLuigi.SayFont() lines!) The function knows which character is speaking, so it should just figure out which font to use based on that, right? Well, that's what the remaining bit of the code I gave you before does, but the explanation is a little complicated. So you can read on, if you want to:

There are actually a couple of different approaches to solving this problem, each with its pros and cons. I'll just explain the way I used before, since it demonstrates a useful AGS feature: What we want is to have a SpeechFont property for each character which specifies which font to use for their speech; something like cLuigi.SpeechFont. There isn't a property like that built into AGS, but we can use custom properties to add something very similar. Let's add a custom property called "SpeechFont". How to do this is explained in the manual under "Other Features | Custom Properties". Make the type Number (we'll use it to refer to the font number) and set the default to 1 (in AGS, font number 0 is the default normal text font and font number 1 is the default speech font).

Now in the editor, set the value of cLuigi's SpeechFont property to CloisterBlack's font number, and similarly for other characters with the fonts you want them to use.

Now we can change our function to use the value of this property instead of taking the font as an argument:

Code: ags
void SayFont(this Character*, String message)
{
  FontType oldSpeechFont = Game.SpeechFont;
  int speechFont = this.GetProperty("SpeechFont");    // This is the key line that's changed
  Game.SpeechFont = speechFont;
  this.Say(message);
  Game.SpeechFont = oldSpeechFont;  
}

Also change the header to match, of course, and remove the font argument from the places where you call the function. Test. Does it work? Notice how apart from backing up oldSpeechFont, this is exactly the code I gave you before. Hopefully it makes sense this time around!

You may have noticed that before we were using a FontType variable to set the speech font, but now we're using an int. Well, the reason for this is that FontType is a built-int enum (short for "enumerated type": see the manual under "built-in enumerated types"). An enum is essentially a list of options: for example, a Direction enum might have the options: eUp, eDown, eLeft, eRight, eForward, eBackward. But secretly, enums are just numbers, with the each option translated by AGS into its number in the list. So we can always use an int instead of one of the enum values (and in this case we have to, since custom properties can't be enums)... However, if we give a number that isn't in the list, things are going to break!

That's the drawback of this solution: if you put in a number that isn't in the list of fonts as the SpeechFont value for one of the characters, it's going to crash when you run it. Also, since you're just going by font number and not font name, if you remove a font or reorder the fonts, the numbers aren't going to match any more, and suddenly all characters will use the wrong font. And it's a bit inconvenient to enter the font number instead of the font name in the SpeechFont property, particularly if you have a lot of fonts, or if you can't remember whether to count from 0 or from 1. Finally, custom properties can only be set in the editor, you can't change them at run-time, so you're stuck with the value you set. That's why personally I'd probably use a different solution to simulate a SpeechFont property, but this will do for now.

8. OK, there is one final improvement you can make. The current version will always use the character's SpeechFont value. But what if we only want to use that value most of the time, and sometimes a different font? In that case, what we want is an argument with a default value.

Let's go back to the previous version of the code:

Code: ags
void SayFont(this Character*, String message, FontType speechFont)
{
  // Function body...
}

And the declaration:

Code: ags
/// Displays the message as a line spoken by cSpeaker, using speechFont
import void SayFont(this Character*, String message, FontType speechFont);

We'll change that declaration to look like this:

Code: ags
/// Displays the message as a line spoken by cSpeaker, using speechFont (if provided)
import void SayFont(this Character*, String message, FontType speechFont = -1);

Important! Only change the declaration in the header, not the actual function definition in the script body!
That "= -1" part means that if we call the function without a speechFont specified, it will use -1 as the default. So now we can write either:

Code: ags
  cLuigi.SayFont("blah blah", eFontCloisterBlack); // Will use CloisterBlack

... or:

Code: ags
  cLuigi.SayFont("blah blah"); // Will use font -1 (???)

So what the hell is font -1? Well, it's not a valid font number, obviously. Which is the point: we'll use it as a special value to let the function know not to use it, but to go with the SpeechFont property value instead. Change the function to look like this:

Code: ags
void SayFont(this Character*, String message, FontType speechFont)
{
  FontType oldSpeechFont = Game.SpeechFont;
  if(speechFont == -1)  // If the caller didn't specify a (valid) font, use this character's SpeechFont property
    speechFont = this.GetProperty("SpeechFont");
  Game.SpeechFont = speechFont;
  this.Say(message);
  Game.SpeechFont = oldSpeechFont;  
}

And now if we call cLuigi.SayFont("blah blah"); without a font specified, it will use cLuigi's SpeechFont property, but we can overrule it any time we like.

Do you follow? Test it out!

More than likely, there are some bugs somewhere in this code, which I haven't tested myself. Let me know if anything breaks.

LuminescentJester

Quote from: Snarky on Tue 07/04/2015 15:40:23
Do you follow? Test it out!

More than likely, there are some bugs somewhere in this code, which I haven't tested myself. Let me know if anything breaks.

Woah. This is a lot of text. O_O *takes a few days to catch up on college work before trying to implement everything*

Okay. I followed all the steps. I almost got tripped up by the instruction to change the speech font property to cloisterblack (before I realized you were just asking me to change the number "1" to "3"), and then I got pretty tripped up by the end when you used lowercase "s" in the "speechview" thing.

But at long last we have a beautiful set of codes and whatnot that do exactly as you have envisioned. I just have one problem with this.

The default font is white with a black outline. That's ingenious because if you change the white to another color, the black stays, so its always legible. But all other fonts I import don't do that. (I'm honestly surprised I can import fonts intended for word documents into a gamemaker at all).

The logical thing to do is add back the SierraWithBackground but white looks kinda awful on these backgrounds. If I could change the color of the default background with a line of code that would basically fix this, but I don't seem to see a way for that to happen.

I could write a Gui, and that changes the background color... but it also overwrites the text color, and I can't find any lines to code to change that so that it varies from character to character. I have tried numerous things to change that, including trying to straight up change it midroom with lines like
Code: ags
BlockyGui.TextColor =1024;
, to lines trying to get it to remember character text is different from normal text like
Code: ags
cLuigi.SpeechColor =1024;
to breaking open the huge script we just wrote and trying to get it to add in a variable for speechcolor. (before remembering that even if I succeeded there, it would just be setting Luigi's color again, not the Gui's).

And now its 2:33am and I'm basically out of ideas.

Snarky

I'd have to actually play around with these settings to see exactly how they work in AGS. However, fonts do have a property you can use to set an auto-outline, which might help you out in the short term. (Though this effect doesn't always work well on TTF fonts, depending on the style and size of the letters.)

Cassiebsg

This has turned out in a proper lesson, good job Snarky. someone should add this (or the link to this post) to the manual. ;)
Had no idea about the ///, that'll be handy! :-D
There are those who believe that life here began out there...

SMF spam blocked by CleanTalk