Introduction
Lately, I have been seeing an increase in people using the new Trails object to act as bullets for the guns in their games, and it is… unfortunate, to say the least. So, I decided to create a poll on Twitter to see if people wanted a tutorial as to how to go about doing this correctly, and enough people voted yes, so here we are! Please note that I’m not on @AxisAngle’s level for efficiency, however, I can and will do my best to explain how you can make your own 2D bullets using ImageLabels.
I personally believe the best way to teach someone is to guide them into figuring it out themselves, so I won’t include the code that I used to create mine. I’d prefer that you guys knew what you were doing and did it yourselves, because you’ll feel really accomplished at the end of all this. That being said, considering this is my first tutorial, I’ll ensure to do my best at teaching you.
My Idea
I have attempted creating particle systems at least 20 times before successfully accomplishing something that I could use in a game (and still respect myself enough). My idea has always remained the same, it was accomplishing this idea that became a problem for me. The idea itself was you take 2 points, (the last position, and the new position), and you simply draw a line between the two.
How To Accomplish
Well, I knew that I would need to figure out how to size, position, and rotate the 2D line to make it appear as if it were to be 3D. So first, I needed to create the bullet physics using some fairly basic Vector manipulation and raycasting. If you need to learn more about how vectors work, Axis wrote a fantastic article on it here.
Every time that the function “update” is called, the bullet position is updated. I call update every frame so that the bullet is constantly updating. Every time it is updated, it saves off the last update’s position, so you have a last position and a position variable.
Using this, we can use the camera function WorldToViewportPoint to get the 2D position of 3D elemets. With this, we return a Vector3 that contains the XY of the 2D position, and the Z of this Vector3 represents the depth. This function also returns whether or not the position is visible on the screen. This does not account for whether or not objects are in the way, this simply determines whether or not the 3D position is within the screen’s boundaries, if that makes sense.
For all the below equations, you can sub x1, y1 with lastPos.X, lastPos.Y, and x2,y2 with newPos.X, newPos.Y
Sizing
Now with this, we can use the distance formula ((x2 - x1) ^ 2 + (y2 - y1) ^ 2) ^ 0.5 to calculate what the sizing should be. Use the answer from this in the Size.X.Offset position place. Since objects get smaller the further they are away, recall that in the WorldToViewportPoint function, it also returned depth, so use this in the Size.Y.Offset position place. Keep in mind that if you want it to get smaller instead of larger the further it gets away, you need to make it the denominator of a fraction. For example, 1/4 is smaller than 1/20, right? So if you do 100/depth you’re going to have a size of 1 pixel at 100 studs away. Larger values = larger bullets.
Note: 2D elements can’t have decimals, so use the math.ceil property when sizing to avoid the size 0.3 to be rounded to 0.
Position
To calculate the position, use the midpoint formula (x2 + x1) / 2, (y2 + y1) / 2. It’s as simple as that.
Rotation
I found tricky for a while, mostly because I didn’t realize that GuiObjects use degrees for rotation, and not radians, which was just an amateur mistake, but hey, it was a good learning point. Now for rotation, I used atan2 to figure out the rotation, and then I multiplied the result by 180 and divided by pi (radian * 180 / pi = radian to degree). If you don’t know how to use atan2, despite the answer being quite simple, I would recommend looking it up so that you’re not just copying and pasting everything I’m saying right now and you actually learn something.
Now with those 3 equations, you should be able to draw the line between the two points. But, you may notice that with this, it freaks out if the bullet comes straight at or away from your camera. To solve this, I added a constraint to it. I said if the YSize is greater than the XSize, then to set the XSize to the YSize. In Lua terms:
xSize = ySize < xSize and xSize or ySize.
Motion Blur
Motion blur is simply taking the position in the last frame and “streaking” the object to the new frame, so instead of saving the last 3D position, you simply save the last 2D position, and then you draw the line from the last 2D position to the new 3D position.
Conclusion
With all of this in mind, hopefully you can figure out how to make a particle system of your own. Sorry for not providing the direct code from my own scripts, but I write this with the hope that you will be able to figure out how to make the bullet physics yourselves and use the information I have shared to, so you can apply this information to your games and better yourself as a programmer overall.
If you would like to see the most recent public build of my particle system, you can visit the place here.