Using Magnifier module in an unconventional way

Started by Akril15, Sun 24/05/2015 05:25:53

Previous topic - Next topic

Akril15

Hello,

It's been a while since anyone has replied to the Magnifier module's thread, so I figured it couldn't hurt to start my own.

I'm working on a game where occasionally, a small section of the background (and any objects in the vicinity) are visually distorted via a brief animation like heat waves or water ripples. For a while, I had no idea how to do this, but I realized that the Magnifier module could be modified to do something like this.

As it turns out, it does...sort of.

If I come up with a section of code like this (not complete!):
Code: ags

//magcounter is a variable that is being increased while this sequence is happening
        if (magcounter==60) Magnifier.Sprite=7505; //first frame of the animation
       if (magcounter==65) Magnifier.Sprite=7504;
       if (magcounter==70) Magnifier.Sprite=7503;
       if (magcounter==75) Magnifier.Sprite=7502;
       if (magcounter==80) Magnifier.Sprite=7501;
       if (magcounter==85) Magnifier.Sprite=7500;
       if (magcounter==90) Magnifier.Sprite=7499;
      if (magcounter==95) Magnifier.Sprite=1150; //last frame of the animation
      if (magcounter>=100)  {
        Magnifier.Enabled=false;  //turning off the magnifier also resets magcounter to 0
     //code for what happens after animation is over goes here...
      }

And enter it in the section of the script where I want the animation to play, it doesn't work (it stays completely transparent until it ends), but if I set up the script to trigger this code from repeatedly_execute, it does work. The problem is that calling it from repeatedly_execute is pretty inconvenient, especially if there's a lot of code that comes after the animation.

I've grappled with this problem numerous times as I've worked on my game, and no matter how I try to figure out what's going on, I'm always forced to give up. Now, I've finally decided to ask for help here.

Does anyone know I can solve this problem, or if there's a way of doing what I'm trying to do without using the module?

Ghost

Maybe you could use the UNDERWATER Module? It's specifically made for animated "ripple effects".

Akril15

I tried using the Underwater module, but it only seems capable of creating rippling effects that are square (I think walk-behinds were needed if a non-square shape was wanted?). I couldn't find any way of assigning a specific sprite to the rippling, and it all seemed much too complex for what I had in mind.

Monsieur OUXX

You addressed several points:

1) I'm surprised when you say that the magnififer module doesn't work with square areas, just like the underwater module and every ancient module. But if it works with custom shapes, then it's cool.

2) About the repeatedly_execute: maybe you don't see the effects of your script because that would require the display to be refreshed, and it's refreshed only at each game loop. No matter how much you change the X and Y variables of things, or how much you do GetSurface/ReleaseSurface on sprites, it all appears on screen after your custom script for this game loop ends, that is: when the game loop is finished, which gives back the control to the core engine. So, did you try putting a few Wait(1) here and there to see what happens?
 

Akril15

Quote from: Monsieur OUXX on Fri 05/06/2015 09:08:40
2) About the repeatedly_execute: maybe you don't see the effects of your script because that would require the display to be refreshed, and it's refreshed only at each game loop. No matter how much you change the X and Y variables of things, or how much you do GetSurface/ReleaseSurface on sprites, it all appears on screen after your custom script for this game loop ends, that is: when the game loop is finished, which gives back the control to the core engine. So, did you try putting a few Wait(1) here and there to see what happens?
I've tried placing Wait(1); in various points in the script. My non-rep_exec version of the script looks roughly like this:
Code: ags

while (magcounter<100) {
      if (magcounter==60) Magnifier.Sprite=7505; //first frame of the animation
       if (magcounter==65) Magnifier.Sprite=7504;
       if (magcounter==70) Magnifier.Sprite=7503;
       if (magcounter==75) Magnifier.Sprite=7502;
       if (magcounter==80) Magnifier.Sprite=7501;
       if (magcounter==85) Magnifier.Sprite=7500;
       if (magcounter==90) Magnifier.Sprite=7499;
      if (magcounter==95) Magnifier.Sprite=1150; //last frame of the animation
      if (magcounter>=100) Magnifier.Enabled=false;  //turning off the magnifier also resets magcounter to 0
        Wait(1);
      }
}

