includes how to use it to make ring menus and more!

  • Kazesui
  • 05/27/2011 04:39 PM
This tutorial will provide a rm2k3 project with some common events which you can paste into your own project and use for various purposes. The project also contains a few examples of what you can do with these trigonometric functions, like creating a ring menu.
This tutorial will explain how to use the event scripts, but will not go into great detail as to how they actually work (i.e. black magic, just accept it).
Also, this tutorial will require a minimum of intermediate level at eventing for you to be able to keep up.

The project template:

Don't know trigonometry? click here

I will explain shortly how the trigonometric functions themself work, and I feel that the best way to do it, is with an illustration:

In a circle with radius 1000, if you have an angle rising from the x axis, the trigonometric functions will return the coordinates in x and y on the circle given the angle.
In this example, the angle is 30 degrees. with a 30 degree angle, the line generated from that will hit the circle at 866 along the x axis and 500 along the y axis.
if you multiply the radius of the circle sine of 30 degrees, i.e. 1000*sin(30) you get 500, and 1000*cos(30) will give you 866.

If we have a picture which will move instantly to coordinate x and y, and these are generated getting the sine and cosine for a angle, then increase the angle before moving the picture again, it will move around in a circle. This is one way to understand trigonometry for some uses, and it can be pretty useful

If you still don't get angles to well and stuff, you could give this little game a try: Trigonometry game

The project includes 5 common event scripts. They require the variables 101 to 160 to work properly. There are a few here and there among them which aren't being used, and some could be repositioned without too much trouble, but most likely it will be easier just to keep those variables free when using these events.

So let's look at the event scripts, the first being "CreateTrigTable".
it looks like this:

The first variable operation has a comment above it saying "Set ID of argument variable (angle)". What this means, is that the number you set that variable equal to, will be ID of the variable used as the angle for determining the value of the sine and cosine. At default, it's set to 1, meaning that you're very first variable in the list will be used unless you set it to something else.
Remember, it's the number you should change, not the variable itself.

Same deal for the next two variable operations. These signify in which variables the result should be placed. The first is which variable the cosine should be returned to, and the second which variable to sine should be returned to.
The next variable operation deals with where you want the variables containing the "lookup table" to be. it's set to be stored from variable 10000 to 10724 by default, i.e. outside the normal variable array range, so it's less likely that you have to do anything with this variable.

Once these four variables are set, you don't have to do anything more with the event. Just call it once, and then you can call the event "GetCoSinusA()" to get the trigonometric values.
Let's do an example: let's say the variable 0001 contains the value 60, and you've called upon "CreatTrigTable" with it's default values. Then variable 0002 and 0003 will now have the values 500 and 866. The trigonometric values are multiplied by 1000 to avoid the problem of having no decimal point numbers. And that's all there is to using it.

If you're fine by just knowing how to use it move on, if you want to know a bit more about it works click here:

CreateTrigTable does, as said, create a lookup table. What this means is that it stores the values of sine and cosine for all angles from 0 to 359, and the GetCoSinus converts whatever angle you used to one ranging from 0 to 359, and then does take the startID of the lookup table and add the angle as offset so that it points to variable with the correct trigonometric value, so that it can send it to the designated "return" variables.
It's done this way since doing a lookup like this is simply considerably faster than any alternative way and is sane enough when you only have 720 values to store anyway. The 4 extra variables are used for storing information from which variable it should get the angle, and where it should return the sine and cosine and the last is just 0 to signify end of info and beginning of lookup table.

And now we'll look at the 3 other scripts. Again we have a "create" event, this time for ArcTan:

And this works much in the same way as last time. First 3 variables here define from where the "arguments" should come, like the angle in "GetCoSinus".
First variable is the value you would normally put in a arctan to return the angle in degrees.
Second variable is a length in the x direction, and the third a length in the y direction.
the fourth variable is where the result should be returned to, and the fifth is where to store some information used.

Once the create has been called once, you can start use GetAngleA and GetAngleB.
GetAngleA only uses the first argument and returns a positive angle from 0 to 90.
GetAngleB uses the argument two and three and returns a positive angle from 0 to 359

