Is there a function that tints the *entire room* (background)

Started by bx83, Sun 19/03/2017 01:49:27

Previous topic - Next topic

bx83

Hi

Been looking at character.Tint (tints a character), object.Tint (tints an object), and the game.SetAmbientTint (both characters and objects).

But is there a way to tint the *whole room background*? Even if it requires CreateDrawingSurfaceFromBackground, DrawImage, etc. that involve you drawing on to the background.

Or should I just make tinted versions of each background in Photoshop, then draw them on to the standard background?

Cassiebsg

I don't know if you can tint the BG, sorry.

But you can create a single one color sprite the size of your BG, and then draw it over the BG with a transparency setting.
There are those who believe that life here began out there...

Snarky

There are a couple of ways...

The easiest is to put a "color filter" layer over the whole screen (like Cassiebsg says): this will tint the background, characters and objects all together. Just create a full-screen (non-clickable) GUI, set its background to the color you want, and set its transparency to the right level.

The other is to copy the background onto a DynamicSprite, tint that, and then draw it back to the background:

Code: ags
  DynamicSprite* bgSprite = DynamicSprite.CreateFromBackground();
  bgSprite.Tint(r,g,b,s,l); // You'll have to supply these values;
  // Now copy it back to the background
  DrawingSurface* bgSurface = Room.GetDrawingSurfaceForBackground();
  bgSurface.DrawImage(0, 0, bgSprite.Graphic);
  // Clean up
  bgSurface.Release();
  bgSprite.Delete();


Finally, you can also create a tinted version in Photoshop and draw that to the background in the same way (using bgSurface.DrawImage()), or by having it as a separate room background and changing the background frame.

Monsieur OUXX

the better solution (without resorting to Photoshop) is the one using the "tint" setting in a dynamic sprite.
Because the so-called tint overlay will ruin the contrast (it's like you're painting some red (for example) on top of the scene, instead of turning colors into red -- do you get the difference?). It works, it's fast, but it's the second-best solution.

However that solution also requires you to set the tint setting on the characters, objects etc.
 

bx83

Hi, sorry to bring this up again, but I never was totally happy with it.
Naturally, I'm 90% the way there, but just can't finish this last part.

I have 2x background images - one tinted red (with a few scaray satanic details), the other just the normal background image. Both are identical except for the tinting of one.

I then have a small graphic, 240x240 (the background is 1024x768), which is a black circle with a white border. This acts as the 'spotlight' between the backgrounds - or, the 'candle light'. I have a candle which, when lit, see's through the background to another 'dimension', with objects in it, not normally accessable.

I also need transparency (of 90) on the tinted background to view this red tint. With transparency set to 0, I can't see the background (or hence, it's tint).
But with transparency set to 90 (as in normal), the spotlight leaves a trail in it's wake, on black backgrounded things. In other words, the transparency trail shows up obviously the darker the background.

I already have a function (called Candlelight_on) which shows images translucency depending on how close they are to the mouse x,y. This is fine - I can just have this.

The process of using backgrounds is too slow. Being repeated 40 times a second (either copy the background and paste it over itself again; or take a different backgruond, and paste itself over again) moves the game walking speed to about 1/2 or 1/4.

In room code:
Code: ags

function room_RepExec()
{

  if (CandleLit) {     //the inventory item 'candle' has been lit

   Candlelight_on(oCoin1, iCoin1W);                //set translucency of objects based on closeness to mouse/candle
   Candlelight_on(oGoldIngot, iGoldBrick);         //set translucency of objects based on closeness to mouse/candle
   Candlelight_on(oGoldPen, iGoldPen);             //set translucency of objects based on closeness to mouse/candle
    
   DynamicSprite *sprite = DynamicSprite.CreateFromExistingSprite(169, true);  //tinted background
   DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();            //standard background
      
   sprite.Crop(mouse.x-65, mouse.y-65, 240, 240);    //crop it

   sprite.CopyTransparencyMask(1957);                                //use the black circle in white background transparency mask - no other colours will work
   surface.DrawImage(mouse.x-65, mouse.y-65, sprite.Graphic, 20);    //copy to background
			
   surface.DrawImage(0, 0, 487, 90);            //tinted image - if I set it to (0,0,487,0), it no longer has a trail -- but also no longer is tinted
      
   surface.Release();
   sprite.Delete();
    
  } else {
    
   DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();        //get standard bg image
   surface.DrawImage(0, 0, 487, 0);                                        //copy it over itself
   surface.Release();
 
   oCoin1.Visible=false;            //set hidden objects to non-visible
   oGoldIngot.Visible=false;        //set hidden objects to non-visible
   oGoldPen.Visible=false;          //set hidden objects to non-visible

  }
}