And as logical as it looks to me, it still doesn't work. I've tried placing Wait(1); at the beginning, both the end and the beginning, and after every individual "if" statement. The animation still doesn't play. It either doesn't show up at all or gets stuck on the first frame.

Crimson Wizard

#5
I would try to see how the module drawing works. Maybe the sprite is redrawn on some particular event. Like the call of repeatedly_execute... just a thought.

Akril15

The Magnifier script has a single line in its repeatedly_execute section that reads Magnifier.Update();, which seems to do just what you described. I tried adding this function at the end of the script in my last post (as well as the beginning and after each individual "if" statement), but unfortunately, nothing changed.

If it helps at all, here's the contents of the Module's .asc and .ash scripts:

.ash:
Spoiler
Code: ags
/*******************************************\

              AGS SCRIPT MODULE
                  MAGNIFIER
               by monkey_05_06

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

 Description:
 
   The Magnifier module implements a "magnifying glass" style effect into your game
   to scale a specific section of the screen.

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

 Dependencies:

  AGS v3.1.2+

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

 Macros (#define-s):

  Magnifier_VERSION
    Defines the current version of the module, formatted as a float.

  Magnifier_VERSION_100
    Defines version 1.0 of the module.

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

 Properties:

GUI* Magnifier.AGSGUI

  Gets/sets the AGS GUI used to display the magnifier effect. The GUI should not
  have any controls. The background will be set to reflect the magnified image of
  whatever is displayed behind it. This must be set before the effect may be
  enabled.

bool Magnifier.Enabled

  Gets/sets whether the magnifier effect is enabled, turning on Magnifier.AGSGUI,
  which shows the magnified image of whatever the mouse is over.

bool Magnifier.HideMouseCursor

  Gets/sets whether the mouse cursor should be hidden while the effect is enabled.

writeprotected int Magnifier.X

  Displays the X co-ordinate the magnifier effect is currently centering. This will
  be updated to reflect mouse.x and indicates the real X co-ordinate of the point
  displayed in the center of the effect.

int Magnifier.XOffset

  Gets/sets the X co-ordinate offset to position the effect as desired. This does
  not affect Magnifier.X, but rather just displaces the Magnifier.AGSGUI.

writeprotected int Magnifier.Y

  Displays the Y co-ordinate the magnifier effect is currently centering. This will
  be updated to reflect mouse.y and indicates the real Y co-ordinate of the point
  displayed in the center of the effect.

int Magnifier.YOffset

  Gets/sets the Y co-ordinate offset to position the effect as desired. This does
  not affect Magnifier.Y, but rather just displaces the Magnifier.AGSGUI.

int Magnifier.Sprite

  Gets/sets the sprite slot to use as the "magnifying glass" that will be displayed
  over the magnified background image. Supports alpha channeled images so you may
  have, for example, a slight blue tint over the magnified area. Note that at this
  time anti-aliasing on the outer edge of the "frame" will not work properly, you
  should only have any alpha information in the "lens" area.

float Magnifier.ScaleFactor

  Gets/sets the scaling factor for the magnifier effect. That is, a value of 2.0
  will display a portion of the background image the same size as Magnifier.Sprite
  centered around (Magnifier.X, Magnifier.Y) scaled to 200% or twice its normal
  size. Higher values are more system intensive so it is not recommended to set
  this value above 2.5. It is also possible to supply a value less than 1.0 to
  create a reversed effect, shrinking the displayed area instead of magnifying it.

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

 Example:

  // inside of game_start
  Magnifier.AGSGUI = gMagnifier; // set the GUI we'll be using to display the effect
  Magnifier.Sprite = 11; // set the sprite to use as the "magnifying glass" drawn on top of the magnified area
  Magnifier.HideMouseCursor = true; // we don't need the mouse getting in the way
  Magnifier.ScaleFactor = 2.0; // we'll upscale the area to 2x normal size
  Magnifier.XOffset = -26; // and we'll offset the effect by half the width/height of our sprite
  Magnifier.YOffset = -26; // so it is displayed directly over the area it's magnifying

  // on_key_press
  if (keycode == 'M') Magnifier.Enabled = !Magnifier.Enabled; // toggle the effect when we press M

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

 Changelog:

  Version:     1.0
  Author:      monkey_05_06
  Date:        April 2009
  Description: First public release.

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

 Licensing:

  Permission is hereby granted, free of charge, to any person obtaining a  copy  of
  this script module and associated documentation files (the "Module"), to deal  in
  the Module  without  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell copies  of  the
  Module, and to permit persons to whom the Module is furnished to do  so,  subject
  to the following conditions:

  The above copyright notice and this permission notice shall be  included  in  all
  copies or substantial portions of the Module.

  THE MODULE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF  MERCHANTABILITY,  FITNESS  FOR  A
  PARTICULAR PURPOSE  AND  NONINFRINGEMENT.  IN  NO  EVENT  SHALL  THE  AUTHORS  OR
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR  IN  CONNECTION
  WITH THE MODULE OR THE USE OR OTHER DEALINGS IN THE MODULE.

\*******************************************/

