help with scanning effect

Started by Mantra of Doom, Sat 15/08/2009 16:57:10

Previous topic - Next topic

Mantra of Doom

Hello all. I'm working on a text parser game where the player character is a remotely controlled robot. This little robot has the ability to do a general scan of the room and can easily find the objects and hotspots that it can interact with.

Here's an example, done in photoshop that demonstrates what I'd like to do:

Before scan effect


Scan effect last for a brief time and then shuts off


I'm just not sure where to start. I thought about swapping object images when the effect is on, but I don't think that'd really be the best way to do it. If anybody has any ideas on a direction for this, I'm willing to listen.

And, of course, I'll post my code in case anyone else might want it.

Thanks in advance!
"Imitation is the sincerest form of imitation."

monkey0506

#1
Actually swapping the object's image might be the easiest way to do it. What you could do is import each of the object images sequentially where you have the "normal" sprite and then the next sprite slot is devoted to the "highlighted" sprite. As far as the text, you could have an invisible character in the room (I'll refer to this as cScanner).

You could then do something like:

Code: ags
// GlobalScript.asc
bool scanning = false; // whether we are scanning
int scanID = 0; // the ID of the object we are currently showing as highlighted
bool scannerSpeaking = false; // whether cScanner was speaking the last game loop

function StartScan() {
  if (scanning) return;
  scanning = true;
  if (cScanner.Room != player.Room) cScanner.ChangeRoom(player.Room);
}

function StopScan() {
  scanning = false;
  if (scannerSpeaking) {
    cScanner.Say("");
    if (scanID < Room.ObjectCount) object[scanID].Graphic--; // if we highlighted an object, set its graphic back
  }
  scanID = 0;
  scannerSpeaking = false;
}

function repeatedly_execute() {
  if (scanning) { // if we are scanning the room
    while ((scanID < Room.ObjectCount) && (!object[scanID].Visible)) scanID++;
    if (scanID == Room.ObjectCount) { // if we've scanned all the objects, stop scanning
      StopScan();
      return;
    }
    object[scanID].Graphic++; // highlight the object
    cScanner.x = object[scanID].X; // position cScanner
    cScanner.y = object[scanID].Y;
    cScanner.Say(object[scanID].Name); // display the object's name
  }
}

function repeatedly_execute_always() {
  if (scanning) {
    if (!cScanner.Speaking) {
      if (scannerSpeaking) { // scanner WAS speaking last loop
        object[scanID].Graphic--; // restore the normal graphic of this object
        scanID++; // go to the next object
        while ((scanID < Room.ObjectCount) && (!object[scanID].Visible)) scanID++;
        if (scanID == Room.ObjectCount) { // if we've scanned all the objects, stop scanning
          StopScan();
          return;
        }
      }
    }
  }
  scannerSpeaking = cScanner.Speaking; // store whether cScanner was speaking this loop
}

// GlobalScript.ash
import function StartScan();
import function StopScan();


Then you can just call StartScan(); to scan through all of the objects in the room. If you need to stop scanning early then you can call StopScan();.

I haven't tested this code, but it should work.

EDIT: I've only just realized that you said you would post your code if requested...did you already script it yourself? :o

I've made the change so that you shouldn't be calling Character.Say in rep_ex_always anymore...Now I'll try and test the code out. ;)

17 August: I actually got this working the way it should (tested fully). One last thing you may consider for the cScanner character is setting its transparency to 100 (or you could do some type of animation or something).

Oh also, as I discovered, even though we aren't going to see cScanner, he must have a SpeechView set (even if it's the same as his normal view).

- Added 2 lines that should prevent hidden objects from being scanned. ;)

GarageGothic

#2
monkey is right that it's probably easiest to just switch object sprites, but I put together something similar for font rendering the other day, so here's a bit of auto-outlining code (for Objects only):

Code: ags
DynamicSprite* scansprite;
ViewFrame* tempframe;

