A solution for character sprites like overlays ?

Started by Baguettator, Fri 12/03/2021 19:05:21

Previous topic - Next topic

Baguettator

Hi !

In my game, I have 110 characters that have sprites to show their state, like disease, wounds etc. A character is displayed with :

- his face

- a number of transparent sprites that cover his face on differents points

For now, my characters are using views with about 20 sprites per character, each sprite being a possibility, like a combination : 1 wound, 1 wound + 1 disease, 1 wound + 2 diseases, 2 wounds, 2 wounds + 1 disease, 2 wounds + 2 diseases, 3 wounds, etc...

This means that I have plenty of sprites to show their state (about 2500 in total).

But now I have progressed in coding, I am asking myself : is there an easier way to do that ?

My character don't "move" as my game is not an adventure game but an application for a board game (called zombicide, if any knows it :) ). The characters are placed in some places according to the situation, and are moved through different rooms or positions, but they don't move like a character in adventure games (walking, running etc...).

I use a struct of characters for a lot of their proprieties. So character is a good thing for my coding.

My question is so : is there a way to have a sprite like a pile of overlays on their face's sprite, instead of 2500 sprites for each combinations of their state situation ?

Crimson Wizard

#1
You can create dynamic sprites, paint them over with any combination of sprites, and assign to view frames.
So you need only as much views as you have characters on screen at the same time, and you keep generating their frames when you need to change character looks.

The option without use of dynamic sprites is to have one game character displayed using several characters, one character per "feature". This is roughly how some people implement character shadows or reflections (using second character as a shadow).

Baguettator

Hmm ok sounds great !

So instead to import 2500 sprites in the game, I could have only the "masks" and create the 2500 sprites at game_start with dynamic sprites to fill the views ? (I have several views inside them one character uses one loop, so a loop has about 20 sprites)

It won't get the game lagging to have so many Dynamic sprites at the same time ?

Or I could only create the dynamic sprites for characters that are used by the player ?

Baguettator

So I just tested and managed to do with a dynamic sprite for 1 character, so I have great hopes that I could manage to work for all characters like that !

I saw that arrays of dynamic sprites are possible to use, that's great too.

My question is that :

- should I create as many dynamic sprites as number of states per character (so, about +2500 dynamic sprites to create at the game_start) ?

- or should I only create a dynamic sprite per character, and update it each time the character's state changes ?

If creating thousands of dynamic sprites is not a problem for the game, well I prefer that solution as it won't change all of my scripting I have done yet.

If not, well I will have to work longer and review my scripts to adapt it to a new system.

I have the feeling that it would be better to have few dynamic sprites for the engine because they are stocked into the memory, and if there are too much, it could lag the game. But I don't really know anything about that, so... What will you advice ?

Crimson Wizard

#4
Quote from: Baguettator on Sat 13/03/2021 09:17:47
- should I create as many dynamic sprites as number of states per character (so, about +2500 dynamic sprites to create at the game_start) ?

- or should I only create a dynamic sprite per character, and update it each time the character's state changes ?

Technically you can go both ways, personally I would go with the second option because creating redundant objects in memory of which you only use small percent at a time is usually a bad thing. The only reason to do so is if your character states switch so fast that repainting sprites causes game to lag, in which case it's a better idea to precreate all or some of them.

Also, it's very important to note that all the dynamic objects are added to the game saves' size, because game must write all the sprites you created somewhere to be able to restore them later. This is also a reason to not precreate everything at once if that may be avoided.

eri0o

Shit, I really miss playing zombicide! Is this to track each player characters state?

Baguettator

#6
Yes EriOo :)

Well in fact, I am creating a Survival Mode with AGS (like a campaign mode with lots of features and new gameplay mechannics). If you want to try it or play-test it, it would be with pleasure ! We have a discord channel dedicated to that :)

EDIT :  yeah, I have not posted my game in development in the forum because I don't know if it is legitimate. As it is not a "game", but an app for a board game. I thought it is not the matter here, anyway, if some are interested and that I can release my alpha version here, it will be with pleasure :)

@Crimson Wizard : thanks for the advice, I'm going to change my code ! I am always happy when I can sweep some dust in my code (and moreover  : spring is coming, isn't it ? ;) )

Baguettator

Well, I tried to create the new function but I have a problem : when I modify a character's state, it works only 1 time. After that, the image doesn't change anymore. And I don't know why...

There is my function (in my Personnage struct):

Code: ags
DynamicSprite* etatpersos[TOTAL_PERSONNAGES]; // 1 Dynamic sprite per character, TOTAL_PERSONNAGES is a constant value.

