[SOLVED] Selecting graphics for GUI depending on parameters

Started by Lord Vetinari, Mon 21/08/2017 14:46:28

Previous topic - Next topic

Lord Vetinari

Hello, I once again need your help figuring out how to update a system in my game in a sane way.

Context: in the bottom right corner of my game's screen there's a GUI that dispalys the avatar of the protagonist's starship's AI, Stantok (think Holly from Red Dwarf). It is made of a floating head, torso and hands. It is not actually animated, however it does change it's pose depending on it's “mood”, let's say; this “mood” is tracked by an integer variable whose values are not incremental, they just indicate a specific state (so 0 means neutral, 1 happy, 2 surprised, 3 sarcastic, etc.). Then there are add-ons that represent the ship's systems and are meant to be displayed on top of each part as if they were weared gadgets; they can be activated and deactivated by the player and are handled graphics-wise using the gStantok GUI buttons graphics. On top of all this, there's a ShipPowerLevel variable that tracks how much of the ship's power grid is restored and how much stuff is activated (so, once again, it's not a linear progression) and, from the graphics point of view, means that the add-ons look a bit different depending on that variable's value.
So, to recap, in terms of graphics there are a GUI background that is used to display the generic figure of the avatar and is updated every time AI's mood changes, and a series of GUI buttons that display some gadgets and they too are updated every time the mood changes because they need to match the base figure's pose, plus updated every time the player fixes some bits of the ship AND every time the player chooses to activate or deactivate something on the ship's computer.
In terms of parameters, we have the mood (which changes mostly on it's own), activated gadgets (which are activated manually so depend entirely on the player) and power level (which is only indirectly controlled by the player)

Now, the gameplay logic for all of this is in place and working properly and it's good enough (at least for me), what I'm really unhappy with is the logic that handles the graphics of this GUI elements.
Right now it works with a very brute force and dumb approach, which means a maze of if-else clauses that checks all possible combinations and is really unwieldy and basically impossible to upgrade/expand (not to mention read and debug) without major headaches; what I'd like to do is to make it more flexible and modular and possibly use less game resources to boot.

The current function is something like this:
Code: ags

void UpdateStantokGUI() {
	if (StantokMood == 1) {
		gStantok.BackgroundGraphic = 15;  //or whatever the sprite ID number is
		if (HandsActiveSystem == 1) {
			if (ShipPowerLevel == 1) {
				btnStantokHandsGear.NormalGraphics = 25;
			}
			else if (ShipPowerLevel == 2) {
				btnStantokHandsGear.NormalGraphics = 26;
			}
			//repeat the above for each power level
		}
		//repeat the above for each system and each “body” part.
	}
	else if (StantokMood ==2) {
	//repeat all the above for each mood state.
}


What I'd like to do is simplify that function to something like this:

Code: ags

if (StantokMood == 1) {
	gStantok.BackgroundGraphic = 15;
	btnStantokHandsGear.NormalGraphic = HandsGraphicsID; // by which I mean something like a pointer or variable containing the proper graphics ID depending on type, pose and upgrade, all defined and selected elsewhere.
	btnStantokHeadGear. NormalGraphic = HeadGraphicsID; //same
	btnStantokTorsoGear.NormalGraphic = TorsoGraphicsID;//same
}
else if (StantokMood = 2) {
//repeat
}
//and so on for the other mood values.


and use another function, hopefully less convoluted than the one I have now, to figure out which are the proper graphics at any given moment. The final goal is to make this logic completely indipendent from the actual sprite IDs so that adding new add-ons or removing/replacing exhisting ones becomes as easy and painless as possible, without messing too much (if any) with the functions that handle this GUI or the scripts that call these functions.

Therefore I started to create a struct that contains the list as int variables of all the sprite IDs correlated to any specific add-on, then created an array of these structs, so that each array entry represents one add-on. The (admittedly pretty vague) idea for this second function was some kind of “sorting engine” that could tell the game “this array entry and this struct entry are the ones we're currently using”.
Then I discovered why you need good plans, because the whole thing started to go down the wrong track and finally derailed when I discovered that I can't create a pointer to a custom array or struct (which was the way I thought I could use to set the right array entries as the currently used ones), so I had to once again use the if-else nest.

Help, please? Am I missing something or is this approach completely wrong for what I'd like to do? What should I look at in that case? Thanks in advance

Khris

Your setup basically requires a three-dimensional array. Unless I missed very recent engine changes, AGS doesn't directly support those; you can fake two-dimensional ones by using structs though.
However, AGS kind of *does* have a built-in three dimensional array that happens to store sprites: views.

Your code would basically look like this:
Code: ags
void UpdateStantokGUI() {
  ViewFrame *vf;
  vf = Game.GetViewFrame(StantokMood + 6, 0, 0); // StantokMood == 1 -> VIEW #7
  gStantok.BackgroundGraphic = vf.Graphic; // first frame of first loop stores GUI background
  // hands
  vf = Game.GetViewFrame(StantokMood + 6, HandsActiveSystem, ShipPowerLevel - 1);
  btnStantokHandsGear.NormalGraphic = vf.Graphic;
  // repeat for other buttons
}


You can now assign your button graphics to your views, their loops and frames without having to worry about sprite numbers.

I used views for the moods, but you can of course switch up the order of parameters so that one view has all the possible sprites for a single button.

Lord Vetinari

Thank you! I never thought I could use views that way.
I understand most of your code, correct me if I'm wrong: create a pointer, assing to it the frame stored in a specific position in a view, assign the pointer's value to the GUI element, then "recycle" the same pointer for the next GUI element and so on, right? One thing I don't understand, though (forgive me, but as a self-taught programmer my knowledge and understanding of code and AGS specifically is very erratic): why did you put +6 on the view argument on line 3 and a -1 on the frame argument on line 6? I mean specifically the + and the -.

Snarky

I don't know about the +6, but the -1 is because you've numbered the ShipPowerLevel options from 1, while frames are numbered from 0. (BTW, this is a typical case for using enums: you have variable that can take on some small set of different values, where each value represents a different option. Instead of having the values be just 1, 2, 3, etc., the enum allows you to give the options meaningful names.)

Khris

Quote from: Lord Vetinari on Mon 21/08/2017 20:29:38assign the pointer's value to the GUI element
Yes, what I actually want is to read the sprite slot assigned to the view's frame and set it as .NormalGraphic, and the only way to do that is to get a reference to the ViewFrame, then read its .Graphic.

The "+ 6" was just an example; as my code comment was supposed to illustrate, you most likely already have used a few views for your characters, so if the first Stantok view is VIEW #7, you'd have to add 6 to StantokMood to get the VIEW number.

If you're already using 11 views, and the first Stantok view is going to be #12, you need to use StantokMood + 11 instead.
I didn't add anything to HandsActiveSystem because my example code used loop #0 for the GUI background.
As for ShipPowerLevel - 1, see Snarky's reply.

Lord Vetinari

Oh, now I get it! I missed that the value of the variables matches the view, loop and frame position. Now it makes a lot more sense (I guess I shouldn't try to read code in the evening otherwise I don't understand a thing :P).

Thank you, I'm going to set this up at once!

(Sorry, I can't add a [solved] tag because the title is too long).

Snarky

How about "[SOLVED] Selecting graphics for GUI depending on parameters"?

Lord Vetinari

Right, done. I didn't know if I was allowed to change the title that much (even thought the meaning is the same); on some forums I've been on it's stuff that can get you banned, as silly as it might sound.

Snarky

Nah, it's fine. Repeatedly editing the title just for the sake of it might be frowned upon, but a straight-forward, reasonable edit for good reason is absolutely encouraged (though of course it's always better to get the thread title right the first time around).

SMF spam blocked by CleanTalk