[SOLVED] A Dynamic Array to position characters one next to the other

Started by Baguettator, Mon 08/06/2020 22:09:20

Previous topic - Next topic

Baguettator

Hi every one, (and apologize for my english, I'm a French Guy, you know how french are good for english)

It has been a few weeks I work on a game on AGS, and I don't manage to do something very important for the game.

In a new game, the player has to choose several characters (6 exactly), and then during the game, maybe he will loose some characters, or find others etc... (characters are like a "team" for the player, and the player character is invisible because it is not useful for my game).

What I want to do is to choose the 6 characters (between 50) and when they have been chosen, they appear in a room, one next to the other, at the bottom of the room.

For that, I Started to make some variable and array at the beginning of Global Script.asc :
Code: ags
int nbpersoscolonie; // counts the number of characters that appear in the room
Character* persoscolonie[]; // the character array


In the game_start function I define the array :

persoscolonie = new Character[nbpersoscolonie];

I choose the 6 characters (in game), then I click on "OK" (a button, no matter), and by clicking in this button it does something like this :

Code: ags
function OK_OnClick(button* OK_button)
{
if [amy has been selected]
{
cAmy.ChangeRoom(6);
persoscolonie[nbpersoscolonie]=cAmy;
nbpersoscolonie++;
}
// and again for each of the 50 characters, so the 6 selected characters are identified and placed in room 6


Then, I create a function to "place" the selected characters in room 6 :

Code: ags
void UpdatePositionsJoueurs()
{
int PosX = 13; // X coord. basic (for the 1st character)
for (int s=0 ; s < nbpersoscolonie ; s++)
{
if (persoscolonie[s].Room == 6) // if this character is in room 6, he will be placed correctly
{
if (s<20) // because if the player has a team with more than 20 characters, they will be placed on two lines. So there is the first line
{
persoscolonie[s].x = PosX;
persoscolonie[s].y = 840;
PosX += 95 ; // Décalage horizontal en pixels pour le prochain
}
if (s>=20) // second line if needed
{
persoscolonie[s].x = PosX;
persoscolonie[s].y = 960;
}
}
}
}


But when I want to test it, the game crashes, saying "array out of bounds". I don't know where the error is, and maybe I didn't make that with the good way...

If Someone could help me for making that working, it will be very cooooooooooooool ! :)

Many thanks !

Khris

You need to provide the size when you create the dynamic array:
Code: ags
  persoscolonie = new Character[50];


You used the size variable which was initialized to 0, and thus created an array with 0 elements :)

Baguettator

Ow, thanks for the reply !

But, I wonder it was possible to have an array with a dynamic size, isn't it ?

What I need, in my case, is an array like persoscolonie[nbpersoscolonie]

As nbpersoscolonie is counting the number of characters the player's team has, it may be more or less during the game, so I need to "adapt" the array with the number of character at the moment.

Maybe it's not possible to do that ?

Khris

You could change the size during the game, but you will get a completely new, empty array if you do that.
However there's really no point, just pick a maximum size like 50 and use that. It doesn't have to be a dynamic array either btw, just use a fixed size one.

Code: ags
// header
#define MAX_CHARACTERS 50
import Character* persoscolonie[MAX_CHARACTERS];

// main script
Character* persoscolonie[MAX_CHARACTERS];
export persoscolonie;

Crimson Wizard

To elaborate a little, AGS script does not have a resizable array type at the moment, so if you really need this you have to write the resizing process by hand :)
Usually the algorithm is this:
1. Create new array of new size (assigned to a temp local variable).
2. Copy items over from old array.
3. Assign new array to the global variable (the old one will get automatically deleted when you do).


On the other hand, with dynamic array you may use Game.CharacterCount to make it enough to store all possible characters. Indeed that will mean extra unused slots, but since character count is usually low (as in not thousands) this should not affect the game perfomance a bit.

Matti

Instead of doing this 50 times:

Code: ags
if [amy has been selected]
{
cAmy.ChangeRoom(6);
persoscolonie[nbpersoscolonie]=cAmy;
nbpersoscolonie++;
}
// and again for each of the 50 characters, so the 6 selected characters are identified and placed in room 6


