GUI Scripting: On Entity's Surface

From The DarkMod Wiki
Revision as of 17:35, 4 November 2022 by Geep (talk | contribs) (Add category tag)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

This is a part of a series, whose hub is GUI Scripting Language

Introduction

A mapper can apply a GUI to an entity's surface, typically a rectangular face. Such a GUI cannot be interactive to the degree possible in Doom 3, because TDM disallows that (e.g., doesn't show a mouse cursor) in order to improve frobbing. (For more about that, see GUI Scripting: Limitations of On-Surface GUIs.)

Still, as detailed below, you may be able to provide some useful visual effects, driven by gui timer, script timer, or repeated frobbing or external triggering. And non-interactive guis support videos.

If you need to create a selection menu on a surface, that is possible with limitations; see GUI Scripting: In-World Menu Examples. Alternatively, instead of a surface GUI, cobble the desired functionality from buttons or frobable surfaces, script functions, and entity visibility toggling.

For Brush-Based Entities

Basic Technique

Let's assume your entity is a func_static or derivative; such entities have spawnarg "gui_noninteractive 1" set by default. To apply your GUI, texture one of your entity's surfaces with standard material "textures/common/entityGui" (or your customized version of that). After fitting it to the surface with Surface Inspector, this will appear in DR as a blue rectangle with "Entity GUI" text. If you look into the material's description, you will see it includes a "guisurf entity" attribute. This means that the path to the gui will be found, not in the material definition, but in the entity's "gui" spawnarg. So add that to the entity. For an example, see the cutscene_video material discussed in the Movie Theatre method of Full-Screen Video Cutscenes.

At runtime, when the entity is spawned, its GUI is autoloaded.

Ensuring the Fit

As mentioned, after applying GUI texture, be sure to use "Fit" in DR's Surface Inspector. Also, if you fit the texture and later resize the entity, be sure to re-fit the GUI texture. Surprisingly, if you make the entity smaller and don't refit, the texture rectangle will retain its original world size, exceeding the face of the entity. This will not be very evident in DR (except for the effect on a subtle brown border around the blue Entity GUI), but at runtime.

Multiple GUIs or Custom GUI Materials

An entity can actually have up to 3 different "guisurf" materials on its surfaces, with this pairing:

  • "guisurf entity" (included in textures/common/entityGui) & "gui"
  • "guisurf entity2" (included in textures/common/entityGui2) & "gui2"
  • "guisurf entity3" (included in textures/common/entityGui3) & "gui3"

(If your entity is a func_static or derivative, in DR's Entity Inspector, selecting a "gui" spawnarg will give you a hint about these 3.)

Alternatively, a custom material may directly specify the GUI with form "guisurf <guifile>".

For Model-Based Entities

The foregoing described a simple brush-derived entity. If the entity is based on a model (like a sign plaque), the usual approach is to pair it with an abutting "decal", a patch turned into a func_static which then becomes the real entity to which the gui-related spawnargs are applied. If you intend to reuse this pairing, you can export it as a prefab.

For details, in the context of a "text decal", see Text Decals for Signs etc.# Customising and Making your Own.

Example of a Timed Visual Effect

Suppose you have a simple func_static wall with some overall opaque texture. And you want to change one surface to flash alternatively between bright green and invisible. So, as above:

  • Put an "Entity GUI" on the desired face.
  • In your /guis/ folder, create a greenflash.gui file (as below), and add a "gui" spawnarg to your wall to point to it, e.g. "gui guis/greenflash.gui"
  • You could also give the wall a spawnarg override "gui_noninteractive 0", though it doesn't seem to make a difference.

Here's a greenflash.gui file that flashes every second:

windowDef mygreenflash {
  rect 0, 0, 640, 480
  backcolor 0,1,0,1  // green
  visible 1
  notime 0

  onTime 0 {
    set "visible" "0";
  }

  onTime 1000 {
    set "visible" "1";
  }

  onTime 2000 {
    resetTime 0;
  }
}

Example of a Frob-Driven Visual Effect

Now, say you want to alter the foregoing example so the visibility change happens, not on a fixed schedule, but instead every time you frob the object.

IMPORTANT: For frob highlighting, an "Entity GUI" surface will appear non-existent , so knowing if the object's ready to frob can be tricky. When in doubt here, frob on one of the opaque sides instead, or (in 2.10) test with cvar "r_frobOutline 2" (Even better, TDM generally has separate objects to frob and to hold the "Entity GUI", which solves this. For instance, see GUI Scripting: Sign Text Example.)

To your wall entity, add a standard spawnarg frob_action_script, with the name of a new script function (called "onFrob" here) placed in one of your FM's .script files. The frob_action_script mechanism requires a void-returning function. So to get data back from the function, invent a GUI state variable "is_visible". This is referenced in your .gui with a "gui::" prefix, and in your .script using standard TDM script function calls setGui... or getGui... Thus, this solution from greebo:

windowDef mygreenflash {
  rect 0, 0, 640, 480
  backcolor 0,1,0,1
  visible "gui::is_visible"
  notime 0
}

The "visible property" is now bound to the is_visible GUI state variable. Which is "0" by default, so the green window starts out hidden. The script toggles the visibility by inverting the state flag:

void onFrob(entity me)
{
  sys.println("frobbed");

  // Invert the is_visible variable that is bound to the visible property of the mygreenflash window
  me.setGuiInt(1, "is_visible", 1 - me.getGuiInt(1, "is_visible"));
  // Note: The first argument "1" refers to the first entity GUI as defined by the "gui" "guis/greenflash.gui" spawnarg
}

The boolean inversion line is just a concise way to do:

  if(me.getGuiInt(1, "is_visible") == 0) {
    me.setGuiInt(1, "is_visible", 1);
  } else {
    me.setGuiInt(1, "is_visible", 0);
  }

Another Example of a Frob-Driven Visual Effect, with Transitions

For the GUI, the foregoing binding solution is elegant, but not always possible. In this example, two overlapping background figures (stock images from tdm_gui01.pk4\dds\guis\assets\mainmenu) are alternatively faded in and out on each frob, using "transition" statements instead of "visible". The onFrob function is changed to simply:

void onFrob(entity me) {
  sys.println("frobbed");
  me.setGuiInt(1, "frobbed", 1); // Pulse the frobbed variable. GUI will reset it.
  // Note: The first argument "1" refers to the first entity GUI as defined by the "gui" "guis/test.gui" spawnarg
}

A helper variable whichbackground is added. This could easily be expanded to more that two images.

windowDef swapbackgrounds {
  rect 0, 0, 640, 480
  visible 1
  backcolor 0,0,0,1
  notime 0
  float whichbackground 0 // Only init to zero supported

  windowDef background0 {
    rect 160, 120, 320, 240
    matcolor 1,1,1,1
    background "guis/assets/mainmenu/sidefigure1"
  }

  windowDef background1 {
    rect 160, 120, 320, 240
    matcolor 1,1,1,0
    background "guis/assets/mainmenu/sidefigure6"
  }

  onTime 10 {
    if("gui::frobbed") {
      set "gui::frobbed" 0;
      if("whichbackground" == 0) {
         set "whichbackground" "1";
         transition "background0::matcolor" "1 1 1 1" "1 1 1 0" "2000";
         transition "background1::matcolor" "1 1 1 0" "1 1 1 1" "2000";
      } else {
         set "whichbackground" "0";
         transition "background0::matcolor" "1 1 1 0" "1 1 1 1" "2000";
         transition "background1::matcolor" "1 1 1 1" "1 1 1 0" "2000";
      }
    }
  }

  onTime 100 {
    resetTime 0;
  }
}

For More