I know this can be a little hard to process, if you don't already know too much about trigonometry, and the terms might be a bit difficult as well. So I will now show a few examples to demonstrate how it works and how it can be used.

Basic ring menu

This example shows how to make a simple ring menu, which rotates when you click to either side.

First off, you need to call upon CreateTrigTable to allow the usage of GetCoSinus.
Remember, it GetCoSinus won't work without CreateTrigTable having been called once, but we only need to call it a single time in the game, so it shouldn't be called more than once either.
In this example this is done by letting the upper left event called "Button Handler" being set to autostart and then calling a switch when it's done, which runs on parallel process and waits for the player to hit the cancel key.
Once that happens it sets on a switch which activates a auto-start event controlling the menu which looks a bit like this:

Hero we can see two variables RingOrigo x and y. They mark the center point of a circle in screen relative coordinates and are currently set to the middle of the screen. Next, there's a variable Radius sets how big the circle should be. The menu items will follow along the lines of the circle, so the bigger the radius, the further away from each other they will be.
the Angle variable is set to 0, so that the items will always created in the same order when the menu is opened.
After this, this event's page 1 is called:

Here we can see the "GetCoSinus" being used. It takes the current angle and returns the cosine and sine in variable 2 and 3, which are set to variables for the different menu items (through pointers) with 90 degrees between them.
After the variables have been set, they are multiplied by the radius, and then divided by 1000 since the sine and cosine are already 1000 times bigger than normal. finally x coordinates are added the x coordinate of origo, and same for y.
the angle is subtracted by 360 to return it to it's orginal value before calling this page.

Back in the page handling the flow of the menu, you can now press left or right to rotate the menu. This is done by calling page 2:

This page sets up a label loop and a variable which counts the amount of times you've looped. The angle is increased by 3 for each time, and this is something you can increase or decrease to make it spin faster or slower. You should just make sure that you scale the amounts of iteration done in the loop accordingly by adjusting the number in the branch. It then calls page 1 which gets coordinates given any spesific angle and repeats until the loop has done 30 iterations, meaning a total increase of 90 degrees.
The Rotate Left event is pretty much the same, except that it decreases the angle by -3 instead.

And that's pretty much all there is to this basic ring menu. You can inspect it more closely by looking at the project in the maker.
There you can also find a more Fancy ring menu.

This ring menu, takes it a step further and scales the sizes of the items depending on their angle, as well as it rotates in an ellipse rather than a circle.
It also grays out the other items which aren't currently selected.

All in all, most code is the same, except we now have 2 radiuses. One for the x direction and one for the y direction, with the y one being quite smaller. This creates the ellipse shape. Also instead of calling "move picture" directly, it now goes through other event pages which have quite a few branches which will trigger different move picture depending on the angle.

Stealth System

Trigonometry can also be used to create guards which look in a certain direction, but who can't see through walls. For this, we need both CreateTrigTable and CreateArcTan to have been called, so a similar event for initializing both of them like with the ring menu would be neccessary.

You're within the sight range of this guard, but since you're hiding behind a wall, he can't see you. But when you're out in the open like this:

You're spotted. So let's take a look at the code which the guard has to detect wether he sees you or not.

First, we get the screen relative coordinates of the guard. Tile based should work too, but since we need to move the picture with screen relative ones anyway, we might as well. Screen relative coordinates are more accurate as well, so wouldn't be stupid to get accustomed to using them. the -8 before the move picture is just for aesthetical reasons (as long as followed by +8 afterwards).
We then subtract the the hero coordinates from the guard coordinates (important, the object which "looks" need to be the one which gets subtracted).

Once we have the differences we store them in the variables we'll use as argument 2 and 3 for GetAngleB, but we won't call it yet. First we'll multiply the guard coordinates with themselves and add both of them into a variable and the check if it's less than the radius timed itself.
In this example a radius of 100 pixels is used, so if the difference in coordinates multiplied by themself and added into one is less than 10000, we know that the hero is in a circle of radius 100 of the guard.

We then call GetAngleB to get the angle between them and call Page 1 which does a few branches to check the angle. If the guard is facing down, then the hero is in visible range is the angle between them is between 30 and 150 degrees, if the guard has a 120 degree wide sight range.
notice that since this is on a computer the y axis is positive downwards, meaning the the angles go clockwise rather than counterclockwise.

