"Force" a screen update to show changes to Object.Graphic or Object.Visible

Started by fernewelten, Mon 21/05/2018 03:51:19

Previous topic - Next topic

fernewelten

Hello all,

I'm in the middle of a cutscene and I need to open a cupboard door. I usually handle this by having two sprites for the object - one for the open door, one for the closed door - and assigning the right sprite to <object>.Graphic. When the respective event ends, the screen is redrawn, and the new state of the cupboard (open/closed) shows.

This doesn't work in the cutscene, though, because just assigning to <object>.Graphic does NOT by itself make the screen redraw; and the cutscene needs to carry on, so relinquishing control by letting the event code end isn't a good option either.

To explain more clearly, here's some code:
Code: ags

// Clicking the bucket with the mouse shall start the cutscene
function oBucket_Interact()
{
    ...
    player.Say("Let's put away the blue cup now!");
    player.Walk(5,5, eBlock); // to the cupboard
    oCupboard.Graphic = OPENDOORGRAPHIC;
    // Screen needs to update here!
    Wait(40);
    oBlueCupInCupboard.Visible = true;
    // Screen needs to update here!
    Wait(40);
    oCupboard.Graphic = CLOSEDDOORGRAPHIC;
    player.Walk(77,77, eBlock);
    ...



So how do I force a screen update? Using FadeOut(); FadeIn() works, but this is drastic if I have to do it all the time.

eri0o

I think that should work, pretty sure I do that, but you could also create a view, assign to the object and animate forward or backwards.

I know in rare cases that a character doesn't update I solve by grabbing it's surface and drawing a transparent pixel in a transparent place and release the surface.

ManicMatt

That's weird, I didn't even know about this graphic command and I've made 2 AGS games!

Like eri0o says though, assign a view and put your animations in the loops.

So you'll use this code, changing the numbers to what your view and loops etc should be:

Code: ags

oCupboard.SetView(10);
oCupboard.Animate(0, 2, eOnce, eBlock) ;


You might only need to set the view, but im not sure if it will use loop 0 by default, I can't remember.

Also, you'll quickly realise that without any if statements, like a variable, if the player interacts with the bucket again, those actions will repeat themselves. Not sure if you're aware of that.

Crimson Wizard

Quote from: fernewelten on Mon 21/05/2018 03:51:19
This doesn't work in the cutscene, though, because just assigning to <object>.Graphic does NOT by itself make the screen redraw; and the cutscene needs to carry on, so relinquishing control by letting the event code end isn't a good option either.
Quote from: eri0o on Mon 21/05/2018 04:00:37
I think that should work, pretty sure I do that

Yes, I also think that should work, calling "Wait" is supposed to do exactly that - it gives control back to the engine.
Are you saying it does not work in your case? This might be some weird bug, or maybe there are some unusual circumstances.

Is this block of code inside a StartCutscene/EndCutscene pair?
Are OPENDOORGRAPHIC and CLOSEDDOORGRAPHIC static (regular) sprites, or dynamic sprites?
Double check that oCupboard is visible and you are not looking at the part of the background?




Quote from: eri0o on Mon 21/05/2018 04:00:37
I know in rare cases that a character doesn't update I solve by grabbing it's surface and drawing a transparent pixel in a transparent place and release the surface.

I don't do much games, so met this situation only once when changing the sprite of the ViewFrame to dynamic sprite. I did similar thing, ordering to draw a pixel outside of the bitmap:
Code: ags

// HACK: poke AGS to refresh the character cache
DrawingSurface *ds = this.dSprite.GetDrawingSurface();
ds.DrawPixel(-1, -1);
ds.Release();

But that was a very specific case, because nor the Graphic neither View property of the character was changed, but the frame of the View, which happened to be current character's frame.

fernewelten

Quote from: ManicMatt on Mon 21/05/2018 08:13:00
That's weird, I didn't even know about this graphic command and I've made 2 AGS games!

So how do you handle doors? The game I made was shock full of them (a kitchen with lots of drawers, cupboards and so on), so I automated them: I gave objects a "State" property that coded whether they were open, then wrote:
Code: ags

String STATE_PROP = "State";
void ToggleDoorObject(Object *o, int graphic0, int x0, int y0, 
                                 int graphic1, int x1, int y1)
{
    // flip "State" property of object
    bool cur_state = o.GetProperty(STATE_PROP); 
    bool new_state = (!cur_state);
    o.SetProperty(STATE_PROP, new_state);
    
    // set position and graphics according to the new state
    if(new_state) {
        o.Graphic = graphic1;
        o.SetPosition(x1, y1);
        return;
    }
    o.Graphic = graphic0;
    o.SetPosition(x0, y0);
}    


I determined the correct positions of the open and closed sprites in the room editor by choosing the corresponding sprite and moving it in place, then noting the StartX and StartY coordinates. Then, I had
Code: ags

void ToggleDrawer1() {
    ToggleDoorObject(oDrawer1, 156, 77 23, // coo. copied from room editor
                               157, 60,11);
}
void ToggleDrawer2() { ... }
void ToggleDrawer3() { ... }
...
void ToggleBottomCabinetDoor1() { ... }
...

Cassiebsg

Uhm... you animate the object like ManicMatt explained...

create a view with the closed door as the first frame, then add the next few frames untill you have the sprite with the open door (or vise-versa). I usualy don't have the last sprite with door open, and just turn of the object visibility to false (and the BG has the view with the open door)...

Then you just animate the object as needed... And you can check if the door is open or close by checking if the object is visible or not (no need to use a bool).
There are those who believe that life here began out there...

Crimson Wizard

Quote from: fernewelten on Mon 21/05/2018 10:50:26
So how do you handle doors? The game I made was shock full of them (a kitchen with lots of drawers, cupboards and so on), so I automated them: I gave objects a "State" property that coded whether they were open, then wrote

ManicMatt does not know Graphic property probably because he was not around for a while, as he mentioned before.

I see people keep suggesting using animated views, but the way you do it is supposed to work. This is perfectly correct method.
There is something else that makes it not look like you want, some mistake or oversight.
(or a bug in the engine, idk)

Snarky

If there's no animation and you only have the open frame and closed frame, creating and animating a View seems like overkill. fernewelten's solution should work in principle, so there's either a bug somewhere in the script, or in AGS.

(Personally I'd probably have the two sprites aligned within a larger canvas, so that you wouldn't need to reposition the object when the door opens/closes, but that's just a detail.)

Edit: Crimson Wizard got in ahead of me.

eri0o

1- you talk about TWO objects and repositioning, but your code only has ONE object.

2- instead of creating a variable to track state, just read back .Graphic property of the object and see if it's the open or closed graphic.

Snarky

Quote from: eri0o on Mon 21/05/2018 11:21:54
1- you talk about TWO objects and repositioning, but your code only has ONE object.

No, man, you misunderstand. It's one object but two sprites (and two sets of coordinates). One or the other is used depending on the object state.

Quote from: eri0o on Mon 21/05/2018 11:21:54
2- instead of creating a variable to track state, just read back .Graphic property of the object and see if it's the open or closed graphic.

But then fernewelten would need to store the sprite ID of the open and/or closed graphic instead. Which maybe wouldn't be a bad idea (along with aligning the sprites, it would completely remove the need for separate functions for each door), but is to the side of the problem at hand.

ManicMatt

Woah, that state property looks complicated and unnecessary to me, but Crimson would know better than me if that is the case. But I cannot help but get the feeling you're making more work for you than you need to, as impressive as that coding is!

Your first script you shared, there's ... at the start and end, does that mean there is more code that we haven't seen within that function that is related to the code?

As for your question, I just use global variables, with an int or bool depending on what it is, like, telling the game if my character is at the top of some stairs or the bottom when the player uses them.

Code: ags

function hStairs_Interact()
{
if (IsEgoAtTopOfExteriorStairs==true){
  cEgo.Walk(325, 222, eBlock, eAnywhere);
  cEgo.ChangeView(27);
  cEgo.Animate(0, 4, eRepeat, eNoBlock);
  cEgo.Walk(321, 319, eBlock, eAnywhere);
  cEgo.ChangeView(1);
  IsEgoAtTopOfExteriorStairs=false;
  }
else if (IsEgoAtTopOfExteriorStairs==false){ 
  cEgo.Walk(323, 323, eBlock, eWalkableAreas);
  cEgo.ChangeView(26);
  cEgo.Animate(0, 4, eRepeat, eNoBlock);
   cEgo.Walk(299, 225, eBlock, eAnywhere);
   cEgo.ChangeView(1);
  IsEgoAtTopOfExteriorStairs=true;
  }
}


Crimson will probably tell me that's a weird method or something lol, but it appears to work! I don't know how this would work with this graphics thing I've first heard about on this thread lol. Where does AGS even know where these sprites are, I'm confused and the manual seems to mention it only briefly with one example of code that doesn't look like yours!

Crimson Wizard

Quote from: ManicMatt on Mon 21/05/2018 11:49:33
Crimson will probably tell me that's a weird method or something
No, this is not a weird method, but it does not seem to be related to the objects or Graphic property?

Anyway there could be many different ways to achieve same effect. The reason to have a script that fernewelten posted above, is to provide a generic solution that could be repeatedly used in many situations. It also allows to check the state of the door, which may be necessary for some game logic.

Personally I would also use current door's sprite as an indication of whether door is closed or opened, but then again, having a custom property for that allows you to change graphical implementation while keeping same logic working.

The difference between using a script variable and custom property is that custom property is automatically bound to an object, while you have to manage variables yourself and make sure you use correct ones for particular object.


Quote from: ManicMatt on Mon 21/05/2018 11:49:33
I don't know how this would work with this graphics thing I've first heard about on this thread lol. Where does AGS even know where these sprites are
Object remembers whether it was told to have a single graphic (by setting Object.Graphic) or a view (by calling Object.SetView / Animate).
Iirc Object.Graphic always returns current object graphic. If it was set explicitly it will return what it was set, if it has a View, it returns the current sprite number from a View frame.

fernewelten

Quote from: Crimson Wizard on Mon 21/05/2018 10:08:22
Are you saying it does not work in your case? This might be some weird bug, or maybe there are some unusual circumstances.

You're right in suspecting that the situation is more complicated as described, I simplified somewhat. In my own view, what I came up with is messy, but I don't see a better way:

So, the user combines two objects, and the “cutscene” begins. I start off in <object>_UseInv().

A letter arrives (I use <object>.Visible = true), and cEgo walks to the letter (player.Walk(...eBlock)) and picks it up. Then, the letter is shown in big in a GUI gLetter ("Pause game when shown") for the user to read. Here's a snag: This means that my control of the thread is gone at this point, I must let the user do his thing and click the X when they are done reading and decide to click it.


So how do I pick up the thread again? What I came up with is the following: I've defined a global integer variable gvHook, and I set this to an integer (it happens to be 1) before calling gLetter.Visible = true;. In the event for closing the window, I do:

Code: ags

function btnLetter_Close_OnClick(GUIControl *control, MouseButton button)
{
    gLetter.Visible = false;
    if (gvHook > 0) {
        CallRoomScript(gvHook);
    }
}


So here I am back in business, but now in the on_call() function of my room script.
Code: ags

function on_call(int the_hook)
{
    gv_Hook = 0; // clear to prevent unwanted multiple calls
    switch(the_hook) {
    case 1: 
        Hook1();
        break;
    
    case 2: 
        Hook2();
        break;
        
    case 3:
        Hook3();
        break;
    ...
    }
}


The next chunk of my "cutscene" is coded in Hook1(), and it will run until that same GUI must be called up again and show something similar, but different. But now, I've prepared my way: At that point, I set "gvHook = 2; gLetter.Visible = true" and can be assured that I will regain control of the thread in Hook2() after the user has clicked the x later on.

Basically, I'm repurposing the RoomCall() mechanism as a way of storing a "callback function" that will be called whenever a GUI ends. The on_call function is a poor man's replacement for function pointers. I can use this same mechanism for all guis, when I need it, as long as I allocate different numbers for different pieces of code (namely 4 for Hook4(); 5 for Hook5() and so on) and include the code snippet given above in the close event of each gui.

The glitch happened in such a Hook() function, i.e., indirectly in the on_call() function of a room.

I've just tried it out after a short and fretful night's sleep, and today, without having changed anything but a reboot and AGS startup, the code started to work. So I'm not sure whether this is just a one-time compilation glitch or whether this is linked to certain actions or tests that I performed yesterday.

I'll report when I know more.

Thanks for the suggestion, also to eri0o: The drawing surface idea a handy trick for forcing a refresh to keep in mind.

ManicMatt

Crimson - Well, it was just an example as they asked me, it would be the same principle with a door object using a bool. But yes I could do the sprite number too, I did that with a rotating puzzle thinking about it,

Oh okay I thought they'd be typing all that out for each object!


Okay, I think this graphic thing is beyond me for the time being. I seem to be doing okay with my scripted events recently.


fernewelten

Quote from: eri0o on Mon 21/05/2018 04:00:37
I think that should work, pretty sure I do that, but you could also create a view, assign to the object and animate forward or backwards.

Good idea; this is what I'll probably do when I animate the doors "properly", that is, have them close slowly with a nice whup sound in the end, or open slowly squeakily. For the moment, though, I just have one "door open" and one "door closed" frame for each door. I find it time consuming enough to animate the characters in front, side, back view, talking and not talking - so this will have priority right now. For the moment, I can't do more for the doors than showing them either open or closed in a binary fashion.


fernewelten

Quote from: ManicMatt on Mon 21/05/2018 13:41:53
Okay, I think this graphic thing is beyond me for the time being. I seem to be doing okay with my scripted events recently.


There are many ways leading to Rome - if you've found one that works, then use it. :) I might do something similar myself if there were only some few doors that I need to handle, or if all the different doors need special logic anyway so I can't reuse generalized code.

Cassiebsg

Oh I thought you talking about "how to animate the doors open/close", not just switch sprites.
I'm no expert in this, but think you still over complicating things.
First if you are planning on animating later on, then just write the code for that. And start by aligning and making them same size (or at least have some origin) the open/close door sprites. Also, like I said, if you assume that all your "open doors" are part of the BG (and thus you draw all rooms with the door open, then you just need to turn on/off the close door sprite. No need for such complicated code.

Also, you can still use animated view, code it as shown above, put only the sprite of the close door in it. Then later on, if you decide to animate and add the sprites, all you need to do is open the view, add the sprites to the loop and then test to see if you satisfied with the animation or if you need to adjust the speed... but all the code is already done.

There are those who believe that life here began out there...

Crimson Wizard

Cassiebsg, with all due respect, but on contrary you are simplifying things, probably not realizing possible cases when simple solution won't work.

A sole example:
Quote from: Cassiebsg on Mon 21/05/2018 14:35:26
Also, like I said, if you assume that all your "open doors" are part of the BG (and thus you draw all rooms with the door open, then you just need to turn on/off the close door sprite. No need for such complicated code.

Imagine there is some other object that may be covered by opened doors. Or situation when a character that may walk behind ones. Then the solution to have opened doors as part of background won't work. If there is something behind the door, like hidden objects, then you won't be able to have closed doors as part of the background either.

This is just one of the reasons why for a large game project you would want to code a modular generic code, like fernewelten did.

Another possible reason to have distinct functions for changing doors, or other object states, is that when project becomes large, and especially if there are multiple people working on it, it becomes difficult to track what is going on, and what setting graphic to an object means in each particular case.

There may be even more reason, like, for instance, you would like to add particular event, like sound playing every time a door is closed or opened.
Having a function that explicitly is called "ToggleDoor" or similar will make things much more clear and safer. Indeed, at the cost of extra code.

Cassiebsg

There are those who believe that life here began out there...

Crimson Wizard

Quote from: Cassiebsg on Mon 21/05/2018 14:48:46
Okay then, I'll shut up now and let the experts talk. :-D

Uhh :/, that's not what I meant to happen, but I felt the need to explain why the complex code is written sometimes. In simple games you probably won't care, but in larger ones it really helps.

SMF spam blocked by CleanTalk