DrawImage on DrawingSurface not deleting

Started by Demicrusaius, Fri 01/03/2013 12:16:10

Previous topic - Next topic

Demicrusaius

I'm having a hard time figuring out how to get this to work. It seems really simply, but isn't cooperating.
I'm trying to put an image on screen and display it until the player clicks a button. Everything works except I can't get the image to go away until I leave the room. The DrawingSurface.Release doesn't do anything and I tried using DynamicSprites instead and using DynamicSprite.Delete but they never leave until you leave the room. If you save it, the image will be there when you return.
I have a feeling that it has something to do with room.Getsurfaceforbackground writing the image as part of the background, but I can't get it to clear anywhere.

Code: AGS
DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();
 surface.DrawImage(75, 85, 216, 0, 215, 130);
 surface.Release();
It is better to light one small candle than to curse the darkness.

Crimson Wizard

#1
You are doing it wrong.
1. Drawing on room background does not go away until player leave the room and come back. This operation actually changes the background image, it does not create a separate graphic.
2. DrawingSurface.Release function does not delete the image, it simply signifies that image will no longer be drawn upon. "Drawing surface" object itself is not the image, it may be thought of as a "tool" for drawing on image. When you release it, the "tool" is deleted, but image stay.

For temporary images on screen use Overlays instead.
Code: ags

Overlay *o = Overlay.CreateGraphical(x,y, sprite_id);
WaitMouseKey (some_time);
o.Remove();

That creates an image on top of everything (except GUI, afaik).
If you need something above room background, but below characters, use Room Object and turn its visibility on/off.

Khris

You can restore the original background like this:

Code: ags
  DrawingSurface* surface = Room.GetDrawingSurfaceForBackground();
  DrawingSurface* backup = surface.CreateCopy();
  surface.DrawImage(75, 85, 216, 0, 215, 130);
  WaitMouseKey(3600 * GetGameSpeed());  // one hour passes, keypress, click
  surface.DrawSurface(backup);
  backup.Release();
  surface.Release();


Although I'd probably use a GUI to display an image, unless the image is supposed to appear behind Objects and Characters.

Demicrusaius

I tried using the backup surface restore, but the restore needs to be handled by a separate function. You push one button, it displays the image, you push another, it disappears. When I try to restore the backup surface, I get the undefined token error (though it was defined in the function before it)
I took out the Room.Drawing surface and am using dynamicsprite.getdrawing surface now, but it still behaves the same.
I tried using overlay's too, but the problem there is that (as far as I know) it can't be resized or pull a picture from a file. I'm using DynamicSprite* Picture=DynamicSprite.CreateFromFile("Thispicture.pcx") and it displays but won't erase. Is there a better way to create a sprite from a file that doesn't write it to the background?

Code: AGS
function bLoad_OnClick(GUIControl *control, MouseButton button)
{
  if (Game.GetSaveSlotDescription(1)!=null)
  {
DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile("Slot1Save.pcx");
 if (Slot1Screen==null)
 {
 DrawingSurface *surface = Slot1Screen.GetDrawingSurface();
 surface.DrawImage(75, 85, 216, 0, 215, 130);
 surface.Release();
 }
     if (Slot1Screen!=null)
     {
     DrawingSurface *surface = Slot1Screen.GetDrawingSurface();
     surface.DrawImage(75, 85, Slot1Screen.Graphic, 0, 215, 130);
     surface.Release();
     }
  }
  else 
  {
    DynamicSprite* Slot1Screen=DynamicSprite.CreateFromExistingSprite(215);
    DrawingSurface *surface = Slot1Screen.GetDrawingSurface();
    surface.DrawImage(75, 85, 215, 0, 215, 130);
    surface.Release();
  }
player.Transparency=100;
gOptionsMenu.Visible=false;
gLoad.Visible=true;
gMainMenu.Visible=false;
}

function bSaveSlot1_OnClick(GUIControl *control, MouseButton button)
{
player.Transparency=0;
gSave.Visible=false;
gMainMenu.Visible=true;
//I want to erase the pictures here. I tried redefining them and then erasing, but no visible change.
SaveScreenShot("Slot1Save.pcx");
SaveGameSlot(1, "Save.");
}
It is better to light one small candle than to curse the darkness.

Crimson Wizard

#4
Quote from: Demicrusaius on Fri 01/03/2013 20:42:21
I tried using the backup surface restore, but the restore needs to be handled by a separate function. You push one button, it displays the image, you push another, it disappears. When I try to restore the backup surface, I get the undefined token error (though it was defined in the function before it)
Do you mean, you declare DynamicSprite in one function, then trying to use it in another? If that's the case, this certainly won't work, because variables, that are declared in a function will exist only until function ends.
Also, because you declare DynamicSprite *Slot1Screen inside function, it will be destroyed as soon as function ends, and you will loose the image.
You need to use global variable (declared outside of any function).

