Day 3 - HUD, enemies, AI, and more!
Good morning Night City -Cyberpunk 2077
My morning is not a morning, but an evening, and it starts after a good sip of caffè latte: I am ready to face the day. I have a lot of ideas in my head and I want to make them reality.
I had an interesting discussion some time ago with some friends of mine about the difference of cappuccino, caffè latte, and latte macchiato. I am not a coffee expert, but I knew that the difference is in the amount of milk and coffee. The articles I read on the internet are not very clear, however this article here explained by “nientepopodimeno” but Caffè Borbone is very clear and I recommend it to you if you’re interested.
We need first to resolve the performance problem: a dear friend of mine advised me to reduce the size of the map since I would make less effort that way. I decide to throw down the whole dungeon generation algorithm and redo it almost from scratch.
I’ve watched some videos and read some codes online, but this excellent video brought to you by “TheZZAZZGlitch” is really interesting and explains in a visual way an approach to generate dungeons.
The generation algorithm is very interesting: first it generates rooms as I do, then unlike me who immediately tries to connect them, it generates “dumb” rooms (called 1x1) and tries to connect them with existing rooms. It also does another thing: it generates a special border (soft border) that does not allow it to be crossed or passed in any way.
I reason a little bit about how to do that: generate the rooms, okay, which I already had. I can import the 1x1 idea: sounds good to me!
To my surprise it is an approach that works very well: by increasing the number of 1x1 rooms I can increase or decrease the complexity of a dungeon.
Basically it is like connecting multiple rooms together: an approach that works very well!
Before I re-create the collisions and “the walkable and the unwalkable”, I want to make sure that I generate the items and everything that is needed to go from one world to another correctly.
It occurs to me that I have to generate items, money, etc., and these should not be in corridors. Grr, this is difficult.
Going back to the performance problem, I decide to reduce the size of the map to 56x32 pixels. Since I did my algorithm in a way that I can change the size of the map, I can do this without any problem and with some surprise I see that the performance has improved a lot, generations are almost instantaneous.
I am very happy and this is the result!
Things can’t stop here:** I have to generate the items and the money**. I have to generate the exit that will take me to the next level. I have to generate the enemies and some elementary game elements: key, chest, exit, enemy, boss, trap. After all, I can treat everything as walkable/not walkable as well.
I create a dictionary of tuples: the name is something I know, the tuple there is number (which I will need in the final map), the “common” name, and a color that will then be replaced by a texture in the future.
{
"key": (1, "key", (255, 255, 0)),
"R#@*JE@": (0, "nothing", (0, 0, 0)),
...
}
The idea is to draw the map and depending on what is in the map, draw something different.
The overlapping problem
It also occurs to me that there might be overlapping problems: however, if I calibrate the spawn order well, I should not have such problems.
The overlapping problem is a problem that occurs when I spawn an item, and then I spawn an item on top of it.
For example, I can safely decide to spawn enemies, chests, etc., and only after stairs, since if stairs replace an enemy, it is less serious than having an enemy replace stairs, it would make the game impossible.
Graphically there is not much changes, but in practice I always have an exit and something on my map.
Movement and collisions
Now we have to move on to movement and collisions: I simply have to make a function that checks if I can move on a certain tile, and if yes then move, otherwise not. It will suffice to treat enemies as blocks and everything else as something walkable.
The next step will be to define each entity behavior. Let’s start with stairs -> I decide that every time a valid movement is made events happen, so I use the strategy of the boolean “moved and not moved”. It works.
Enemies AI Idea
Now I have to deal with the AI of the enemies. I want to define different strategies, but for now I am content with a “random” approach.
A first implementation is this: find all the reds, if they can move go in the direction they randomly choose. This approach in terms of memory sucks: it would be better for me in generation phase to save the position of the enemies and update it as they perform movements.
Bugs
But here’s some bugs arise:
- if I decide to move on a tile that has already been chosen by an enemy, I am not a solid object so both I and the enemy overlap.
- solved in a simple way by assigning me the property of being solid
- since I have defined that every object when stepped on and is “walkable” becomes a normal tile again, enemies do the same thing with stairs. In other words: the enemy steals the stairs! This is not good and to solve it I have to handle the limit case of the stairs (between them they should not kill each other as they are all solid).
Technically, I could also change the order of generation: I now generate the rooms, generate the dumb rooms, connect them, generate the items and game elements, however, to save resources and time, I could generate the rooms, place the player, place the items generate the 1x1 tiles and then connect them.
Another bug: enemies and I move in the same tile and overlap: this is not good, and it is because I was never updating the fact that the area where I was standing was free and I had moved to another area. Instead, every time I move, I have to set the tile where I am as “walkable” and the new one as “not walkable,” which comes from the fact that I occupy it.
Another approach I came up with is to simply define whether the playing field is walkable or not.
Enemies AI: let’s attack!
The time has finally come: define the IA behind the enemies: meanwhile, I want to define three kind of enemies, one that is very stupid and keeps doing what I have been doing so far (wandering around randomly), one that is smart and chases the player, and the other that is greedy and steals everything stealable
I create the AI for the first and the second kind, but not the third: I have to think about how to implement it.
Now I find myself in endless dungeons. I definitely need to implement a quick attack just to make enemies disappear. For now I settle for “last key pressed is up, so I shoot up”. I can implement it by writing that the last key pressed “looks” in a certain direction, regardless of whether or not you can move.
HUD
It’s late but I can make it!
I take a short break, and after a while, I find out about pr.end_mode_2d()
which allows you to draw a HUD.
That’s fantastic! In the top left corner I want the stats counter in general, and at the bottom I want a textbox: the messages go away after two seconds (120 then, because I’m at 60 frames per second) and the hud has to disappear.
It occurs to me to make a buffer for the messages, and eventually show it slowly by removing each string.
Here’s an example and why I told the 120 frames per second thing:
messages_buffer.insert(0, ["Level up! You are now level " + str(player['level']), 120, pr.GREEN])
Messages are placed in a list, and each message is a list of three elements: the message, the number of frames it has to stay, and the color.
At the end of the game loop, I check if the message has to be removed, and if so, I remove it, otherwise decrease the number of frames it has to stay until it reaches 0 and shows the message in the box.
This is the result: