Optimising room interactions for Global Script

Started by subspark, Wed 26/09/2007 07:32:56

Previous topic - Next topic

subspark

I am trying to make my code more efficient in the sense that when the mouse is moved over a hotspot, it's corrosponding icon (oWhatever) is faded in. Problem is, I can't figure out how to tell the game that an object belongs to a hotspot and that when the mouse is over one, to fade the correct one in.

Here is a snippet from the room code that I want to make part of the global script to save me having to set up each hotspot with each object tediously.
Code: ags
function room_a() {  // Set the icon of the hot/obj/char when cursor hovers over it
  // script for room: Repeatedly execute
  Hotspot *theHotspot = Hotspot.GetAtScreenXY( mouse.x, mouse.y );
  if (GUI.GetAtScreenXY(mouse.x, mouse.y) == null ) { // If there is no GUI visible,
    if (Hotspot.GetAtScreenXY(mouse.x, mouse.y) == hOdddevice) { // If the mouse is over the odd device hotspot,
      FadeObjectIn_NoBlock(oOdddevice,0,-20); // fade the hotspot's icon in.
    }
    else if (Hotspot.GetAtScreenXY(mouse.x, mouse.y) == hTalldoor) { // If the mouse is over the tall entrance hostpot,
      FadeObjectIn_NoBlock(oTalldoor,0,-20); // fade the hotspot's icon in.
    }	
    else if (Hotspot.GetAtScreenXY(mouse.x, mouse.y) == null) { // If the mouse is over nothing,
      FadeObjectOut_NoBlock(oOdddevice,100,-20); // Fade the Odd Device icon out.
      FadeObjectOut_NoBlock(oTalldoor,100,-20); // Fade the Tall Door icon out.
    }	// Do the same for all hotspots/objects/characters you use in this room.
  }
  else if (gInventory.Visible == true) { // If the inventory GUI is visible, ignore hotspots/objects/characters.
  }
}


Here is some psuedo code that gives an impression of what I want to acheive:
Code: ags
function repeatedly_execute() {
  // put anything you want to happen every game cycle here
  Object*oHotspotIcon=Object.ForHotspotAt(mouse.x, mouse.y); // The hotspot's icon is an object that can be tied with the hotspot.
  if (gInventory.Visible == true) { // If the inventory GUI is visible, ignore hotspots/objects/characters.
    if (GetLocationType(mouse.x,mouse.y)==eLocationHotspot) {
    }
    else if (mouse.x > 300) { // If mouse moves off the right hand side of the Inventory GUI, fade it out.
      FadeGuiOut_NoBlock(gInventory,100,-15);
      FadeGuiOut_NoBlock(gInventoryback,100,-15);
    }
    else if (mouse.x < 20) { // If mouse moves off the left hand side of the Inventory GUI, fade it out.
      if (mouse.y < 140) {
        FadeGuiOut_NoBlock(gInventory,100,-15);
        FadeGuiOut_NoBlock(gInventoryback,100,-15);
      }
    }
    else if (mouse.y < 10) { // If mouse moves off the top of the Inventory GUI, fade it out.
      FadeGuiOut_NoBlock(gInventory,100,-15);
      FadeGuiOut_NoBlock(gInventoryback,100,-15);
    }
    else { //If the mouse is over a hotspot, fade in the corrosponding Icon.
      if (GetLocationType(mouse.x,mouse.y)==eLocationHotspot) {
      FadeObjectIn_NoBlock(oHotspotIcon,0,-20);
      }
    }
  }
}


Completely non-working code, perhaps ridiculous to some, however if some of you get what I am trying to do, could you give an example of how to achieve this?
Thanks in advance, guys.

Cheers,
Paul.

EDIT: DARN! I posted this in the wrong forum. This ain't so beginner I don't think. Can a moderator please move this thread to the Technical Forum. Thanks.

Ashen

The application's not particularly beginner-ly, but the answer is. It's even on the front page of the BFAQ:

Quote
NOTE: Please keep the following in mind. Most of our problems can be easily solved using variables. Need to change a hotspot's state in another room? Use variables! Need an object to be used only once? Use variables!

So to answer your question "how to tell the game that an object belongs to a hotspot": USE VARIABLES. Create an array to store the Object number (or you could even create an array of Object pointers, and simplify a little further), something like:
Code: ags

Object *HotIcon[AGS_MAX_OBJECTS];

