Creating and moving dynamic sprites

Started by AnasAbdin, Sat 19/03/2016 19:39:33

Previous topic - Next topic

AnasAbdin

Hey all :)

I am stuck at this, I am trying the following:

When the player clicks a hotspot, a dynamic sprite appears at (x1,y1) and moves to (x2,y2) NoBlock style, then disappears or get ignored behind a walkbehind if possible.
I managed to make the dynamic sprite appear, but I can't make it move non-block. And I can't control its baseline (it is even possible?)

This is by far what I managed to do: (creating and displaying the sprite) :-[
Code: ags
DynamicSprite* stone = DynamicSprite.CreateFromExistingSprite(object[1].Graphic);
DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();
surface.DrawImage(100, 280, stone.Graphic);
surface.Release();
stone.Delete();


Edit:
The player can click the hotspot again while the dynamic sprites from the previous clicks are displaying and moving.

Note that when the use of dynamic sprites is introduced to me.. I panic.

Game info:

AGS 3.3.3.0
res: 640x400 32-bit
Direct3D 9

Crimson Wizard

#1
I would like to clarify concepts a bit.

First, dynamic sprite can not move anywhere. Dynamic sprite is simply like a normal sprite, just that you create by script command (rather than import in the editor), and that can be deleted at runtime (unlike sprites you import in the editor).
Therefore, do not think of it as some entity which may move, etc, just think of it as a usual sprite.

Secondly, in your script example what you do is: taking existing sprite, making a copy of it, drawing that copy over room background, then deleting the copy. Why creating the dynamic sprite at first place? You will achieve same doing just:
Code: ags

DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();
surface.DrawImage(100, 280, object[1].Graphic);
surface.Release();


Third, the non-blocking actions are usually scripted with the use of the repeatedly_execute(), or its counterpart in room script. In such function you update object's visuals and check whether it reached the wanted destination.

When you draw something on the drawing surface yourself, and want the picture to appear moving then the usual technique is:
* copy the the surface background to memory;
* draw image in first position
* wait a little
* restore the part of background where the image was drawn
* draw image in next position
* repeat until done.
Of course, this requires some scripting, but AGS has number of entities that can move and draw themselves, like characters, room objects, guis and overlays, and in usual case you may just pick one of these to make your life easier.

The simpliest way to make some image move over room is to use a room object. You seem to have an object already, the one you take a graphic from. Why not making that object move instead? Or make a second object, assign same image, move it, then hide it?

Since I do not know your game details, in the following example I assumed that you cannot move original object for some reason, that's why it moved some extra object instead.
When object appears
Code: ags

oTheObjectClone.Graphic = object[1].Graphic;
oTheObjectClone.X = 100;
oTheObjectClone.Y = 280;
oTheObjectClone.Visible = true;


In the room's repeated function:
Code: ags

if (oTheObjectClone.Visible)
{
    // object is visible? update its position
    oTheObjectClone.X++;
    // check if the destination was reached
    if (oTheObjectClone.X == 200)
    {
        // we are done, hide the object
        oTheObjectClone.Visible = false;
    }
}

AnasAbdin

Thanks CW for the thorough clarification and help, I appreciate it :)

I am trying to make the player softly throw something, it moves to a fixed position behind a walkbehind. The player can throw as many of this object even if some existing objects are still mid-throw. After testing the timing, the maximum number of visible thrown objects is 7. I can create this effect with simple room objects. This would require some nested if statements which I don't mind at all. I thought it would be much interesting to use dynamic sprites ;)

The effect is equivalent to a space shooter game where the player's ship is shooting as many missles.

Crimson Wizard

#3
I see.

You could use dynamic sprites to depict flying items, of course, but you will have to deal with baseline/walkbehinds issue. The problem is that Room Objects calculate their Z (depth) position automatically. If you are going to draw right on top of the room background, your flying stones will become a part of background: they will always be BEHIND other object (and character), and ignore walk-behinds.

So, the choice of surface to draw upon is essential.

If you can do this action only in one room, I may suggest creating a large object, covering all the stone flying path (or even whole room, for simplicity). Create a fully transparent sprite of corresponding size, and assign it to the object. Now, instead of drawing your stones on the room background, draw them on this object's sprite.
You can setup object's baseline to make flying stones appear above or behind some other things in room.
In other words, think of this room object as a "drawing layer".

If this action may be performed in several rooms, you may use dummy characters for the same purpose (locking them to the view with 1 large transparent sprite).

In either case, the dynamic sprite you use for that object or character should be declared in the global scope of the script (outside of any function), this way it will be kept in memory all time (otherwise it will be lost as soon as function ends).

Also, with dynamic sprite, since your are drawing everything yourself, you will have to remember and track the stone's coordinates yourself too.

Following is a simple example (assuming you are creating a room-sized object with transparent background in your room):

1. Outside of any function declare a dynamic sprite:
Code: ags

