Making current state of a room to turn grayscale and freeze for showing credits

Started by Gal Shemesh, Fri 21/07/2023 14:50:08

Previous topic - Next topic

Gal Shemesh

Hi everyone,

Tried to look for this in the forum but can't seem to find that anyone has asked about it.

I would like to know if there's an opition in AGS to take the current state of a given room (meaning the exact frame of everything in it), and to make it gradually turn into a grayscale form of itself - like a cross-fade between 2 rooms?

I was thinking about 2 scenarios of using this:

1. When the player takes a close-up look on an inventory item, and then everything but the close-up item turns into a greyscale color, and so the player can be focued only on the colored image of the item; there's this "The Riddle of Master Lu" game that I really like which behaves like this when using/looking on inventory items. Here's a direct link for a specific time in a YouTube walkthrough video showing what I mean.

2. A colored credits rollup, showing on a froze frame of a room.

For the second scenario - I could just take a screenshot of the frame that I want in a given room and make a whole new room from it. And then to have a cross-fade between the rooms and have my colored credits rollup showing on top of it. But that's not an ideal way to achieveing this, as it will be only for that specific frame screenshot that I took. And if I decide to make some changes to the room I'll have to re-create that screenshot. And for the first scenario that will not work as I want this to happen dynamically in every place and state that the player is in.

Thanks
Gal Shemesh,
goldeng

Snarky

There is currently no good way to do this in AGS unless you:

(1) Use a plugin; or
(2) Work in 256 colors (8-bit color depth) — which lets you change colors via palette manipulation

In principle you could grab a screenshot of the whole screen, loop pixel-by-pixel across the whole image, read the RGB value of each pixel, calculate the corresponding grayscale value, and write that to a separate DrawingSurface (and then fade that in over the screen), but in practice this will be too slow, and also GetPixel and DrawPixel only work with 16-bit color and therefore don't give accurate results.

The other workaround would be to supply grayscale versions of every background and sprite in the game, in order to construct a grayscale version of the screen, and again fade that in on top of the real screen. This would be a lot of work and would nearly double the size of the game.

A problem with 256-color games (apart from how they restrict your graphics) is that many modern graphics drivers no longer reliably support 256-color mode, so many players may not be able to run your game.

Really, a plugin is your best option—that would also allow other, arbitrary graphical effects, such as blur. (Strangeland is one AGS game that uses a plugin for graphical effects.)

Gal Shemesh

Thanks @Snarky for the prompt reply. I really don't want to relay on 256 color palette game, nor to have every background in my game twice as it won't be pratical since even if I would go in that route, this will only be for the background while I'll still need to turn each and every object and character in the room to greyscale as well.

I thought that there might be a plugin for doing such thing, but alas I don't find any - this may be off-topic but how do plugins are written for AGS anyway? Maybe I could ask a programmer friend to write such plugin so everyone could enjoy.
Gal Shemesh,
goldeng

Snarky


Crimson Wizard

Perhaps the cheap version of this is to use AmbientTint or a grey GUI/overlay with varying level of translucency?
At least I'd try that first and see how that works.

Gal Shemesh

Gal Shemesh,
goldeng

Nahuel

Quote from: Crimson Wizard on Fri 21/07/2023 17:41:36Perhaps the cheap version of this is to use AmbientTint or a grey GUI/overlay with varying level of translucency?
At least I'd try that first and see how that works.

This solution requires a small detail, for objects, the prop UseRoomAreaLighting = true



And for the player it should be reset after the tinting if it will be required to be keep the colour.

Code: ags
  
SetBackgroundFrame(1); // sets the 1st frame of the background which was imported as greyscale
SetAmbientTint(192, 192, 192, 100, 80); // set tint grey for all objects and characters
cEgo.Tint(0,0,0,0,100); // resets the tint.

I think maybe the character in future rooms will require tinting and it's maybe not convenient to set UseRoomAreaLighting = false

This is a simple demo using a grey background as frame 1 and setting Animate background to false.



