[Tutorial] How to create a speedometer
#1

How to create a speedometer
This tutorial is in response to this thread in which I offered to create a tutorial. Most of the tutorials on speedometers are pretty old (some from 2010), don't use per-player textdraws and are thus seriously outdated. The following tutorial is based on Fallout's SpeedoMeter I came across long ago. This tutorial assumes you know basic pawn. That is variable declaration, variable initialization, arrays, loops, tags, callbacks and functions. If you don't know what some of these mean, maybe you know the concepts, but you just didn't knew their names. So just read on.


This is what we'll be creating (sort of):


1. Introduction:

(Note: it seems that this introduction has enough information, that it has become its own tutorial.)

This tutorial involves textdraws so we'll need to know what those are first. Even though this information can be found on the wiki, what follows are some things that I find important enough to include in this tutorial. If you already know what the difference is between a global and a per-player textdraw, skip to 2. But I do recommend just reading it quickly, maybe you find out something you didn't knew.

1.1 What is a textdraw:

A textdraw is text that can be drawn anywhere on a player's screen for an indefinite period of time. It can be simple text like a clock, a website address, a score and so on. It can also be a something complex like a health bar, show preview models, show sprites and so on. The text can be drawn in a box. We can also only use the box, by providing empty text. When you search on ******, you can find some nice examples of what is possible with textdraws.

1.2 Kinds of textdraws:

There are two kinds of textdraws: global and per-player, which we'll explore in the following sections.

1.2.1 Global textdraw:

A global textdraw is the same for every player, for example a clock. Either it is shown to a player or it is not. You can only create 2048 global textdraws! A solution to this problem is explored in the following section about Per-player textdraws. Continue first with this section though, so the next one will be easier to understand.


Example:
  • We have a clock textdraw that shows 10:50.
  • Player0 sees the textdraw.
  • Player1 sees the textdraw.
  • Player2 doesn't see the textdraw.
  • When the time changes, we want this textdraw to show the new time, and every player currently seeing the textdraw will see the updated time.
1.2.1.1 Declaration:
Code:
new Text:myFancyTD;
As you can see, we use the 'Text' tag to indicate that the 'myFancyTD' variable is a textdraw. Another example of a tag is 'Float'.
Code:
new Float:myFancyFloat;
See the similarities?

1.2.1.2 Initialization:

We also need to initialize the variable with something, otherwise this textdraw might conflict with others as you add more. It is actually very important that you do this, otherwise the first textdraw you create will work fine, but when you add more and more some might start to show information of others and have some other weird behaviour. It's really hard to debug and to find where the problem lies, because the compiler will give you no error or warning. So don't forget to do this.
Code:
new Text:myFancyTD = Text:INVALID_TEXT_DRAW;
Don't forget to use the 'Text' tag as well before 'INVALID_TEXT_DRAW'.

1.2.1.3 Functions:

A list of the functions can be found here. Note that they all have 'TextDraw' in their name. You can not use a global textdraw function on a per-player textdraw, or vice versa. Otherwise the compiler will give you a tag mismatch warning.

1.2.2 Per-player textdraw:

A per-player textdraw is unique for every player, for example the speed of his vehicle. That means that every player can see the textdraw with personal information in it, or not see the textdraw. You can create 256 per-player textdraws for every player! That's a whole lot more than just 2048 global ones.


Example:
  • We have a score textdraw that shows the player's score.
  • Player0 has score 50, so his textdraw needs to show 50.
  • Player1 has score 100, so his textdraw needs to show 100.
  • Player2 doesn't see his textdraw.
  • When player0's score changes we only want his textdraw to change, not other player's textdraws.
1.2.2.1 Declaration:
Code:
new PlayerText:myFancyTD[MAX_PLAYERS];
As you can see, we use the 'PlayerText' tag this time, not the 'Text' tag. The 'myFancyTD' textdraw is also an array this time. This is necessary because as we stated before, the textdraw is unique for every player, so we need a separate instance for every player.