//And then to assign them:
HotIcon[hTalldoor.ID] = oTalldoor;
// (You could create a function to do this too, e.g. SetHotspotObject(hTalldoor, oTalldoor);


Then you could use the HotObj array much like you used Object.ForHotspotAt(mouse.x, mouse.y) in your pseudocode - just do a Hotspot.GetAtScreenXY(mouse.x, mouse.y) and plug the result in.

However, it seems a bit wasteful of Objects. Why not just use one, and change it's graphic as needed? You could still use the same basic method, just store the spriteslot in an int array. Or better yet, could you use a GUI and be completely room independant?
I know what you're thinking ... Don't think that.

subspark

#2
Perfect Ashen. You've answered most of my questions, even the ones in my head that I wasn't ready to post. Your a troop.
I had a hunch arrays might be the answer here, however I've never written one. Theres a first time for everything, right!?

As you suggested, I'm using a function that assigns a hotspot a corresponding Icon sprite number. The sprite number is used in the GUI depending on what hotspot the mouse is over.
So my code would go like this? (I only have two hotspots for now).

Code: ags
#sectionstart SetHotspotIcon  // DO NOT EDIT OR REMOVE THIS LINE
function SetHotspotIcon(Hotspot, int sprite) {
  SetHotspotIcon(hTalldoor, 75);
  SetHotspotIcon(hOdddevice, 32);
}
#sectionend SetHotspotObject  // DO NOT EDIT OR REMOVE THIS LINE


#sectionstart repeatedly_execute  // DO NOT EDIT OR REMOVE THIS LINE
function repeatedly_execute() {
  if (GetLocationType(mouse.x,mouse.y)==eLocationHotspot) {
    gIconBox.Visible = true;
    DisplayHotspotIcon(mouse.x,mouse.y);  //// How exactly would something like this go according to my new function?/////
  }
}


Most appreciated, man. Thank you.

Cheers,
Paul.

EDIT:
I'm a little stuck on the function side of things now. To give you a better example of my small game prototype, it's available below if any one wants to take a look at it and tell me where I've gone wrong. It's basically a reworked old Loom Template.
http://www.shuugouteki.net/paul/Development/Adlanto.zip (409KB)

Cheers,
Paul.

Ashen

#3
I was working on this when you posted. I took a look at your prototype and it still holds up:

Code: ags

#sectionstart SetHotspotIcon  // DO NOT EDIT OR REMOVE THIS LINE
function SetHotspotIcon(Hotspot, int sprite) {
  SetHotspotIcon(hTalldoor, 75);
  SetHotspotIcon(hOdddevice, 32);
}
#sectionend SetHotspotObject  // DO NOT EDIT OR REMOVE THIS LINE


That doesn't even make sense (the version in the download is even worse)... You declare the function, and then call it inside itself without actually telling it what it should do.
It should be more like:
Code: ags

function SetHotspotIcon(Hotspot *theHot, int sprite) {
  HotIcon[theHot.ID] = sprite;
}

// and then, e.g. in player enters room:
SetHotspotIcon (hTalldoor, 75);
SetHotspotIcon (hOdddevice, 32);



But, since I read in another thread you're using the 2.8 beta, you could use the extender functions, to make it neater:
Code: ags

int HotIcon[AGS_MAX_HOTSPOTS]

function SetIcon(this Hotspot*, int sprite) {
  HotIcon[this.ID] = sprite;
}

// And then:
hTalldoor.SetIcon(75);
hOdddevice.SetIcon(32);


The fading part should be pretty simple too. Since it looks like you're using Lazarus' FadeThingsNonBlocking module, you should be able to slot that into a rep_ex check somewhere:
Code: ags

Hotspot *theHot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);
if (theHot != hotspot[0]) {
  gIconBox.BackgroundGraphic = HotIcon[theHot.ID];
  FadeGUI_NoBlock(gIconBox, 0, -20);}
}


There's more to it, obviously, but that should be the start.
I know what you're thinking ... Don't think that.

subspark

#4
Thanks a bunch, Ashen.

As you mentioned with 2.72, the following commands go in player_enters_room, right? albeit in 2.72 style, however with 2.8 do they also go in the player_enters_room and can I put them in before fade in?
hTalldoor.SetIcon(75);
hOdddevice.SetIcon(32);

Is there any way to simply define the HotspotIcons in the global script or a module script. The idea is to make everything as neat as possible and I don't really want to have to define Hotspot Icons in every room script.

