Main Hero and Animations
Before diving into the weapons mechanisms, I want to tell you a little about what the hero consists of. In recent articles, We've already told you that once we decided to abandon in-engine animator and write our own.
All the animations that we've got in game, especially the main character, who is a set of rendered frames. As you can understand, our hero has dozens of states and each one of them contain several animations. There are also transitional animations. And all of them are "hand drawing pixels".
Therefore, we have our own animations management system which hero states are interact with.
When the hero enters the melee attack states, our manager triggers different attack animations instantly. So each frame contains "hero in a certain position with a weapon".
Firearms are little bit more complicated. The hero can be in one state, and the firearm is in another. For example, our hero is in motion or in idle, and the weapon can be in state reloading or fire. Exactly because of that we create layer processing for animator.
For those states where the hero can use firearms, the hero animation is presented without hands. Firearms are drawn along with the hero's hands in different aiming positions.
Therefore, when the hero is aiming, moving the barrel to different positions, we do not spin the weapon sprite. We display the desired rendered frame of the weapon's rotation. This definitely added work for everyone, but at the end — we've got the one and only a FAIR PIXEL.
Dosens of Parameters
That's just how it was, that we use the same structure for firearms and melee weapons settings. From one side, it's not very convenient for editing. For example, if setting up a melee weapon, there is no need in seeing part of parameters that only a firearm has. But if you are not a lazy programmer or you have a tool programmer in your team, then it's only a matter of time to write processing for the editor. Unfortunately, we haven’t done it so far.
It seems to be obvious that naming should always be clear, even if these are variables and you are only one programmer in small team. When settings of weapon unit consist of hundreds of fields, then this is a blast! Remember to keep a short description of the parameter. While working on weapons balance, most likely it won't be the programmers job, but the programmer will be the one who should unravel ancient programming manuscripts to game designers.
In addition to the obvious parameters, we also store those that are associated with animation or exist due to the frame-by-frame animation approach and the absence of an "out of the box" animator.
Let us give you an example for a firearm shot and reload. The hero shoots in motion. The walking animation plays. The weapon reads the settings from the animation shot (frame sprites and spacing) and then animation starts. During the playback of shot animation, at the moment of frames switching, the number of current animation frame synchronizes with the weapon settings parameters, which stores the frame of the shot. If the animation frame is right, then the weapon should fire.
The same situation is with reloading. The only difference is that during the shot, the player must aim. And for recharging, this is not necessary. But we'll talk about aiming itself a little bit later.
During the shot, the weapon must know the point of projectile exit. And during aiming - the length of the barrel so that the hero rested with the weapon against obstacles or moved in the opposite direction if he was already close to the object. To make this happened, we store a dataset for each aiming frame. So to hundreds of parameters, you can safely add a hundred parameters related to animation.
The Projectile itself can be described in dozens of parameters, it's one of important architecture parts. Top of the mountain. So we will consider in more details below.
Depending on the firearm, after the shot or before, the bullet casing flies out of the weapon. To do this, we store the stock, delay, or animation frame number, effect number, and sound information.
In melee weapons there is also a personal list of parameters that's not used in firearms. It also didn't appear immediately and didn't change once. During the strike practice, lot of parameters can be checked for each frame. During the strike, the hero must move forward or backward, creating a "damaging reaction", a reaction to the impact and also movement...
Both melee weapons and firearms share the same customization structure. Melee Strike, creates calls that store part of the Projectile settings.
In the beginning there were two types of Projectile: the melee and the bullet. Today the number of types has reached 20. For example: rocket, homing missile, laser, electric field, acid smoke, grenade ...
Prefab, speed, distance, damage level, knockback force, birth effect, main effect, destruction effect, sound settings, sound wave propagation to attract enemies, and more.
The prefab may be missing. The weapons themselves can have settings of the same Projectile type, but with different prefabs. For example, enemy rocket launchers can fire visually different rockets.
The main idea of Manager is to control pool of projectiles.
In our project we use the rule - as less MonoBehaviour objects with Start / Update methods on the scene, as better it is for us. Yes, the scene is full of different "scripted" objects, but MonoBehaviour-methods Start and / or Update (and other heresy) have only one king!
On the scene, among the hero, there may be various objects that may have weapons (opponents, turrets, etc.). When such an object is initialized, it initializes the weapon, which in turn instructs the Projectile Manager to create a pool of projectiles of a certain type.
During the shot, the weapon draws a projectile from the Projectile Manager pool. Then he sets various parameters for the projectile (position, direction, layers, etc.) and activates it. After that, the projectile can live on its own, being updated with the help of the Projectile Manager.
As mentioned above, while using a firearm our hero shows two animations: a body without arms and hands with a weapon.
Weapons have got next states such as pullout, aiming, attack, reload, load, and hide. There are corresponding animations for these states.
When hero finds a new weapon, our controller prepares animations for the weapon states. Frames for these animations are stored in the weapon preferences.
The controller, in the presence of a firearm, always keeps track of two things: aiming and reloading.
Ofc player can start reloading without aiming. And here the pullout and reload states should be triggered sequentially. During reloading, our hero can be damaged or interrupted by another state (jump, tackle, block). Therefore, on the side of the weapon, we store the frame parameter, after which the clip or bullet is added (protected).
Shooting can start in three hero states: idle, run, and walk. When the weapon controller catches the aim command and the hero is in the run state, the hero state switches to walking. The weapon controller includes a hero animation layer, which displays a weapon with arms, and the hero state (idle or walk) transitions to body animation without arms.
While firing, the weapon controller triggers algorithm of Recoil. On the weapon side, parameters for recoil are stored: duration, delay, number of frames and final delay (if necessary). Basically, Recoil controls or assigns a frame of aiming animation to the weapon controller.
Aiming parameters are stored in each weapon and there are a lot of them too.
Aim type, distance, aiming sensitivity, minimum / maximum radius, sight birth radius, duration of different sight states, reload radius, step of increasing the radius during firing, time of increasing / decreasing the radius, and more.
We use different visual types of scopes, and objects themselves are stored in interface objects general storage. The sight consists of four elements, which move away from the center of the object during a shot, movement and reloading. In the state when the hero is standing still, the elements of the sight are reduced to center.
The scope also has states: normal, birth, reload, and completion. The crosshair aim states is to influence the crosshair distance elements and the color alpha.
When the Firearm Controller receives input device data, it calls the Aim Controller's Start method. The Aim Controller prepares the data with scope objects, and after that starts the birth state. The state of birth of the sight brings the sight elements to the center ...
In parallel data from the input device may change, so the Aim Controller updates the sight direction and position.
Also during the update our Aim Controller calculates including the “scope frame”. This frame depends on the sight direction, the lower and upper boundaries of one sector. The sector boundaries are calculated once based on sector height. And the Firearm Controller reads this data for weapons and animations.
Shot, Bullet & Effects
Let's look at the entire chain of “shot” process by using the example of a 'Pistol Weapon" and a "Bullet".
"Pistol Weapon" inherited from "Firearm", "Firearm" from "Weapon". "Hero" creates and updates "Firearm Controller". "The Firearm Controller" creates the desired animations, monitors the "Firearm", "Aim Controller" and "Recoil".
"Pistol Weapon", at the time of creation, asks the "Projectile Manager" to create a "Bullet" pool.
"The Firearm Controller" keeps tracking the input device and scope data. When there is aiming and the shot command appears, then the 'Firearm" is activating, in this case the "Pistol Weapon".
"Firearm" contacts the "Projectile Manager", gets a link to a free and needed "Projectile". After receiving a missile, "Firearm" sets it up and launches (turns on). Depending on the type of weapon, other treatments may also be triggered, such as ejection of a cartridge case, generation of a camera shake pulse, and so on.
Bullet Projectile turns on!
The "Hit" object is filled with data. It stores various properties at the stage of turning on and hitting the projectile. The link or clone of "Hit" can then be passed on to other entities.
First, "Bullet" checks via "Physics2D.OverlapCircleNonAlloc" if missile was born in a place where objects are already present at a distance of 1 pixel. If there is a space to fly/move, then the "Physics2D.RaycastNonAlloc" check starts.
Both detect the presence of external damage (IDamage) on objects, and objects Surface components with coverage tags.
Again, all of these checks can return the Through property, which means - passes through this object.
Any of the projectiles can target "Surface Hit". It's a static class whose task is to handle the projectiles hit on various surfaces, causing sounds, effects, and sometimes even acting on an object if its parent implements the damage interface (IDamage).
For each type of Projectile, in the effects and sounds dictionaries, an interaction connection of this projectile type can be created with a surface.
When the bullet turns to "Surface Hit" and transfers the data of collision, "Surface Hit" will find the desired effect in the dictionaries and transfer it to the "Effects Manager" so that it can launch the desired effect from the pool.
Also, the bullet can turn to the Effects Manager to enable effects such as burst, trail, and more.
Returning to the Firearm Controller. After a shot, such entities as a weapon, cartridge case, projectile, flash and flight effect, sound effects - each live its own life. Each entity is updated by a specific manager and is most often a pool member.
Cold Weapon and Hero (Melee Controller)
The implementation of cold weapons on the hero side has got differences with firearms. Firearms work in parallel with the hero "idle" and "walk" states. And the cold one is connected specifically with the state of the hero "Melee State".
The hero creates a "Melee Controller", just like a firearm. But the "Melee Controller" has less tasks.
Our controller prepares weapon animations, binds to Melee State calls. Then it configures sound handling, and stores a reference to a specific "Weapon" instance. And if necessary, the "Melee Controller" triggers the hero's "Melee State".
"The Melee State" varies by the states of the strike: preparation, normal strike, preparation for a strong strike, and a strong strike. For each such state, the weapon stores frames for animations. And the "Melee Controller" collects these animations.
Many conditions affect the switching of the hit state: changes in the commands of the input device, animation frame, stamina...
For example, the strike starts with the “preparation” state. The corresponding animation turns on. When the animation ends, an input device event check it back. And then there can be a transition to the state of "normal blow" or "preparation for a strong one."
Also, the hero can enter into regular attacks series. On the side of the weapon, in the frame animation settings, we're storing marker for the ability to use this animation in a series of hits.
Hope you understand our pain and the pain of programmer who have build this architecture all alone.
So this is the end of story. Hope it was helpful for ya. If you've got any questions, feel free to ask.
We've got a Steam page already, so it would be very helpful to have you among our subscribers.
Check out our Twitter and subscribe.