..you should use the character's ID instead of the name. That way you will only need to write that part once.

Code: ags
if [character has been selected] //check the selected character's ID
{
character[selected].ChangeRoom(6);
persoscolonie[nbpersoscolonie]=character[selected];
nbpersoscolonie++;
}

Snarky

Yeah, and do it within a loop. Assuming the 50 characters have IDs from 1 to 50 (with invisible player as character[0]):

Code: ags
  for(int i=1; i<=50; i++)
  {
    if(...) // character[i] is selected
    {
      character[i].ChangeRoom(6);
      persoscolonie[nbpersoscolonie]=character[i];
      nbpersoscolonie++;
    }
  }

Baguettator

OK thanks a lot, I got it working ! :)

Now I want to do another similar thing, but my brain is getting tired... I'm hardworking, really, but my eyes can't see anything good now... :(

When the characters are selected by the player, they come in room 6 (the camp, we can say), where the player can see his wonderful team, well aligned at the bottom of the room !

Note : the player character is invisible, he doesn't play with it. In fact, my game is an application to play a "table game" with a new gameplay (Zombicide, if someone knows !).

Time for him to choose which characters are going to go in expedition. So he will have to choose at least 1 character to go in expedition with.

For that, I made another array, so in my header :
def MAXPERSOSEXPEDITION 32 // no use to have it more, the player won't have 30 characters at the same time, but in case of...

and in the global script, I created an array as I did for the characters in the camp (room 6), so :
int persosexpedition
int indexpersosexpedition;

and when the game starts :
persosexpedition = new Character[MAXPERSOSEXPEDITION];

I created two functions to get the characters in expedition :
void UpdatePositionsExpedition(this Character*)
Code: ags
{
int PosX = 58;  // Position X de base à définir
for (int s=0 ; s < nbpersosexpedition ; s++)
{
  if (persosexpedition[s].Room == 6) // si le character est présent dans la room je le positionne
  {
    if (s<16)
    {
      persosexpedition[s].x = PosX;
      persosexpedition[s].y = 950;
      PosX += 95 ; // Décalage horizontal en pixels pour le prochain
    }
    if (s>=16)
    {
      persosexpedition[s].x = PosX;
      persosexpedition[s].y = 1070;
      PosX += 95 ;
    }
  }
}
}

void AjouteDansExpedition(this Character*)
{
if ( indexpersosexpedition < MAXPERSOSEXPEDITION)
  {
    persosexpedition[indexpersosexpedition] = this; // ajoute le character dans le tableau
    indexpersosexpedition++; // passe à l'index suivant
  }
}


When I click on a character, a GUI appears with buttons that can act on the character (deal damage, heal, get ill and things like that). No problem with that, it works.
And how I proceeded for my thing to do : when I'm in room 6 (the camp), if I click the "Goexpedition" Button, my mouse.Mode become "eModePickup" (it is "eModeWalkto" by default), and a GUI appears : "You have to choose your characters to go in expedition". With that, I have to click on the characters (so with the cursor mouse.Mode=eModePickup), when I click on them, they are "ready", if I click again, they stay at the camp (cancel the "ready" in case it was an error).

Then I have to confirm that the characters selected are going in expedition, I have to click the "OK" button, then player character goes to room 7 with the characters selected for the expedition.

I imagine a loop that is going to scan wich characters are going  to the expedition, and then get them aligned (as in the camp !).

I have to say that I did a Structure for the characters, they have some attributes (dealt damage, disease etc...) and function.

I have set "Expedition" as an attribute (a bool in fact), when I click the character with eMousePickup, thischaracter.Expedition=true. It become false, if I click again to cancel it.

For example with Amy (a character, a pretty girl :) ) :