1.2.2.2 Initialization:

Again, we need to initialize the variable with something to avoid conflicts. This time, we need to initialize every elemnent of the array. This can be done with the {, ...} syntax.
Code:
new PlayerText:myFancyTD[MAX_PLAYERS] = {PlayerText:INVALID_TEXT_DRAW, ...};
Don't forget to use the 'PlayerText' tag as well before 'INVALID_TEXT_DRAW'.

1.2.2.3 Functions:

A list of the functions can be found here. Note that they all have 'PlayerTextDraw' in their name, not just 'TextDraw'! Again, you can not use a global textdraw function on a per-player textdraw, or vice versa. Otherwise the compiler will give you a tag mismatch warning.

1.3 Tool to simplify the creation of textdraws:

Since textdraws can only be created with code, it can be quite difficult to get some things right. So to develop your textdraws visually, there exists a handy tool by adri1. Just keep on reading for now, you can play with that tool later when you understand how everything works.

2. Declaring and initializing the necessary variables:

We'll be using both kinds of textdraws in this tutorial. Global textdraws will be used to create a nice looking UI. They are definitely not needed to make the speedometer work, but I wanted to show both kinds of textdraws. Per-player textdraws will be used to show the personal information. If you don't understand what some of following code does, go back to 1.


The following code should be placed at the top of your script, under the includes and defines.

2.1 Global textdraw variables:

We need 5 global textdraws: 4 black lines and 1 transparent background (see picture). The black lines and background are the same for every player, so that's why we can use global textdraws here.
Code:
new Text:SpeedoBackground = Text:INVALID_TEXT_DRAW;
new Text:SpeedoLineAbove = Text:INVALID_TEXT_DRAW;
new Text:SpeedoLineLeft = Text:INVALID_TEXT_DRAW;
new Text:SpeedoLineRight = Text:INVALID_TEXT_DRAW;
new Text:SpeedoLineUnder = Text:INVALID_TEXT_DRAW;
2.2 Per-player textdraw variable:

We need 1 per-player textdraw, that holds the information about the vehicle the player is currently in (see picture).
Code:
new PlayerText:SpeedoCarInfo[MAX_PLAYERS] = {PlayerText:INVALID_TEXT_DRAW, ...};
2.3 Bool variable:

We also need to keep track if the speedometer is shown to a player. For example, we don't need to hide it when it's already hidden. Therefore we need an array of bools, and thus the 'bool' tag. We also use array initialization to initialize every element of the array.
Code:
new bool:SpeedoShown[MAX_PLAYERS] = {false, ...};
2.4 Timer variable:

At last, we need one timer that will work for all players. No need to use a timer for each player.
Code:
new SpeedoStatusTimer = -1;
3. Setting up everything before we use it + cleanup:

I'm not going to explain the functions and parameters, since this tutorial is already longer than I wanted it to be and this is not a tutorial on textdraws or timers. Refer to the wiki to know what every function does.

3.1 Global textdraws:

Since global textdraws are the same for every player, we only need to create them once. We will do this when the server starts, more specifically, under OnGameModeInit. All functions have 'TextDraw' in their name, not 'PlayerTextDraw'!

3.1.1 OnGameModeInit:

None of the global textdraws have text. We can create a textdraw without text by using an underscore as the text. We can make a textdraw box transparent by using the alpha values of the color.
Code:
SpeedoBackground = TextDrawCreate(481.0,353.0,"_"); //No text
TextDrawUseBox(SpeedoBackground,1);
TextDrawBoxColor(SpeedoBackground,0x00000033); //20% visible, so very transparent
TextDrawTextSize(SpeedoBackground,596.0,0.0);
TextDrawAlignment(SpeedoBackground,0);
TextDrawBackgroundColor(SpeedoBackground,0x000000ff);
TextDrawFont(SpeedoBackground,3);
TextDrawLetterSize(SpeedoBackground,1.0,9.5);
TextDrawColor(SpeedoBackground,0xffffffff);
TextDrawSetOutline(SpeedoBackground,1);
TextDrawSetProportional(SpeedoBackground,1);
TextDrawSetShadow(SpeedoBackground,1);

