SOLVED: Equipped items AND basic how-to on dynamic sprites

Started by johanvepa, Sun 08/06/2014 00:12:47

Previous topic - Next topic

johanvepa

This thread:
http://www.adventuregamestudio.co.uk/forums/index.php?topic=42816.msg568540#msg568540
gave me inspiration for a set of equipable items, though I had to make do with a simpler solution.
It works, but with performance issues.

This i what I do:

Besides the main character cEgo, I have three characters, cWeapon, cArmor and cShield, whose sprites are as their names imply. They follow cEgo around, thus creating the illusion of cEgo being clothed in certain equipment.

When interacting with an object, oCombat, in my testing room (room 5), cEgo is set to view 2 (combat view), and the three equipment characters are set to follow cEgo, like this:
Code: ags


function oCombat_Interact()
{
if (eKeyboardMovement_Tapping == true)  //not in combat mode, the following vode sets combat mode
  {
  KeyboardMovement.SetMode(eKeyboardMovement_Pressing);
  cEgo.ChangeView(2); //combat view

  cWeapon.ChangeRoom(5, 100, 100);
  cShield.ChangeRoom(5, 100, 100);
  cArmor.ChangeRoom(5, 100, 100);

  cShield.FollowCharacter(cEgo, FOLLOW_EXACTLY, 1); //drawn behind cEgo
  cArmor.FollowCharacter(cEgo, FOLLOW_EXACTLY, 0); //drawn in front of cEgo
  cWeapon.FollowCharacter(cArmor, FOLLOW_EXACTLY, 0); //drawn in front of cArmor
  
  cShield.ChangeView(9); //cape wrapped around arm
  //two other characters still in their "invisible mode", view 10, which is a sprite of blank space

  cWeapon.Loop=0;
  cShield.Loop=0;
  cArmor.Loop=0;
  }
  
else //if already in combat mode, reset to normal
  {
  KeyboardMovement.SetMode(eKeyboardMovement_Tapping);
  cEgo.ChangeView(1); //normal view
  
  cWeapon.ChangeView(10);
  cWeapon.FollowCharacter(null);
  cWeapon.ChangeRoom(3, 100, 100); //somewhere else
  
  cArmor.ChangeView(10);
  cArmor.FollowCharacter(null);
  cArmor.ChangeRoom(3, 100, 100);
  
  cShield.ChangeView(10);
  cShield.FollowCharacter(null);
  cShield.ChangeRoom(3, 100, 100);
    
  }
}



In my room 5, I also have a multitude of objects that when interacted with sets the view of each of the characters cWeapon, cArmor and cShield. Thus, for example the player can clothe himself in chain mail (cArmor is set to view 9, chainmail, by interacting with a certain object in my test room). cWeapon may switch view to dagger, to short sword, to long sword, in the same way.

In room's rep_exec function I have:

Code: ags