When I try and save the room, the error says SetHotspotIcon is not a public member of hotspot. So when I move the above code into the global script under function_game_start the error says undefined token hTalldoor.

I am a little unsure about the placement of those two commands.

Cheers,
Paul.

Ashen

If SetHotspotIcon is declared in the Global script, it obviously needs to be imported in the Global Header before it can be used in Room scripts. Also, check what your code actually says. In your post you switch between SetIcon and SetHotspotIcon. That might just be a slip up in your post, but SetHotspotIcon isn't a Hotspot extender function, so hWhatever.SetHotspotIcon might also return that error. hWhatever.SetIcon should work, however (once you import it).

Room-dependant things (like Hotspots and Objects) can't be refered to by their Script-o-Names in the Global Script, which is why you get the 'undefined token' error - in the Global Script, hTalldoor doesn't exist. You have to refer to them by number, e.g.:
Code: ags

hotspot[1].SetIcon(75);
hotspot[2].SetIcon(32);
// etc


This means you can only set the current Rooms Hotposts in Global SCipt, so it's actually neater and easier to define them in rooms (IMO, at least). If you were really desperate to keep them in one place, you could maybe use the eEventEnterRoomBeforeFadein parameter of on_event, create a condition for every single room, and define them by the numbers there.

Since there are no blocking parts to the function to screw things up, there shouldn't be a problem using it in 'before fadein' events. This is true for 2.72 and 2.8.
I know what you're thinking ... Don't think that.

subspark

QuoteThat might just be a slip up in your post, but SetHotspotIcon isn't a Hotspot extender function
Oh yeah i changed it in my code to SetHotspotIcon because I need to write 2 more functions that apply to characters and objects. SetHotspot icon tells me clearly that it is for a hotspot. Unless I can declare objects and characters under the same SetIcon function then I really want to have function  names that I can tell apart. Would SetHotIcon be better or can SetIcon be made to work with both hotspots, characters and objects?

Thanks Ashen,
Paul.

monkey0506

If you're using extender methods then it's possible to define them like this (you do have to create separate functions for the different types, but you can use the same name if using extenders):

Code: ags
// script header
import void SetIcon(this Character*, int slot);
import void SetIcon(this Hotspot*, int slot);
import void SetIcon(this Object*, int slot);
import int GetIcon(this Character*);
import int GetIcon(this Hotspot*);
import int GetIcon(this Object*);

// global/module script
int CharacterIcon[];
int HotspotIcon[];
int ObjectIcon[];

#ifndef AGS_MAX_SPRITES
#define AGS_MAX_SPRITES 30000
#endif
#ifndef AGS_MAX_ROOMS
#define AGS_MAX_ROOMS 1000
#endif

function game_start() {
  CharacterIcon = new int[Game.CharacterCount];
  HotspotIcon = new int[AGS_MAX_ROOMS * AGS_MAX_HOTSPOTS];
  ObjectIcon = new int[AGS_MAX_ROOMS * AGS_MAX_OBJECTS];
  }

void SetIcon(this Character*, int slot) {
  if ((slot < 0) || (slot >= AGS_MAX_SPRITES)) slot = 0;
  if (CharacterIcon != null) CharacterIcon[this.ID] = slot;
  }

void SetIcon(this Hotspot*, int slot) {
  if ((slot < 0) || (slot >= AGS_MAX_SPRITES)) slot = 0;
  if (HotspotIcon != null) HotspotIcon[(player.Room * AGS_MAX_HOTSPOTS) + this.ID] = slot;
  }

void SetIcon(this Object*, int slot) {
  if ((slot < 0) || (slot >= AGS_MAX_SPRITES)) slot = 0;
  if (ObjectIcon != null) ObjectIcon[(player.Room * AGS_MAX_OBJECTS) + this.ID] = slot;
  }

int GetIcon(this Character*) {
  return CharacterIcon[this.ID];
  }

int GetIcon(this Hotspot*) {
  return HotspotIcon[(player.Room * AGS_MAX_HOTSPOTS) + this.ID];
  }

int GetIcon(this Object*) {
  return ObjectIcon[(player.Room * AGS_MAX_OBJECTS) + this.ID];
  }

// wherever

hotspot[5].SetIcon(62); // sets hotspot 5's icon to sprite slot 62

// later

if (hotspot[5].GetIcon() == 62) {
  // hotspot 5's icon is 62
  // do stuff
  }