Code: ags
function cAmy_AnyClick()
{
  if (mouse.Mode==eModeWalkto)
  {  
playerID=cAmy.GetProperty("PersonnageID");
if (personnage[playerID].Zombivant==0)
{
  gInterfacepersos.Visible=true;
  Fondinterfacepersos.NormalGraphic=1517;
  Interfacepersonom.Text=personnage[playerID].Nom;
}
else 
{
  gInterfacepersoszombivant.Visible=true;
  Fondinterfacepersoszombivant.NormalGraphic=1527;
  Interfacezombinom.Text=personnage[playerID].Nom;
}
  }
  else
  {
    playerID=cAmy.GetProperty("PersonnageID");
  if (personnage[playerID].Expedition==false)
  {
    nbpersosexpedition++;
    personnage[playerID].Expedition=true;
    personnage[playerID].CHAR_FRAME=24;
    personnage[playerID].Acteur.LockViewFrame(personnage[playerID].CHAR_VIEW, personnage[playerID].CHAR_LOOP, personnage[playerID].CHAR_FRAME);
  }
  else
  {
    nbpersosexpedition--;
    personnage[playerID].Expedition=false;
    personnage[playerID].Changementetat();
    personnage[playerID].Acteur.LockViewFrame(personnage[playerID].CHAR_VIEW, personnage[playerID].CHAR_LOOP, personnage[playerID].CHAR_FRAME);
  }
  }
}


But it doesn't work...

And I'm so tired that surely my post is not clear at all...

Anyway, at least one question : is it a good way to do what I want to do ? The loop with the array, etc...?
Code: ags

Sorry I'm surely not clear enough, I'm going eat something and aybe it will be better after...

Baguettator

OK, so I'm back, and please, apologise for my last post, it was a lot desesperate, and very confuse...

So my problem is now to get these characters in expedition room. THe problem seems that I don't manage to initialise the dynamic array.

How I proceed : one array for characters in the "base" (room 6), with this array, one function can add characters to this array (like you find someone who joins the player's team). And one array for characters in expedition, with the same fonctions that can add characters to the expedition (room 7).

So when I select characters in the base (to order them to go in expedition), I include them in the array for the expedition. At the end of the expedition, I need to clean the array of expedition (because, maybe the next expedition, they won't participate, it will be other characters...).

How to clean an array and refill it with new values ?

At the beginning of the game, I defined it like : persosexpedition = new Character[MAXPERSOSEXPEDITION] (where MAXPERSOSEXPEDITION is a constant).
And when the expedition ends, I tried something like :
persosexpedition = new Character[1]; (to clean the array by changing his value)
persosexpedition = new Character[MAXPERSOSEXPEDITION]; (to set the new empty array with the right value)

I think that's the problem because I made a loop to fill the array and position the characters, and sometimes I got an error : "Null pointer referenced". It is about the line of the loop :
Code: ags
for (i=0 ; i < nbpersosexpedition ; i++) // nbpersosexpedition is the variable which counts how many characters have been selected to go in expedition

if (persosexpedition[i].Room==7) // THIS LINE CRASHES, don't know why...
// persosexpedition[i] is positionned correctly in the room 7

Khris

Did you also reset  nbpersosexpedition ?

You can also do this:

Code: ags
  if (persosexpedition[i] != null && persosexpedition[i].Room == 7) ...


Since expressions are evaluated lazily and from left to right, if the first test fails, the right part won't cause a null pointer exception.

Baguettator

Thanks for the help ! With this correction, the crash has disappeared. But the game doesn't work as intended  :(

I'm pretty sure that when I try to "empty" an array, it doesn't work. What I need to do is "actualise" the array, so I empty it, then I refill it. But I don't manage to do that.

Is it possible to do such thing ? And if Yes, how ?

Khris

I tried this and it works fine:

Code: ags
Character* chars[];
int charCount = 0;

void AddChar(Character* c) {
  Character* tmp[] = new Character[charCount + 1];       // create new dynamic array with one more element
  for (int i = 0; i < charCount; i++) tmp[i] = chars[i]; // copy old array
  tmp[charCount] = c;                                    // put new character as last element
  charCount++;                                           // increase count
  chars = tmp;                                           // make pointer point to new array
}

Baguettator

Thanks for the help, got it working !

My new problem is that I tried to reinitialize the array (because I need add a new character in persosexpedition), so I made it like that :

Code: ags
function OKpersotrouve_OnClick(GUIControl *control, MouseButton button) // I need to reinitialize the array when I click on this button
{
  persosexpedition = null;
  persosexpedition = new Character(MAXPERSOSEXPEDITION);


But AGS crashes and say : "built in type character cannot be instantiated directly"

I don't understand why, because higher in my global script, I have another button that is scripted the same way, and AGS didn't crash since I wrote the script of the new button.

Any idea why it is forbidden ?

Khris

1. you can remove the  persosexpedition = null;  line since you're overwriting the variable in the following line anyway
2. you have accidentally used round instead of square brackets

Baguettator

Ow, sorry, so stupid I am...  ;-D

And why if I place characters in a room, and then make a GUI appear in that room, characters are "behind" the GUI ? We can't see them or interact with them.

What I wanted to do is to place them "on" the GUI. Anyway to do that ?

Khris

GUIs are always drawn on top of everything else.
If you just want to show images of the characters on the GUI, you can add buttons to it and set the button images to your characters' sprites (that way you can click them).
Alternatively you can create a DynamicSprite, draw the character sprites to it, then set it as background graphic for the GUI.

However if you just want to show a box or something like that behind the characters, you can draw it to the room background instead.

Baguettator

OK I understand. But I can't touch the background image, it's important.

I tried to set the GUI itself "unclickable", but with that option, the button are not clickable too...

Maybe a solution would be that I create a big transparent button that covers the GUI entierly, and that button is not clickable ?

By the way, what I need is to click trough the GUI the background image of the GUI, that is transparent in my situation), but that GUI has buttons that I want to be clickable...

EDIT : maybe by setting temporarly the GUI non-clickable ?

Khris

Can you describe what you're trying to achieve without using AGS specific words like GUI? (cause it feels like we're heading straight into xy problem territory)
What is your goal here? To have clickable characters at the bottom of the screen while the player scrolls through the room? If so, you can set their positions in repeatedly_execute, this way they don't move when the viewport / camera moves.

Baguettator

OK, sorry for the abstract story :)