function ScanObject(this Object*) {
  int inneroutlinewidth = 4;
  int outeroutlinewidth = 2;
  int innercolor = 64426;
  int outercolor = 65518;
  DynamicSprite* outlinesprite = DynamicSprite.Create(Game.SpriteWidth[this.Graphic], Game.SpriteHeight[this.Graphic], false);
  DynamicSprite* transmapsprite = DynamicSprite.CreateFromExistingSprite(this.Graphic, false); //to destroy alpha channel
  DrawingSurface* outlinesurface = outlinesprite.GetDrawingSurface();
  outlinesurface.Clear(outercolor);
  outlinesurface.Release();
  outlinesprite.CopyTransparencyMask(transmapsprite.Graphic);
  scansprite = DynamicSprite.Create(outlinesprite.Width+((inneroutlinewidth+outeroutlinewidth)*2), outlinesprite.Height+((inneroutlinewidth+outeroutlinewidth)*2), false);
  DrawingSurface* scansurface = scansprite.GetDrawingSurface();
  
  int pixelcount = 0;
  int zero = outeroutlinewidth+inneroutlinewidth;
  while (pixelcount <= outeroutlinewidth+inneroutlinewidth) {
    scansurface.DrawImage(zero, zero-pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero, zero+pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero-pixelcount, zero, outlinesprite.Graphic);
    scansurface.DrawImage(zero+pixelcount, zero, outlinesprite.Graphic);
    scansurface.DrawImage(zero-pixelcount, zero-pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero-pixelcount, zero+pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero+pixelcount, zero-pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero+pixelcount, zero+pixelcount, outlinesprite.Graphic);
    pixelcount++;
    }
  
  outlinesprite = DynamicSprite.Create(Game.SpriteWidth[this.Graphic], Game.SpriteHeight[this.Graphic], false);
  outlinesurface = outlinesprite.GetDrawingSurface();
  outlinesurface.Clear(innercolor);
  outlinesurface.Release();
  outlinesprite.CopyTransparencyMask(transmapsprite.Graphic);
  
  pixelcount = 0;
  while (pixelcount <= inneroutlinewidth) {
    scansurface.DrawImage(zero, zero-pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero, zero+pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero-pixelcount, zero, outlinesprite.Graphic);
    scansurface.DrawImage(zero+pixelcount, zero, outlinesprite.Graphic);
    scansurface.DrawImage(zero-pixelcount, zero-pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero-pixelcount, zero+pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero+pixelcount, zero-pixelcount, outlinesprite.Graphic);
    scansurface.DrawImage(zero+pixelcount, zero+pixelcount, outlinesprite.Graphic);
    pixelcount++;
    }
  scansurface.Release();
  tempframe = Game.GetViewFrame(cTemp.View, cTemp.Loop, cTemp.Frame);
  tempframe.Graphic = scansprite.Graphic;
  cTemp.ChangeRoom(player.Room, this.X+(scansprite.Width/2)-zero, this.Y+zero);
  cTemp.Transparency = 100;
  if (this.Baseline == 0) cTemp.Baseline = this.Y-(zero+1);
  else cTemp.Baseline = this.Baseline - 1;
  int temptrans = 100;
  while (temptrans > 0) {
    temptrans = temptrans - 4;
    cTemp.Transparency = temptrans;
    Wait(1);
    } 
  Wait(80);
  while (temptrans < 100) {
    temptrans = temptrans + 4;
    cTemp.Transparency = temptrans;
    Wait(1);
    } 
  }


The code uses a character named cTemp to display the outline graphic. The blocking calls at the end were just me being lazy - of course once the sprites has been drawn you can display it however you want, using a timer or whatnot. The outlines are a bit rough, nothing to do about that, but I think you could make the outline look better by using a premade circular gradient texture instead of just using DrawingSurface.Clear for the color.

Probably a lot of optimization can be done (I think the outline drawing runs one cycle too long), but for now it works at least.

Edit: Oh, and make sure that the cTemp character doesn't use the same viewframe as any other character.

Khris

I coded a module that shows all hotspots and objects a while ago and rewrote it for 3.1.2 just a few days ago.

http://www.2dadventure.com/ags/ShowHotspots2.0.zip

Here's some example code from game_start:

Code: ags
  ShowHotspots.SetKey('H');       // press H key to show hotspots
  ShowHotspots.SetFont(eFontFont2, false, false, 5, 97, 167);   // label uses font 2, no umlauts, no caps and color(5, 97, 167)
  ShowHotspots.SetIcon(215, 6, 6);   // icon is in sprite slot 215, its center at 6;6
  ShowHotspots.Enable();


This will put the icon sprite and the name on the center of every hotspot/object as long as the key is pressed.
If you look at the module script's on_key_press, you'll find calls to _Prepare(), _ShowHotspots() and _CleanUp().
You can easily rewrite it to get called after a certain parser command and the loop in between to automatically terminate after a given period of time.

It doesn't outline stuff though.

Mantra of Doom

Okay gang, I did scrap the horrible bits of code I pieced together... it was an ugly "swap images" code, but I didn't think of making the image numbers consecutive. (I knew I was making more work for myself)

So I tried out the first two codes to see how they'd work.

Monkey, your code and I get an saying that I can't call a blocking function from rep ex always on this line:
Code: ags
cScanner.Say(object[scanID].Name); // display the object's name



Garage, I like the idea of your code. And I really want to see how it looks, but I'm not sure where to put it. I'm going to play around with this a little bit more when I have some more time.

Khris, I'll test out that module and see how that goes... I thought there was a module that did that, but I couldn't remember what it was called.

