"Teleportation" system with GUI buttons SOLVED!

Started by Akril15, Thu 29/02/2024 07:43:03

Previous topic - Next topic

Akril15

I'm trying to come up with a script that's quickly becoming more and more complicated as I try to work out the details.

Long story short, I'd like one button in center of the Sierra-style icon bar that makes a menu appear under the icon bar with a row of buttons on the bar which represent rooms the player can "teleport" to. (I also want this button to double as a custom cursor mode.)

The complexity arises from:
1) The number of locations changing during the course of the game depending on whether the player has unlocked an available location or not
2) Whether the player is in one of the available locations or not
3) Wanting the row of buttons to be centered under the button that makes the row visible (the issue that's giving the biggest problem)

I created something like this in a previous game, but the code for that was pretty messy, and I'm sure there's a much more streamlined way of doing it that would also implement the features mentioned above. I got as far as several lines of "if player is in one of the rooms associated with Destination A, make Button 1 be visible and have the graphic for Destination B and be at X=200, make Button 2 visible and have the graphic for Destination C, etc." before realizing that this just wasn't going to work.

Any advice on how I could approach this issue would be much appreciated. Thanks in advance!

NB: At the moment, the buttons are part of the icon bar GUI. I was considering relocating them to a GUI of their own (which might make the centering problem more manageable), but I thought I'd ask for tips here before I spent any more time banging my head against the wall.

Khris

#1
Start with a list of locations:

Header:
Code: ags
enum TeleportLocationName {
  eTLEngineRoom, eTLCrewQuarters
};

struct str_TeleportLocation {
  bool Available;
  int SpriteSlot;
  int RoomNumber;
  int X;
  int Y;
};

import str_TeleportLocation TeleportLocation[10];

Main:
Code: ags
str_TeleportLocation TeleportLocation[10]; export TeleportLocation;
int tlCount;

  // in game_start
  TeleportLocation[eTLEngineRoom].SpriteSlot = 123;
  TeleportLocation[eTLEngineRoom].RoomNumber = 2;
  TeleportLocation[eTLEngineRoom].X = 123;
  TeleportLocation[eTLEngineRoom].Y = 45;
  // etc.
  tlCount = 6; // 6 total 

Setting up the buttons is done by iterating over the list:

Code: ags
void SetUpTeleportButtons() {
  int firstTeleportButtonID = 11; // actual ID goes here
  int availableCount = 0;
  // set button sprites and count available locations
  for (int i = 0; i < tlCount; i++) {
    if (TeleportLocation[i].Available && TeleportLocation[i].RoomNumber != player.Room) {
      Button* b = gIconbar.Controls[availableCount + firstTeleportButtonID].AsButton;
      b.NormalGraphic = TeleportLocation[i].SpriteSlot;
      availableCount++;
    }
  }
  // position buttons and turn them (in)visible
  for (int i = 0; i < tlCount; i++) {
    Button* b = gIconbar.Controls[i + firstTeleportButtonID].AsButton;
    b.Visible = i < availableCount;
    b.X = 160 - (availableCount * (b.Width + 2) - 2) / 2 + i * (b.Width + 2);
  }
}

Here's the onClick for all buttons
Code: ags
function btnTeleport_OnClick(GUIControl* control, MouseButton button) {
  Button *b = control.AsButton;
  if (b == null || button != eMouseLeft) return;
  for (int i = 0; i < tlCount; i++) {
    if (TeleportLocation[i].SpriteSlot == b.NormalGraphic) {
      player.ChangeRoom(TeleportLocation[i].RoomNumber, TeleportLocation[i].X, TeleportLocation[i].Y);
    }
  }
}

Akril15

#2
Wow, thank you so much, Khris! I'm still trying to get my head around some of the code, but I think I have a rough idea about what it does.

I think I've put everything where it's supposed to go and made the required changes to make it work, but I've still got a couple of questions:

1) What toggles the row of location buttons on and off? Nothing showed up when I tried putting TeleportLocation[eTL_bw].Available=true; in game_start (I renamed eTLEngineRoom to eTL_bw) and running SetUpTeleportButtons(); when the "turn on locations" button on the icon bar is clicked. Got this figured out.

2) What does the 10 in TeleportLocation[10]; denote? It seems to mean the total number of locations, but I thought that was what tlCount referred to.