Life isn't a game. Let's develop a life-like-game.

Crimson Wizard

@Nahuel I don't remember, does AmbientTint not affect the room background?

If Tint command is acceptable in terms of visuals, then you could apply same effect to the background using DynamicSprite and Tint method. This will save on making extra variant of background for each room.

1. Create a single global DynamicSprite (you need only one, as only 1 room will be affected at a time).
2. For this effect: create or resize this dynamic sprite to the size of the room's viewport. (You could use room's size, but that will be redundant if the room is scrolling, and scrolling is paused during this effect,)
3. Get room bg's drawing surface, and paint the visible region on the dynamic sprite.
4. Do DynamicSprite.Tint(...).
5. Paint DynamicSprite's contents onto the room's BG.
6. Hmm.. I think you also need another dynamic sprite to store the original BG's portion and copy it back later.

Nahuel

Quote from: Crimson Wizard on Fri 21/07/2023 20:31:35@Nahuel I don't remember, does AmbientTint not affect the room background?

@Crimson Wizard As far as I know, the room is not affected. I've created a greyscale room and added to the frame 1, and set AnimateRoom: false

QuoteIf it works for the objects, in terms of visuals, then you could apply same effect to the background using DynamicSprite and Tint method. This will save on making extra variant of background for each room.

But it does affect objects and also characters (when using UseRoomAreaLighting: true) So it will be possible then to create a DynamicSprite and place it and then AreaTint that could work! I'll give it a try, I'm not that familiar with DynamicSprite but I will try some stuff and come back.  :smiley:
Life isn't a game. Let's develop a life-like-game.

Crimson Wizard

Quote from: Nahuel on Fri 21/07/2023 20:37:34But it does affect objects and also characters (when using UseRoomAreaLighting: true) So it will be possible then to create a DynamicSprite and place it and then AreaTint that could work! I'll give it a try, I'm not that familiar with DynamicSprite but I will try some stuff and come back.  :smiley:

You are probably talking about placing a dynamic sprite as an object or character, and make it tinted using ambient tint too?

I thought about it, but am concerned that this may conflict with walk-behinds, so you will have to re-adjust baselines everywhere.

It's safer to tint the dynamic sprite itself and paint on room background, please see the steps I added to my previous post.

Nahuel

@Crimson Wizard it worked!

Code: ags
DynamicSprite *RoomBefore;

function room_AfterFadeIn()
{
  RoomBefore = DynamicSprite.CreateFromBackground();
  RoomBefore.Tint(190, 190, 190, 100, 100);
  
  DrawingSurface *ds = Room.GetDrawingSurfaceForBackground();
  ds.DrawImage(0, 0, RoomBefore.Graphic);
  ds.Release();
  
  SetAmbientTint(190, 190, 190, 100, 100);
}



I'm only testing the room_AfterFadeIn without going back yet but mainly it's working  :-D

EDIT: Forgot SetAmbientTint
Life isn't a game. Let's develop a life-like-game.

Snarky

That's very neat, Nahuel, although it makes the tinted characters/objects perceptibly brighter, which might not look so good if they have been designed to blend into the background.

Now can you make it fade?

Nahuel

You are totally right @Snarky I was passing the Luminance up to 100

Before



Code: ags
SetAmbientTint(190, 190, 190, 100, 80);



I don't know how to fade it properly I will maybe use a timer and a counter for that. I was trying to get the Tween module to achieve fade, but I need to do it for each object and character unfortunately. There's no TweenAmbientTint equivalent.

I will try a few stuff but surely I will have a few questions.

Life isn't a game. Let's develop a life-like-game.

Crimson Wizard

This thread made me concerned about the Room missing a Tint method for its bgs. Having that would also make it much faster, as the Tint property is applied using shaders with Direct3D/OpenGL gfx driver, while tinting the dynamic sprite currently makes it change the pixels.

Nahuel

Quote from: Snarky on Fri 21/07/2023 21:44:18Now can you make it fade?