void Personnage::Changementetat()
{
  int numero;
  for (int i=1 ; i<TOTAL_PERSONNAGES ; i++)
  {
    if (personnage[i]==this) numero=i; // I find the character that I am changing the state
  }
  
  // I delete the dynamic sprite if it already exists, in case of
  if (etatpersos[numero]!=null) etatpersos[numero].Delete();
  
  // base is for the character's face image. other variables are for attributes : wounds, disease and infection
  int malad, bless, infect, base;
  if (this.mort==true)
  {
    base=1709;
  }
  else
  {
    
    // if the character is a zombivor, the base is not the same that if it is not
    if (this.zombivant==false)
    {
      ViewFrame* i=Game.GetViewFrame(this.CHAR_VIEW, this.CHAR_LOOP, 0);
      base=i.Graphic;
      bless=this.blessure;
      malad=this.maladie;
      infect=this.infecte;
    }
    else if (this.zombivant==true)
    {
      ViewFrame* i=Game.GetViewFrame(this.CHAR_VIEW, this.CHAR_LOOP, 1);
      base=i.Graphic;
      bless=this.blessure;
    }
  }
  
  // now, I "paint" the dynamic sprite according to the character's base image and his attributes
  // and I think the error should be there, as I didn't use so much the dynamic sprites
  etatpersos[numero]=DynamicSprite.CreateFromExistingSprite(base);
  DrawingSurface* s=etatpersos[numero].GetDrawingSurface();
  if (bless>0)
  {
    if (bless==1) s.DrawImage(0, 0, 3338);
    if (bless==2 && this.zombivant==false) s.DrawImage(0, 0, 3337);
    if (bless==2 && this.zombivant==true) s.DrawImage(0, 0, 3344);
    if (bless==3) s.DrawImage(0, 0, 3345);
    if (bless==4) s.DrawImage(0, 0, 3346);
  }
  
  if (malad>0)
  {
    if (malad==1) s.DrawImage(0, 0, 3339);
    if (malad==2) s.DrawImage(0, 0, 3341);
    if (malad==3) s.DrawImage(0, 0, 3343);
  }
  
  if (infect>0) s.DrawImage(0, 0, 3340);
  
  etatpersos[numero]=DynamicSprite.CreateFromDrawingSurface(s, 0, 0, 90, 110);
  s.Release();
  // I put the dynamic sprite in the character's view
  ViewFrame* f=Game.GetViewFrame(this.CHAR_VIEW, this.CHAR_LOOP, 2);
  f.Graphic=etatpersos[numero].Graphic;
  // Then I give this frame to the character
  this.Acteur.LockViewFrame(this.CHAR_VIEW, this.CHAR_LOOP, 2);
}

Baguettator

Hi all,

Sorry for the multipost, but I do that to inform you of the problem.

I managed to fix it, but I think there is a problem in the engine (or something else).

As I said, my code above didn't work after the first time I call the function. The dynamic sprite didn't update.

In fact, I tried to LocViewFrame my character to another frame (a sprite that is the frame 0) just BEFORE I apply the dynamic sprite to the frame 3 and then I lockviewframe my character with the frame 3.

And it worked !

But it seems to be a problem, or I don't understand how a dynamic sprite is working. Because I needed to lock on another sprite, and then lock to the dynamic sprite to have it displayed correctly. Is it an engine problem ?

Also, I found naother problem that should be engine's responsability (but I honnestly don't know how to proof it) : I pause the game with PauseGame(), and sometimes, I need to UnPauseGame() twice to REALLY unpause the game. But I believe only once would be needed...?

Baguettator

Well, strangely my function doesn't work anymore... Was it a dream ? Anyway, it doesn't work.

If anybody could help me, I will be grateful ! :)

Crimson Wizard

#10
Quote from: Baguettator on Sun 14/03/2021 08:57:36
Also, I found naother problem that should be engine's responsability (but I honnestly don't know how to proof it) : I pause the game with PauseGame(), and sometimes, I need to UnPauseGame() twice to REALLY unpause the game. But I believe only once would be needed...?

From the manual:
Quote
PauseGame

Stops AGS processing character movement and animations. This has the same effect on the game as happens when a modal GUI is popped up. PauseGame() works as a counter, so if you call it twice, you will need to call UnPauseGame() game twice too to resume game.

I believe these two functions were designed to work as a "nesting" pause to let you design "nested" states in game each of which pauses the game, so that game also unpaused when the last blocking state has ended, so to speak. This also helps to script modules that may pause game, as unpausing game inside a module can break it in case you also paused it in your global script.



Regarding dynamic sprite, I did not have spare time to check that out, but I will try to do later today.

Crimson Wizard

Regarding the dynamic sprite code.

This line is completely unnecessary:
Code: ags
etatpersos[numero]=DynamicSprite.CreateFromDrawingSurface(s, 0, 0, 90, 110);

as you have already created dynamic sprite in etatpersos[numero] and painting on it. This line will just create a copy of something you've already painted and replace previous sprite.
I am surprised this does not cause any engine errors, as you are replacing a sprite that DrawingSurface was taken from, meaning that drawing surface now corresponds to non-existing sprite.

Maybe it's causing some problem, although not sure. I don't see any other problems at the moment...

Baguettator

Many thanks Crimson Wizard ! It works now as intended :)

In fact, I got engine error when I put this line under the "s.Release()" line. It said something like "DrawingSurface is intended to be used before".

Everything seems to be working now, thanks you !

And I noticed the UnpauseGame() problem, maybe my script is getting Paused twice, but I will see !

SMF spam blocked by CleanTalk