help with building a custom function for default interactions

Started by daniel, Thu 11/05/2017 10:10:40

Previous topic - Next topic

daniel

hi,

i'd like to build a custom function to quickly call default interaction animations for my character.
my character has views for interacting "high", "middle" and "low" (i.e. vCharInteractHigh etc.) with corresponding animation loops for facing "up", "down", "left" and "right" (0 - 3).

what i would like to end up with is a function that works something like this:

Code: ags
CharacterInteract (high, down);

or
Code: ags
CharacterInteract (middle, left);

etc.

i'm pretty much clueless how to achieve this. my guess is i'd need to use "pointers" somehow...?
which, unfortunately i still haven't managed to wrap my head around completely what they are or how they work.

any help would be appreciated.

cheers!

Snarky

OK, so the basic answer is pretty simple, but if you're interested in learning, I'll start with an explanation of pointers.

Spoiler
A "pointer" (more or less synonymous with "reference", sometimes also called a "handle") is a variable that "points" to some data structure, like an object or a struct (for example a Character, GUI or AudioClip). You can think of it as a link, bookmark or shortcut to that thing. It lets you access the data structure, in order to call its methods and read or set its properties. You create pointers like any other variable, but write a "*" between the type and the variable name. So let's say you want a variable that points/refers to a Character (a "Character pointer"):

Code: ags
  Character *myCharacterPointer; // Some people put the asterisk with the character name...
  Character* myOtherCharacterPointer;  // ... but I think it makes more sense to write it as part of the type
  // (it doesn't make a difference to the program, it's just a style difference)


When you use it later on, you always write it without the asterisk:

Code: ags
  myCharacterPointer.SetAsPlayer();


In AGS you can only have pointers to structs (not to ints, bools, enums, etc.), and currently they're only really used for the built-in structs (like Character, Object, AudioChannel, etc.). Although... Strings are actually a kind of pointer under the hood, but they're a little different and don't use the asterisk notation at all, so they don't count for our purposes. Most of the variables you define in the editor (the names of characters, objects, hotspots, GUIs, GUI controls, audio clips) are pointers.

A lot of the time, you don't have to worry about the difference between a pointer and any other variable, or even think about whether a variable is actually a pointer (and therefore, you shouldn't actually use "Pointer" in the variable name). However, there are some cases where it matters:

1. You can have lots of pointers (with different variable names) all pointing to the same object. There will still only be one copy of the object, and if you modify it using one of the pointers, it will affect it for all the others:

Code: ags
  Button* button1 = gMenu.Controls[0]; // A GUI defined through the editor, where the first control is a button
  Button* button2 = gMenu.Controls[0]; // A second pointer, pointing to THE SAME button as button1
  
  button2.Enabled = true;
  button1.Enabled = false;
  if(button2.Enabled) // This will be FALSE, because you disabled the button using the button1 pointer 
  {
    // Do stuff
  }


2. Pointers can be "null", meaning that they're not set to point to any (valid) object. When you create a pointer without immediately assigning it, this is the default value. You can't call methods or access properties of something that is null, and if you try the program will crash with the dreaded "Null Exception":

Code: ags
  Button* button1 = gMenu.Controls[0];
  Button* button2; // We forgot to set this to anything...
  
  button1.Enabled = false;
  if(button2.Enabled) // This will CRASH, because button2 == null, and you can't look up the value of null.Enabled
  {
    // Do stuff
  }


You can also set a pointer variable to null explicitly, typically to distinguish valid and invalid variables (for example, if your game allows you to recruit other characters as a player companion, you want to set the Companion pointer to null whenever there isn't a companion).

3. You can change what object a pointer points to, without affecting the original object.

Code: ags
  Label* statusLabel = gMenu.Controls[6];
  // ...
  statusLabel.Visible = false;
  statusLabel.Text = "hidden text";
  // ...
  statusLabel = lblConsoleStatus; // A variable name defined in the editor
  // statusLabel now refers to a DIFFERENT label, but the original statusLabel (gMenu.Controls[6]) still exists.
  // It is still invisible and still has "hidden text" as its content

  if(statusLabel.Text == "hidden text") // This is no longer comparing with the statusLabel.Text we set, but the text of lblConsoleStatus
    statusLabel.Visible = true;         // This will NOT show the statusLabel we set to Visible = false


You can't do this with the pointers set in the editor, though; they're read-only (meaning they'll always point to the original object; otherwise you could lose the ability to access the objects they refer to).

There's a whole big thing about what this means for variable scope, the possibility of memory leaks, garbage collection etc., but that's not something you really need to worry about unless you're working with DynamicSprites or doing something very complex.
[close]

It can be a bit difficult to wrap your head around at first, but it's actually very simple once it clicks.

Anyway, to do what you want, yes, you use a Character pointer:

Code: ags
// Define this enum somewhere
enum InteractionHeight
{
  eHigh,
  eMiddle,
  eLow
};

void CharacterInteract(Character* c, InteractionHeight height, CharacterDirection direction)
{
  int view;
  if(height == eHigh)
    // TODO: figure out which view to use (not sure how you're storing this)
  else if(height == eMiddle)
    // TODO: figure out which view to use (not sure how you're storing this)
  else if(height == eLow)
    // TODO: figure out which view to use (not sure how you're storing this)

  c.LockView(view);
  c.Animate(direction, 3); // The CharacterDirection values match the loop numbering in the AGS editor (0=down, 1=left, 2=right, 3=up), so arrange the animations like that within the view
  c.UnlockView();
}


Now you can call it like so:

Code: ags
  CharacterInteract(cBuddy, eHigh, eDirectionLeft);


There is another little tweak you can do, which is to use an extender function. It's basically the same thing, just a slightly different syntax:

Code: ags
// Notice the difference in the first argument
void CharacterInteract(this Character*, InteractionHeight height, CharacterDirection direction)
{
  int view;
  if(height == eHigh)
    // TODO: figure out which view to use (not sure how you're storing this)
  else if(height == eMiddle)
    // TODO: figure out which view to use (not sure how you're storing this)
  else if(height == eLow)
    // TODO: figure out which view to use (not sure how you're storing this)

  this.LockView(view);        // Notice we use "this" instead of "c"
  this.Animate(direction, 3); // The "3" is arbitrary; set whatever speed you like
  this.UnlockView(); 
}


And now you can call it like:

Code: ags
  cBuddy.CharacterInteract(eLow, eDirectionUp);

daniel

awesome, thanks for that detailed explanation!:)

i'm definitely interested in learning so that's very useful to me.

Mandle

Snarky, your pointer tutorial should go in the manual!

Seriously, I've never really understood them until just now.

daniel

Quote from: Mandle on Fri 12/05/2017 15:41:55
Snarky, your pointer tutorial should go in the manual!

Seriously, I've never really understood them until just now.

i agree...or at least into the BFAQ. i read the pointer section in there a couple of times and didn't get it at all.

SMF spam blocked by CleanTalk