Is there a way to animate GUI background image [SOLVED]

Started by iamlowlikeyou, Thu 11/11/2010 00:56:45

Previous topic - Next topic

iamlowlikeyou

...Possibly by assigning a VIEW as the background image in some way?

I have a GUI with a background image and some invisible buttons -
when I move the mouse over the buttons, I want the background image to animate once and end on the last frame of the animation, until mouse moves away.
Any ideas how to do this, if it isn't possible to animate the background image?

monkey0506

#1
Check out the GUIAnimation module.

With it you can then do, for example:

Code: ags
// animate the GUI in loop 1 of the VMYGUI view with animation (frame) delay of 5, once and non-blocking..
gMygui.LockView(VMYGUI);
gMygui.Animate(1, 5, eOnce, eNoBlock);

// release the GUI but don't restore the previous background graphic
gMygui.UnlockView(false);


Edit: Modified the example here to better fit your description.

Note that if you're only running the animation once it will stop automatically, but you should still call UnlockView just to clear the other information stored by the module.

iamlowlikeyou

#2
Thanks!
I'll take a look at that right away :)

edit:

Sorry if I'm a dummy, but what do I do with the scm file?

edit:

Sorry again, I figured it out now :D

iamlowlikeyou

However, I have a problem;

Code: ags

GUIControl*button=bLook.GetAtScreenXY(mouse.x,mouse.y);
  if (button==bLook) {
    gFunction.LockView(VEYE);
    gFunction.Animate(1, 5, eOnce, eNoBlock);
  }


As long as the mouse is still on the button, the view stays at frame 1, but when I move the mouse away, it finishes the animation.
Is there a way to make it run through the animation and stay on the last frame, until the mouse leaves the button?
I just realised, that this might not be related to the animation script, but rather me making a flaw in the button script, but perhaps someone can spot the flaw?

Khris

It's supposed to be
Code: ags
  GUIControl*button=GUIControl.GetAtScreenXY(mouse.x,mouse.y);


Regardless, what happens is the animation is started 40 times per second, i.e. it never gets the chance to actually animate the GUI.
This should work:

Code: ags
  if (button==bLook && !gFunction.IsAnimating()) {

monkey0506

Where did you put the code? Non-blocking animations are updated from repeatedly_execute_always so they should be functional pretty much anywhere..

Also, I just want to mention that you should probably be more careful how you're retrieving that button. The pointer you have named button is actually of the type GUIControl. If you ever wrote (or tried to use) a custom function that expected a Button* and tried to pass a GUIControl*, you would get a run-time error that you can't cast from type GUIControl* to Button*.

You can cast from type Button* to its base type GUIControl*, which is why "button==bLook" doesn't give you any problems. They are both valid GUIControls.

In this particular instance it therefore isn't particularly impacting, but I'd recommed you try to wean yourself of this potentially misleading scripting habit by instead doing something such as:

Code: ags
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  if (control == bLook) {
    gFunction.LockView(VEYE);
    gFunction.Animate(1, 5, eOnce, eNoBlock);
  }


Or possibly even consider taking it a step further and doing:

Code: ags
  if ((control != null) && (control.AsButton != null) && (control.AsButton == bLook)) {


Which is the safest route overall.

Edit: Good call Khris on realizing he probably put this in repeatedly_execute.

iamlowlikeyou

#6
Thanks a lot for the replies!
I don't fully understand all the lines written, so I'll have to study it some more, but now I know where to start.

And yes, I put it in repeatedly execute.

edit:

However, when I simply copy the new code into my repeatedly execute, it still doesn't finish the animation until the mouse leaves the button again. As long as mouse is over button, the animation stays at frame 1.
Any idea?

monkey0506

The module internally uses a boolean value to track whether the GUIs are currently being animated or not, which is set to true immediately when GUI.Animate is called (provided a valid view has already been assigned).

So when you say you copied the "new code" were you referring to Khris' code, or mine? The code snippet I provided would not fix the issue, it was simply a matter of proper programming. Khris was the one who identified that you were calling the Animate function from within repeatedly_execute, which is why he said to ensure that the GUI was not animating prior to calling the function. What, exactly, does your code look like right now? It should look something similar to:

Code: ags
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  if ((control == bLook) && (!gFunction.IsAnimating())) {
    gFunction.LockView(VEYE);
    gFunction.Animate(1, 5, eOnce, eNoBlock);
  }

iamlowlikeyou

@monkey_05_06:
Sorry for not being accurate enough in my wording.
I have now cleaned up my code, and copied the exact code, you wrote above.

The function now is: when mouse moves over button, the gui background animation starts - if mouse stays over button, the gui background animation keeps repeating - when mouse moves away from button, the gui background animation stops and stays at the last frame. So far so good...

Is it possible to make it like this:
when mouse moves over button and stays over button, the gui background animation plays once and stays at the last frame -
when mouse moves away from button, the gui background switches back to its default graphic.

That's the function I'm looking for, and I'd be very grateful if you have an idea how to make it?

monkey0506

Okay, you're now describing a very different scenario than in your last post, but I understand why it's doing that. You're going to need a couple other things..

Code: ags
// GlobalScript.asc
// put this at the top of the script
GUIControl *lastControl; // last GUIControl the mouse was over
bool animatedGUI = false; // whether the GUI has been animated since this control was moved over

// repeatedly_execute
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    animatedGUI = false; // reset this..
  }
  lastControl = control; // store this..
  if ((control == bLook) && (!animatedGUI)) {
    gFunction.LockView(VEYE);
    gFunction.Animate(1, 5, eOnce, eNoBlock);
    animatedGUI = true;
  }


We're now tracking which was the last GUIControl the mouse was over which allows us to make sure we're not calling UnlockView unnecessarily. We are also using a new variable to track whether the GUI has been animated since the mouse moved over this control as opposed to simply whether or not the GUI is animating. This gives us better control to ensure that Animate is in fact only called once.

iamlowlikeyou

#10
@monkey_05_06:
This is what I've been after all the time, but sorry for not describing it properly.

I have now copied your code into my script, but at the moment it's too complex for me to understand fully.
However now it works like a charm!

Thank you for your help and patience :)

edit:
I figure it would be possible without too much trouble, to make it so that when mouse moves away from the button again, the gui plays an animation from another view once before it unlocks?

monkey0506

You're welcome, and I'm glad to hear it's working. As for your new question, you can modify the code such as:

Code: ags
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    gFunction.LockView(VRESTORE);
    gFunction.Animate(1, 5, eOnce, eNoBlock);
    animatedGUI = false; // reset this..
  }
  if ((gFunction.GetView() == VRESTORE) && (!gFunction.IsAnimating())) gFunction.UnlockView();
  // ..rest of the previous code..


Just use your second view's name in place of VRESTORE. It's important to call UnlockView before calling the second LockView though because otherwise the module will store the wrong graphic as the normal background graphic.

iamlowlikeyou

Okay, I didn't say this before, because it wasn't relevant to get it working that far. But now it is;
I have one GUI with 3 different buttons.
Each button makes the gui display a seperate view, when mouse moves over. So as long as it just unlocks the view when mouse moves away from the buttons, it's of course working with the previous code.
But is it also possible to have them run 3 different views when mouse moves away from each seperate button?

You can see my code so far here...
Code: ags

//top of main script:
  GUIControl *lastControl; // last GUIControl the mouse was over
  bool animatedGUI = false; // whether the GUI has been animated since this control was moved over

