GameDevHQ — Day 22 — NovaStar Dev Diary: Putting the Weapon System Together
Today’s chunk of development consisted of working on two main features, integrating the weapons that have been created into the weapon system for the game and completing the charge laser weapon which received another round of revisions.
The Weapon Selection System
The weapon system was developed by my teammate Mar which acts as the primary logic that the game uses to decide which weapon is active at any given time. The system works off of using an enum structure and a switch statement to check which weapon should be active and then updates the active weapon whenever the player picks up a powerup. The enum setup allows for better readability for each weapon state within the code and inspector menu and serves the similar purpose within a switch statement as using a simple integer would. The structure of the selection system best accommodates the simpler weapons we have designed as pressing the fire button will simply lead to a case check and then instantiation of the active shot type which acts on its own scripting. This means that implementation of the following shot types were straightforward processes:
- Single Shot
- Double Shot
- Triple Shot
- Wave Shot
- Small Shot
- Giant Shot
More complex shot mechanics can still be integrated into this system but required a bit more modifying to get working. Attempting to get the charge laser to work within this system was the primary focus of today’s development.
Revising the Charge Laser
Since the weapon selection system has been copied over to my repository I can now work on designing any new weapons to work along with this system rather than within the testing models I was working with previously. Adapting to the charge laser to work within this system required similar behaviors to the design I discussed in yesterday’s blog post but some components of the process needed to be broken up into separate parts partially for functionality purposes and partially to reduce clutter within the PlayerWeaponsFire() script. In the previous version of the charge laser, all functionality took place in a single script and the function that allowed the relevant sprites to stick to the player model were handled from a global controller object. Additionally the way the sprites for both the charging animation and the laser were handled by placing them in the scene as deactivated objects and reactivating them when needed.
In the new version of the charge laser the sprites are instantiated instead and some of the actions are processed within a script held by the charging sprite. Activation of the entire process is still handled by checking for the player to provide input by pressing the specified fire button, in this case the space key, which then instantiates the charging animation object and sets the player as a value of one of the variables in the script that is attached to the sprite.
The newly created charging sprite will record the time it was instantiated and then wait for the player to release the fire key. On release the function performs similar functions to the old design where the duration the key was held is tracked up to a maximum of three seconds and a minimum of 0.75 seconds. If the player does not charge up to the minimum duration then the shot attempt is canceled, the player is returned to a neutral state and the then the sprite object destroys itself. If they do hold it past the minimum then the function will record the hold duration time and then pass this time to a function back within the PlayerWeaponsFire() script.
The reason I felt that this part of the process was better segmented off into a separate script on an object from the player was that it allowed the GetKeyUp detection as well as the constant time tracking to only be active when they are relevant rather than needed during the charging process. Rather than implementing more checks within the PlayerWeaponsFire() script to disable these functions when they are not needed we simply remove them from the scene altogether until their container is instantiated to carry out the specific function they are design for. Once the charge sprite script finishes its operation the processes return to the PlayerWeaponsFire() script where the charge sprite is destroyed and the firing processes are carried out in the same method they were in the previous version except instead of activating an already present laser object we instantiate a clone of the prefab every time and access its animator.
Finally a coroutine runs in a similar fashion to the previous version where it counts from the total charged time down to zero upon which it will then wait for the laser to reach the ending animation state and then destroy itself after a 0.3 second delay to allow time for the ending animation to fully play out. After this point the player’s ability to fire is reenabled and the process can repeat again.
Fixing Sprite Rotations
An issue that I had with the previous design was the method I had used to allow the sprites to follow stick to the player ship. In the old design I assigned a script that I would apply to a global object to update the position of deactivated objects to match that of the player ship but with a specified offset to place them in the correct positions. The reason this was needed was because setting the sprites as children of the player object allowed them to easily follow the ship but they would also follow the player’s rotation and mess with their visibility as a result, this meant that assigning the sprite to the global space and adjusting their positions was a more viable option at least on first pass. My problem with this design was that the script I was using could did the job well but could only handle one object per script this meant that in its current version I had to apply multiple copies of the same script to the global object to manage the positions of multiple objects, in this case being the laser sprite and the charging sprite. This method worked well enough when the two objects involved with the laser operation were being disabled/enabled when in operation because we could provide the objects in the scene to serialized fields in the editor. This method did not work once the design switch to instantiating the sprite objects as they could not be easily assigned to these two global scripts since both instances of the script had the same name. This was still workable but was clunky and could lead to possible confusion or trouble down the line.
To simplify this and accommodate the new instantiation behavior the Follower() script was placed onto the player object and on instantiation the new object would be assigned as the follower object in the Follower() script and an integer value would be changed to dictate which offset values would be used. This means that we would only need one copy of the Follower() put into effect and when the charge sprite or the laser sprite is completed with their operation then can easily be deleted and swapped in for the next sprite that needs to be tracked.
This method seemed effective enough but further research yielded one more solution that allowed for further optimization. My findings were a way allow an object to counteract the rotation of its parent which means it can always maintain its original rotation position. This method was very simple and required only one line of code to be entered in the Update() block for it’s own script. This script could then be applied to any of the sprite object that needed to always be facing the camera even if their parent object rotates.
This discovery meant that the previous version of the Follower() script that was just described could be scrapped and instead we could simply apply this new CounterRotation() script to the sprite prefabs and instantiate them as children of the player. This means that any repositioning was handled automatically due to the sprites being children of the player and if the player rotates during movement the sprite will continue to properly face the camera. This final change allowed for further optimization and a much cleaner and more functional design than we had previously. This now meant we had six functional weapon types that the player could dynamically switch between which is a solid foundation for us to build off of in the coming days.
Overall this was a very productive session and a good place to leave off on for the weekend. The plan now is to continue weapon development with the implementation of the homing shot and to push these completed features up to the dev build. Until then I hope you all have a good weekend and mahalo for reading. Aloha!