#ifdef AGS_SUPPORTS_IFVER
  #ifver 3.1.2
    #define MAGNIFIER_VERSION 1.0
    #define MAGNIFIER_VERSION_100
  #endif
#endif
#ifndef MAGNIFIER_VERSION
  #error Magnifier module error!: This module requires AGS v3.1.2 or higher! Please upgrade to a higher version of AGS to use this module.
#endif

struct MagnifierType {
  ///Magnifier module: Gets/sets the AGS GUI used by the magnifier effect.
  GUI *AGSGUI;
  ///Magnifier module: Gets/sets whether the magnifier effect is currently enabled.
  bool Enabled;
  ///Magnifier module: Gets/sets whether the mouse cursor should be hidden when the magnifier effect is enabled.
 // bool HideMouseCursor;
  ///Magnifier module: Gets the current X-coordinate for the magnifier effect.
  writeprotected int X;
  ///Magnifier module: Gets the current Y-coordinate for the magnifier effect.
  writeprotected int Y;
  ///Magnifier module: Gets/sets the X-coordinate offset for the magnifier effect.
  int XOffset;
  ///Magnifier module: Gets/sets the Y-coordinate offset for the magnifier effect.
  int YOffset;
  ///Magnifier module: Gets/sets the sprite slot to use as the "magnifying glass" for the magnifier effect.
  int Sprite;
  ///Magnifier module: Gets/sets the scaling factor for the magnifier effect.
  float ScaleFactor;
  ///Magnifier module: Gets the DynamicSprite used by our GUI.
  writeprotected DynamicSprite *BackgroundSprite;
  protected bool prevenabled;
  protected int prevmodesprite;
  protected bool prevhidemouse;
};

import MagnifierType Magnifier;
import void Update(this MagnifierType*);
[close]

.asc (I blocked out all sections related to making the magnifier follow the cursor around, since I want it to remain in one spot):
Spoiler
Code: ags
MagnifierType Magnifier;
export Magnifier;
bool CursorModeEnabled[];

bool IsModeEnabled(this Mouse*, CursorMode mode) {
  CursorMode prevmode = this.Mode;
  this.Mode = mode;
  bool enabled = (this.Mode == mode);
  this.Mode = prevmode;
  return enabled;
}

function game_start() {
  CursorModeEnabled = new bool[Game.MouseCursorCount];
//  Magnifier.HideMouseCursor = true;
  Magnifier.ScaleFactor = 2.0;
}

