Have you ever wondered how anyone was able to create that magical cursor in the World of Goo? Wonder no more, noble Dev.Mag reader, for this article will explain how you can put that gooey cursor into your game, in just 15 minutes. We will be using Game Maker Pro as our development tool, so to get started, open up a new project.
Add an object and call it ‘GooCursor’. A cursor is always drawn last out of all the objects in the room. This is because you want the cursor to always appear above the other sprites. To achieve this, give the ‘GooCursor’ object a very low depth; -10 000 for example. Enable the ‘persistent’ option, so that this object does not get destroyed when rooms are switched.
Now that the object has been created, add it to a newly created, or existing room – preferably the first room of a project. You can change the background colour of your room to what ever suits you; in this example I am using orange. Next, change the room caption to ‘WoGcursor’. I’ve decided to go with a high room speed of 60, to maintain a smooth motion for the goo cursor.
Create Event
Next, add the code for the ‘Create’ and ‘Draw’ events for the ‘GooCursor’ object. Let’s start with the ‘Create’ event. First, we need to hide the default cursor using the following code:
window_set_cursor(cr_none);
We need variables to represent the characteristics of the goo cursor.
trail = 30; // length of goo trail radius = 18; // radius of main goo circle outline = 3; // outline in pixels surrounding the goo flow = 2; // increasing this value produces a smoother goo trail scale = 0.75; // scale difference between the front and back of the trail
The goo cursor is made by joining many circles together in a sequence. The ‘trail’ variable represents the number of circles; it is also an indication on how long our arrays should be. The arrays that we will need are those for the ‘curPosX’ and ‘curPosY’. These arrays are used for holding the mouse position. An array ‘size’ will be used for holding the size of the circles, from largest to smallest.
A ‘for’ statement is used to loop through the array indexes; starting from 0 until ‘i’ is no longer smaller than ‘trail’. So the array index goes from 0 – 29, which gives us 30 places to hold information in each array. This ‘for’ statement is mainly used to assign a starting value to the indexes in the array:
for (i = 0; i < trail; i += 1) { curPosX[i] = mouse_x; curPosY[i] = mouse_y; size[i] = radius - i / trail * radius * scale; // create an array that decreases its value as it goes through its indexes }
Now we need to assign the ‘index’ variable that will hold the current position of largest circle in the goo cursor; this is also the main circle that all the other circles will trail behind.
index = 0;
Draw Event
Now it’s time to add the code for the ‘draw’ event. You will notice that I have combined the code from the ‘step’ event, into the draw event. This is possible because the ‘draw’ event is called just as many times as the ‘step’ event, and will not produce any problems with timing.
To start this event off, add 1 to the ‘index’ variable. Next, check if ‘index = trail’, then set ‘index’ back to the first index of the array, which is 0. Remember, ‘trail’ represents the length of the array, and the array starts from 0 and ends off at 29; so if the ‘index’ variable reaches 30, it falls out of range for the array. To avoid this, change the value of ‘index’ back to the first index, which is 0.
index += 1; if (index = trail) { index = 0; }
Next, set the position of the mouse’s ‘x’, and ‘y’ co-ordinates to the arrays ‘curPosX’ and ‘curPosY’ at the current index that holds the position of the main circle in the goo cursur.
curPosX[index] = mouse_x; curPosY[index] = mouse_y;
Now you create a temp variable ‘t’ that will be used as a place holder to check how many indexes have been looped through.
t = 0;
Now comes a ‘for’ statement that will loop from the current index of the main circle, then go backwards through the array; starting from the largest circle and ending off at the smallest circle. The reason we loop backwards is because the position of the main circle jumps one place up every time the draw event is called, leaving the circle following it one index behind. This loop is used to prevent the circles from separating from each other when they get pulled swiftly. Each time the loop goes through the body part, it has to check the distance between each of the circles. For example, if there are two circles, this loop only has one cycle, because it is checking the distance between the two circles. The formula for checking how many times the loop needs to execute is:
number of circles – 1
Instead of checking if ‘i < trail’ we will be checking if ‘t < trail – 1′.
We can’t use ‘i’ this time because ‘i’ can range from 0 – 29 by the time the loop needs to stop; so the statement used to check ‘i’ will vary. Another problem will arise if an array with an index of -1 is used; such as checking the position between the first index 0 with the previous index, which is a negative. To solve this problem, add an ‘if’ statement to check if ‘i = 0′; in this case the previous index will be ‘trail – 1′, which is 29. Use the variable ‘prevIndex’ to hold the position of the previous position of the index in the arrays.
for (i = index; t < trail - 1; i -= 1) { if (i = 0) { prevIndex = trail – 1; } else { prevIndex = i – 1; }
Next, gather the distance and direction between the previous index and the current index. Use the variables ‘distance’ and ‘angle’ to hold these values.
distance = point_distance(curPosX[prevIndex], curPosY[prevIndex] , curPosX[i], curPosY[i]); angle = point_direction(curPosX[prevIndex], curPosY[prevIndex] , curPosX[i], curPosY[i]);
Check if the distance between the two circles is larger than the size of the largest circle’s radius; which is then divided by the flow variable, which will determine the smoothness of the goo. The larger the value you assign to flow, the shorter goo trail behind the cursor will be. This is because the circles are being pulled closer together. If the distance is large enough, then the difference between the distances will be added to the smaller circle, bringing it closer to the bigger circle.
if (distance > size[t] / flow) { curPosX[prevIndex] += lengthdir_x(distance - size[t] / flow, angle); curPosY[prevIndex] += lengthdir_y(distance - size[t] / flow, angle); }
Just before ending off the first loop, check if ‘i = 0′; if the loop subtracts 1 from ‘i’, then ‘i’ will equal -1, leaving it out of range for the arrays. To fix this, will make ‘i’ = trail. When 1 gets subtracted from ‘i’ it will become 30 – 1; remember trail = 30. This will put ‘i’ at the index at the end of the array. Then add 1 to ‘t’ to prevent the loop from repeating itself endlessly, causing the program to stall.
if (i = 0) { i = trail; } t += 1;
The next ‘for’ loop is used to draw the circles forming the outline around the goo cursor. We use ‘t’ again in this loop, in the same way we used it in the previous loop. This time we will just be drawing the circles to the screen, starting from the largest. You can change the colour of ‘c_white’, which represents the colour of the outline surrounding the goo to any colour you prefer. Again before ending the loop, check if i = 0 to prevent ‘i’ from becoming a negative; this results in your program crashing when checking for a value of a negative index in an array.
t = 0; for (i = index; t < trail; i -= 1) { draw_circle_color(curPosX[i], curPosY[i], size[t] + outline, c_white, c_white, 0); if (i = 0) { i = trail; } t += 1; }
The last ‘for’ loop works the same as the one above, except this time, we are drawing the black circles above the white circles. This forms the insides of the goo cursor. We also leave out the + outline part when giving the value for the radius of the circle. Again, change ‘c_black’ to a preferred colour.
t = 0; for (i = index; t < trail; i -= 1) { draw_circle_color(curPosX[i], curPosY[i], size[t], c_black, c_black, 0); if (i = 0) { i = trail; } t += 1; }
Having followed all these steps, you should have your very own gooey cursor!
Hi,
I’m trying to port this to AS3 and it would really help to see everything in one snippet, instead of in small chunks. I’m confused by the order in which you assemble it. So a text document with the full source would be highly appreciated.
Good job so far.
Contact me at john.persson[at]hyperisland.se
Thanks,
/John
Hi John,
If it helps, you can also look at an example of a World of Goo cursor written in XNA by Catalin Zima in the mean time.
http://www.catalinzima.com/samples/game-features-replicator/world-of-goo-cursor/
– edg3
my friend can you give me a gmk.i did but didnt work.i cant understand why isnt it work?
http://gmc.yoyogames.com/index.php?showtopic=512284 sir is this system edit or different systems ? which one?
I think that is a different one. Unfortunately we don’t have the GMK file in our archives, but I will see if the author still has it. -ht