in Global script code:
Code: ags
function Candlelight_on (Object *Obj, InventoryItem *Inv)
{
  if (!cJulius.HasInventory(Inv)) {

    Obj.Visible=true;

    if ((mouse.x - Obj.X) >= 0) {
      if ((mouse.y - Obj.Y) >=0) {
        trans = (( (mouse.x - Obj.X) + (mouse.y - Obj.Y) ) /2);
      } else
      if ((mouse.y - Obj.Y) <=0){
        trans = (( (mouse.x - Obj.X) - (mouse.y - Obj.Y) ) /2);
      }
    } else
    if ((mouse.x - Obj.X) <= 0){
      if ((mouse.y - Obj.Y) >= 0){
        trans = (( (mouse.x - Obj.X) - (mouse.y - Obj.Y) ) /2);
      } else
      if ((mouse.y - Obj.Y) <= 0){
        trans = (( (mouse.x - Obj.X) + (mouse.y - Obj.Y) ) /2);
      }
    }
  

    if (trans<0) {
      trans=trans*-1;
    }
    if (trans>100) {
      trans=100;
    }
    
    Obj.Transparency=trans;
  }
}




I read this following code, but didn't fully understand:
Quote from: Snarky on Sun 19/03/2017 08:50:57
The easiest is to put a "color filter" layer over the whole screen (like Cassiebsg says): this will tint the background, characters and objects all together. Just create a full-screen (non-clickable) GUI, set its background to the color you want, and set its transparency to the right level.

The other is to copy the background onto a DynamicSprite, tint that, and then draw it back to the background:

Code: ags
  DynamicSprite* bgSprite = DynamicSprite.CreateFromBackground();
  bgSprite.Tint(r,g,b,s,l); // You'll have to supply these values;
  // Now copy it back to the background
  DrawingSurface* bgSurface = Room.GetDrawingSurfaceForBackground();
  bgSurface.DrawImage(0, 0, bgSprite.Graphic);
  // Clean up
  bgSurface.Release();
  bgSprite.Delete();


The first (entire screen, GUI, tinted bg) seem's good, but would it not be as slow (or would it...? either way, I don't know, er, how to do it...)
The second one (tint the background as-you-go, which seems the be the one I want) just displays a transparent, non-tinted circle, on a 100% black background, and then the rest of the image isn't black.

A VIDEO OF THE ABOVE CODE, FOR ELUCIDATION

SO BASICALLY: all I'm looking for is how to draw a tinted circle, not tinted background. With only the circle tinted, all other details/objects can just be that; objects.

Snarky

I actually think the trail effect is kinda cool, but the fixes for these problems are pretty simple. I don't have time right now, but if no one else responds I'll have a crack at it later today.

bx83

Fantastic, you have no idea what this means - thankyou :)

Khris

