ID:32442
 
A BYONDscape Classic!


Welcome back. Today's tip will show you how to add a user-friendly button allowing users to join and start games, like I have in my game Runica.



When figuring out how to code a new feature for your game, it helps to decide just what you want the feature to do. We need:

1. An icon

2. That sits in a stat panel

3. That does things when you click it.

Objs have icons to represent them, and objs have Click() procs, and objs can sit in stat panels; therefore, we will use an obj. Additionally, we want our button to say "join" if a user hasn't joined the game, and "start" if he has. That means our button will show different things to different users... so we also need:

4. Each user to have his own button

5. "Join" and "start" icon_states for the button's icon.

Let's make a button obj first.

obj
button
icon = 'button.dmi'


Now, of course, we have to make button.dmi. Make a "join" and a "start" icon_state. (For those unfamiliar with icon states, each time you click the palette button, you're making a new one... double-click underneath the new image to name it.) For optimal user-friendliness, your "join" icon_state should be an image of the word "join," and your "start" state should say "start."

So. We've made the prototype for our button. Now to ensure that each user gets one:

mob
var
obj/button/mybutton

Login()
mybutton = new
mybutton.icon_state = "join"
..()


There. What we just did was to create a new mob variable, named "mybutton," designed to hold an obj/button. We put a couple lines in mob/Login() as well that create a new obj/button for the user when he logs in, placing it in that mybutton variable, and making its icon_state "join." Now each user has their own button, which will say "join" when they enter the game.

Unfortunately, they won't be able to see it, since we haven't displayed it anywhere. For that we need a stat panel.

client
Stat()
statpanel("Game")
stat("Click to",mob.mybutton)


This creates a new panel called "Game," in which users will see the words "Click to" followed by a button saying "join." (If you prefer, you can insert the stat code line after a statpanel that already exists in your game.)

Now for making the button do what it's supposed to. If we're going to be differentiating between playing and watching users (which we are!), we need a variable for that. I lean toward a global list of players you can add people to. And we need to alter the button prototype to make use of its Click() proc.

var/list/players

obj
button
icon = 'button.dmi'
Click()
if (icon_state == "join")
world << "[usr] joins the game!"
players += usr //add user to list of players
icon_state = "start"

if (icon_state == "start")
world << "[usr] starts the game!"
Start() //go to the proc that starts your game


Think fast! Can you spot the problems with the above code? Well, I'll tell you.

1. The join code doesn't check to see if the user is already joined

2. The start code doesn't check to see if the game is already started

3. Worst of all, we check to see if the icon_state is "start" right after the join code sets it to "start"!

These are common runtime bugs for game-joining code. Let's fix them.

list/players
var/started //new variable to tell us if the game has started yet

obj
button
icon = 'button.dmi'
Click()
if (icon_state == "join")
if (playing.Find(usr))
usr << "You have already joined."
return 0

world << "[usr] joins the game!"
players += usr
icon_state = "start"
return 1

if (icon_state == "start")
if (started)
usr << "Pay attention! The game has already started!"
return 0

world << "[usr] starts the game!"
started = 1
Start()
return 1


What we return doesn't matter as much as the fact that we're returning, but in situations where it doesn't matter (no other proc called this one looking for an answer), I like to use "return 1" to indicate that the user was successful in what he was trying to do, and "return 0" to indicate that he wasn't. It might come in handy if I'm quickly scanning my code.

Now we have a basic joining and starting system! You might add other niceties yourself, such as an if statement in client/Stat() so the "Click to" stat line is shown only when no game is in progress, or code to make the button switch back to "join" again when the game has ended. Or you can use mob variables, stat panels, and obj/Click() to make a whole interface with lots of buttons that do different things... toggle sounds on and off, make your character invisible, heck, even switch you to other stat panels. The world of user-friendly buttons is your oyster, my friend.

Z's Peeves

Stat panels have some great uses in games, but like just about everything, it's possible to use them unwisely. Stat panels rarely hurt a game... until you have so many that the user has to scroll with those little left and right arrows to get to them all. If the first use of stat panels is to have quick access to important info or commands, making them hard to get to defeats the purpose.

When your game has so many stat panels that their tabs can't all be seen at once, consider removing some. Ask your players which panels are the most useful and turn the rest into verbs. Using these new verbs would display the relevant info right in the text window. Some players are more mouse-oriented, and some are more keyboard-oriented; you can even consider making verb-activated alternatives for all your stat panels. That way, players who prefer typing "inv" to clicking the inventory tab will be happy. Until they find something else to complain about.
There must be some typo going on in the last code sample. There's clearly some inconsistent indentation after the first if() in the Click() proc, which I'm guessing should all be indented twice to the right.
Good catch! There were some spaces and tabs intermingled in there, and it must have broken when I replaced the PRE tags with DM tags. That's the kind of quality editorship we need!
A better way to make those buttons in the statpanel is to have a global object rather than a local one, as it won't count towards the object limit.

But how do you assign different icons for different players to it then? Images.

Just let your global statobject create an image at New(). When a player needs to see a different state, add the appropriate image to the client's screen.
var/list/players :)
AD: that makes sense -- though I suspect if a game is in danger of hitting the object limit, a single statpanel button object for each player is probably the least of the programmer's problems. :)

XDWX: good catch! Fixed.
Wow thanks i've always wounder how to do that.