DynamicSprite* CustomLayerSpr;

2. In the room's "Before Fade-in" event:
Code: ags

// here we create a room-sized dynamic sprite
CustomLayerSpr = DynamicSprite.Create(Room.Width, Room.Height);
// and assign it to the object we have in that room
oDrawingLayer.Graphic = CustomLayerSpr.Graphic;
// make sure object is aligned with room's 0;0 coordinates
oDrawingLayer.X = 0;
oDrawingLayer.Y = 0;

3. In the room's "Leave Room" event:
Code: ags

// safely dispose of our dynamic sprite
oDrawingLayer.Graphic = 0;
CustomLayerSpr.Delete();


4. Now, for the flying stone, declare some variables to keep its position (outside of any function, again):
Code: ags

bool IsStoneFlying; // this will tell if stone is currently flying and should be drawn
int StoneX; // stone's x coord
int StoneY; // stone's y coord
int StoneGraphic; // stone's sprite index (could be common sprite, or dynamic, does not matter)

5. At the point where the stone should be launched:
Code: ags

// set up initial stone position
IsStoneFlying = true;
StoneX = 100;
StoneY = 280;
StoneGraphic = XXX; // insert the actual number of sprite you are using for the stone
// draw the stone first time
DrawingSurface* surface = CustomLayerSpr.GetDrawingSurface();
surface.DrawImage(StoneX, StoneY, StoneGraphic);
surface.Release();

6. In the room's "Repeatedly execute" event:
Code: ags

// only draw stone if it's flying, naturally;
if (IsStoneFlying)
{
    // Note that all time we will be drawing upon the CustomLayerSpr dynamic sprite we created above.
    // Since we assigned that dynamic sprite to the Room object, anything we draw will appear upon that object automatically.

    // Get the dynamic sprite's drawing surface, to draw upon
    DrawingSurface* surface = CustomLayerSpr.GetDrawingSurface();

    // Get the stone sprite's width and height (just for script readability)
    int stone_width = Game.SpriteWidth[StoneGraphic];
    int stone_height = Game.SpriteHeight[StoneGraphic];
    // Now, erase the old position of the stone
    surface.DrawingColor = COLOR_TRANSPARENT;
    surface.DrawRectangle(StoneX, StoneY, StoneX + stone_width, StoneY + stone_height);
    // Move the stone a bit
    StoneX = StoneX + 1; // you should use your own formulae for this, here it is just for example
    // Check if the stone has reached the destination
    if (StoneX == 300)
    {
        // We are finished
        IsStoneFlying = false; // we do not draw stone anymore
    }
    else
    {
        // Not yet, so draw the stone on a new place
        surface.DrawImage(StoneX, StoneY, StoneGraphic);
    }
    surface.Release();
}



This is generally how it may work.


//------------------------------------------------------------------------------

If you need multiple objects drawn at same time, create arrays instead of single variables to track stone's coordinates, e.g.:
Code: ags

// Determine how many stones you may have flying at the same time
#define MAX_STONES 20

bool IsStoneFlying[MAX_STONES]; // this will tell if stone is currently flying and should be drawn
int StoneX[MAX_STONES]; // stone's x coord
int StoneY[MAX_STONES]; // stone's y coord
int StoneGraphic[MAX_STONES]; // stone's sprite index (could be common sprite, or dynamic, does not matter)


And do that drawing in a loop:
Code: ags

int stone_index = 0;
while (stone_index < MAX_STONES)
{
    if (IsStoneFlying[stone_index])
    {
         <..  same code as above ....>
    }
    stone_index++;
}


The way to find a "free" stone when you have to launch a new one:
Code: ags

int FindFreeStone()
{
    int stone_index = 0;
    while (stone_index < MAX_STONES)
    {
        if (!IsStoneFlying[stone_index])
        {
            return stone_index;
        }
        stone_index++;
    }
    return -1;
}

Then launching new stone will be like:
Code: ags

int free_stone = FindFreeStone();
if (free_stone < 0)
    return; // no free stone slot!
// set up initial stone position
IsStoneFlying[free_stone] = true;
StoneX[free_stone] = 100;
StoneY[free_stone] = 280;
<.... etc ....>

AnasAbdin

Man this is really cool ;-D
I learned so much from this post about dynamic sprites more than anywhere else. I used your example without arrays (test one stone) and it worked like a charm. I tried switching into arrays for more stones, I'm not sure what I've done wrong but the game got heavy. I will find a workaround, I just wanted to post here asap to thank you!

I tried another approach for the problem, by creating 4 objects for each stone. The character animations for picking up and throwing the stone determines the time between each 2 stones. I found out that four stones are enough if the player kept throwing. I guess I'll save your example for near future cases where I need more than 10 objects (nod)

Again I can't tell you how excited I am about your help and explanation :)

SMF spam blocked by CleanTalk