![]() |
(Bullet sprite by Nicolas Katsis) |
This part is solely on how I implemented the bullets shown in the video in Part 1. There are two parts to this split across two scripts. The first is its behaviour and the second is the spawning of the bullet.
First of all, it's behaviour.
The first thing that occurs on spawn is that the bullet assigns itself to a group of objects known as "P_Bullet". You will notice in all the snippets of code that many of the variables and groups for this object are listed as "P_something". This is because we initially considered the idea of giving the player two different guns that they could swap between: A pistol with a higher ammo count and longer range but weaker damage, and a shotgun with a much higher damage but fewer bullets and shorter range. The P_ was a reference to 'Pistol' bullets and S_ was a reference to 'Shotgun' bullets. Unfortunately, due to time constraints, the Shotgun was never introduced.
For those unaware, Godot has a feature known as groups that allows you to tag objects with a group like a class but without any additional attributes. Its main use-case is for aid in collisions. In this situation, it was to easily identify whether the bullet was from a shotgun or pistol and therefore how enemies would react accordingly. This feature, of course, never made it in, but the code was already in place.
Now for the really hard bit that ended up being only one line of code: The forward motion. Visible on line 11 in the screenshot above, I got it working (with help from the internet) by adding the position to itself (both x and y), multiplied by a factor to determine speed, ever frame. The function _process(delta): operates like the "Event tick" node in Unreal Engine in that it is called every frame. As mentioned in the comment as well, delta is the time elapsed between each frame. This is often used to stabilise certain actions so that the amount of generated frames per second (which can vary depending on hardware and software of the end user) do not influence how often or how fast they act (i.e. without delta, the bullets would be faster at higher fps and slower at lower fps).
Finally, there's collision. Once again, I'm using groups to detect whether the object in question should be collided with. So, the player character itself and the ghost (which we decided should not be killable) are part of a "NoImpact" group, which means on contact the bullet will go straight through them. Anything else it collides with will despawn the bullet. The code to decide action based on this collision is held within the colliding object and not here. For the bullet, its job is done and it despawns.
This side was the much easier part to programme. And it was the spawning side of things that proved to be my nightmare.
Initially, my code consisted of only lines 22, 23, 26 and 30. These correctly spawned a bullet when the 'fire' button (left mouse click) was pressed, but there was one big problem. In Godot, everything is within a hierarchy and through the paradigm of inheritance, certain aspects of Child nodes are determined by their parent. One of those, is global transform, which is the global position and rotation of the object. By default, spawning a bullet made it a child of the Gun, which meant that it followed any changes made to the gun's position and rotation. That meant that if I moved the mouse after firing, the bullet would also move relative to it. And if I moved or jumped with the player, the bullets would ALSO move and jump (while still travelling forward).
The solution, after hours of trial and error, was reported online by setting the bullet to be a child of the root node after spawn. In Godot, everything operates within a 'scene' that can act as a level, menu or whatever else you want it to be, and these scenes can be strung together to make different parts of a game. The root node is the top of the hierarchy inside the scene and has virtually no data attached to it, so little is inherited. On top of that, certain scenes can be imported into each other as lower level objects. This is so that objects with a lot of child nodes and code can be compiled into a modular object that you can duplicate and place as many times as you want inside the scene, like an Unreal Engine blueprint.
The command for this is known as 'owner' (line 28) which essentially calls the top node of the scene tree. However, this created a small problem, since the Player was a scene in itself and thus the owner from the Gun was not the root node of the level, but rather the Player's CharacterBody2D, which meant that while the bullets did not rotate or move in accordance with the Gun/mouse cursor, they did still move with the players position (running and jumping). There was also another issue, in that the bullet was now being spawned at the centre of the player and colliding with it.
At this point, I had spent almost an entire day trying to figure this thing out. And so, with a lot of frustration, I called it quits for the night. I'm mentioning this because it is important to understand that programming requires a level head and if you get overly annoyed at something, you likely won't be able to as easily figure out the solution, since frustration will cloud your thinking. It is better to take a break and try again another time, than to try and brute force your way to a solution and stress yourself out.
And it was that exact mentality that allowed me to fix the problem, as within the first hour of the following morning, I fixed it with lines 27 and 29 as well as some modifications to the collision layers. Before calling the owner, line 27 changes that node from the top of the current scene tree to the top of the Parent scene tree, setting it to the root node 0. After being moved there, Line 29 then sets the bullet's position and rotation to that of a Marker2D node, which followed the tip of the gun sprite, since otherwise the bullet would not inherit any positioning and spawn at (0, 0) in the level. However, this is only initial and so the bullet will not track these attributes again, allowing it to move forward without any influence from other vectors. Essentially, it now works!
To be completely transparent, if you're wondering what the flash variable and if statement below the bullet spawn are in the screenshot, they are for an animation with the gun sprite to show a muzzle flash upon firing. They are not important for making the bullets work so you can disregard them. The important lines of code are lines 22, 23 and 26 - 29 with line 30 if you are implementing a reload mechanic (more on that in the next post).
But yeah, that is how you make free-flying bullets, relative to the mouse position in 2D Godot. Hopefully that saves some development time in the future, either for me or someone else.
- JDM
Comments
Post a Comment