I tried responding three times, but you guys kept posting! Thanks guys.
"Imitation is the sincerest form of imitation."

Tijne

#5
I agree that Monkey's method would be the easiest.  If you don't want to / didn't already put the graphics of scanned/not scanned in numerical order, you can add a Property to Objects for a "Normal Sprite" and a "Scanned Sprite" and read the value for each object exactly.


Here's an additional method;

Make one view for all objects -- Create two frames for each object for each loop, the first frame being the object's standard look and the second frame being the objects "scanned" look.  Put a large delay on the second frame (Maybe 120 for 3 seconds?).  Then to display a scan, all you have to do in the global script is animate each object in the room in their current loop nonblocking, with a Wait() command after it for the amount of delay.  For added control you can add in cutscene tags so a player can press a key/mouse click to stop the scan early; the code would be like; Example--
Code: ags

function Scan()
{
 StartCutscene(eSkipAnyKeyOrMouseClick);
 int temp = 0;
 while (object[temp] != null)
 {
   object[temp].Animate(object[temp].Loop, 0, eOnce, eNoBlock, eForwards);
   temp++;
 }
 Wait(120); 
 EndCutscene();
}


While this method is kind of pointless, it allows you to actually -animate- the scan with multiple frames.   If you chose this methods, you'd have to set up all the view/loops and add them to their respective objects.  This can make it very tedious and a little unorganized especially for large games or games with a great deal of many objects, but it works great in games with only a few dozen objects and an animation effect would really help improve the scanning ability.




EDIT: Actually now that I look at it, Monkey's code has a start/stop feature.  If you don't want/need that (I don't think you do via your description..) you can do this;
Code: ags

function Scan()
{
 StartCutscene(eSkipAnyKeyOrMouseClick);
 int temp = 0;
 while (object[temp] != null)
 {
   object[temp].Graphic++;
   temp ++;
 }
 Wait(120); 
 temp = 0;
 while (object[temp] != null)
 {
   object[temp].Graphic--;
   temp ++;
 }

 EndCutscene();
}

Basically the same as my last code, except using the graphics manually.


If you want to have a delay and cycle between objects, displaying a text description for each one you can do this:


Code: ags

function Scan()
{
 StartCutscene(eSkipAnyKey);
 int temp = 0;
 while (object[temp] != null)
 {
   object[temp].Graphic++;
   temp ++;
   Scanner(object[temp].X, object[temp].Y,  100, object[temp].Name);
   object[temp].Graphic--;
 }
 EndCutscene();
}


Jim Reed

For a yellow 1 pixel thick outline I would tint the wanted object to yellow, move it 1px up, draw it to the background, move it 1px right (from the original position), draw it to the background and also for down and left. Then remove the tint and place the object at the original coordinates. It doesn't require additional sprites, but it has some cons, too.

monkey0506

That's right. Sorry, as I said I didn't actually test my code or anything sensible like that. I did at one point have a line like player.Say(""); in rep_ex_always...so for some reason I assumed that it was safe to call the function there.

Try changing the existing rep_ex_always function from above to just rep_ex, but move this line:

Code: ags
scannerSpeaking = cScanner.Speaking; // store whether cScanner was speaking this loop


Into rep_ex_always.

I will actually make the modification to the code above...and then maybe test it out. :P

monkey0506

Okay I was having some difficulty with the Speaking property (which you can read all about in the tech forum) but I finally got this script working. Part of what I said should go in rep_ex actually belongs in rep_ex_always (the bit about restoring the graphic) so I'll update the code.

Hope everything works well for you!

Mantra of Doom

Thanks monkey! This is the closest I've gotten so far to the effect I was going after. There is one problem though, it picks up objects that have their visibility set to false. Oh well! I will have to see if I can add a line in there to fix that once I'm not at work and don't have to code in between taking calls. Pesky customers, getting in the way of my game.

To everyone else, thanks, those are all good ideas and I'm going to keep them in mind if things can't work with my invisible objects.
"Imitation is the sincerest form of imitation."

monkey0506

#10
The invisible objects isn't that big of a deal. In two places (above, I'm keeping that code updated to avoid having to repost it over and over) I've added the line:

Code: ags
while ((scanID < Room.ObjectCount) && (!object[scanID].Visible)) scanID++;


Right before it checks whether all the objects have been scanned, so that should make it register only visible objects.

One further problem I've encountered is if you call StopScan in the middle of scanning...it seems to not be working good. I plan to do some further testing on this, but I have to go celebrate my aunt's birthday first. ;)

Basically I think the problem is that the object's graphic is being set back twice instead of only once if you stop scanning early. Like I said, looking into it.

Edit: Actually, a simple visual audit of my code realized I wasn't checking scanning in rep_ex_always. That should fix the StopScan issue. ;)

SMF spam blocked by CleanTalk