Just re the candlelight code:
1. if the player picks up one of these items, then uses (and loses) it elsewhere, it will appear back in the room (unless that's what you want, of course)
2. distance in a 2D coordinate system:
Code: ags
  float dx = IntToFloat(mouse.x - Obj.X), dy = IntToFloat(mouse.y - Obj.Y);
  int dist = FloatToInt(Math.Sqrt(dx * dx + dy * dy), eRoundNearest);
  int trans = (dist <= 100) * dist + (dist > 100) * 100; // the multiplication casts the bools to 1 or 0

Snarky

Try this:

Code: ags
#define ROOM_LIGHT 169
#define HIGHLIGHTER 1957
#define HIGHLIGHT_TRANSPARENCY 50

#define OFFSET_X 65
#define OFFSET_Y 65

int oldMouseX;
int oldMouseY;
bool wasOn=false;
DynamicSprite* plainRoom;

int abs(int x)
{
  if(x<0) return -x;
  return x;
}
int min(int a, int b)
{
  if(a<b) return a;
  return b;
}

void CandleLight(this Object*, InventoryItem* item)
{
  this.Visible = !player.HasInventory(item);
  if(this.Visible)
  {
    int t = (abs(this.X - mouse.x) + abs(this.Y - mouse.y))/2;
    this.Transparency = min(t, 100);
  }
}

function room_RepExec()
{
  if(plainRoom == null)
    plainRoom = DynamicSprite.CreateFromBackground();
  if(CandleLit)
  {    
    if(!wasOn || oldMouseX != mouse.x || oldMouseY != mouse.y)  // We need to redraw
    {
      oCoin1.Candlelight(iCoin1W);                //set translucency of objects based on closeness to mouse/candle
      oGoldIngot.Candlelight(iGoldBrick);         //set translucency of objects based on closeness to mouse/candle
      oGoldPen.Candlelight(iGoldPen);             //set translucency of objects based on closeness to mouse/candle
      
      DrawingSurface* roomSurface = Room.GetDrawingSurfaceForBackground();
      
      if(wasOn) // Erase the highlight from last frame
      {
        DynamicSprite* eraseSprite = DynamicSprite.CreateFromExistingSprite(HIGHLIGHTER, false);
        DrawingSurface* eraseSurface = eraseSprite.GetDrawingSurface();
        eraseSurface.DrawImage(OFFSET_X - oldMouseX, OFFSET_Y - oldMouseY, plainRoom.Graphic);
        eraseSurface.Release();
        roomSurface.DrawImage(oldMouseX-OFFSET_X, oldMouseY-OFFSET_Y, eraseSprite.Graphic);
        eraseSprite.Delete();
      }
      
      // Draw this highlight
      DynamicSprite* highlightSprite = DynamicSprite.CreateFromExistingSprite(HIGHLIGHTER, true);
      DrawingSurface* highlightSurface = highlightSprite.GetDrawingSurface();
      highlightSurface.DrawImage(OFFSET_X - mouse.x, OFFSET_Y - mouse.y, ROOM_LIGHT);
      highlightSprite.CopyTransparencyMask(HIGHLIGHTER);
      highlightSurface.Release();
      
      roomSurface.DrawImage(mouse.x-OFFSET_X, mouse.y-OFFSET_Y, highlightSprite.Graphic, HIGHLIGHT_TRANSPARENCY);    //copy to background
      roomSurface.Release();
      highlightSprite.Delete();

    }
  }
  else if(wasOn) // Erase the highlight from last frame
  {
    oCoin1.Visible = false;
    oGoldIngot.Visible = false;
    oGoldPen.Visible = false;
    
    DrawingSurface* roomSurface = Room.GetDrawingSurfaceForBackground();
    DynamicSprite* eraseSprite = DynamicSprite.CreateFromExistingSprite(HIGHLIGHTER, false);
    DrawingSurface* eraseSurface = eraseSprite.GetDrawingSurface();
    eraseSurface.DrawImage(OFFSET_X - oldMouseX, OFFSET_Y - oldMouseY, plainRoom.Graphic);
    eraseSurface.Release();
    roomSurface.DrawImage(oldMouseX-OFFSET_X, oldMouseY-OFFSET_Y, eraseSprite.Graphic);
    roomSurface.Release();
    eraseSprite.Delete();
  }
  
  oldMouseX = mouse.x;
  oldMouseY = mouse.y;
  wasOn = CandleLit;
}


AGS doesn't seem to have a very good way of making a copy of a small part of a sprite efficiently: you have to first copy the whole thing and then crop it, which is likely slow. I tried an alternative approach (creating a small sprite and drawing the larger sprite to it with a negative offset) just to see if it would work â€" I have no evidence that it is actually any faster. But I have added a couple of optimizations to eliminate unnecessary redrawing.

It's possible that this code could have a bug where if you leave the room while the highlight is on, when you return that highlight will still be there as a permanent part of the background. If so, just make plainRoom a global variable.

Also, I used your distance formula for the objects (just the mean of delta x and y, or half the Manhattan distance), but the cartesian distance calculation Khris gives is certainly more accurate and can be slotted in instead.

bx83

But all I wanted was a yellow *tinted* circle; not anything involving 2 backgrounds, one of which is already a tinted image :/

In other words:

in globalscript:
Code: ags

function Candlelight_on (Object *Obj, InventoryItem *Inv)
{

	if (!cJulius.HasInventory(Inv)) {
		Obj.Visible=true;
		
		float dx = IntToFloat(mouse.x - Obj.X), dy = IntToFloat(mouse.y - Obj.Y);
		int dist = FloatToInt(Maths.Sqrt(dx * dx + dy * dy), eRoundNearest);
		trans = (dist <= 100) * dist + (dist > 100) * 100; // the multiplication casts the bools to 1 or 0

		Obj.Transparency=trans;
	}
}


in room script:
Code: ags

if (CandleLit) {
    Candlelight_on(oObject, iInv);
}else{
    oObject.visible=false;
}


For other background features would become unclickable objects rather than a whole background with a) different features and b) *tinted another colour*

