Changing a character's look: multiple AGS characters or multiple views?

Started by Lord Vetinari, Tue 17/03/2015 09:38:14

Previous topic - Next topic

Lord Vetinari

After quite a few unreleased test games, I'm trying to make something more complex. In this game, the main character's sidekick is an AI thingy that lives in the protagonist's wrist watch. I'm planning to make a closeup of the watch in the bottom right corner. The appearence of the AI's head, arms and torso will change a few times throughout the game depending on the solution of certain puzzles. I'd like to give the player relative freedom to choose which goal to tackle first, which means that I can't draw a linear progression of the AI's looks.
I think I know how to do this, my question is: what is better from the coding, performance and memory usage point of view? Three overlapping AGS characters (head, torso, arms) so that I can change each one's view individually, or a single AGS character with an exponentially larger amount of views and loops to cover each potential combination?

monkey0506

I've actually thought about trying to come up with a system for breaking a Character apart like this. I've never actually tested anything like this, but the main concern would be memory consumption vs. runtime performance. Obviously if the memory consumption goes too high then it will, in fact, result in runtime performance issues, but that would be an exceptional case anyway.

Using a single character with different views for every single costume change would obviously take up the most memory (bloating your file size), but that may be the best route depending on your needs, though I wouldn't count on it. Using multiple characters will probably be best. What I might do in this case (single character) would be to treat the character's views as a (pseudo) three-dimensional array like this:

Code: ags
#define AI_HEAD_COUNT 5
#define AI_TORSO_COUNT 10
#define AI_ARMS_COUNT 3
int aiView[]; // pseudo 3D array, aiView[head][torso][arms], stores view number for head, torso, arms combination
int aiHead = 0; // AI current head
int aiTorso = 0; // AI current torso
int aiArms = 0; // AI current arms

int GetAIViewIndex(int head, int torso, int arms) // returns index of aiView[head][torso][arms]
{
  // TODO: validate parameters? (e.g., head > AI_HEAD_COUNT?)
  return (((head * AI_TORSO_COUNT) + torso) * AI_ARMS_COUNT) + arms;
}

int GetAIView(int head, int torso, int arms) // returns the actual view number of the matching view for head, torso, and arms
{
  // TODO: return AI_DEFAULT_VIEW + GetAIViewIndex(head, torso, arms);
  // see note below
  return aiView[GetAIViewIndex(head, torso, arms)];
}

void SetAIView(int head, int torso, int arms, int view) // sets the view number for this head, torso, arms combination
{
  // in fact, storing this in an array might be overkill (see note below)
  aiView[GetAIViewIndex(head, torso, arms)] = view;
}

function game_start()
{
  aiView = new int[AI_HEAD_COUNT * AI_TORSO_COUNT * AI_ARM_COUNT];
  // now, we'll initialize the views...
  int view = AI_DEFAULT_VIEW; // TODO: replace AI_DEFAULT_VIEW with the FIRST of your SEQUENTIALLY NUMBERED views
  // TODO: note, views should be numbered sequentially!
  // The way this works:
  // * Set the "normal" view to have the lowest view number
  // * Now, KEEPING the SAME head AND torso, set each change of the arms to the subsequent view number
  // * Now, KEEPING the SAME head, change to the next torso, then set each set of arms to the subsequent view
  // * Do this for each combination of THIS head, all torsos, and all arms
  // * Now, change to the next head, default torso, and iterate through all arms, setting the subsequent view...
  // That's probably confusing! I'm beginning to doubt you'll actually go for this route anyway...
  // You could also reduce memory consumption by NOT storing this in an array and just use AI_DEFAULT_VIEW + GetAIViewIndex(head, torso, arms) instead.
  // This is more conceptual to try and help visualize how the views should be ordered.
  int head = 0;
  int torso = 0;
  int arms = 0;
  while (head < AI_HEAD_COUNT)
  {
    torso = 0;
    while (torso < AI_TORSO_COUNT)
    {
      arms = 0;
      while (arms < AI_ARMS_COUNT)
      {
        SetAIView(head, torso, arms, view);
        view++; // assumes all views are sequentially numbered
        arms++;
      }
      torso++;
    }
    head++;
  }
}

void ChangeAIView(int head, int torso, int arms) // changes character cAI's View, based on head, torso, and arms (must be set with SetAIView first - see game_start)
{
  cAI.ChangeView(GetAIView(head, torso, arms));
}

void ChangeAIHead(int head)
{
  aiHead = head;
  ChangeAIView(aiHead, aiTorso, aiArms);
}

void ChangeAITorso(int torso)
{
  aiTorso = torso;
  ChangeAIView(aiHead, aiTorso, aiArms);
}

void ChangeAIArms(int arms)
{
  aiArms = arms;
  ChangeAIView(aiHead, aiTorso, aiArms);
}


That's really more less complicated than it looks, but the idea is to specifically control the order/numbering of the views so that you can then just offset from a default view number to get the view you need.

All of that said, unless you have dozens of characters on screen at the same time, having multiple Character objects representing the head, torso, and arms of a single character probably won't cause so much of a slow-down that you would need to resort to creating hundreds of separate views (and thousands upon thousands of images).

Snarky

The other question is how much animation this AI character is going to have. If it's effectively a static image sitting down in the corner of the screen, you might not even need to make it a character: it could be a stack of three GUI buttons, for example. Or you could draw the sprite dynamically yourself. A character is mainly useful if it needs to walk around, face multiple directions, or will be animated during speech (and even in that case you can work around it).

In fact, if you're going to have scrolling rooms, making the character stay on the same position on-screen as the room scrolls will actually be a bit of a pain, since characters are positioned relative to room coordinates, and because of an AGS engine bug that changes the viewport between your script and the screen rendering.

Lord Vetinari

Thanks for the answers!
I think I'll be having at most 4 characters in a room, up to six if I split the AI in three. Is that too muck? I'm not planning to have any scrolling room, I imagined that it won't be easy to keep the watch in the right place.
The head is definetly animated (the AI talks), ideally the arms too. I could use a GUI for the torso, I didn't think about that.
What's the workaround to animate a GUI?

Snarky

The AGS Awards ceremony client has 40+ characters on screen simultaneously, so six will be no problem whatsoever. To animate a GUI, you typically use a button, (optionally) set the NormalGraphic, and call Animate() on it. The main difference from animating a character is that unlike with walkcycles, the animation doesn't skip the first frame when looping, so you shouldn't put the standing frames in the same view/loop as the walking frames. The "workaround" is just that you have to start and stop the talking animation when the speech starts and stops, which takes a little bit of work to set up.

Lord Vetinari

Thanks for the answer, I'll look into that.
I'm a bit wary about performances because the manual, wiki and some tutorials keep talking about that, it's hard for a newbie to understand to what extent you can push the engine.

monkey0506

Quote from: Lord Vetinari on Thu 19/03/2015 20:49:34it's hard for a newbie to understand to what extent you can push the engine.

That's a constantly evolving value though, and is entirely dependent on the version of AGS you're using as well as the END-user's hardware specs.

However, I'm fairly certain that even low-end computers should still be able to handle at least 20-30 characters on screen with recent versions of AGS, without incurring any performance hits. Test things out yourself early on and often (and on as many systems as you can), and you should be able to reasonably gauge if there's any bottlenecks.

SMF spam blocked by CleanTalk