What I want to do is to align characters that the player have to choose. He has to choose some characters that join his team, and the others get out of the room.

So my plan was to display a button with a large image that could be a "background" for the characters (to get it more beautiful !).

Because I can't act on the background of the room (because it is useful for another thing, and if I draw a sprite on the background, I will loose important things that are on the background's sprite.

That's why I wanted to use a GUI. But apparently, it is not possible, so at the moment, I don't think about that solution :)

At least you know another way to do it ?

Snarky

You can make a dedicated room for this and switch to it when you want to make the selection.
OR
You can put the character sprites on buttons.

Matti

You could also switch to another background frame of the same room that also contains a background image for the characters.

Edit: I'm still sure if you want small backgrounds for each character or one background for all selected characters together.

Baguettator

OK thanks, I will try that :)

Another question : is it possible to have an array of buttons ?

Like : Button* buttonsofGUI1[nbbuttonsGUI1]

I want to make visible one button of a GUI and invisible the others. Maybe it is possible ?

Khris

Possible, but if you only put buttons on the GUI, you can use the GUI's  .Controls[]  array.

Baguettator

I only put buttons in that GUI. Just the button that has the ID "0" is a "OK" button to exit the GUI. I have 50 others buttons (ID 1 -> ID 50) between them I want to pick just one visible, the others invisible.

Is it something like :

Code: ags
Button* boutons = new Button[myGui.ControlCount];
  int nombreBoutons = 0;
  for (int n = 0; n < myGui.ControlCount; n++){
    GUIControl *control = myGui.Controls[n];
    if (control.AsButton != null) {
      boutons[nombreBoutons] = control.AsButton;
      nombreBoutons++;
    }
  }
boutonvisible=Random(nombreBoutons - 1) + 1 // to not choose the button with ID=0
boutons[boutonvisible].Visible=true;


And when I close the GUI, I can get all the buttons invisible again :

Code: ags
Button* boutons = new Button[myGui.ControlCount];
  int nombreBoutons = 0;
  for (int n = 0; n < myGui.ControlCount; n++){
    GUIControl *control = myGui.Controls[n];
    if (control.AsButton != null) {
      boutons[nombreBoutons] = control.AsButton;
      nombreBoutons++;
    }
  }
for (int m = 0; n < nombreBoutons ; m++)
{
boutons[m].Visible=false;
}


Am I right ?

SMF spam blocked by CleanTalk