function room_RepExec()
{
  
if (cEgo.View == 2) //cEgo is in combat view
  {
  cWeapon.Frame=cEgo.Frame;
  cShield.Frame=cEgo.Frame;
  cArmor.Frame=cEgo.Frame;
  }

  


Now, I can get this to work, the cEgo looks clothed in the armor of choice and with weapon at hand.

However, there are performance issues: The armor and shield blinks, not much, but just enough for the original character image to occasionally show underneath the armor and for the shield to look like its not really there.

Can I alter my code in a way that solves this blinking issue?


EDIT 1: Changed the "walking order" of characters so the weapon is drawn in front, then the armor, then cEgo, then shield or cape.


EDIT 2: Tried out swapping the followcharacter command with
Code: ags

cWeapon.x=player.x;
cWeapon.y=player.y;

and so forth. No difference.

Also noticed that the sprites of the weapons and armor characters seem to be visible on the screen slightly after the player character sprite has updated. To elaborate: When player character is walking to the right side of the screen, the armor characeter sprite is blinking and seen slightly to the left of the character. When the player character is walking left, the armor character sprite is blinking slightly to the right of the player.


EDIT 3: I suspected there might be differences between running local rooms's scripting and globalscript. I've relocated the abovementioned code to repeatedly_execute in globalscript.asc instead of having it in room_RepExec. No difference. It seems the sprites of the player character and the sprites of the following character are not updated at the same instant.

johanvepa

Tried something new and would have expected it to work.

Instead of either using Followcharacter or setting x, y coordinates to the same as cEgo and frame as the same as cEgo in rep_exec, instead I went to the KeyboardMovement_102.asc and in this part:
Code: ags
function repeatedly_execute() {
	//--------------------------------------------------
	// Pressing mode
	//--------------------------------------------------

under THIS line:
Code: ags
player.WalkStraight(player.x + dx, player.y + dy, eNoBlock); // walk player character to the new coordinates

I added THESE:
Code: ags
		
cWeapon.WalkStraight(cWeapon.x + dx, cWeapon.y + dy, eNoBlock); // walk weapon character to the new coordinates
cArmor.WalkStraight(cArmor.x + dx, cArmor.y + dy, eNoBlock); // walk armor character to the new coordinates
cShield.WalkStraight(cShield.x + dx, cShield.y + dy, eNoBlock); // walk shield character to the new coordinates


Also, I switched THIS part:
Code: ags
if (newdirection == eKeyboardMovement_Stop)  player.StopMoving();

with THIS:
Code: ags
if (newdirection == eKeyboardMovement_Stop) 
      {
      player.StopMoving();
      cWeapon.StopMoving();
      cArmor.StopMoving();
      cShield.StopMoving();
      }


This gave the interesting result of seeming to work, as the two characters move in unison and there doesn't seem to be any blinking.

Hovewer, this presented another problem: It seems any of the four characters that were supposed to be moving whenever the arrow keys are pressed will only move occasionally.

Consequently, they will sometimes move in unison, sometimes only one of them will move. For example, cEgo may just be standing there while his shield walks away by itself. I can't seem to pinpoint what triggers one or more of the four characters to move.

Khris

What I would do is compose dynamic sprites whenever the equipment changes and build a view from them using an existing one and Game.GetViewFrame().
This will fix all your issues and doesn't have to rely on potentially glitchy behavior of characters using FOLLOW_EXACTLY.

johanvepa

Thank you for, as always, both providing a clear and simple solution, and reminding me how little I know of this wonderful game engine   :)

I was nearly there, though, as I discovered all my characters were SOLID, probably forcing the game engine to try and tear them apart. Anyway, even making them not solid only partially solved the non-synchrone issue.

I'll look into dynamic sprites right away. Thank you again.

johanvepa

I need a how-to on this. I've spent several evenings now, trying to figure out how to use these dynamic sprites, and I can't find anything in the manual that does not take it for granted that I know what it's talking about in the first place.

Where can I seek information on the basic-basics?


Khris

First you'll need an array of DynamicSprites to store the composed walkcycle sprites:
Code: ags
DynamicSprite *playerview[40];


Say the payer has 4 loops, each with 10 sprites. Next you'll need a function that composes a sprite.
Code: ags
int GetSlot(int view, int loop, int frame) {
  ViewFrame *vf = Game.GetViewFrame(view, loop, frame);
  return vf.Graphic;
}

void ComposePlayerSprite(int loop, int frame) {
  int i = loop * 10 + frame; // DynSprite array index
  int player_slot = GetSlot(EGOWALK, loop, frame); // get uneqipped sprite slot
  playerview[i] = DynamicSprite.CreateFromExistingSprite(player_slot);
  DrawingSurface *ds = playerview[i].GetDrawingSurface();
  // draw shield
  ds.DrawImage(0, 0, GetSlot(SHIELDVIEW, loop, frame));
  // if loop == 2, draw player again on top, since shield is behind player
  if (loop == 2) ds.DrawImage(0, 0, player_slot);
  ds.Release();
  ViewFrame *vf = Game.GetViewFrame(EQUIPPEDPLAYER, loop, frame);
  vf.Graphic = playerview[i].Graphic;  // assign slot to dynamic view's viewframe
}


Finally, this function needs to be called for each loop and frame.

There's nothing truly dynamic here yet; currently this function will always combine player + shield.
You have to add other equipment and check variables accordingly before drawing it.

johanvepa

#6
Thanks a lot Khris, thats very inspiring.
I'm still just toying with the basics here, though  :wink:

Anyway, I tried out what my understanding tells me is a basic way of repainting a viewframe.
I put together this simple code, initiated in the game by interacting with an object I call iEye:

Code: ags

function iEye_Interact()
{
DynamicSprite *MySprite[9];
MySprite[0] = DynamicSprite.Create(80, 80);
DrawingSurface *MySurface = MySprite[0].GetDrawingSurface();
MySurface.DrawImage(0, 0, 2052); //game template's Exit button graphic
MySurface.Release();
ViewFrame *MyViewFrame = Game.GetViewFrame(1, 0, 0);
MyViewFrame.Graphic = MySprite[0].Graphic;
}


As far as I understand the above, I call a dynamic sprite array of 10 sprites. Sprite no. 0, called MySprite[0], I create from scratch, with 80X80 pixels.
Then I call a drawingsurface, called MySurface, which is MySprite[0]'s surface.
On MySprite[0]'s surface I draw an image of graphicsprite no. 2052.
Then I release the surface, telling the game I am done drawing.
Then I call a ViewFrame, which is view no. 1, loop 0, frame 0, and call it MyViewFrame. On that viewframe, I paste the new graphic I just made at MySprite[0].

I would expect this: As soon as I interact with the object iEye, frame 0 of loop 0 of view 1 would become graphicsprite no. 2052 (the exit button sprite of the game template).

Only it doesn't. Instead, frame 0 of loop 0 of view 1 simply becomes a blue cup.

What am I doing wrong?

Khris

Since you declare *MySprite[9] (which are 9 sprites with indices 0-8 btw) as local to the function, it will cease to exist once the function has finished.
You need to declare the DynamicSprites outside so they're kept in memory.

johanvepa

#8
In globalscript.asc:
Code: ags

DynamicSprite *MySprite[10];


Still in globalscript.asc:
Code: ags

function Edit()
{
MySprite[0] = DynamicSprite.Create(80, 80); //the approx. pixel height/width of the player character
DrawingSurface *MySurface = MySprite[0].GetDrawingSurface();
MySurface.DrawImage(0, 0, 2052); //game template's Exit button graphic
MySurface.Release();
ViewFrame *MyViewFrame = Game.GetViewFrame(1, 0, 0);
MyViewFrame.Graphic = MySprite[0].Graphic;
}
export Edit;


In globalscript.ash:
Code: ags

import function Edit();


In local room script, interacting with an object called iEye:
Code: ags

function iEye_Interact()
{
Edit();
}



AwwwRIGHT, we've got basic functionality tested and working from where I'm standing :=
The above changes frame 0 of loop 0 in view 1 to graphic sprite no. 2052.

Wo-hoo! The possibilities just unfold in my mind from here on :-D

Thank you so much for the solution, and for the very inspiring code you put together. You certainly set some things in motion with my scripting ;-D

Ny next really great challenge will be against myself, so as not to over-complicate matters, because now I just want to plaster my little pet project with dynamic sprites...

johanvepa

#9
Quote from: Khris on Sat 14/06/2014 09:51:03
*MySprite[9] (which are 9 sprites with indices 0-8 btw)

Wait, are you saying, if I want 40 sprites, I should declare sprite[40], but these sprites will be enumerated with indices 0-39?!?

That's good to know! If I got it right?

EDIT: I just read the manual.

Khris

Yes, it's the same with every array. The size is stated in the declaration, but the numbering starts at 0 (pretty much in every language except Lua ;))

johanvepa

I'm at a loss again. Damn.

I made the code below, and it works! But only for loop no. 0, not for any of the other loops.

The result is that ego - when in view 4 - becomes visible only when he is walking downwards (loop 0).

(For the context: I use the same 7 graphic sprites for all 4 loops in a view for testing purposes. Ego should therefore look the same, regardless of walking direction (I haven't done the artwork for loops 1-3 yet). I tried inserting random graphics into the loops, with no change.)


Code: ags
// main global script file



//7 frames for each loop * 4 loops, 28 frames
DynamicSprite *MySprite[28];

//integers needed for providing the view of each piece of equipment. Values are set in local room script when equipping item
int shieldisview;
export shieldisview;
int armorisview;
export armorisview;
int weaponisview;
export weaponisview;

//whenever equipping an item in local script
function AlterEgoCombatView()
{
  int MySpriteNo;
  MySpriteNo = 0;
  int currentloop; 
  currentloop = 0;
  int currentframe;

  while (currentloop <= 3)
  {
    currentframe = 0;
    while (currentframe <= 6)
    {
      //create new blank dynamicsprite
      MySprite[MySpriteNo] = DynamicSprite.Create(54, 43);

      //call blank dynamicsprite's surface
      DrawingSurface *MySurface = MySprite[MySpriteNo].GetDrawingSurface();

      //draw current shieldview onto surface
      ViewFrame *ShieldViewFrame = Game.GetViewFrame(shieldisview, currentloop, currentframe);
      MySurface.DrawImage(0, 0, ShieldViewFrame.Graphic);

      //draw view of ego without equipment onto surface
      ViewFrame *EgoView = Game.GetViewFrame(2, currentloop, currentframe);
      MySurface.DrawImage(0, 0, EgoView.Graphic);
      
      //draw current armorview onto surface
      ViewFrame *ArmorViewFrame = Game.GetViewFrame(armorisview, currentloop, currentframe);
      MySurface.DrawImage(0, 0, ArmorViewFrame.Graphic);

      //draw current weaponview onto surface
      ViewFrame *WeaponViewFrame = Game.GetViewFrame(weaponisview, currentloop, currentframe);
      MySurface.DrawImage(0, 0, WeaponViewFrame.Graphic);

      //done drawing MySprite[DynamicSpriteNo], release for use
      MySurface.Release();

      //call viewframe of view 4, which is to become egos combat view
      ViewFrame *MyViewFrame = Game.GetViewFrame(4, currentloop, currentframe);
      //define graphic of a viewframe in view 4 as the current graphic of MySprite[DynamicSpriteNo]
      MyViewFrame.Graphic = MySprite[MySpriteNo].Graphic;

      //must use new dynamic sprites for next frames and subsequent loops
      MySpriteNo ++;
      //if not done with loop, go to next frame and repeat
      currentframe ++;
    }
    //if not done with view, go to next loop and repeat
    currentloop ++;
  }
  //if not already at view 4, set to view 4.
  cEgo.ChangeView(4);
}
export AlterEgoCombatView;


//when returning egos view to default view 1, all 28 dynamic sprites must be deleted.
function Uponreturning()
{
  int MySpriteDelete;
  while (MySpriteDelete <= 27)
  {
    if (MySprite[MySpriteDelete] != null)
    {
      MySprite[MySpriteDelete].Delete();
      MySpriteDelete ++;
    }
  }
  Display("Deleted 28 dynamic sprites from array MySprite[]");
}
export Uponreturning;



EDIT: Fixed according to all of Khris' solutions above and below.

Khris

Insert this as line 25 of your code:
Code: ags
    currentframe = 0;  // reset inner loop's iteration variable


(You should also fix your indentation. It increases after each {, and goes back to the left with each }.)

johanvepa

OF COURSE (laugh)

I spent all morning trying to find the error :tongue:

Thank you Khris. You are amazing.

Khris

I wish,
I actually plugged your code into a test game and was stumped for quite some time before I realized the line was missing :D

Just for reference, here's the code with proper indentation:

Code: ags
// main global script file
int shieldisview;
export shieldisview;
int armorisview;
export armorisview;
int weaponisview;
export weaponisview;
//integers needed for providing the view of each piece of equipment. Values are set locally.

DynamicSprite *MySprite[28];
//we have 7 frames for each loop of view 4, and 4 loops = 28 frames

function AlterEgoCombatView()
{
  int MySpriteNo;
  MySpriteNo = 0;

  int currentloop; 
  int currentframe;
  //variables used in the following

  currentloop = 0;
  while (currentloop <= 3)
  {
    currentframe = 0;
    while (currentframe <= 6)
    {
      MySprite[MySpriteNo] = DynamicSprite.Create(54, 43);
      //creates a new blank dynamicsprite

      DrawingSurface *MySurface = MySprite[MySpriteNo].GetDrawingSurface();
      //calls the blank dynamicsprite's surface

      ViewFrame *ShieldViewFrame = Game.GetViewFrame(shieldisview, currentloop, currentframe);
      MySurface.DrawImage(0, 0, ShieldViewFrame.Graphic);
      //draws shieldview on surface, shieldview varies with equipment

      ViewFrame *EgoView = Game.GetViewFrame(2, currentloop, currentframe);
      MySurface.DrawImage(0, 0, EgoView.Graphic);
      //view 2 = ego without any equipment
    
      ViewFrame *ArmorViewFrame = Game.GetViewFrame(armorisview, currentloop, currentframe);
      MySurface.DrawImage(0, 0, ArmorViewFrame.Graphic);
      //draws armorview on surface, armor varies with equipment

      ViewFrame *WeaponViewFrame = Game.GetViewFrame(weaponisview, currentloop, currentframe);
      MySurface.DrawImage(0, 0, WeaponViewFrame.Graphic);
      //draws weaponview on surface, weapon varies with equipment

      MySurface.Release();
      //we are done drawing MySprite[DynamicSpriteNo], with shield behind ego, behind armor, behind weapon

      ViewFrame *MyViewFrame = Game.GetViewFrame(4, currentloop, currentframe);
      //calls the viewframe of what is to become egos combat view, view 4
      MyViewFrame.Graphic = MySprite[MySpriteNo].Graphic;
      //defines the graphic of that particular frame as the current MySprite's graphic

      MySpriteNo ++;
      //must use new dynamic sprite for next frame and yet more in subsequent loops
      currentframe ++;
      //go to next frame and repeat
    }
    currentloop ++;
    //when done with one series of frames, go to next loop and repeat
  }

  cEgo.ChangeView(4);
  //if not already view 4, set to view 4.
}
export AlterEgoCombatView;


Also, comments are usually placed above the respective code line(s). Of course all of that is completely up to you, it just makes things easier if you stick to some conventions is what I find, since you will have less trouble understandig other people's code.

johanvepa

You're right, I'm getting sloppy with the indentation. That happens when I get eager ;)

On another note, I forgot about deleting the sprites after use and got warning messages. I have edited the code, and the above post, for indentation and added yet one more function to take care of deleting MySprite's dynamic sprites. I have the pleasure to announce that it works.




Khris

Note that you can initialize a variable in the declaration:
int i = 0;
ints are initialized to 0 by default anyway though, so
int MySpriteDelete;
is enough.

You should definitely use this though:
Code: ags
    if (MySprite[MySpriteDelete] != null) MySprite[MySpriteDelete].Delete();

Otherwise you'll get a Null Pointer crash if you happen to call the function without having created the sprites.


SMF spam blocked by CleanTalk