SpeedoLineAbove = TextDrawCreate(481.0,353.0,"_"); //No text
TextDrawUseBox(SpeedoLineAbove,1);
TextDrawBoxColor(SpeedoLineAbove,0x000000ff); //100% visible, so solid
TextDrawTextSize(SpeedoLineAbove,596.0,0.0);
TextDrawAlignment(SpeedoLineAbove,0);
TextDrawBackgroundColor(SpeedoLineAbove,0x000000ff);
TextDrawFont(SpeedoLineAbove,3);
TextDrawLetterSize(SpeedoLineAbove,1.0,-0.10);
TextDrawColor(SpeedoLineAbove,0xffffffff);
TextDrawSetOutline(SpeedoLineAbove,1);
TextDrawSetProportional(SpeedoLineAbove,1);
TextDrawSetShadow(SpeedoLineAbove,1);

SpeedoLineLeft = TextDrawCreate(481.0,353.0,"_"); //No text
TextDrawUseBox(SpeedoLineLeft,1);
TextDrawBoxColor(SpeedoLineLeft,0x000000ff); //100% visible, so solid
TextDrawTextSize(SpeedoLineLeft,480.0,0.0);
TextDrawAlignment(SpeedoLineLeft,0);
TextDrawBackgroundColor(SpeedoLineLeft,0x000000ff);
TextDrawFont(SpeedoLineLeft,3);
TextDrawLetterSize(SpeedoLineLeft,1.0,9.50);
TextDrawColor(SpeedoLineLeft,0xffffffff);
TextDrawSetOutline(SpeedoLineLeft,1);
TextDrawSetProportional(SpeedoLineLeft,1);
TextDrawSetShadow(SpeedoLineLeft,1);

SpeedoLineRight = TextDrawCreate(597.0,353.0,"_"); //No text
TextDrawUseBox(SpeedoLineRight,1);
TextDrawBoxColor(SpeedoLineRight,0x000000ff); //100% visible, so solid
TextDrawTextSize(SpeedoLineRight,596.0,0.0);
TextDrawAlignment(SpeedoLineRight,0);
TextDrawBackgroundColor(SpeedoLineRight,0x000000ff);
TextDrawFont(SpeedoLineRight,3);
TextDrawLetterSize(SpeedoLineRight,1.0,9.50);
TextDrawColor(SpeedoLineRight,0xffffffff);
TextDrawSetOutline(SpeedoLineRight,1);
TextDrawSetProportional(SpeedoLineRight,1);
TextDrawSetShadow(SpeedoLineRight,1);

SpeedoLineUnder = TextDrawCreate(481.0,440.0,"_"); //No text
TextDrawUseBox(SpeedoLineUnder,1);
TextDrawBoxColor(SpeedoLineUnder,0x000000ff); //100% visible, so solid
TextDrawTextSize(SpeedoLineUnder,596.0,0.0);
TextDrawAlignment(SpeedoLineUnder,0);
TextDrawBackgroundColor(SpeedoLineUnder,0x000000ff);
TextDrawFont(SpeedoLineUnder,3);
TextDrawLetterSize(SpeedoLineUnder,1.0,-0.10);
TextDrawColor(SpeedoLineUnder,0xffffffff);
TextDrawSetOutline(SpeedoLineUnder,1);
TextDrawSetProportional(SpeedoLineUnder,1);
TextDrawSetShadow(SpeedoLineUnder,1);
3.1.2 OnGameModeExit:

It's always good to clean up everything, especially when you use the '/rcon gmx' command. Destroy the global textdraws and reset the variables.
Code:
TextDrawDestroy(SpeedoBackground);
TextDrawDestroy(SpeedoLineAbove);
TextDrawDestroy(SpeedoLineLeft);
TextDrawDestroy(SpeedoLineRight);
TextDrawDestroy(SpeedoLineUnder);
SpeedoBackground = Text:INVALID_TEXT_DRAW;
SpeedoLineAbove = Text:INVALID_TEXT_DRAW;
SpeedoLineLeft = Text:INVALID_TEXT_DRAW;
SpeedoLineRight = Text:INVALID_TEXT_DRAW;
SpeedoLineUnder = Text:INVALID_TEXT_DRAW;
3.2 Per-player textdraws:

Since per-player textdraws are unique for every player and are automatically destroyed in OnPlayerDisconnect (see wiki), we need to create them every time a player joins. So, that is under OnPlayerConnect. All functions have 'PlayerTextDraw' in their name, not just 'TextDraw'!

3.2.1 OnPlayerConnect:

This textdraw will contain text, but not at the start, so we just provide an empty string with the underscore in the meantime. Note that for some reason we need to create a per-player textdraw with CreatePlayerTextDraw instead of PlayerTextDrawCreate. All other functions do have 'PlayerTextDraw' in front. Since 'SpeedoCarInfo' is an array of per-player textdraws, we need to provide which element of the array we are using in every function. At last there is also an additional parameter that we need in every function, to know which player we are talking about. If you don't understand this, read on.
Code:
SpeedoCarInfo[playerid] = CreatePlayerTextDraw(playerid, 485.0, 355.0, "_"); //No text
PlayerTextDrawBackgroundColor(playerid, SpeedoCarInfo[playerid], 0x000000ff);
PlayerTextDrawAlignment(playerid, SpeedoCarInfo[playerid],0);
PlayerTextDrawFont(playerid, SpeedoCarInfo[playerid], 1);
PlayerTextDrawLetterSize(playerid, SpeedoCarInfo[playerid], 0.30, 1.0);
PlayerTextDrawColor(playerid, SpeedoCarInfo[playerid], 0xffffffff);
PlayerTextDrawSetOutline(playerid, SpeedoCarInfo[playerid], 1);
PlayerTextDrawSetProportional(playerid, SpeedoCarInfo[playerid], 1);
PlayerTextDrawSetShadow(playerid, SpeedoCarInfo[playerid], 1);
See the following code for the difference between a global and per-player textdraw function.
Code:
TextDrawFont(SpeedoBackground, 3); //Global textdraw
PlayerTextDrawFont(playerid, SpeedoCarInfo[playerid], 1); //Per-player textdraw
You might think that since we already use the playerid in 'SpeedoCarInfo[playerid]', why we need an additional playerid parameter?


When you use 'SpeedoCarInfo[playerid]' you're using a variable of the PlayerText type. When you use 'playerid' you're using a variable of the Integer type. Remember that a per-player textdraw is unique for each player? In other words, it is linked to that player. So the 'playerid' parameter is needed to know which player the textdraw belongs to and the 'SpeedoCarInfo[playerid]' is needed to know which textdraw of that player we mean. Here's another explanation:
  • SpeedoCarInfo is an array.
  • Every element of the array contains a PlayerText textdraw.
  • So the array is of type PlayerText.
  • To access an element of the array, we use an index.
  • An index is an integer.
  • We've chosen a logical system in which the textdraw of player with ID 0 is located in the array at the element with index 0.
If you still don't get why you need an array, read this post.

3.2.2 OnPlayerDisconnect:

Destroy the per-player textdraw and reset the variable.
Code:
PlayerTextDrawDestroy(playerid, SpeedoCarInfo[playerid]);
SpeedoCarInfo[playerid] = PlayerText:INVALID_TEXT_DRAW;
This is actually not needed, since per-player textdraws get destroyed automatically when the player disconnects. So you don't need to add this code. It won't do anything wrong if you do though.