With this, we'll know if the hero is within the normal sight range, but we still don't know if the hero is hiding between a wall or not, so to check that, we call another event page.

We now take the angle between the hero and the guard, and call on GetCoSinus to get the cosine and sine values for that angle. And we then create a label loop.
Hero we take 2 variables, x with cosine value, and y with the sine value. we multiply them by the radius then divide by 1000 to get the pixel coordinates, we add the coordinates of the guard which is the center of this circle, we then divide by 16 to get the tile coordinates and then stores the terrain ID at those coordinates. By setting the terrain ID of the tiles on the chipset we don't want the guard to be able to look through to a spesific value, we can now if there's something in the way of sight. Here we've chosen 8 as the spesific value to be checked for.

If it doesn't fit a variable SightEnd will be set to the radius and multiplied by itself, and then checked with a branch if it's bigger than "guard x" which contains the length between guard and hero calculated earlier. If not, radius is added by one and another iterations start. I it's bigger, a switch BlockedSight will be set to off, meaning there was no obstacle blocking the view of the guard.

And thus, we have a system which tells if guards can see the hero, limited by the view. Using the trigonometric scripts, you could also make a rotating camera which checks for intruders. There's lot of stuff for which you can use trigonometry, so download the project template and have fun :3


Pages: first 12 next last
If, say, you want circular motion but your intended number of frames doesn't divide nicely into the given lookup table's, there's an easy approximation trick to get circular motion or construct your own lookup table.

Because d/dx(cos x) = -sin x and d/dx(sin x) = cos x, each frame you can basically just have:

y += x/c
x -= y/c

for some constant c not too large relative to the range of x and y; in something like RM2K(3) this might require keeping, say, x*256 and y*256 and using those in the calculation then dividing down.

The above has a period of basically 2*pi*c iterations, though there can be a touch of rounding error.

Ellipses you can do with:
y += x/a
x -= y/b
(this gets you a period of 2*pi*sqrt(a*b) and a y-axis length of (x-axis length)*sqrt(b/a) IIRC.)

