Late happy new year! Went on a trip for a week, hence the huge time gap between blog sections. Apologies for that.
To create truly engaging NPCs in game development, it is essential to implement believable behaviors, including line-of-sight awareness and compelling death animations. In this section, we explore the tutorial's way of implementing said methods to immersify the game environment.
We can copy the player's ray casting method from raycasting.py into the NPC class. Of course, several screen-related lines will need to be rewritten since displaying NPCs' stats is unnecessary in live gameplay.
As enemy vision need not be referenced to guide faux 3D projection on the screen, the new method merely calculates the NPC's physical relations with the player – whether they are in the same tile or within each other's line-of-sight.
This variant of ray casting builds a horizontal ray to detect the player or a wall in the game world. It iterates up to a maximum depth, calculating the tile coordinates at each step.
If the tile is the player's position, it records the distance to the player and stops. If the tile is a wall, it records the distance to the wall and stops. If neither condition is met, it continues the ray cast ad infinitum.
The same applies to the ray vertical coordinates section.
Here is how this iteration of the program simulates line of sight: if the player is directly in front of the NPC, the horizontal and vertical distances will be the same. However, if the player is at an angle to the NPC, the horizontal and vertical distances will be different. By taking the maximum of these two values, the program is effectively calculating the distance to the player along the diagonal direction, which is the shortest distance between the NPC and the player.
By using this diagonal distance, the program can determine if the player is within the NPC's line of sight. If the player's diagonal distance is less than the wall's diagonal distance, it means that the player is closer to the NPC than the wall, and therefore, the NPC can see the player.
To debug whether the line-of-sight method works, the method below draws the NPC on our 2D map, and when it can see the player in the open, draws an orange line between them. It handily disappears when the player hides behind a wall.
Take cover!
By building our line-of-sight method with wall distance in mind, firing weapons at or behind walls will no longer damage NPCs, improving game logic believability and immersion. Cover-piercing weaponry is a story for another time…
It is all coming together now.
The tutorial proposes creating another check method for NPC health. The creator is noted to make their codes modular, making it easier for starters to read and decouple. I will take note of this approach.
Back to check_hit_in_npc(), instruct the program to deplete NPC health and check their liveliness over the whole function's run-time.
If you recall the previous Pygame starter project, one-time animations have their number of sprites per deque counted. This variable (number of sprites in folder) serves as the animation trigger that stores the state of an animation (playing or not).
Put the method at the bottom of run_logic()…
And kill to your heart's content!
If any of you feel that the death animation is running a bit slower than you prefer, and you have no energy to build a new method to change it, you can create a global event to hasten your death animations.
Global events affect every nook and cranny of a program. It is typically a high-level event that is designed to decouple different parts of any program, not just in animation and game logic. In our current case, we are creating a global event that makes our sprite deques scroll through contained images much quicker.
Imagine the normative school building (program) with many rooms (modules). Such a building would have a public address system (global event system), any message or announcement (signals or flags) can be broadcasted to all rooms simultaneously.
By making the global event a quickly timed event, it can be implemented as a condition for the death animation of an NPC, allowing for a faster increment of the frame counter and thus a faster progression of the animation, while making it accessible to adjust in the main file.
Just like how anything can be spoken from a school public speaker, global events can be used to direct many different methods in a program.