void ShowCursor(this MagnifierType*) {
  mouse.ChangeModeGraphic(mouse.Mode, this.prevmodesprite);
  int i = 0;
  while (i < Game.MouseCursorCount) {
    if ((i != mouse.Mode) && (CursorModeEnabled[i])) mouse.EnableMode(i);
    i++;
  }
}

void HideCursor(this MagnifierType*) {
  this.prevmodesprite = mouse.GetModeGraphic(mouse.Mode);
  mouse.ChangeModeGraphic(mouse.Mode, 0);
  int i = 0;
  while (i < Game.MouseCursorCount) {
    if (i != mouse.Mode) {
      CursorModeEnabled[i] = mouse.IsModeEnabled(i);
      mouse.DisableMode(i);
    }
    i++;
  }
}

void Update(this MagnifierType*) {
  if ((this.AGSGUI == null) || (this.Sprite <= 0)) {
    this.Enabled = false;
    return;
  }
  if (this.prevenabled != this.Enabled) { // toggle on/off
/*    if ((this.HideMouseCursor) || (this.prevhidemouse)) {
      if (this.Enabled) this.HideCursor();
      else this.ShowCursor();
    } 
  }  
  else if ((this.prevhidemouse != this.HideMouseCursor) && (this.Enabled)) {
    if (this.HideMouseCursor) this.HideCursor();
    else this.ShowCursor(); */
  } 
//  this.prevhidemouse = this.HideMouseCursor;
  if (this.ScaleFactor <= 0.0) this.ScaleFactor = 0.1;
  this.prevenabled = this.Enabled;
  this.AGSGUI.Visible = false;
  if (!this.Enabled) return;
  this.AGSGUI.BackgroundGraphic = 0;
  this.X = cEgo.x;
  this.Y = cEgo.y-80;
  // this.X = mouse.x;
 // this.Y = mouse.y;
  if ((this.X + this.XOffset) < 0) this.AGSGUI.X = 0;
  else if ((this.X + this.XOffset) >= System.ViewportWidth) this.AGSGUI.X = (System.ViewportWidth - 1);
  else this.AGSGUI.X = (this.X + this.XOffset);
  if ((this.Y + this.YOffset) < 0) this.AGSGUI.Y = 0;
  else if ((this.Y + this.YOffset) >= System.ViewportHeight) this.AGSGUI.Y = (System.ViewportHeight - 1);
  else this.AGSGUI.Y = (this.Y + this.YOffset);
  DynamicSprite *sprite = DynamicSprite.CreateFromExistingSprite(this.Sprite, false);
  int x = (FloatToInt(IntToFloat(this.X) * this.ScaleFactor) + this.XOffset);
  int y = (FloatToInt(IntToFloat(this.Y) * this.ScaleFactor) + this.YOffset);
  sprite.ChangeCanvasSize(FloatToInt(IntToFloat(System.ViewportWidth) * this.ScaleFactor), FloatToInt(IntToFloat(System.ViewportHeight) * this.ScaleFactor), x, y);
  this.BackgroundSprite = DynamicSprite.CreateFromBackground();
  DrawingSurface *surface = this.BackgroundSprite.GetDrawingSurface();
  int i = 0;

 while ((i < Game.CharacterCount) || (i < Room.ObjectCount)) {
    if (i < Game.CharacterCount) {
      if (character[i].Room == player.Room) {
      ViewFrame *frame = Game.GetViewFrame(player.View, player.Loop, player.Frame);
    if (player.Room==44) {
        int w = ((Game.SpriteWidth[frame.Graphic] * character[i].Scaling) / 100);
        int h = ((Game.SpriteHeight[frame.Graphic] * character[i].Scaling) / 100);
        surface.DrawImage(character[i].x - (w / 2), character[i].y - h, frame.Graphic, 0, w, h);
         }
       } 
    }
    
    if (i < Room.ObjectCount) {
      if (object[i].Visible) {
        int graphic = object[i].Graphic;
        if (object[i].View) {
          ViewFrame *frame = Game.GetViewFrame(object[i].View, object[i].Loop, object[i].Frame);
          graphic = frame.Graphic;
        }
        int w = Game.SpriteWidth[graphic];
        int h = Game.SpriteHeight[graphic];
        if (!object[i].IgnoreScaling) {
          int scale = GetScalingAt(object[i].X, object[i].Y);
          w = ((w * scale) / 100);
          h = ((h * scale) / 100);
        }
        surface.DrawImage(object[i].X, object[i].Y - Game.SpriteHeight[graphic], graphic);

      }
    }
    i++;
 }
  surface.Release();
  this.BackgroundSprite.Resize(FloatToInt(IntToFloat(this.BackgroundSprite.Width) * this.ScaleFactor), FloatToInt(IntToFloat(this.BackgroundSprite.Height) * this.ScaleFactor));
  this.BackgroundSprite.CopyTransparencyMask(sprite.Graphic);
  int w = Game.SpriteWidth[this.Sprite];
  int h = Game.SpriteHeight[this.Sprite];
  int ww = this.BackgroundSprite.Width;
  int hh = this.BackgroundSprite.Height;
  if ((ww > w) && (hh > h)) this.BackgroundSprite.Crop(x, y, w, h);
  else if (ww > w) {
    this.BackgroundSprite.Crop(x, 0, w, hh);
    if (hh < h) this.BackgroundSprite.ChangeCanvasSize(w, h, 0, (h - hh) / 2);
  }
  else if (hh > h) {
    this.BackgroundSprite.Crop(0, y, ww, h);
    if (ww < w) this.BackgroundSprite.ChangeCanvasSize(w, h, (w - ww) / 2, 0);
  }
  else this.BackgroundSprite.ChangeCanvasSize(w, h, (w - ww) / 2, (h - hh) / 2);
  if ((ww <= w) || (hh <= h)) {
    sprite = this.BackgroundSprite;
    this.BackgroundSprite = DynamicSprite.Create(w, h, false);
    surface = this.BackgroundSprite.GetDrawingSurface();
    surface.Clear(0);
    surface.DrawImage(0, 0, sprite.Graphic);
    surface.Release();
  }
  surface = this.BackgroundSprite.GetDrawingSurface();
  if (this.ScaleFactor < 1.0) {
    surface.DrawingColor = 0;
    int xm = FloatToInt(IntToFloat(System.ViewportWidth) * this.ScaleFactor);
    int xx = (x + w);
    if (x < 0) surface.DrawRectangle(0, 0, -x, surface.Height);
    if (xx >= xm) surface.DrawRectangle(xm - x, 0, xx - x, surface.Height);
    int ym = FloatToInt(IntToFloat(System.ViewportHeight) * this.ScaleFactor);
    int yy = (y + h);
    if (y < 0) surface.DrawRectangle(0, 0, surface.Width, -y);
    if (yy >= ym) surface.DrawRectangle(0, ym - y, surface.Width, yy - y);
    if ((x < 0) || (y < 0) || (xx >= xm) || (yy >= ym)) {
      surface.Release();
      sprite = DynamicSprite.CreateFromExistingSprite(this.Sprite, false);
      this.BackgroundSprite.CopyTransparencyMask(sprite.Graphic);
      surface = this.BackgroundSprite.GetDrawingSurface();
      
    }
  }
  surface.DrawImage(0, 0, this.Sprite);
  surface.Release();
  x = (this.X + this.XOffset);
  y = (this.Y + this.YOffset);
  int xx = (x + w);
  int yy = (y + h);
  if ((xx <= 0) || (yy <= 0) || (x >= System.ViewportWidth) || (y >= System.ViewportHeight)) {
    this.BackgroundSprite = null;
    this.AGSGUI.BackgroundGraphic = 0;
    this.AGSGUI.Width = 1;
    this.AGSGUI.Height = 1;
  }
  else {
    if ((x < 0) && (y < 0)) this.BackgroundSprite.Crop(-x, -y, this.BackgroundSprite.Width + x, this.BackgroundSprite.Height + y);
    else if (x < 0) this.BackgroundSprite.Crop(-x, 0, this.BackgroundSprite.Width + x, this.BackgroundSprite.Height);
    else if (y < 0) this.BackgroundSprite.Crop(0, -y, this.BackgroundSprite.Width, this.BackgroundSprite.Height + y);
    this.AGSGUI.BackgroundGraphic = this.BackgroundSprite.Graphic;
    this.AGSGUI.Width = this.BackgroundSprite.Width;
    this.AGSGUI.Height = this.BackgroundSprite.Height;
  }
  this.AGSGUI.Visible = true;

}