//repeatedly execute:
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    gFunction.LockView(VEYERESTORE);
    gFunction.Animate(0, 5, eOnce, eNoBlock);
    animatedGUI = false; // reset this..
  }
  if ((gFunction.GetView() == VEYERESTORE) && (!gFunction.IsAnimating())){
    gFunction.UnlockView();
  }
  
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    animatedGUI = false; // reset this..
  }
  lastControl = control; // store this..
  if ((control == bLook) && (!animatedGUI)) {
    gFunction.LockView(VEYE);
    gFunction.Animate(0, 1, eOnce, eNoBlock);
    animatedGUI = true;
  }
  if ((control == bMouth) && (!animatedGUI)) {
    gFunction.LockView(VMOUTH);
    gFunction.Animate(0, 1, eOnce, eNoBlock);
    animatedGUI = true;
  }
  if ((control == bHand) && (!animatedGUI)) {
    gFunction.LockView(VHAND);
    gFunction.Animate(0, 1, eOnce, eNoBlock);
    animatedGUI = true;
  }


If you can point out any flaws or "bad coding", it would be much appreciated -
as well as perhaps come up with an idea for how to do what I described above?

monkey0506

Code: ags
//top of main script:
  GUIControl *lastControl; // last GUIControl the mouse was over
  bool animatedGUI = false; // whether the GUI has been animated since this control was moved over

//repeatedly execute:
  int view = gFunction.GetView();
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    if (view == VEYE) gFunction.LockView(VEYERESTORE);
    else if (view == VMOUTH) gFunction.LockView(VMOUTHRESTORE);
    else if (view == VHAND) gFunction.LockView(VHANDRESTORE);
    else view = 0;
    if (view) gFunction.Animate(0, 5, eOnce, eNoBlock);
    animatedGUI = false; // reset this..
  }
  lastControl = control; // store this..
  view = gFunction.GetView(); // update this in case it's changed
  if (((view == VEYERESTORE) || (view == VMOUTHRESTORE) || (view == VHANDRESTORE)) && (!gFunction.IsAnimating())){
    gFunction.UnlockView();
  }
  if (!animatedGUI) {
    animatedGUI = true;
    if (control == bLook) gFunction.LockView(VEYE);
    else if (control == bMouth) gFunction.LockView(VMOUTH);
    else if (control == bHand) gFunction.LockView(VHAND);
    else animatedGUI = false;
    if (animatedGUI) gFunction.Animate(0, 1, eOnce, eNoBlock);
  }


The changes I made were for the most part not anything that you necessarily did wrong but just make it a bit less repetetive and a slight bit of optimization. You had duplicated the checks to see if the GUI needed to be restored, so I removed the second copy. You were also doing a lot of sequential if statements, but only expecting one of them at most to ever be true. For that it's somewhat faster for the engine if you use else ifs. Also, you were checking animatedGUI every time you were checking the control. In that case it's faster to just nest the conditions (as I did above where you check animatedGUI first and then in the inner condition block check the control). Beyond that I just shifted things around to prevent duplicate code.

Finally, I added in checks to see which view we were restoring from so that we'd know which view to animate the GUI with for the restoration.

Don't be too put off if this stuff doesn't make sense to you yet though..scripting just takes time to learn. The most important thing is just try to understand the logic of what you're trying to do. Then once you learn how to speak to AGS effectively, the rest is gravy. ;)

iamlowlikeyou

Thanks again!
I have now implemented this code, and it works like it should -
but... (always the but) it seems that if the mouse moves too quickly from button to button, it gets stuck with one of the views. I'm not sure, but perhaps it happens when one of the restore views has not been allowed to finish before a new view kicks in. However I don't understand the code enough to be able to identify to cause.
Does this make any sense to you, and is there any way around it?

Apart from that, it's really working great  ;D

monkey0506

