Custom Inventory-hover over inventory items to reveal an image

Started by Brian925, Thu 05/01/2023 09:38:38

Previous topic - Next topic

Brian925

Hello,
I have a custom inventory GUI and I'm trying to figure out if there is a way to hover over an inventory item and have it reveal an image that explains the purpose of the item. It's difficult for me to navigate the inventory item behaviors in the GlobalScript. I assume I have to repeatedly execute a call out to whether the x and y are on the inventory item, but if the inventory changes position, the x and y will change. Is there a way to directly say "if this inventory item is hovered over with the cursor in interact mode, an image (stored in a view, 3 possible loops that each hold a still image) will pop up"? As a heads up,  my inventory GUI is not in it's own room, it is called out from the top bar and hovers over any room the character is currently in.

Buttons have the option to set another image for hovering, but inventory items don't, so curious.

Thanks

Snarky


Khris

Here's how to detect the two relevant events ("cursor moves on top of item X", "cursor leaves item X"):

Code: ags
// add this above the repeatedly_execute_always function, if it exists
InventoryItem* prevItem;
function handle_inv_images() {
  InventoryItem* currentItem = InventoryItem.GetAtScreenXY(mouse.x, mouse.y);
  if (currentItem != prevItem) {
    if (prevItem != null) {
      // mouse was moved away from prevItem
      // cleanup here, possibly only if currentItem == null
    }
    if (currentItem != null) {
      // mouse was moved over currentItem
      // display info GUI, etc.
    }
  }
  prevItem = currentItem;
}

Code: ags
// this function might already exist, otherwise simply add it
function repeatedly_execute_always() {
  handle_inv_images();
}

Brian925

Thanks, Snarky, Khris, as always your help is appreciated.

Khris, tried your code and it did the job well with a single inventory item. Within the code you posted, is it possible to set that up for unique inventory items, i.e, iTickets shows GUI1, iGlove shows GUI 2, etc? The inventory items are dynamic, so they are not always in the same XY position, they move when inventory comes and goes. My first instinct is to do some sort of if/than with an Active Inventory Command, but i'm not sure how to call it out

Khris

You shouldn't have to use multiple GUIs for this. Say you have a single GUI with a button (btnImage) and a label (lblDescription).

You can now do this:
Code: ags
  btnImage.NormalGraphic = image[currentItem.ID];
  lblDescription.Text = text[currentItem.ID];

This requires two arrays populated by you in game_start, like:
Code: ags
int image[20];
String text[20];

function game_start() {
  image[iKeycard.ID] = 123; // keycard sprite
  text[iKeycard.ID] = "It's the keycard you found in the dining room.";
  // etc.
}

You can also use custom properties for this; add two properties that apply to inventory items only, an integer ("image_slot") and a text one ("description"). Then just enter the info in there for each item. Finally, use these lines instead:

Code: ags
  btnImage.NormalGraphic = currentItem.GetProperty("image_slot");
  lblDescription.Text = currentItem.GetTextProperty("description");

Brian925

Code: ags
 btnImage.NormalGraphic = image[currentItem.ID];

Is the button the only thing I need to customize in this part of the code? I wasn't sure if other things needed replacement or if they were calling out to other parts of the code. Can you explain what the right hand side of this is doing? I know it's saying that my button should show up depending on what the current item is, but I don't know where it's getting the information from.

Is 'images' in line 5 supposed to be 'image'?

Khris

Yeah, fixed.

The idea is to store a sprite slot (integer) for each item. You can do this via custom properties, or via an array. If you use an array, you can use the inv item's ID as array index.

The array code doesn't need anything else, the right side is just a single element of the image array, i.e. the sprite slot.

int image[20]; creates 20 integers, image[0] to image[19].
If the keycard has an ID of 3, line 5 becomes image[3] = 123;
Later, when the player is hovering over the keycard and thus currentItem == iKeycard, this number is now read back and assigned as NormalGraphic to the button.

Brian925

Thank you for being so thorough.

There is something about this code that isn't clicking with me and I've been fighting with it for a couple days.
Right now I have this:
Code: ags
int image[20];
at the top of my global script, not embedded in a function.

then I have this:
Code: ags
 {
image[6] = 123; // keycard sprite
}
in my game start function

Finally I have this:
Code: ags
  bWindowTickets.NormalGraphic = image[34]
as unembeded in the global script.

I'm realizing that the original code was using a text string as a return for the mouse over, but I took it out because my inventory item hover would create an IMAGE, not text. What I thought I was doing here was mousing over iTickets (which I changed to image 6) and returning bWindowTickets, which is a button in a GUI.

Right now it is currerntly returning Parse Error, unexpected bWindowTickets.

bWindowTickets is a button in a GUI, named properly in the design properties. That's where I'm at.


Khris

My original code assumed that hovering an item would
a) display a sprite on a GUI button
and
b) display text via a GUI label

a) requires storing a sprite slot for each inv item
b) requires storing a string for each inv item

Hence two arrays, an int one for the sprite slots, and a string one for the texts.

There's three issues with
Code: ags
bWindowTickets.NormalGraphic = image[34]
1. no semi-colon at the end of the line
2. a hard-coded 34, which makes no sense, since the code/line is supposed to work for each item dynamically
3. unembedded, which will not work at all; assignments can only happen inside functions

The statement should go in line 12 of my original reply, and it should use currentItem

Brian925

Thanks. After some research I was kind of able to mimic what you did with the int and String arrays in a blank game.

I'm curious though, (and I've attached an image) how I could apply this to inventory images that return an image value that is always in the same spot even though the inventory positions will change once they are used?

Since the inventory is already stored, do I need to create an array for that, or are the arrays specifically for the return values?

In theory, (forgive me, still learning) could I do something like this?

Code: ags
int image[20];
at the top of the global script to create the array for the window images

Code: ags
function game_start() {
  image[iTickets.ID] = bWindowTickets; // keycard sprite
  image[iBlender.ID] = bWindowBlender;
  image[iGlove.ID] = bWindowGlove;
// trying to connect array GUI buttons to their inventory counterparts
}
to set the descriptions

and then populate that information into this?:


Code: ags
InventoryItem* prevItem;
function handle_inv_images() {
  InventoryItem* currentItem = InventoryItem.GetAtScreenXY(mouse.x, mouse.y);
  if (currentItem != prevItem) {
    if (prevItem != null) {
      // Mouse OFF the current item
      // cleanup here, possibly only if currentItem == null
    bDefaultWindow.Visible=true;
    
    }
    if (currentItem != null) {
      // mouse OVER currernt item
      // display info GUI, etc.
      bDefaultWindow.Visible=false;
//populate the windows here?
    }
  }
  prevItem = currentItem;
}

I appreciate the patience, trying to study up on my own as much as possible



Khris

It sounds like you want a button for each item. This is possible but not necessary, all you need is the sprite slot for each item, then put the associated image on a single, static button.

Let's say you've created the description image for iTickets and imported it into AGS. It ends up being sprite #73.
To show the correct image when the player is hovering over iTickets, you want to display sprite 73.
The easiest way to display an image in a GUI is to add a button to the GUI, make it "dead" (i.e. not react to clicks / hovering) and set the sprite as the button's .NormalGraphic.

Let's assume the button is called "btnItemDesc".
What we now want is code that essentially does btnItemDesc.NormalGraphic = 73; if the currentItem is iTickets.
That code is also supposed to use a different image / sprite slot number, when currentItem is iBlender, and so on.

The long way to do this is:
Code: ags
  if (currentItem == iTickets) btnItemDesc.NormalGraphic = 73;
  else if (currentItem == iBlender) btnItemDesc.NormalGraphic = 74;
  // ... lots more of the same duplicate mess here

This is incredibly ineffective obviously, given that we can use a single line instead. This requires to somehow get from currentItem == iTickets to 73, but also from currentItem == iBlender to 74, same for the rest of the items. The ideal system is always one that requires as few changes to existing code as possible when you add or change something elsewhere, ideally zero changes.

There are multiple ways to do this. Using a table where AGS can look up the number is one way, this is implemented using an array of integers.

Another way is to use specific numbers for the sprites and basic addition (this is a new solution I somehow didn't mention yet). After importing a sprite, you can change its slot number by right-clicking it. You can now pick any unused slot. Assuming iTickets is the first inv item and thus its ID is 1, you can change the slot number of the description sprite to for instance 101. Or 501. Doesn't really matter, as long as the difference between an item's ID and the associated sprite slot is always the same. (I'll use 100 for the example code below)

Now in the code we can do this:
Code: ags
    if (prevItem != null) {
      // Mouse OFF the PREVIOUS item
      // cleanup here, possibly only if currentItem == null
      btnItemDesc.Visible = false; // we actually want to hide the button here
    }
    if (currentItem != null) {
      // mouse OVER CURRENT item
      // display info GUI, etc.
      btnItemDesc.NormalGraphic = currentItem.ID + 100; // we no longer need an array
      btnItemDesc.Visible = true; // show button
    }

(To clarify the meaning of "current" and "previous" item, this is in the context of a single frame, i.e. 1/40th of a second by default; "currentItem" is the item under the mouse in the current frame, "previousItem" is the item under the mouse 1/40 seconds ago)

Snarky

(Got sniped by Khris, but posting anyway in case having it put a different way helps you grasp it more easily.)

Why do you think you need multiple separate buttons? If you're only displaying one image/button at any one time, you should just have one button, and change its graphic to the image you want to display whenever that changes. And that's pretty much what the original code by Khris does.

Analogies can sometimes be more confusing than helpful, but to me your question sounds like, having understood how to link a TV program to a particular TV channel, you now want to figure out how to link each TV channel to a separate TV, because you think that every time you want to watch a particular show you will need to disconnect the current TV, then look up which TV is tuned to the station you want, and plug that one in. And maybe that would be possible, but actually the right way is to just look up what channel the program you want to watch is on, and switch your one TV to that station.

I don't mean to imply that your question is stupid in any way. Just that I think you've got the mental model of what you need to do a bit wrong.

Brian925

Thanks guys,

Snarky, I originally thought the most effective way to return the images was in a GUI button from Khris' original explanation that returned a string. I see now that it adds steps where it doesn't belong for returning images instead of text.

I agree about the wrong mental model. As a self taught coder, I have a lot of blind spots. In the past, I could survive with a bunch of if/than statements, but this time around, I'm trying to really understand putting arrays together and consolidating my code. When I'm given example code, I'm not always sure what I should replace with my content, which parts are returning previous code and which parts are calling premade functions I'm not aware of. Just need more hours.

Khris, that one worked perfect. Kind of a cool concept to label the sprite number at a recognizable interval to save some coding time. Thanks so much for both of your help.




SMF spam blocked by CleanTalk