function repeatedly_execute() {
  Magnifier.Update();
}
[close]


Khris

The module is non-blocking; any change you make to Magnifier.Sprite has no effect until the screen is redrawn.

Everything depends on how and where you are calling the code you posted. Calling Wait(1) after changing the sprite should update the screen accordingly though.
The code you posted is an infinite loop with an additional closing brace at the end. Unless you show us the exact code you're using or upload the game somewhere, I don't think we can be of much help.

Crimson Wizard

#8
Quote from: Khris on Tue 23/06/2015 22:29:04
The module is non-blocking; any change you make to Magnifier.Sprite has no effect until the screen is redrawn.

Everything depends on how and where you are calling the code you posted. Calling Wait(1) after changing the sprite should update the screen accordingly though.
Khris, Magnifier updates Sprite in repeatedly_execute, which is not same as when screen is redrawn.
repeatedly_execute function does not run when you call "Wait". You would need repeatedly_execute_always() for this.


E: Frankly I am worried about Magnifier recreating dynamic sprite at every update rep-exec call. Unless I am mistaken. I think it could be optimized to remember last sprite and not redraw if it did not change.

Akril15

I took another look at my script and realized I was making it more complex than it needed to be. I've stripped it down to only its essential parts:
Code: ags

gMagnifier.Transparency=0;
  gMagnifier.X=208;
 gMagnifier.Y=295;
   Magnifier.XOffset=-35;
   Magnifier.YOffset=0;