I explained variable scope once before, maybe this will be useful:
http://www.adventuregamestudio.co.uk/forums/index.php?topic=47664.msg636446596#msg636446596

Quote from: Demicrusaius on Fri 01/03/2013 20:42:21
I tried using overlay's too, but the problem there is that (as far as I know) it can't be resized or pull a picture from a file.
This is done in two steps: first, you create a new graphic (using DynamicSprite), then assign that new image to the Overlay:
Code: ags

DynamicSprite* Picture=DynamicSprite.CreateFromFile("Thispicture.pcx");
Overlay *o = Overlay.CreateGraphical(x,y, Picture.Graphic); // Here we assign our dynamic sprite's ID to the overlay.

Demicrusaius

So I need to import the dynamic sprite instances or just the overlay or both? I can't get any of them to work in the .ash because it says it can't assign a value to initial variable.
Code: AGS
import DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile(215);
It is better to light one small candle than to curse the darkness.

Crimson Wizard

#6
Quote from: Demicrusaius on Fri 01/03/2013 21:20:13
So I need to import the dynamic sprite instances or just the overlay or both? I can't get any of them to work in the .ash because it says it can't assign a value to initial variable.
Code: AGS
import DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile(215);


First of all, you cannot do anything with the value when you declare a variable. Same, when you declare an import.
Second, if I understand correctly what are you trying to do, than you don't need to separate functions or global variables at all.

There were two solutions mentioned:
1. Using overlay.
2. Saving and restoring part of background.

1. If you use Overlay to display an image, loaded from file, until player presses a key, use this code in appropriate function:
Code: ags

function DisplayMyPic() // Well, use the function you want
{
   DynamicSprite* Picture=DynamicSprite.CreateFromFile("Thispicture.pcx"); // create a dynamic sprite from file
   Overlay *o = Overlay.CreateGraphical(75,85, Picture.Graphic); // Here we assign our dynamic sprite's ID to the overlay.
   WaitMouseKey (3600 * GetGameSpeed()); // Here we wait until time passes, or player presses any key (or clicks mouse)
   o.Remove(); // remove overlay
   Picture.Delete(); // delete the image
} 