Tried fading with a timer (not perfect but meh) also the gif is skipping a few frames but...... here's the result:



I will try also tinting and adding a timer for the background when released also...
EDIT: Created an ugly attempt  :undecided:



EDIT 2: adding fade back:



EDIT 3: I'm thinking using https://edmundito.gitbook.io/ags-tween/bonus/tweeneasing

example from the doc

Code: ags
static float TweenEasing.GetAmount(float elapsed, float duration, TweenEasingType easingType)

// ....

float elapsed = 0;
float duration = 100.0;
while (elapsed < duration) {
    gIconbar.Transparency = FloatToInt(TweenEasing.GetAmount(elapsed, duration, eEaseInSineTween), eRoundNearest);
    elapsed += 1.0;
    Wait(1);
}
Life isn't a game. Let's develop a life-like-game.

Gal Shemesh

I'm so glad to see the interest of members in my query. Everything that was done so far is beyond my knowledge and understanding, but the results look fantastic!

As a noob in AGS (even though I'm getting progress), to me a built-in feature (or plugin) to make such thing would be best, but I'm not sure how tedious of development in the engine it requires, so I'm not even asking.

Meanwhile, in the game I'm working on I changed the auto FadeOutAndIn feature in the General Settings to Instant, so I could have complete control on when to play transitions when changing rooms and when not to. I tried to make a duplication of a room in greyscale color and wanted to make it gradually transition to it with a CrossFade, but I couldn't find any way to manually call specifically for the CrossFade transition from the script; there's only way to call to FadeIn() and FadeOut().

Anyway, the AGS Fake Screen + Box Blur by @Dualnames for making the room greyscale while other elements on-top of it could remain in color also looks great; I haven't tested it yet, so I'm not sure if it can gradually make the room to go greyscale like a cross-fade and without any blur.
Gal Shemesh,
goldeng

Gal Shemesh

Only now got to the point in my game that I could finally put this into use - using @Nahuel's code I was able to achieve this almost fantastic result! I just implemented this as a Void that I could call when clicking on varies things, such as buttons for opening menus, etc. I also coded the transition effect. It looks like this:

Code: ags
void grayscaleScreen()
{
  PauseGame();
  DynamicSprite* RoomBefore;
  RoomBefore = DynamicSprite.CreateFromBackground();
  RoomBefore.Tint(190, 190, 190, 100, 100);
  int dsTrans = 100;
  int sat = 0;
  int lum = 0;
  while (dsTrans > 0)
  {
    dsTrans = dsTrans - 5;
    sat = sat + 5;
    if (sat > 100)
    {
      sat = 100;
    }
    if (lum < 80)
    {
      lum = lum + 5;
    }
    if (lum > 80)
    {
      lum = 80;
    }
    DrawingSurface* ds = Room.GetDrawingSurfaceForBackground();
    ds.DrawImage(0, 0, RoomBefore.Graphic, dsTrans);
    ds.Release();
    SetAmbientTint(190, 190, 190, sat, lum);
    Wait(2);
  }
}

There are a few issues, though:

1. The player character's sprites are brighter than this in-door room. So I used 'player.LightLevel' to make him a little darker. But this caused him to be isolated from the AmbientTint effect altogether. I also tried using player.Tint to make him darker but this one isolates him as well.

2. Overlay speech text, such as the 'SayBackground speech' that is shown in the screenshot still remains in full color.



I wonder if there's a way to make this grayscale effect happen on a screenshot, that is taken from the room when triggered, which is then drawn on the screen the same way as in this method - if possible, that would probably make things much easier as non individual elements would need to be tinted, as everything will be merged down in the screenshot.

EDIT:
Ok, almost nailed it - I managed to make a screenshot which contains everything in the room in it and to make it grayscale - this allows to desregard needing to tint rooms and objects / character separately. I used 2 global variables of the DynamicSprite type for storing the room's state: one for tinting into a grayscale image and another for reverting back to full color - if this is the correct route; I'm not sure yet. Since my rooms are positioned 30 pixels lower on the screen, I had to make the screenshot drawn 30 pixels lower on the Y axis. The code looks like this:

Code: ags
void grayscaleFadeIn()
{
  PauseGame();  
  mouse.Visible = false;
  screenToGrayscale = DynamicSprite.CreateFromScreenShot();
  screenBeforeGrayscale = DynamicSprite.CreateFromScreenShot();
  screenToGrayscale.Tint(190, 190, 190, 100, 100);
  int screen = 100;
  while (screen > 0)
  {
    screen = screen - 5;
    DrawingSurface* ds = Room.GetDrawingSurfaceForBackground();
    ds.DrawImage(0, -30, screenToGrayscale.Graphic, screen);
    ds.Release();
    Wait(2);
  }
  mouse.Visible = true;
}
Now, a new issue arise which is that all of the room elements (characters and objects) remain showing on the screen, while the full grayscale screenshot is drawn at the back of them. Is there a way to draw the screenshot in-front?
Gal Shemesh,
goldeng

Gal Shemesh

Ok, it's done! 8-) making another comment instead of editing my previous one so anyone who gets here who's interested in this thing will be able to see the progress and differences.

With @Nahuel's great code above, I learned about the DynamicSprite type which I wasn't familiar with before, and the ability to draw it onto the screen and make 'tint' adjustments to it. Though, it affects the room's background separately and objects and characters separately - in some cases some will prefer / need this to work this way, especially when for example you wish to make a playable scene while changing the color tint.

However, in my scenario I actually wanted to 'freeze' the entire screen with everything that is currenly showing on it, to make it grayscale and to show GUIs / close-up inventory items sprites onto it. So I tried using a 'screenshot' method instead. But when passing a screenshot of the room into the global variable and drawing it on the screen using 'DrawingSurface* ds.DrawImage' the screenshot was actually dawn behind all objects and characters. So with the help of @Crimson Wizard who helped me on Discord to tune the code for drawing the screenshot using an 'overlay' instead, the magic finally happens!

I can't thank you enough you guys. Thank you so much for your kind and professional help! ;-D

Here's an animation GIF demonstating the final thing. I'm also using a custom inventory interaction (override built-in inventory window click handling is set to TRUE) so I show there how it works; on this bit I also wish to thank @Khris for the help to other member in this thread that I also learned from.

I'm attaching the grayscale effect code below for anyone who's interested (or if I ever forget  (laugh)):



The code - I made a dedicated script for holding this code which serves as a 'module', for separating it from the Global Script. I called the script "Grayscale Fade" and the following code is in its header:
Code: ags
// gUI is the GUI that holds the actions and inventory. I don't want it in the screenshot so I turn it off before taking it
// screenToGrayscale is a DynamicSprite* type global variable
// screenOverlay is an Overlay* type global variable

void grayscaleFadeIn()
{
  gUI.Visible = false; // hide the UI before taking the screenshot
  mouse.Visible = false; // hide the mouse cursor before taking the screenshot
  Wait(1); // allow enough time for the GUI and mouse to get hidden, or else they will be integrated into the screenshot
  PauseGame(); // pause the game before taking the screenshot
  screenToGrayscale = DynamicSprite.CreateFromScreenShot(); // save a DynamicSprite type screenshot of the screen into the 'screenToGrayscale' variable for making into grayscale
  screenToGrayscale.Tint(190, 190, 190, 100, 100); // set the grayscale level on the screenshot in 'screenToGrayscale'
  screenOverlay = Overlay.CreateGraphical(0, 0, screenToGrayscale.Graphic); // passing the screenshot from 'screenToGrayscale' into global overlay object type variable 'screenOverlay' for drawing it on the screen (on top everything)
  mouse.Visible = true; // un-hide mouse cursor
  int screenshot = 100; // set the initial transparancy value for the screenshot in a local variable named 'screenshot' (100 = fully transparent)
  while (screenshot > 0) // a 'while' loop for making the transition effect - checks if the 'screenshot' local variable's value is greater than 0 (not opaque / semi-transparent)
  {
    screenshot = screenshot - 5; // subtract transparancy increment from the local variable towards transparancy
    screenOverlay.Transparency = screenshot; // set the transparancy value of the overlay object type as the local variable's 'screenshot' current value
    Wait(2); // a short wait to see the result on screen before the 'while' loop repeats itself
  }
}

// call this void when willing to exit from the current grayscale pause and return to the game
void grayscaleFadeOut()
{
  int screenshot = 0;  // set the initial transparancy value for the screenshot in a local variable named 'screenshot' (0 = opaque)
  while (screenshot < 100) // a 'while' loop for making the transition effect - checks if the 'screenshot' local variable's value is lower than 100 (not fully transparent)
  {
    screenshot = screenshot + 5; // add transparancy increment from the local variable towards opaque
    screenOverlay.Transparency = screenshot; // set the transparancy value of the overlay object type as the local variable's 'screenshot' current value
    Wait(2); // a short wait to see the result on screen before the 'while' loop repeats itself
  }
  screenOverlay.Remove(); // after while loop is over, remove the overlay object from the screen
  gUI.Visible = true; // un-hide gUI
  UnPauseGame(); // unpause the game
}

This function can be called anywhere you like. For example, when clicking on a button that should reveal a settings panel GUI:
Code: ags
function btnSettings_OnClick(GUIControl *control, MouseButton button)
{
  grayscaleFadeIn();
  gSettings.Visible = true;
}

Or when close-examine (look at) of an inventory item - for this I first made a global 'int' variable that I pass the close-up graphic sprite index number into when looking on a certain inventory item. Then I call the grayscale function to play and when it's done I'm calling another function for actually drawing that sprite on the screen - that other function I placed on my dedicated UI script header, but you can place it in the Global Script header as well:
Code: ags
// replace 'x' and 'y' where you wish the sprite to be drawn on
void inventoryCloseUp()
{
  inventoryItemCloseUpOverlay = Overlay.CreateGraphical(x, y, inventoryItemCloseUpSprite); // passing the close-up sprite stored in 'inventoryItemCloseUpSprite' into global overlay object type 'inventoryItemCloseUpOverlay' for drawing it on the screen (on top everything)
}

And here's the 'Look at' inventory item code:
Code: ags
// replace 'x' with the close-up image sprite number
function iKey_Look()
{
  inventoryItemCloseUpSprite = x;
  grayscaleFadeIn();
  inventoryCloseUp();
}
Gal Shemesh,
goldeng

Gal Shemesh

And for anyone who wish to have an out-of-the-box module, I've just made and shared it here.
Several things were fixed in it and there's no need for global variables anymore. Just download, import and use. :)
Gal Shemesh,
goldeng

edmundito

I saw this post on Discord. You can also use the tween module to set the transparency of the overlay.

Instead of:
Code: ags
void grayscaleFadeOut()
{
  int screenshot = 0;  // set the initial transparancy value for the screenshot in a local variable named 'screenshot' (0 = opaque)
  while (screenshot < 100) // a 'while' loop for making the transition effect - checks if the 'screenshot' local variable's value is lower than 100 (not fully transparent)
  {
    screenshot = screenshot + 5; // add transparancy increment from the local variable towards opaque
    screenOverlay.Transparency = screenshot; // set the transparancy value of the overlay object type as the local variable's 'screenshot' current value
    Wait(2); // a short wait to see the result on screen before the 'while' loop repeats itself
  }
  screenOverlay.Remove(); // after while loop is over, remove the overlay object from the screen
  gUI.Visible = true; // un-hide gUI
  UnPauseGame(); // unpause the game
}

You can add the tween module and set the transparency:

Code: ags
void grayscaleFadeOut()
{
  screenOverlay.TweenFadeOut(1.0);
  screenOverlay.Remove();
  gUI.Visible = true;
  UnPauseGame();
}
The Tween Module now supports AGS 3.6.0!

SMF spam blocked by CleanTalk