LockView is actually internally calling UnlockView(false) although retrospectively it would probably make a lot more sense to have it call UnlockView(true)..add this line inside the if (!animatedGUI) { block:

Code: ags
    if (view) gFunction.UnlockView();



iamlowlikeyou

If I put this line where you said, it doesn't perform the restore views...

And another flaw I just discovered - if mouse moves very quickly from button to button the gui normal background disappears. This might have something to do with the "view = 0" part?

Jeez, this whole thing turned out to be much more of an obstacle, than I first imagined :D

monkey0506

Change if (!animatedGUI) { to else if (!animatedGUI) {.. ::)

Might help if I tested any of this..

Should now look like:

Code: ags
//top of main script:
  GUIControl *lastControl; // last GUIControl the mouse was over
  bool animatedGUI = false; // whether the GUI has been animated since this control was moved over

//repeatedly execute:
  int view = gFunction.GetView();
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    if (view == VEYE) gFunction.LockView(VEYERESTORE);
    else if (view == VMOUTH) gFunction.LockView(VMOUTHRESTORE);
    else if (view == VHAND) gFunction.LockView(VHANDRESTORE);
    else view = 0;
    if (view) gFunction.Animate(0, 5, eOnce, eNoBlock);
    animatedGUI = false; // reset this..
  }
  lastControl = control; // store this..
  view = gFunction.GetView(); // update this in case it's changed
  if (((view == VEYERESTORE) || (view == VMOUTHRESTORE) || (view == VHANDRESTORE)) && (!gFunction.IsAnimating())){
    gFunction.UnlockView();
  }
  else if (!animatedGUI) {
    if (view) gFunction.UnlockView();
    animatedGUI = true;
    if (control == bLook) gFunction.LockView(VEYE);
    else if (control == bMouth) gFunction.LockView(VMOUTH);
    else if (control == bHand) gFunction.LockView(VHAND);
    else animatedGUI = false;
    if (animatedGUI) gFunction.Animate(0, 1, eOnce, eNoBlock);
  }

iamlowlikeyou

I have now copied the code above and replaced everything to be sure...

Okay, so now it doesn't make any of the flaws I mentioned before, but it still doesn't perform the restore views - it simply unlocks the views when mouse moves away from the buttons.

As I said, I can't grasp the code completely, but in the "else if (!animatedGUI)" block doesn't the "if (view) gFunction.UnlockView();" override the restore views?

monkey0506

Urgh..you're right! Haha..Okay, this should work:

Code: ags
//top of main script:
  GUIControl *lastControl; // last GUIControl the mouse was over
  bool animatedGUI = false; // whether the GUI has been animated since this control was moved over

//repeatedly execute:
  int view = gFunction.GetView();
  GUIControl *control = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
  if ((control != lastControl) && (animatedGUI)) {
    gFunction.UnlockView(); // mouse moved off previous control, restore normal GUI background
    if (view == VEYE) gFunction.LockView(VEYERESTORE);
    else if (view == VMOUTH) gFunction.LockView(VMOUTHRESTORE);
    else if (view == VHAND) gFunction.LockView(VHANDRESTORE);
    else view = 0;
    if (view) gFunction.Animate(0, 5, eOnce, eNoBlock);
    animatedGUI = false; // reset this..
  }
  lastControl = control; // store this..
  view = gFunction.GetView(); // update this in case it's changed
  if ((view == VEYERESTORE) || (view == VMOUTHRESTORE) || (view == VHANDRESTORE)) {
    if (!gFunction.IsAnimating()) gFunction.UnlockView();
  }
  else if (!animatedGUI) {
    if (view) gFunction.UnlockView();
    animatedGUI = true;
    if (control == bLook) gFunction.LockView(VEYE);
    else if (control == bMouth) gFunction.LockView(VMOUTH);
    else if (control == bHand) gFunction.LockView(VHAND);
    else animatedGUI = false;
    if (animatedGUI) gFunction.Animate(0, 1, eOnce, eNoBlock);
  }


The condition regarding the restore views was dependent on the GUI not animating and when it was animating the animatedGUI variable was false..like I said..testing the code would have helped immensely. Sometimes something that is easy to recognize in the runtime environment is a lot harder to recognize on a computer which doesn't have AGS installed! D'oh!

SMF spam blocked by CleanTalk