EDIT: I've got a non-clickable button that appears beneath the row of location buttons. I got it to appear by adding a line to SetUpTeleportButtons();, but the one button that should be showing up still doesn't.
I got the button to show up, but it's not centered yet.
I got one button to be centered by changing telbutton.X to half of my game's width (obvious in hindsight), but I'm having trouble getting a second button to appear.

Khris

#3
The 10 is the maximum number. tlCount is the actual number.

So if you have seven locations total, just set it to 7 in game start. It's mostly for the loops that iterate over all locations.

Assuming you have seven locations and five are available, and the buttons have the IDs 11 to 17, the code should position them correctly, but I haven't tested any of it.

Say a button is 30 pixels wide, then for the first button, 160 - (availableCount * (b.Width + 2) - 2) / 2 + i * (b.Width + 2) is 160 - (5 * (30 + 2) - 2) / 2 + 0 * (30 + 2) which is 160 - 158 / 2 or 81.
The next button should be at 81 + 32 i.e. 113, then 145 (the center button), 177 and 209. The last button ends at 239, which is 81 pixels from the right edge.

If your game uses a higher resolution, simply replace the 160 at the start with half your screen width or Screen.Width / 2.
Or gIconbar.Width / 2.

Crimson Wizard

Quote from: Khris on Fri 01/03/2024 13:00:34If your games uses a higher resolution, simply replace the 160 at the start with half your screen width or Game.Camera.Width / 2.

Buttons are placed in screen coordinates, but Camera is in room coordinates, and it tells the size of the view inside room.

The proper screen size is Screen.Width and Screen.Height.

Akril15

#5
The maximum number of locations visible at any time should be 9 (tlCount is set to 13), but if I set TeleportLocation to 9, I get an "Array index out of bounds" error when I try starting the game, highlighting the first line of the 9th destination recorded under game_start (SpriteSlot, Room, X, Y). If I set TeleportLocation to 14, howeverm the game starts fine. (I also made sure that there are no skips in the GUI button ID number sequence.)

My buttons are 60 pixels wide and my game is 1067 pixels wide (an unfortunate early design mistake ), so I've set telbutton.X to 533. I still only see one button when I've set two buttons to be active, though.

I also just realized that the button I set to appear isn't even using the appropriate graphic -- it's using the same one I replaced the default gray rectangle with, even though I assigned the right sprite to it.

I'm also not clear on how to set the various destinations as available/not available based on which rooms the player is in (each destination is made up of multiple rooms, but the player can only teleport to one of the rooms) and whether or not the destination in question has been unlocked. I'm guessing that this section has to do with it, but I don't quite understand how to make use of it:

Code: ags
  // set button sprites and count available locations
  for (int i = 0; i < tlCount; i++) {
    
    if (TeleportLocation[i].Available && TeleportLocation[i].RoomNumber != player.Room) {
      Button* telbutton = gIconbar.Controls[availableCount + firstTeleportButtonID].AsButton;
      telbutton.NormalGraphic = TeleportLocation[i].SpriteSlot;
      availableCount++;
    }



Khris

If you have a total of 13 locations, you need to

a) replace the 10 in both the import and declaration line with 13
b) in game_start, set TeleportLocation[0] to TeleportLocation[12], then tlCount to 13
(This is because arrays use zero-based indexing.)

Next, set TeleportLocation[?].Available to true or false at any point during the game, then call SetUpTeleportButtons() to update the buttons' visibility and position (alternatively, call it when the player clicks the teleport button)

As for a location being connected to multiple rooms, that's not reflected by the code yet. I'll get to that later today or tomorrow.

Akril15

I didn't see TeleportLocation[0] anywhere in your original post. If I add TeleportLocation[12] to game_start before tlCount, the game gives me a parse error at the TeleportLocation line when I try to start it.

I've also figured out how to use TeleportLocation[?].Available. Thanks again for your help.

Khris

#8
I was using an example enum so you don't have to remember the index numbers but can use names instead.
Not sure if the first enum value is 0 if you don't state it explicitly though.
If it's 1 instead, you can use
Code: ags
enum TeleportLocationName {
  eTLEngineRoom = 0, eTLCrewQuarters = 1, ...
};
then use these enum values to set up your locations.

Also, when you get error messages, please always show your actual code and the exact error message. You shouldn't get a parse but an out of bounds error, so something else might be wrong.

Akril15

#9
I added numbers to the entries in enum TeleportLocationName, starting with 0 and ending with 12, and the game now starts without any problems.

I'm still seeing the wrong sprite used for the one location I've made available, even though I made sure that the sprite I wanted that location to use was declared in TeleportLocation[name].SpriteSlot = 32145;. Whatever graphic I use for the first location button is the one that ends up being used by the game, even if I try changing it with TeleportLocation[name].SpriteSlot.

EDIT: Also, I still can't get more than one location button to appear when I set multiple locations as available. Only the first one shows up.

Khris

Found the main problem:
Code: ags
    b.X = (160 - (availableCount * (b.Width + 2) - 2) / 2) + i * (b.Width + 2);

Not sure why you need the parens though.

Akril15

Quote from: Khris on Sun 03/03/2024 17:17:39Found the main problem:
Code: ags
    b.X = (160 - (availableCount * (b.Width + 2) - 2) / 2) + i * (b.Width + 2);
I replaced the old line with your new one, but I'm still getting the same problems.

QuoteNot sure why you need the parens though.
Could you please elaborate? I'm unfamiliar with a lot of the methods you're using.

Khris

The formula to calculate the x coordinates was apparently subtracting the entire rest of the term from 160; I added parenthesis to prevent this and then the buttons were placed correctly for me.
I was confused why you need them because the formula is basically 160 - A + B and it seemed to evaluate to 160 - (A + B) so I used (160 - A) + B which fixed the button placement for my test.

Akril15

Okay, that makes things a little clearer. Why is only one button showing up regardless of how many I set as available, and why is the wrong sprite being used even when I tell the game to use a different one with TeleportLocation[name].SpriteSlot?

Khris

Good question; hard to tell from afar.
You could add Display commands to check where buttons are positioned and which sprite is being used. The buttons also need to have the correct .Y, .Width and .Height already.

Akril15

I need a little while to think this over. I may try a different approach that still uses some of your code.

Akril15

I got the locations to show up properly using a different function. It's pretty long and clunky, but it gets the job done.

I'm just having trouble with the onClick function now. I modified your code to this:

Code: ags
function btnTeleport_OnClick(GUIControl* control, MouseButton button) {
  Button *telebtn = control.AsButton;
  if (telebtn == null || button != eMouseLeft) return;
  
  if (telebtn.NormalGraphic==bw_graphic) { //bw_graphic is an int that stores this location's sprite number
    //do stuff
    }
   //...
}

The game starts all right, but nothing happens when I click on any of the buttons I've made available. I've made sure the buttons are clickable and tried displaying a message after the if telebtn == null line, and nothing changes. Nothing past that line seems to work.

Khris

You can remove the line; it makes sure you actually clicked a button, but that is a given anyway unless you enter the function name into some other GUI element's event table.

And it should only react to a left mouse click, which is also a given I guess.

My original code compares the button's image to all location images using a loop; I don't think a single comparison is enough here.

Akril15

#18
I tried removing that line, but the problem is still there.

I've got a bunch of ints at the top of the script (which I should mention isn't the Global script):
Code: ags
int bw_graphic=32145; //number is bw's sprite number
int maze_graphic=32148; //number is maze's sprite slot
int island_graphic=32149; //number is island's sprite slot

Then in your code, I use them to refer to the location buttons:

Code: ags
function btnTeleport_OnClick(GUIControl* control, MouseButton button) {
  Button *telebtn = control.AsButton;
 if (telebtn.NormalGraphic==bw_graphic) {
     player.ChangeRoom(22); //bw
    }

   else if (telebtn.NormalGraphic==maze_graphic) {
     player.ChangeRoom(23); //maze
   }
   
   else if (telebtn.NormalGraphic==island_graphic) {
     player.ChangeRoom(34); //island
   }
}

I tried exporting one of the ints I was using and moving one of them to the Global Variables panel. No change.

EDIT: I thought I needed to put btnTeleport_OnClick(GUIControl* control, MouseButton button); in the buttons' script, but I got an error when I tried that (Parse error in expr near 'MouseButton').

Khris

#19
If my code is in the global script, you can paste "btnTeleport_OnClick" directly into each button's onClick event field (you don't need a separate function for each button).
If you've moved the teleport code to a new script, you will need this:

Code: ags
// your button function
function YourButton_OnClick(GUIControl* control, MouseButton button) {
  // forward call to module / my function
  btnTeleport_OnClick(control, button); // pass along the parameters
}
You can of course again reuse this function for all other buttons.

And you will need to import the function in the module's header so it's visible from the global script:
Code: ags
// module header
import function btnTeleport_OnClick(GUIControl* control, MouseButton button);

SMF spam blocked by CleanTalk