Code like the below:
Code: ags

  DynamicSprite* bgSprite = DynamicSprite.CreateFromBackground();
  bgSprite.Tint(r,g,b,s,l); //THIS TINTING OF CIRCLE GRAPHIC
  // Now copy it back to the background
  DrawingSurface* bgSurface = Room.GetDrawingSurfaceForBackground();
  bgSurface.DrawImage(0, 0, bgSprite.Graphic);
  // Clean up
  bgSurface.Release();
  bgSprite.Delete();


...but different because I could never get it to work :P

Snarky

You could grab a section of the background and then tint that, but you would still have to go through all the same steps to copy the background content onto the sprite, and you would then have to do the tinting as another step each cycle, making it even slower. It's better to have a pre-tinted version of the whole room background and then just grab the section of it that you need each frame.

What you could do, though, is to generate the tinted version of the room background on room load (using the code in my first post in the thread) instead of creating it in an outside program. If you're doing this in a lot of rooms, that's what I would recommend.

Also, another optimization would be to draw the highlighted version onto an object, so that you wouldn't have to erase it after it was drawn. However, this doesn't work if you have walkbehinds (the object either won't tint the walkbehind area, or will be drawn on top of characters and other objects, hiding them), so it has limited applicability.

Or finally, if you're not too concerned about the exact tint effect, just put a semi-transparent red circle on an object (or GUI, if you need it in more than one room) and have it follow your cursor. In other words, do exactly what Cassiebsg told you in the first response, except adapted to what you apparently wanted all along but never actually asked.

bx83

I might go for the Finally solution :P

One question which I can't find answered; is there a way to find an object's height/width without manually setting them as Properties?

Snarky

Fine. My frustration is because I gave you working, tested code, and you dismissed it without identifying any way it doesn't do what it's supposed to do, just because it does it a different way than you expected.

Quote from: bx83 on Mon 25/06/2018 04:38:28
One question which I can't find answered; is there a way to find an object's height/width without manually setting them as Properties?
You have to look up the Game.SpriteWidth/Height of the Object.Graphic.



bx83

Okay I now have the following code:

in globalscript:
Code: ags

function Candlelight_on (Object *Obj, InventoryItem *Inv)
{
  int width = Game.SpriteWidth[Obj.Graphic];
  int height = Game.SpriteHeight[Obj.Graphic];
	
  Obj.Visible = !player.HasInventory(Inv);
  
  if(Obj.Visible)
  {
    float dx = IntToFloat(abs(mouse.x - (Obj.X + (width/2))));
    float dy = IntToFloat(abs(mouse.y - (Obj.Y - (height/2))));
		
    int d = FloatToInt(Maths.Sqrt((dx * dx) + (dy * dy)));
    if (d < 100)
        Obj.Transparency = d;
    else
        Obj.Transparency = 100;
  }
}


in room code:
Code: ags

function room_RepExec()
{

	oCandelight.X=mouse.x+OFFSET_X;
	oCandelight.Y=mouse.y+OFFSET_Y;

	oCandelight.Visible=CandleLit;

	oCandelight.Clickable=CandleLit;
	oCoin1.Clickable=CandleLit;
	oGoldIngot.Clickable=CandleLit;
	oGoldPen.Clickable=CandleLit;
	oDevilsHead.Clickable=CandleLit;
	oStar.Clickable=CandleLit;

	if (CandleLit) {
            Candlelight_on(oCoin1, iCoin1W);
            Candlelight_on(oGoldIngot, iGoldIngot);
            Candlelight_on(oGoldPen, iGoldPen);
            Candlelight_on(oDevilsHead, iMcguffin);
            Candlelight_on(oStar, iMcguffin);
	} else {
	    oCoin1.Visible=CandleLit;
            oGoldIngot.Visible=CandleLit;
	    oGoldPen.Visible=CandleLit;
	    oDevilsHead.Visible=CandleLit;
	    oStar.Visible=CandleLit;
	}
}


'oCandlelight' is the translucent yellow object to display the candlelight. (in previous posts it was red, now it's yellow, whatever).

oCandlelight:
It's 120x120
It's baseline is overidden to 1; this way, it's always behind objects it highlights. Turning off BaselineOverridden, or setting Baseline to 0, has the effect of making it sometimes on top of objects, sometimes below, depending on position.
It's Visibility, Locked and Clickable are set to False. (initially, in the editor)
UseRoomAreaLighting and UseRoomAreaScaling are False.
It has no Description, and it's not Solid.

If I set oCandlelight.Baseline to 1000 (screen is 1024x768), it's always on top of objects; *but I can't click through it*. It's like the whole screen is under a sheet of glass, no clicks anywhere do anything.
If I set oCandlelight.Baseline to 1, it's always underneath; however this means setting all WalkBehind baselines to 0 (or they show up); and all objects - which have a background on them that's the same as the room background - as square's.

Basically:
I need to to be *above* all objects (or anything) all the time,
But
Allow clicks to pass-through.

(Also, making it's non-Clickable doesn't even work as pass-through for clicks, despite this being in the editor description for Clickable???)

I think the solution has something to do with GUI's... but it's taken me a day of experimentation to write this post, it'll take another 400 days to write the solution; or you guys may know it in 5 minutes :)

Crimson Wizard

Quote from: bx83 on Tue 26/06/2018 02:32:05
(Also, making it's non-Clickable doesn't even work as pass-through for clicks, despite this being in the editor description for Clickable???)

Clickable should work, in your script I see you change other objects' Clickable property in room exec, are you sure that logic is working properly?
I may suggest leave all the objects in the room always clickable for now, until you test out the lighting object itself.

Snarky

Room code repeatably_execute, line 9:

Code: ags
        oCandelight.Clickable=CandleLit;


So when the candle is lit, the candlelight is clickable and covers up anything else. There's no reason to ever set this value, just leave it unclickable always.

bx83


bx83

Is there a way to have a global Object? I need to create the oCandlelight object for every room otherwise :/

Crimson Wizard

Quote from: bx83 on Wed 27/06/2018 09:30:20
Is there a way to have a global Object? I need to create the oCandlelight object for every room otherwise :/

Use character with locked view, or GUI.

bx83

What surface would I use on a GUI,a button? Or just the GUI background image?
GUI's also don't seem to preserve alpha channel/transparancy :/

SMF spam blocked by CleanTalk