3.3 Bool variable:

We need to reset the variable when a player joins, because the speedometer is not shown at that moment.

3.3.1 OnPlayerConnect:

Initialize the variable when the player connects.
Code:
SpeedoShown[playerid] = false;
3.3.2 OnPlayerDisconnect:

Reset the variable when the player disconnects.
Code:
SpeedoShown[playerid] = false;
This is not really needed, because we already reset it when the player connects. You will need to do this if you use this variable for something else, like counting how many players have the speedometer visible or something.

3.4 Timer variable:

We have one global timer that should be run always.

3.4.1 OnGameModeInit:

Start a timer that should tick once every tenth of a second (that's definitely fast enough) and that should repeat forever. We don't need timer parameters, since we will loop through all players.
Code:
SpeedoStatusTimer = SetTimer("SpeedoStatus", 100, 1); //The name of the function we will use later
3.4.2 OnGameModeExit:

Kill the timer and reset the variable.
Code:
KillTimer(SpeedoStatusTimer);
SpeedoStatusTimer = -1;
4. The timer:

Now, finally, what we've been working towards. The timer that will handle everything.


The following code should be placed somewhere in your script. I always put it at the bottom. But it doesn't really matter.

4.1 Forwarding and implementation:

We have a timer that calls a function without parameters. So we need to use empty parentheses. As always with public functions, you also need to forward.
Code:
forward SpeedoStatus(); //Remember this name and no parameters from 3.4.1?
public SpeedoStatus() { //Same name and parameters as forward
    //Loop (see next part)
}
4.2 Looping through all online players:

Since this timer will handle every player, we need to use a loop. We don't need to do anything if the player is not online, so don't forget to include the 'IsPlayerConnected' check.
Code:
for(new playerid = 0, highestPlayerid = GetPlayerPoolSize(); playerid <= highestPlayerid; playerid++) {
    if(IsPlayerConnected(playerid)) {
        //In vehicle check (see next part)
    }
}
Note the use of <= instead of <.


Don't use the following.
Code:
for(new playerid = 0; playerid < MAX_PLAYERS; playerid++) {
    if(IsPlayerConnected(playerid)) {
    }
}
With this code, if you have only 5 players and your MAX_PLAYERS is 1000 you will have 995 unnecessary iterations.


Also don't use the following.
Code:
for(new playerid = 0; playerid <= GetPlayerPoolSize(); playerid++) {
    if(IsPlayerConnected(playerid)) {
    }
}
Otherwise it will execute the GetPlayerPoolSize() function every time the 'playerid <= GetPlayerPoolSize()' condition is checked. It's better to get the output of GetPlayerPoolSize() once and put it in another variable. You might not notice the difference if the function you call is not resource expensive. But you definitely will if it has to do something very resource expensive. It's just good practice to make a habit of the first implementation.

4.3 Checking if the player is in a vehicle:

If the player is not in a vehicle, we need to hide the speedometer, otherwise we need to show it.
Code:
if(IsPlayerInAnyVehicle(playerid)) {
    //Not shown check (see next part)
} else {
    //Shown check (see next part)
}
4.4 Checking if the speedometer is hidden to a player:

We only need to show the speedometer when it is not shown yet. But we do need to update the content always, so that must be outside of the check. Don't forget to put the boolean to true, which lets us know that the speedometer is now shown.
Code:
if(!SpeedoShown[playerid]) {
    //Show (see next part)
    SpeedoShown[playerid] = true;
}
//Update the content (explained at the end)
4.5 Checking if the speedometer is shown to a player:

We only need to hide the speedometer when it is not hidden yet. Don't forget to put the boolean to false, which lets us know that the speedometer is now hidden.
Code:
if(SpeedoShown[playerid]) {
    //Hide (see next part)
    SpeedoShown[playerid] = false;
}
4.6 Show the speedometer for a player:

Show both the global and per-player textdraws.
Code:
TextDrawShowForPlayer(playerid, SpeedoLineAbove);
TextDrawShowForPlayer(playerid, SpeedoLineLeft);
TextDrawShowForPlayer(playerid, SpeedoLineRight);
TextDrawShowForPlayer(playerid, SpeedoLineUnder);
TextDrawShowForPlayer(playerid, SpeedoBackground);
PlayerTextDrawShow(playerid, SpeedoCarInfo[playerid]);
4.7 Hide the speedometer for a player:

Hide both the global and per-player textdraws.
Code:
TextDrawHideForPlayer(playerid, SpeedoLineAbove);
TextDrawHideForPlayer(playerid, SpeedoLineLeft);
TextDrawHideForPlayer(playerid, SpeedoLineRight);
TextDrawHideForPlayer(playerid, SpeedoLineUnder);
TextDrawHideForPlayer(playerid, SpeedoBackground);
PlayerTextDrawHide(playerid, SpeedoCarInfo[playerid]);
4.8 Update the content for a player:

Finally the content itself.
Code:
//We need to get in which vehicle the player is
new vehicleid = GetPlayerVehicleID(playerid);

//We need to get what the velocity of that vehicle is
new Float:VelX, Float:VelY, Float:VelZ;
GetVehicleVelocity(vehicleid, VelX, VelY, VelZ);

//We do some calculations with those values, got to this thread for more information
new Float:Speed;
Speed = (floatsqroot(((VelX*VelX)+(VelY*VelY))+(VelZ*VelZ))* 181.5);

//Put the information in a string
new String[8+ 1];
format(String ,sizeof(String), "%ikm/h", floatround(Speed, floatround_floor)); //floatround rounds the float to an integer

//Put the string in the textdraw
PlayerTextDrawSetString(playerid, SpeedoCarInfo[playerid], String);
This code only includes what you'll need to do to show the speed of the vehicle. Other than the health and speed bars it's not that difficult. Now it's up to you to try to implement the other information yourself. Start with the text first, afterwards have a look at the bars (use the search function to know how to create those). If you're adding more text, don't forget to update the size of the string, otherwise it won't show.

5. End word:

Damn this took longer than I expected it would take... Anyway, as always, constructive criticism is definitely welcome.
Reply
#2

NOICE,k keep up
Reply
#3

Quote:
Originally Posted by Eoussama
View Post
NOICE,k keep up
Fastest reading ever?
Reply
#4

Quote:
Originally Posted by Freaksken
View Post
Fastest reading ever?
He's a shitposter. Ninety percent of his content is absolute garbage.


Either way, it looks really good. I didn't read all of it but I wish I had something like this when I was experimenting with speedometers. :P
Reply
#5

Quote:
Originally Posted by Dignity
View Post
I didn't read all of it but I wish I had something like this when I was experimenting with speedometers. :P
Well, it's not something that's meant for someone like you obviously .
Reply
#6

Looks pretty solid man.
Reply
#7

Nice! You've taken a great effort writing and explaining this tutorial very well. Something I'd like to point out though - The global timer runs every 100ms and loops till the highest connected player id. You could also add an explanation regarding the loop can be improved using a custom list or foreach where it contains the player ids of the ones having speedometer activated. It could reduce a lot of time and provide better performance.
Reply
#8

Good job Even though the concept is small you did a great job in explaining it with some extra points and that's how an ideal tutorial should look like.
Reply
#9

Pretty Good job! Thanks man Helped me! REP+
Reply
#10

Amazing tutorial, nicely explained, this should be an example for new tutorial posters/ makers.
Reply
#11

nice tuto usefull !
Reply
#12

Is it possible to resize a pre build clock speedo + if yes how to do ?
Reply
#13

Quote:
Originally Posted by Astonish
View Post
Is it possible to resize a pre build clock speedo + if yes how to do ?
What do you mean by resize? Just change the textdraw coordinates and/or the text size.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)