How To Make 2D Bullet Tracers Using Gui Objects (outdated)

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.

24 Likes

I wouldn’t use frame/label UI objects for this purpose, if you’re not going to use Trails. Try using ImageHandleAdornments or LineHandleAdornments instead that you place/update in the 3D world together with your bullet. You don’t have to deal with converting back/forth between camera/3D coordinates that way, they won’t look aliased, and you won’t have to implement logic to make sure it clips correctly when it is partially or fully obscured by other objects in the scene.

All in all, way less work for probably a better looking result, since you can spend more time tweaking your textures/tracers rather than tweaking your rendering system.

3 Likes

LineHandleAdornments get larger the further they are away. This doesn’t make sense logically. I don’t like how they look very much however, I do agree that there are much simpler methods such as using these. I posted this article to just provide another method on how to make bullets separate from basic ROBLOX built-in methods.

ImageHandleAdornments also don’t rotate to accommodate camera angle. For example, if you look at the back of one, they don’t show up, which of course, can easily be solved by rotating the part you are using in-game to show the player, but this seems unnecessary to me. However, I definitely agree to what you said that there are other methods!

This wouldn’t be an issue as far as I can see, because you describe in your article exactly how to do it already for frames/imagelabels. It would be no different for LineHandleAdornments. You apply the same technique to get the width of each LineHandleAdornment representing each section of the trail. They have a thickness property which is a float (which is another benefit, you don’t have to deal with Frames/ImageLabels rounding the size off to a full unit).

This is fairly trivial work (~20 lines of code max) compared to having to write an entire system that uses 2D UI elements.

I appreciate that you put the work into presenting this, but I just wanted to give a warning that it may be a lot of work to write an efficient system in this way, compared to the alternatives.

1 Like

Yeah man, definitely, I totally agree with you. Not gonna lie, part of the reason I use my 2D UI system is just to say I did it because I’ve always found them to be really cool.

CylinderHandleAdornment is probably what you’re after.

I personally really dislike 2D UI for bullets. LineHandleAdornments would give a better result than 2D UI anyway; just apply the sizing the exact same way. Their thickness is in pixels, not studs.

3 Likes

Well, we all have our opinions, I personally like 2D UI bullets because it shows that someone put thought into the presentation of their bullets beyond the ordinary or built-in methods, however I agree, the built-in methods look nice, but is there not something to be said for people who want to do something outside of the ordinary? To think outside of the box? Once again, I completely agree with you, you can make things look nice with built-in methods, just trying to help developers better themselves to be able to create instances such as 2D UI particles on their own, after all, isn’t that what ROBLOX is all about? “Powering Imagination”? Of course you can use what other people have imagined and created such as Adornments as described, but you can also create things with your own intellect and imagination. It’s the ROBLOXian dream man, but once again, I totally agree with you!

2 Likes

I don’t think you’ve met my friend 8083eaa8229cb3c6cd5230804714b12c yet

6 Likes

Always wondered how to do this in 2D :slight_smile:
Math lol

This is an example of using gun trails. I’ve also used them for rain - you can actually run around 1000 of these a second if you do it right.

In action

1 Like

I remember seeing that! You did great on that man, just out of curiosity, did we use the same methods?

Along with what many others have said, it’s a nice tutorial system but I’ve found it easier to slap together 4 attachments / 2 trails in a “+” shape configuration. Works fine and is fast. Edit: The 4 attachment / 2 trail system is what I did before trails activated FaceCamera. With that property in place I can use one trail with that property active and it’s all set to go.

Late edit: In some cases this sort of system would be beneficial because you can nearly seamlessly add other effects (i.e. sparks flying off of a tracer round) so I guess it’s dependant on usage.

Yeah there has definitely been improvements in Trails lately, I was exaggerating a little when I said that I found Trails to be disgusting, I should clarify, people who do not know how to use Trails properly or do not utilize trails in a fashion that is visually appealing sicken me. However, usually, those types of games I see on Twitter and are never finished, not in final products often. (lol thank goodness) I don’t mean to degrade anyone who uses or prefers the trail system by this tutorial, I always encourage all forms of development, even if they’re bad, I mainly put out this tutorial as I said in it, to improve developers who may be less experienced than others. I fully believe in ROBLOX’s motto to “Power imagination”, I love it, and that’s why I have stayed with ROBLOX for so long! (that and DevEx, let’s be real here)

1 Like

Yeah, makes sense.

1 Like