2. If you are using save/restore part of background method (quoting Khris's code):
Code: ags

function DisplayMyPic()
{
  DrawingSurface* surface = Room.GetDrawingSurfaceForBackground();
  DrawingSurface* backup = surface.CreateCopy();
  DynamicSprite* Picture=DynamicSprite.CreateFromFile("Thispicture.pcx"); // create a dynamic sprite from file
  surface.DrawImage(75, 85, Picture.Graphic); // draw sprite on room background
  Picture.Delete(); // delete sprite, because we do not need it anymore; the room background will remain changed!
  WaitMouseKey(3600 * GetGameSpeed());  // one hour passes, keypress, click
  surface.DrawSurface(backup); // restore room background
  backup.Release(); // freeing surfaces
  surface.Release();
}


As you may notice, both variants use only one function, and do not need global variables.

Crimson Wizard

#7
I apologize, I seem to actually misunderstand what you were saying ( I am probably half-asleep  :-[ ).
You said:
Quote
You push one button, it displays the image, you push another, it disappears.

In this case you do need a global variable. But you still do not need import/export.
The variable should be declared at the beginning of the script, outside of any function:
Code: ags

DynamicSprite* Slot1Screen; // Notice that you do not assign its value here yet

Then you just use Slot1Screen further in script (in any function), without redeclaring it (without writing "DynamicSprite *Slot1Screen").


However, to be honest, I am still getting confused by what are you trying to do.
For example, you wrote:
Code: ags

 DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile("Slot1Save.pcx");
 if (Slot1Screen==null)
 {
 DrawingSurface *surface = Slot1Screen.GetDrawingSurface();
 surface.DrawImage(75, 85, 216, 0, 215, 130);
 surface.Release();
 }

Here, you check if Slot1Screen was not loaded (== null), but then use it to get DrawingSurface. This will cause game to crash, because you can't do anything with an object, which is null.

Demicrusaius

#8
That's perfect! Thank you. My real problem was that I was trying to reference things outside of the functions they were declared in, plus my not importing them correctly. It works perfectly using the background method of
Code: AGS
DrawingSurface* surface; DrawingSurface* backup;
in the heading and then simply typing surface = whatever in each function (Not having autocomplete threw me off, it didn't look like it had imported) followed by
Code: AGS
surface.DrawSurface(backup); surface.release(); backup.Release();
  in a different function to restore it.

Though, I'm uncertain as to why the Drawingsurface.Release(); and dynamicsprite.Delete(); are necessary. The picture leaves before they load. If there is only one instance of whatever sprite and it's only loaded temporarily while they are in that room, does it really affect memory? My current method doesn't even use dynamicsprite.Delete(); but I think I should stick it in with the Drawingsurface.Release's anyway, just to be safe, but that would mean I'd have to import it in the heading as well.

I'm feeling encouraged now, so I'm going to keep playing and see if I can get it to work as an overlay so that I don't have to transparent the player or worry about objects covering the picture. But so far, when I try to create an overlay and use my dynamicsprite as the int slot AGS says it can't turn dynmicsprite into an int.

Quote from: Crimson Wizard on Fri 01/03/2013 22:17:13
However, to be honest, I am still getting confused by what are you trying to do.
For example, you wrote:
Code: ags

 DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile("Slot1Save.pcx");
 if (Slot1Screen==null)
 {
 DrawingSurface *surface = Slot1Screen.GetDrawingSurface();
 surface.DrawImage(75, 85, 216, 0, 215, 130);
 surface.Release();
 }

Here, you check if Slot1Screen was not loaded (== null), but then use it to get DrawingSurface. This will cause game to crash, because you can't do anything with an object, which is null.

I was trying so many different things that it did get left in there, but once I got it working I switched it to this:
Code: AGS

DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile("Slot1Save.pcx");
 if (Slot1Screen==null)
 {
 surface = Room.GetDrawingSurfaceForBackground();
 backup = surface.CreateCopy();
 surface.DrawImage(75, 85, 216, 0, 215, 130); //This pulls from an error message sprite instead of the .pcx file
 }


I apologize, that code that I posted was totally wrong, it became a mess as I was trying new things I didn't fully understand. I'm a trial and error learner. Thanks for being so helpful!
It is better to light one small candle than to curse the darkness.

Khris

You don't have to import anything unless you want to access the same variable in the global script AND room scripts.

If you need it either only in the global script or only in a specific room, you won't need the import/export business.

Also, it looks like you have declared the two DrawingSurfaces in the header, which you shouldn't do at all because it creates multiple separate variables, not a single, global one.

Assuming that the two buttons used for showing and hiding the image are both GUI buttons and thus handled in the global script:

Code: ags
// in GlobalScript.asC, anywhere above the functions that are using them
DrawingSurface* surface; DrawingSurface* backup;

// inside the button functions

  surface = ...


This is all it takes.

------------------

If on the other hand, you need to access a variable in the global script and room scripts:

Code: ags
// header

import DrawingSurface* surface;

// global script

DrawingSurface* surface;
export surface;


You need to release a DrawingSurface after using it, otherwise the changes aren't copied into the video memory. All kinds of weird stuff can happen.
Deleting a DynamicSprite isn't really that important, but you'll get a log with warnings.

Demicrusaius

Yes, I put them in the .asc file outside of a function, but above the functions that use them. This will only create one instance, right? And is it ok that the drawing surfaces don't get released until the player pushes a GUI button later? This could mean that a couple other functions will run before releasing the drawing surfaces and potentially even trying to create multiples. I can't release the backup drawing surface until it's restored, so it could sit for a while. So it won't get written into the video memory until it's no longer needed, which does make me suspicious.
It is better to light one small candle than to curse the darkness.

Demicrusaius

Forgive the double post, but I have a new and similar problem.
I tried using Overlay instead of drawing on background and got this to work beautifully. It reads the image off the disk, resizes it, display it properly and when the player presses the next button, it removes itself. However, the weird thing is I'm not telling it to leave. Just before it does go away, I told it to take a screenshot (updating the file on the disk) and this works, however it updates the screenshot with the old screenshot overlay still on it.

Code: AGS
// main global script file
Overlay* SaveOverlay1;

//Save and Load Functions, this all works
function bSave_OnClick(GUIControl *control, MouseButton button)
{
  if (Game.GetSaveSlotDescription(1)!=null)
  {
DynamicSprite* Slot1Screen=DynamicSprite.CreateFromFile("Slot1Save.pcx");
 if (Slot1Screen==null)
 {
 SaveOverlay1.CreateGraphical(75, 85, 216, true);
 }
     if (Slot1Screen!=null)
     {
     Slot1Screen.Resize(220, 130);
     SaveOverlay1.CreateGraphical(75, 85, Slot1Screen.Graphic, true);
     Slot1Screen.Delete();
     }
  }
  else 
  {
    SaveOverlay1.CreateGraphical(75, 85, 215, true);
  }
gOptionsMenu.Visible=false;
gSave.Visible=true;
gMainMenu.Visible=false;
}

//This function needs to remove the overlay and take a screenshot
function bSaveSlot1_OnClick(GUIControl *control, MouseButton button)
{
gSave.Visible=false;
gMainMenu.Visible=true;
//I want the Overlay to delete here *Deletion Point*
Wait(1);
SaveScreenShot("Slot1Save.pcx");
SaveGameSlot(1, "Save.");
gMainMenu.Clickable = true;
}


Though nothing is currently happening in the *Deletion Point* I have tried using SaveOverlay1.CreateGraphical(0, 0, 0, true); but that does nothing and when I use SaveOverlay1.Remove(); it crashes with a null pointer reference even if I stack them like this:
Code: AGS
SaveOverlay1.CreateGraphical(75, 85, 215, true);
Wait(5);
SaveOverlay1.Remove();
which is crazy because I define it, view it, and then it tells me it doesn't exist. Even Display("Overlay valid before: %d", SaveOverlay1.Valid); causes it to crash with a null pointer reference. Yet, if the script finishes, when it gets back to the top, "main global script file Overlay* SaveOverlay1;" seems to overwrite the image and it disappears. So, it works but not correctly. I can't seem to manually remove the overlay. Maybe I'm defining it at the top of .asc wrong?
It is better to light one small candle than to curse the darkness.

Khris

This is a bit of a problem with AGS actually being too lenient here.

.CreateGraphical() is a static function that returns the Overlay (actually: a pointer to it).
Thus, the proper way to use the command is
Code: ags
SaveOverlay1 = Overlay.CreateGraphical(75, 85, 216, true);

(Which makes sense, because the way you did it, when the command is called, SaveOverlay1 doesn't exist yet; unfortunately, the function gets rerouted to Overlay. in that case. Thus: no compile error, but trouble later on with the pointer being null.)

Now, removing it will work, because SaveOverlay1 is no longer null.

The thing is:
a) the correct usage is shown in example code in the command's manual entry. (right here)
b) the auto-complete window could've also been used to recognize this error, since typing SaveOverlay1. will only show the non-static functions Remove and SetText. Typing Overlay. on the other hands shows only the two static Create functions.

I'd also recommend indenting your code properly. Makes it much more readable and us a lot more eager to help you :D

Demicrusaius

#13
But then it's the same problem as before. Where do I initially define the Overlay? I need to use it in several different functions.
Also, when I type Slot1Screen. all five options pop up in the auto-complete window.
It is better to light one small candle than to curse the darkness.

Khris

#14
If all the functions are in the same script:

Code: ags
// top of script
Overlay* SaveOverlay1;

// display overlay
  SaveOverlay1 = Overlay.CreateGraphical(...);

// remove overlay
  if (SaveOverlay1 != null && SaveOverlay1.Valid) SaveOverlay1.Remove();


I told you to replace SaveOverlay1.CreateGraphical(75, 85, 216, true); with SaveOverlay1 = Overlay.CreateGraphical(75, 85, 216, true);

How does that cause "the same problem as before"? Just replace the line in your code.

According to your code, Slot1Screen is a DynamicSprite, not an Overlay; thus you'll get completely different suggestions from the autocomplete window.

Demicrusaius

Ahh! I thought you were saying that I was defining it incorrectly by placing it at the top of the script. I thought that was creating multiple instances until it eventually reset, but it was actually me using slot1screen.creategraphic over and over.
It makes perfect sense the way you just described it, because like a variable, you are changing it's state by saying what it equals. So it becomes Slot1screen(which has already been defined) = overlay.creatgraphical. This replaces the one instance perfectly. It stays on screen until I manually tell it to leave. Perfect!
I've gone from using GUI buttons, to backgroundgraphic drawing to overlays, but it finally works. Thank both of you so much! I'll cook you pasta any day.
It is better to light one small candle than to curse the darkness.

Khris

You're creating multiple pointers/variables if you put a declaration in the header file. "top of script" doesn't mean "header" or I would have used the word "header" like I did a few posts back, it just means: above everything else, at the very start of the main script.

If you have this
Code: ags
Overlay* SaveOverlay1;
in the header, move it back.

Also, you keep saying Slot1Screen. That's a DynamicSprite.
Regardless, here's how overlays work internally:

AGS keeps a private array of 20 overlay objects. Whenever you call Overlay.Create...(), AGS takes the first unused one, sets it up using the provided parameters and sets its .Valid property to true. Then the function returns that overlay, e.g. overlay[2]. So the Overlay* you set to the result of the function call is now pointing to overlay[2].
The internal drawing routine that composes the screen each frame iterates through the 20 overlays and draws each one that has .Valid == true.
Thus, by calling MyOverlayPointer.Remove(); what actually happens is: overlay[2].Valid = false;
This stops AGS from drawing the overlay and at the same time sets it as being available again.

SMF spam blocked by CleanTalk