I've made use of another 2.8 feature, dynamic arrays. This allows me to easily define a "max room" variable (AGS_MAX_ROOMS) which I'll set to 1000 (because AFAIK the highest room number you can have is 999) for sizing the hotspot/object arrays. If you want to save space, you can lower this amount to 300 as room 299 is the last state-saving room. Basing the size of the hotspot/object arrays on room as well as the hotspot/object maximums allows for state-saving icons. So if you're in room 3, set hotspot 5's icon to sprite slot 62, go to room 4, and then come back, hotspot 5's icon will still be set to slot 62.

subspark

Thanks monkey, however there is a parse error in the expression near new. At least thats what the script compiler thinks.
Is there an error somewhere?

All this complex code is really new to me.
I reckon I'll credit both you and Ashen after this prototype is finished!  8) - Simply amazing stuff guys thank you so very much.

Paul.

monkey0506

Hmmm...are you using 2.8 Beta 7 or higher? Sorry I forgot to mention but you'll need at least beta 7...although it seems if you weren't then it should've crashed when it encountered an array with no size specified...

I just copied/pasted into a test game and it appeared to be working. If you're not using beta 7 or higher and can't possibly consider upgrading, it's not really necessary to use a dynamic array so you could replace the following:

Code: ags
int CharacterIcon[];
int HotspotIcon[];
int ObjectIcon[];

#ifndef AGS_MAX_SPRITES
#define AGS_MAX_SPRITES 30000
#endif
#ifndef AGS_MAX_ROOMS
#define AGS_MAX_ROOMS 1000
#endif

function game_start() {
  CharacterIcon = new int[Game.CharacterCount];
  HotspotIcon = new int[AGS_MAX_ROOMS * AGS_MAX_HOTSPOTS];
  ObjectIcon = new int[AGS_MAX_ROOMS * AGS_MAX_OBJECTS];
  }


With:

Code: ags
#ifndef AGS_MAX_SPRITES
#define AGS_MAX_SPRITES 30000
#endif
#ifndef AGS_MAX_ROOMS
#define AGS_MAX_ROOMS 1000
#endif
#define GAME_MAX_HOTSPOTS 30000 // AGS_MAX_ROOMS * AGS_MAX_HOTSPOTS
#define GAME_MAX_OBJECTS 20000 // AGS_MAX_ROOMS * AGS_MAX_OBJECTS

int CharacterIcon[AGS_MAX_CHARACTERS];
int HotspotIcon[GAME_MAX_HOTSPOTS];
int ObjectIcon[GAME_MAX_OBJECTS];


Then remove all the parts like "if (CharacterIcon != null)", "if (HotspotIcon != null)", and "if (ObjectIcon != null)". Just remove the "if" part, don't completely remove the line.

Other than that the functions should operate normally. I'd really recommend the dynamic array route if possible, but if it's not feasible then this should help you put it back to normal static arrays.

subspark

I'm using beta 11 actually. Your original code still wont work however.

Cheers,
Paul.

Ashen

I don't have a great deal of experience with the betas, I only started using  them at beta11 theo ther day, so I'll let monkey take it from here :)

His code compiles fine for me, in 2.8b11. What exactly was the error message? Is it possible you've missed a semi-colon or brace, or something, in copying the code over?
I know what you're thinking ... Don't think that.

monkey0506

I can find no reason why it shouldn't be working. I've tested the code and it compiles and runs fine.

If you could post the exact error message (as Ashen said) it could help isolate the problem. For example, knowing which line causes the error.

If you'd prefer, I've gone ahead and uploaded the script in a module here.

subspark

#13
Its strange. It's line 13 and it the error is parse erroe in expr near 'new'. if i comment that line out it goes to line 14 and then line 15 after that.
Hmm, this is strange. ???

BTW, I have enforce new style strings and object based scripting on.

EDIT: Ahh somehow I forgot to change left to right operator precedence to left. It should be that by default however I converted a 2.72 game to the latest beta.
That particular problem is solved and thanks for the module!

How exactly do I assign a hotspot/character/object an Icon? I tried a number of things based on the function but nothing seemed to work.

Also what happens now is when the mouse is over a hotspot/object/character, gIconBox is faded in and the normal graphic of a non-clickable button is changed to the sprite assignment for that hotspot.
So I've added repeatedly_execute in your module script and under that function is the following:

Code: ags
function repeatedly_execute() {
  if (gInventory.Visible == false) {
    Hotspot *theHotspot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);
    if (theHotspot != hotspot[0]) {
      bIcon.NormalGraphic = HotspotIcon; //==========LINE 49==========\\
      FadeGuiIn_NoBlock(gIconBox, 0, -20);
      FadeGuiIn_NoBlock(gIconBoxback, 30, -20);
    }
    else {
      FadeGuiOut_NoBlock(gIconBoxback, 100, -15);
      FadeGuiOut_NoBlock(gIconBox, 100, -15);
      bIcon.NormalGraphic = 34;
    }
  }
  else {
  }
}


The error is in line 49 which reads: Type missmatch: cannot convert 'int[]' to 'int'

Cheers,
Paul.

Ashen

What have you tried that didn't work? The answer is right there in monkey's original post:
Code: ags

// wherever

hotspot[5].SetIcon(62); // sets hotspot 5's icon to sprite slot 62


(or object[2].SetIcon, cEgo.SetIcon, etc.)

Remember (as with my code) that you can't use Object or Hotspot Script names in Global script. Character Script names should be OK, though, since they're Global.
I know what you're thinking ... Don't think that.

subspark

#15
Well first off, I had the code outside of a function. That didn't help. I also forgot to add [theHotspot.ID] to the end of that command.
Secondly, you might have noticed, I'm using a button to display the icon graphic for a hotspot.

So, based on my updated post above which, unfortunately, i rushed out the door before I could click the send button, I use a button in the Gui to display the hotspot icons. I use the following code:
Code: ags
function repeatedly_execute() {
  if (gInventory.Visible == false) {
    Hotspot *theHotspot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);
    if (theHotspot != hotspot[0]) {
      bIcon.NormalGraphic = HotspotIcon[theHotspot.ID];  <<<==================== WHAT DO I DO HERE=================||
      
      FadeGuiIn_NoBlock(gIconBox, 0, -20);
      FadeGuiIn_NoBlock(gIconBoxback, 30, -20);
    }
    else {
      FadeGuiOut_NoBlock(gIconBoxback, 100, -15);
      FadeGuiOut_NoBlock(gIconBox, 100, -15);
      bIcon.NormalGraphic = 34;
    }
  }
  else {
  }
}


The script compiles fine, however when I move the mouse over a hotspot, no icon is displayed.  :-[

Thanks,
Paul.

monkey0506

#16
The whole idea of extender methods is that you don't access the [Character/Hotspot/Object]Icon arrays directly anyway, you use the [Character/Hotspot/Object].[Set/Get]Icon methods.

For example, to set bIcon's graphic to the hotspot's icon you would do this:

Code: ags
bIcon.NormalGraphic = theHotspot.GetIcon();


Your whole code snippet should go something like this:

Code: ags
function repeatedly_execute() {
  if (gInventory.Visible == false) {
    Hotspot *theHotspot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);
    if (theHotspot != hotspot[0]) {
      bIcon.NormalGraphic = theHotspot.GetIcon();
      FadeGuiIn_NoBlock(gIconBox, 0, -20);
      FadeGuiIn_NoBlock(gIconBoxback, 30, -20);
    }
    else {
      FadeGuiOut_NoBlock(gIconBoxback, 100, -15);
      FadeGuiOut_NoBlock(gIconBox, 100, -15);
      bIcon.NormalGraphic = 34;
    }
  }
  // the last else was empty and therefore unnecessary
}


And of course unless you've actually SET the icon somewhere, it will always return 0, therefore, no icon is displayed. If you want to initialize the hotspots for your room, you could put something like this in your "first time enters room" functions:

Code: ags
// first time player enters room 1
hotspot[1].SetIcon(50); // alternatively in this function (since it's in the room script) you could use the hotspot's script o-names
hotspot[2].SetIcon(51);
hotspot[3].SetIcon(52);
hotspot[4].SetIcon(53);
hotspot[5].SetIcon(54);

// first time player enters room 2
hotspot[1].SetIcon(60);
hotspot[2].SetIcon(61);
hotspot[3].SetIcon(62);
hotspot[4].SetIcon(63);
hotspot[5].SetIcon(64);

// etc., etc., etc.

subspark

Okay all code here is working now. This is pretty cool and I have to thank both you and Ashen for this fantastic functionality. You'll both be included in the credits when this game is past prototype stages.

Thanks again guys!
Paul.

monkey0506

Glad to hear you finally got it working. It's really not a big deal. I'm happy to have helped. ;)

SMF spam blocked by CleanTalk