Wait(1);
Magnifier.Sprite=7505; //first frame of the animation
 Wait(1);
 Magnifier.Sprite=7504; 
 Wait(1);
 Magnifier.Sprite=7503;
  Wait(1);
  Magnifier.Enabled=false;
  
      Display("test");


This is supposed to be activated automatically during a cutscene, activating the magnifier and making its sprite change repeatedly for a few seconds to create an animation. The word "test" appears just fine, but the frames of the animation never appear. I've tried creating a repeatedly_execute_always() function in the Magnifier's asc file and moving Magnifier_Update() into it, but it still doesn't work. I may export and upload one room of this game exhibiting this problem if no one can make heads or tails of this.

monkey0506

Quote from: Crimson Wizard on Wed 24/06/2015 00:07:36Frankly I am worried about Magnifier recreating dynamic sprite at every update rep-exec call. Unless I am mistaken. I think it could be optimized to remember last sprite and not redraw if it did not change.

Possibly, but that would assume that nothing on the screen had changed, which isn't something the module can really make such a strong guarantee about. Also, I'm not sure that it would affect this usage anyway.

I wrote the Magnifier module some time ago (and am short on time at the moment), but if you try to read back the Magnifier.Sprite, maybe displaying it with a Display command each time it changes, is it being properly updated in the structure? Do you see the initial sprite, or any of the frames?

Akril15

A surprisingly fortuitous turn of events: I compiled a single-room game to display this problem, but when I tested this game, the animation ran perfectly fine.

I did a lot of comparisons between this game and my game, and eventually discovered that the problem was due to two issues:
1) The game's GUIs being set to be hidden when disabled (something which I should have suspected much earlier)
2) The module's Magnifier.Update() function being called from repeatedly_execute and not repeatedly_execute_always

I think this problem can finally be put to rest. Thanks for your help, everyone!

Monsieur OUXX

 

SMF spam blocked by CleanTalk