This Week In Veloren 17
This week we saw the release of 0.2! We take a look at some of the testing, as well as the new combat system by @Timo. Make sure to check out the downloads page to download and play the release!
Big thanks to all of the contributors who have contributed to 0.2! @Zesterer, @imbris, @Timo, @slipped, @Pfau. @Desttinghim, @Algorhythm, @Yeedo, @robojumper, @AngelOnFira, @Qutrin, @xMAC94x, @Cody, @JMS, @LunarEclipse, @humb1t, @Timchenko, @YuriMomo, @RustyBamboo, @Erocs.
Analysis by @YuriMomo
Last weekend, there was a large emphasis on testing. We ran two organized server stress tests. This was so that we could see how the server handled many people the server could handle, but also find as many bugs as possible.
During the testing, many issues were found that slowed down the game. YuriMomo did quite a bit of analysis to help find problems. From this, we got lots of cool information.
In this video, we can see how a frame is rendered. There are a few steps to this.
- Render terrain
- Render character
- Apply post-processing, such as gamma correction, saturation adjustment, and FXAA
With tools that can break apart the frame, we can look a lot deeper into issues, and where processing time is being spent.
Veloren with part of the terrain rendered. As you can see, although Veloren stores what is underground, it is not rendered. This is an optimization that prevents rendering anything that isn't visible. This is a technique known as "face occlusion culling".
On top of that, Veloren also makes use of back-face culling, which hides polygons that are facing away from the camera. In the future, Veloren will also start using frustum culling. This will prevent anything that is not in the field of view of the camera from being rendered.
All of these optimizations help Veloren run on slower systems, which is a key principle of the development.
Combat by @Timo
Fighting with and against other players or NPCs is something that immediately made the game much more fun. This week the combat MR was merged, enabling players to attack one another with knockback, damage, death and respawning.
There are 4 main parts this needed work on:
- A better input system
- Communication between client and server
- Damage and Death
- Red color damage indicator
- Kill command
1. A better input system
In order to allow inputs from the mouse to be treated the same as input from the keyboard, I decided to create a
KeyMouse enum containing possible
Keys and possible
key_map, previously used to map mechanical keys to in-game inputs, was changed to map a
KeyMouse event to an in-game input. In order to not confuse the names, this input to the game is called
An example for a
KeyMouse type is
KeyMouse::Mouse(Left) while the default corresponding
GameInput::Attack. A player can change the
key_map as they wish to make a mechanical key trigger an attack or make mouse buttons move the player.
2. Communication between client and server
A client shouldn't be able to control how much health an enemy has, because this would be very easy to abuse. Therefore the client has to ask the server to make an attack for him and send the results back. But because games need to have fast feedback to make actions like attacking feel good, the client simulates the effect of the attack locally. The structure of the project makes this very easy, as most of the code is shared in the
common crate anyway.
So when a client front-end like
voxygen wants to start an attack, it asks the client to do so. Because things like jumping and attacking are event based rather than continuous like movement, there are NullStorage components for these actions. These components are used as flags to communicate a client is doing something. When the client has a
Attacking flag, an ECS system can check for that and act accordingly. When the attack is over, the flag can be removed.
This was my initial plan, but as it turned out later, players could build a bot to spam attack hundreds of times per second, killing everything in sight. To solve this problem, I added some data to the
Attacking component: A
time field used to determine the time since the attack has started. Now, if a client requests to attack, but the last attack was 1 nanosecond ago, we can just deny it.
As the ECS systems run on both client and server, no extra effort had to be made in order to simulate an attack locally, before the server has time to sync it.
3. Damage and Death
When the ECS system responsible for parsing player inputs realizes an attack has been made, it tries to find out which entities are hit by it. For this, the following code is used:
if entity != b && !stat_b.is_dead() && pos.0.distance_squared(pos_b.0) < 50.0 && dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
This code checks if the entity is even alive, otherwise it wouldn't make sense to attack it. Then it determines if the entity is near enough to the player in order to be affected by it. The last line checks if the player looks in the direction of the target, so if the sword-swing-animation was already implemented, it would hit the entity.
If all of these conditions are true, the entity's health is reduced and knockback is applied (this works by adding some values to the velocity).
If an entity has zero health, another ECS system gets activated and adds the
Dying component flag to it (We still need to implement a check to see if the client is already dead). With this flag, the next time the server ticks, the entity is hidden or removed, depending on whether it is a player or an NPC. As a player needs to be able to respawn, we can't fully delete them.
Respawn is an input like any other. What's special is that
Respawn only works when the character is dead, while
MoveForward only work when the player is alive. Respawning a player is very simple, as it never really died. We just need to increase the player's health and tell him "Hey, you are alive again" and that's it. For a better experience, we also set his position a few blocks in the air to indicate he respawned and was not just invisible for a short time (which technically he was).