Not sure if the lookup table in the article was constructed like this or some other way, I don't have RM2K3 on this computer.
The lookup table given in the project file up there simply stores the values of all values for the different whole angles from 0 to 359 into variables, which later can be referenced by pointers (first 90 was done by manual input, and rest simply by pasting with pointers since it's the same values used over again just in a different order)

It's done this way for efficiency reasons, saving the need of computing the values when needed, and since you have vast number of variables avaible anyway which you're never likely to use, and all whole number angles seem to be precise enough for anything in rm2k3. Usually have to scale by more than one degree per frame since it would otherwise go too slow.

But yeah, not a bad way for computing them if memory is a problem, or if finer angle values than whole are needed
did someone say angels
Wow. That's an inordinate amount of work just to perform a fundamental mathematical operation.
puking up frothing vitriolic sarcastic spittle
Wow. That's an inordinate amount of work just to perform a fundamental mathematical operation.

Did you not see the part with guards not seeing through walls? That is genius. Using my method for guard-detection, you have to put in exceptions for guards manually for each instance where the player could be hidden behind a wall and it takes forever.
Yellow Magic
I'll never regain the bones I lost from my loneliness and sorrow
Just what the doctor ordered! (sorry F-G) Thanks so much.
puking up frothing vitriolic sarcastic spittle
Yellow Magic
I'll never regain the bones I lost from my loneliness and sorrow
Was going to use your guard-detection system for my project. ;(
puking up frothing vitriolic sarcastic spittle
Oh, well totally use this one. It is better! :P
If, say, you want circular motion but your intended number of frames doesn't divide nicely into the given lookup table's, there's an easy approximation trick to get circular motion or construct your own lookup table.Because d/dx(cos x) = -sin x and d/dx(sin x) = cos x, each frame you can basically just have:

y += x/c
x -= y/c

for some constant c not too large relative to the range of x and y; in something like RM2K(3) this might require keeping, say, x*256 and y*256 and using those in the calculation then dividing down.

The above has a period of basically 2*pi*c iterations, though there can be a touch of rounding error.

Ellipses you can do with:
y += x/a
x -= y/b
(this gets you a period of 2*pi*sqrt(a*b) and a y-axis length of (x-axis length)*sqrt(b/a) IIRC.)

Not sure if the lookup table in the article was constructed like this or some other way, I don't have RM2K3 on this computer.

Hmmm, you lost me after that.

This is good I think, least from what I understand. I always had to make my own events for stuff like this, but now I can use this.
All this junk needs to be built in to my engine if you're all wanting it this badly.
puking up frothing vitriolic sarcastic spittle
I don't think you need to worry about building it into your engine unless it would be easy to do so. It's not like every game that gets made with your engine is going to require MGS-style detection or whatever else you could use this for.
It's really easy to include, and I was going to add collision primitives so detection would also be built in (not to mention both would then be faster).
What 'bout using trigonometry in top-down shooters to replace bullets based on events? Is it possible?
Of course it is and it would probably work better.
Returned for this script after a little while...

I'm preparing myself for making a RPG with a bit of stealth system involved. There will be no random encounters whatsoever (I hate 'em!), but hostile groups represented as events on a map. I could do this The Way-style, with said events walking randomly or instantly following player, but hell, isn't that weird for human or android enemies to follow you since you have entered the map? No, they should do that upon detection (only the detecting group will chase the player) or switching an alarm (then all hostiles within the area will be hot on your tail).

So, the point is - I wanted to switch chase mode upon detection by simple toggling a switch, thus activating a new page with event's movement set to follow the Hero. But actually this event stops, and slowly follows its old path. Honestly, I have no idea what to do. This detection system is crucial to my project, as it takes walls in account, and an event will not detect the player through it.
Remember when I actually used to make games? Me neither.
Can multiple events call the trigonometry events at the same time?
Yes. If you want to keep track of the value of the angle you got after calling the event, you should store it into a seperate variable right after calling the event though.
i'm trying to manipulate the variables in my game. the speed of the spin is giving me trouble. from what i understand the angle between items is 90. so you are subtracting 3, and 90 / 3 = 30. so i assumed i could take 90 /5 = 18 and have it spin faster. but when i do they no longer align to the 4 cardinal directions. is there something else that needs to be changed for that?

bah, i made a mistake in my code. it works now.

managed to integrate this into one of my projects. i'm totally gonna use the navi example from your test project to code navi in my Zelda game :D can the image drawing coordinates be used normally? like for calculating hit detection? for example. so if that roaming navi hits an event it can trigger something.

do you have a tutorial for making bullets?
Yes you can in a pretty similar fashion to how I'd detect bullet. I don't have a tutorial on that though. I can give you the general idea though, based on how it was done in Zero Base (which could be good as reference material, if not too cryptic).

The general idea is that you translate the screen coordinates into tile coordinates.
This is done by adding an offset to the screen coordinates, depending on how much the map is panned away from the upper left corner.
I typically do this by adding an event in the upper left corner, get it's screen coordinates and I subtract the screen coordinates of the bullets with that of this event, then add some tiny static value to fine tune to collision.

Once this is done, I get the tile coordinates of this position, and I use this tile coordinates to check if there's an event or terrain below which is an obstacle (similar manner to pixel movement tutorial), and then just check for those id's.

This works fine for non moving events. If they move however, you should check for multiple tiles, to take into account how rm2k3 handles updating tile coordinates of events, and then have a page in the event which can be called for a finer check only on that event if it's a hit. This is to make it more pixel accurate in terms of collisions while keeping it efficient.

A good idea would be to try to check four tiles around the navi sprite/bullet in such a way that most of the time, it would be close to the common center of those four events. This requires some tweaking with the values you add to the screen relative coordinates before converting them to tile based ones.

That's generally how it works
Tell me again why you need trigonometry to make a ring menu? Fuck that, I hated trig class. I'm making a ring menu that uses very simple processes and principles of art and animation. Just make a menu for the main stuff (good enough for SD3, good enough for me), rotate by key press, and don't really rotate at all, just change the picture to make it look like it's turning. About two variables (unless you have submenus) and it's counting. Any art student could it once they understand the process.

Pages: first 12 next last