Creating view graphic from dynamic sprites problem

Started by .M.M., Sat 24/05/2025 20:26:47

Previous topic - Next topic

.M.M.

Hello all,

It seems I've hit a wall with my poor coding skills.  :(
I have a code that creates custom animations based on what the player is equppied with (both clothes and weapons). The original worked like this: each item had custom properties linked with views for each of the default action, the script took sprites from those views and combined into the final look. This worked fine, however, it was tedious and time consuming work setting up all the initial "item views". So I've reworked it - now, the item property points to a sprite with a spritesheet, and the script takes those spritesheets, combines them and transforms it into the final view.
For some reason I can't understand, sometimes it does not work properly. The only part that causes problems is the attack view part - it might be because as opposed to walk and death views, the attack views's differ in number of frames per view and the frame size.
What happens is that especially in the "unarmed" view, some frames are mixed in from different views - ingame it might look as if instead of taking a battle stance with raised fists, a random part of shooting a gun is shown (even from different loop, and not from the first frame).
As you can see from the script, I've tried debugging it by saving the sprites from the sprite array, to actually see what's going on inside. In the folder "sprite_array", it seems there are some leftover sprites if they are not used. For example view[8] used to show attack with a handgun, now it stores attack with a knife - the handgun animation had more sprites, so there are some unused sprites with a handgun at the end. Seems it works as expected.
Now, the "view_array" folder stores all the actual sprites used in the displayed views - and everything looks exactly right. But ingame one of the animation is broken - even though the log panel shows the correct view and loop number is used for the animation, the graphic shown in game doesn't match what's stored in the folder. And honestly, I have no idea how that would even be possible. I must have missed some small detail somewhere!

Sorry for the length and probably overall poor quality of the code - this is what happens when someone with no coding education keeps editing and reworking something for the past 10 years.  (roll)

I totally understand if it's just too much to go through, but maybe someone will be able to spot the problem right away! Or maybe there's some problem right in the core of the idea?

Thank you all! :)

Code: ags
struct ViewGenerating {
DynamicSprite *walk_frame[75]; // maximum should be 8 loops with 9 frames = 72 frames
};

ViewGenerating CustomView[10];
int i_view;
ViewFrame *frame_new;
ViewFrame *frame_old;
DynamicSprite *gv_sprite_base;
DrawingSurface *gv_sprite_base_surface;

function GenerateView(this Character*, ViewType type, int view_index)
{
 // view_index = 0, 1, 2 (based on the weapon)
 i_loop = 0;
 gv_sheet_loop = 0;
 i_loop_frame = 0; // the frame number of the current loop
  /*
  view_clothes[] index:
  shadow    7
  X NOT USED back arm  7
  shoes     6
  trousers  5
  vest      4
  X NOT USED front arm 3
  weapon    3
  hat       2
  (slot 1 and 0 free for now)
  */
 
 if (type == eViewWalk) {
  view_clothes[7] = 20; // shadow sprite
  if (weapon_slot[7] != null) view_clothes[2] = weapon_slot[7].GetProperty("IGWalk"); 
  else view_clothes[2] = 0; // 7 is hat, 0 means skip; index 2 sends it to front
  if (weapon_slot[6] != null) view_clothes[6] = weapon_slot[6].GetProperty("IGWalk"); 
  else view_clothes[6] = 999; // change to sprite number
  if (weapon_slot[5] != null) view_clothes[5] = weapon_slot[5].GetProperty("IGWalk"); 
  else view_clothes[5] = 999; // change to sprite number
  if (weapon_slot[4] != null) view_clothes[4] = weapon_slot[4].GetProperty("IGWalk"); 
  else view_clothes[4] = 999; // change to sprite number
  if (weapon_slot[view_index] != null)
  {
   if (weapon_slot[view_index].GetProperty("weapon_type") == 0)
   {
    if (weapon_slot[view_index].GetTextProperty("special_bonus") == "knife") view_clothes[3] = 13; //view_clothes = sprite number
    else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "axe") view_clothes[3] = 13; // UPDATE WHEN READY
    else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "saber") view_clothes[3] = 13; // UPDATE WHEN READY
    else view_clothes[3] = 0; // fist,  no weapon
   }
   else if (weapon_slot[view_index].GetProperty("weapon_type") == 1) view_clothes[3] = 15;
   else if (weapon_slot[view_index].GetProperty("weapon_type") == 2) view_clothes[3] = 17;
   else view_clothes[3] = 0; // medkit
  }
  else view_clothes[3] = 0; // null weapon
  
  i_view = view_index; // i_view - sets the index for storing sprites (0-8); 0, 1, 2 = walk, 3, 4, 5 = death, 6, 7, 8 = attack
  if (view_index == 0) view_to_generate = PLAYER_WALK0;
  else if (view_index == 1) view_to_generate = PLAYER_WALK1;
  else if (view_index == 2) view_to_generate = PLAYER_WALK2;
  else
  {
   Display("Wrong parameter of GenerateView function.");
   return -1;
  }   
 }
 else if (type == eViewDeath) {
  view_clothes[7] = 37; //shadow 
  if (weapon_slot[7] != null) view_clothes[2] = weapon_slot[7].GetProperty("IGDeath"); 
  else view_clothes[2] = 0; // 7 is hat, 0 means skip
  if (weapon_slot[6] != null) view_clothes[6] = weapon_slot[6].GetProperty("IGDeath"); 
  else view_clothes[6] = 999; // CHANGE TO SPRITE NUMBER 
  if (weapon_slot[5] != null) view_clothes[5] = weapon_slot[5].GetProperty("IGDeath"); 
  else view_clothes[5] = 999; // CHANGE TO SPRITE NUMBER
  if (weapon_slot[4] != null) view_clothes[4] = weapon_slot[4].GetProperty("IGDeath"); 
  else view_clothes[4] = 999; // CHANGE TO SPRITE NUMBER
  if (weapon_slot[view_index] != null)
  {
   if (weapon_slot[view_index].GetProperty("weapon_type") == 0) {
    if (weapon_slot[view_index].GetTextProperty("special_bonus") == "knife") view_clothes[3] = 42; // sprite knife death
    else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "axe") view_clothes[3] = 0;
    else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "saber") view_clothes[3] = 0;
    else view_clothes[3] = 0;
   }
   else if (weapon_slot[view_index].GetProperty("weapon_type") == 1) view_clothes[3] = 41; // sprite handgun death
   else if (weapon_slot[view_index].GetProperty("weapon_type") == 2) view_clothes[3] = 43;
   else view_clothes[3] = 0;
  }
  else view_clothes[3] = 0;
  i_view = view_index+3; // is i_view neccessary?
  if (view_index == 0) view_to_generate = PLAYER_DEATH0;
  else if (view_index == 1) view_to_generate = PLAYER_DEATH1;
  else if (view_index == 2) view_to_generate = PLAYER_DEATH2;
  else
  {
   Display("Wrong parameter of GenerateView function.");
   return -1;
  }
 }
 else if (type == eViewAttack)
 {
  if (weapon_slot[view_index] == null) // nothing => fighting with fists view
  { 
   view_to_generate = PLAYER_A_NULL;
   gv_property = "IGAFist";
  }
  else if (weapon_slot[view_index].GetProperty("weapon_type") == 0) // close combat
  {
   if (weapon_slot[view_index] == iFist)
   {
   view_to_generate = PLAYER_A_NULL;
   gv_property = "IGAFist";
   }
   else
   {
   gv_property = "IGAKnife";
   view_to_generate = PLAYER_A_KNIFE;
   }
  }
  else if (weapon_slot[view_index].GetProperty("weapon_type") == 1) // handguns
  {
   view_to_generate = PLAYER_A_GUN;
   gv_property = "IGAGun";
  }  
  else if (weapon_slot[view_index].GetProperty("weapon_type") == 2) // rifles
  {
   view_to_generate = PLAYER_A_RIFLE;
   gv_property = "IGARifle";
  }
  else // medkits or fallthrough
  {
   view_to_generate = PLAYER_A_NULL;
   gv_property = "IGAFist";
  }
  i_view = 6+view_index; // i_view for attacks: 6, 7, 8
  player_attack_view[view_index] = view_to_generate;  
  gv_i = 7;
   while (gv_i > 3) // other layers handled separately
   {
    if (weapon_slot[gv_i] != null)
    {
     if (gv_i == 7) // hat
     {
      view_clothes[2] = weapon_slot[7].GetProperty(gv_property);
      view_clothes_transition[2] = weapon_slot[7].GetProperty(gv_property.Append("Transition"));
     }
     else
     {
      view_clothes[gv_i] = weapon_slot[gv_i].GetProperty(gv_property);
      view_clothes_transition[gv_i] = weapon_slot[gv_i].GetProperty(gv_property.Append("Transition"));
     }
    }
    else
    { // empty sprites
     if (gv_i == 7) // HAT - specific, no hat means no sprite
     {
       view_clothes[2] = 0;
       view_clothes_transition[2] = 0;      
     }
     if (view_to_generate == PLAYER_A_NULL)
     {
      if (gv_i == 6) // SHOES
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 5) // TROUSERS
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 4) // VEST
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
     }
      else if (view_to_generate == PLAYER_A_KNIFE || view_to_generate == PLAYER_A_SABER || view_to_generate == PLAYER_A_AXE)
     {
      if (gv_i == 6) // SHOES
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 5) // TROUSERS
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 4) // VEST
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
     }
     else if (view_to_generate == PLAYER_A_GUN)
     {
      if (gv_i == 6) // SHOES
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 5) // TROUSERS
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 4) // VEST
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
     }
     else if (view_to_generate == PLAYER_A_RIFLE)
     {
      if (gv_i == 6) // SHOES
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 5) // TROUSERS
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
      else if (gv_i == 4) // VEST
      {
       view_clothes[gv_i] = 999;
       view_clothes_transition[gv_i] = 999;      
      }
     }
    }
    gv_i --;
   }
   // VIEW SPECIFIC SPRITESHEETS
   if (view_to_generate == PLAYER_A_NULL)
   {
    view_clothes[7] = 84; // shadow sprite
    view_clothes_transition[7] = 372;
    view_clothes[3] = 0; // weapon sprite
    view_clothes_transition[3] = 0;
   }
   else if (view_to_generate == PLAYER_A_KNIFE || view_to_generate == PLAYER_A_SABER || view_to_generate == PLAYER_A_AXE)
   {
    view_clothes[7] = 21; // shadow sprite
    view_clothes_transition[7] = 32;
    if (view_to_generate == PLAYER_A_SABER)
    {
     view_clothes[3] = 0; // weapon sprite
     view_clothes_transition[3] = 0;
    }
    else if (view_to_generate == PLAYER_A_AXE)
    {
     view_clothes[3] = 0; // weapon sprite
     view_clothes_transition[3] = 0;
    }
    else // knife
    {
     view_clothes[3] = 98; // weapon sprite
     view_clothes_transition[3] = 99;
    }
   }
   else if (view_to_generate == PLAYER_A_GUN)
   {
    view_clothes[7] = 55; // shadow sprite
    view_clothes_transition[7] = 44;
    view_clothes[3] = 0; // weapon sprite
    view_clothes_transition[3] = 0;
   }
   else if (view_to_generate == PLAYER_A_RIFLE)
   {
    view_clothes[7] = 100; // shadow sprite
    view_clothes_transition[7] = 101;
    view_clothes[3] = 0; // weapon sprite
    view_clothes_transition[3] = 0;
   }        
  }
 
 // setup the base size of the sprite
 
 loopcount = Game.GetLoopCountForView(view_to_generate);
 framecount = Game.GetFrameCountForLoop(view_to_generate, 0);
 i_frame = 0; // loops through the complete array for dynamic sprites
 i_loop = -1; // first tick 
 // draw the sprites
 while (i_loop < loopcount)
 {
  if (i_loop_frame == framecount || i_loop == -1)
  {
   i_loop_frame = 0;
   i_loop ++;
   if (i_loop >= loopcount) break;
   // setup which line of spritesheet to use
   if ((i_loop - 8*(i_loop/8)) == 2) gv_sheet_loop = 1;
   else if ((i_loop - 8*(i_loop/8)) == 3) gv_sheet_loop = 2;
   else if ((i_loop - 8*(i_loop/8)) == 4 || (i_loop - 8*(i_loop/8)) == 6) gv_sheet_loop = 3;
   else if ((i_loop - 8*(i_loop/8)) == 5 || (i_loop - 8*(i_loop/8)) == 7) gv_sheet_loop = 4;
   else gv_sheet_loop = i_loop - 8*(i_loop/8); // makes sure it works for transition too
   if (i_loop < Game.GetLoopCountForView(view_to_generate))
   {
    framecount = Game.GetFrameCountForLoop(view_to_generate, i_loop);
    if (i_loop == 0 || i_loop == 8)
    {
     // start and transition - change the base size of the sprite
     if (i_loop == 0)
     {
      gv_frame_width = PlayerAnimSize[view_to_generate].Width;
      gv_frame_height = PlayerAnimSize[view_to_generate].Height;
     }
     else if (i_loop == 8)
     {
      gv_frame_width = PlayerAnimSize[view_to_generate].TransitionWidth;
      gv_frame_height = PlayerAnimSize[view_to_generate].TransitionHeight;
     }

   if (CustomView[i_view].walk_frame[i_frame] == null) CustomView[i_view].walk_frame[i_frame] = DynamicSprite.Create(gv_frame_width, gv_frame_height, true); // new sprite created
   else CustomView[i_view].walk_frame[i_frame].ChangeCanvasSize(gv_frame_width, gv_frame_height, 0, 0);
   viewsurface = CustomView[i_view].walk_frame[i_frame].GetDrawingSurface(); // surface created from dynamic sprite from the view
   viewsurface.Clear();
   if (i_loop == 0 && i_loop_frame == 1)
   {
   // space for debug message
   }
   i_count = 7; // index of layers, from back to front
   while (i_count > 1) // 0 and 1 is unused for now, can be changed - hairstyles?
   { // cycle through
    if (i_loop < 8)
    { // standard loops
     if (view_clothes[i_count] != 0) viewsurface.DrawImage(0, 0, view_clothes[i_count], 0, gv_frame_width, gv_frame_height, gv_frame_width*i_loop_frame, gv_frame_height*gv_sheet_loop, gv_frame_width, gv_frame_height);
    }
    else
    { // transition - change the spritesheet
     if (view_clothes_transition[i_count] != 0) viewsurface.DrawImage(0, 0, view_clothes_transition[i_count], 0, gv_frame_width, gv_frame_height, gv_frame_width*i_loop_frame, gv_frame_height*gv_sheet_loop, gv_frame_width, gv_frame_height);
    }
    i_count --;
   }
   viewsurface.Release(); // done with one frame
   
   frame_new = Game.GetViewFrame(view_to_generate, i_loop, i_loop_frame);
   frame_new.Graphic = CustomView[i_view].walk_frame[i_frame].Graphic;
   if (type == eViewAttack) // only part that causes problems
   {
    file_name_debug = String.Format("$SAVEGAMEDIR$/view-%d_frame-%d_loop-%d_frame-%d.bmp", i_view, i_frame, i_loop, i_loop_frame);
    CustomView[i_view].walk_frame[i_frame].SaveToFile(file_name_debug);
    
    file_name_debug = String.Format("$SAVEGAMEDIR$/sprites_array/view-%d_frame-%d.bmp", i_view, i_frame);
    CustomView[i_view].walk_frame[i_frame].SaveToFile(file_name_debug);
    
    file_name_debug = String.Format("$SAVEGAMEDIR$/view_array/%d/loop-%d_frame-%d.bmp", view_to_generate, i_loop, i_loop_frame);
    DynamicSprite *debug_sprite = DynamicSprite.CreateFromExistingSprite(frame_new.Graphic);
    debug_sprite.SaveToFile(file_name_debug);
    
   
   }
   i_loop_frame ++;
   i_frame ++;
 }
}

Khris

One thing I've noticed:
Code: ags
  i_loop - 8*(i_loop/8) == 2
can be shortened to
Code: ags
  i_loop % 8 == 2

Anyway, it sounds like the game is using the wrong view, a view with too many frames. So you're using the correct loop and frame but a view that has more frames, and in your animation you first see the correct first X frames, then also frames X+1, X+2 which still have sprites in them from a different view.

Your code uses view_to_generate to grab the number of frames but then uses i_view to actually populate it. If these values differ, and they appear to, then the loop might end up putting the wrong number of frames in the view, i.e. less.

Edit:
Scratch that, I just saw that you are saving the generated frames using view_generate.
If I had to debug this I'd probably refactor the code into a bunch of separate functions. Using one massive function and tons of variables makes this a pain to debug.

.M.M.

Quote from: Khris on Sun 25/05/2025 11:10:59One thing I've noticed:
Code: ags
  i_loop - 8*(i_loop/8) == 2
can be shortened to
Code: ags
  i_loop % 8 == 2

Oh, thanks, that's great! I'll start using it from now on.  :)


Quote from: Khris on Sun 25/05/2025 11:10:59If I had to debug this I'd probably refactor the code into a bunch of separate functions. Using one massive function and tons of variables makes this a pain to debug.

I guess that's the way to go. First I'll try to go through the whole function once more, and if that doesn't help, I'll have to split it into separate parts and debug them individually. Thank you for taking your time with it!  :)

[EDIT]
I guess what might be happening is that the array of dynamic sprites walk_frame inside CustomView is interconnected? Meaning that the value for CustomView[number1].walk_frame[number2].Graphic used by the view might change without accessing it, simply by changing the total number of sprites stored in the variable - is it possible?

SMF spam blocked by CleanTalk