Some incredible people on gamedev.net forum said to me, that it would be great if my posts were little more detailed. So I decided this time I will show you entire process of thinking behind feature implementation. There is also some cool C++!
What I learned at Ubisoft, which is a company of tens of thousands of people, is better planning and organizing work. It helped me in my personal projects too – if you have very limited time it’s best to plan ahead and make best time of it. Let’s start simple:
The goal / user story / desired outcome
We want to have all the players in the proper positions on the field. They should switch positions following volleyball rules.
The reasoning / why?
It’s a road to implementing serve receiving mechanics – in order to receive a serve, we need at least 3 players on the receiving team and one player on the opposite team serving. They need to be positioned correctly. However, instead of only implementing proper position behavior for 4 players we can go all the way and make all of them – we will need it anyway later.
For starters let’s assume we will use “school book” positions. Obviously, in the real volleyball game, players optimize where they stand, within the rules, based on the strategy and common sense. In our case we will start simple, assuming there are 6 fixed positions on the team’s half of the field.
Here’s how it looks like:
- A way to specify positions on the field, so we can send players there during play,
- An AI behavior that will make a player to go to his desired position,
- Switching each team current setting by keyboard input,
- Some form of UI that will show us player positions / roles and current team settings so we know what’s going on.
Looks like a plan. It might be actually a good idea to put it in some kind of tracking software, like JIRA, and even play with assigning time estimates. Then track how much time each of these tasks actually took and compare with your estimates. You can learn a lot about project management just by this simple exercise.
There is additional benefit of making detailed plans – whenever a feature looks too big, or you feel intimidated by it, try to split it into smaller tasks – not only it will help you “materialize” your thoughts, it will look less scary on paper, and also finishing every single small task will give you feeling of achievement and boost motivation to move on.
So let’s tackle it step by step.
1. Specifying player positions
Theoretically, the easiest way to implement this would be a list of 2D vectors. However it would be a disaster to create them. What is the position of the setter in meters in setting 5? (-3, -6)? Yeah, I don’t think so. That’s why I decided to place special waypoints on the field manually, and then assign them in a special “table”. I can then simply retrieve their positions and use them as AI targets.
Here’s how they look on the field (little black circles):
Now for the data structure, that could hold references to them and return result when asked. Waypoints need to be clasified by team, player role and current team setting. We can go with nested maps (dictionaries) and an array – I know, performance disaster! But we simply don’t care right now – if we end up profiling the game and it would turn out that this simple position finding takes significant amount of time, we will worry then.
Initially, I implemented it all in blueprints. However, there was a bug preventing me from retrieving proper objects, so I moved the whole data structures and supporting enums and structs to C++. Here’s how it looks like:
TMap<EPlayerRoles, FSettings> Positions;
class VOLLEY_API AFieldPositionsCode : public AActor
TMap<EPlayingTeams, FPositions> FieldPositions;
FVector2D GetFieldPosition(EPlayingTeams Team, EPlayerRoles PlayerRole, int Setting);
TIP: Define all your common enums and data structures in C++ code from the start – it will save you hassle later. While you can easily access C++ objects from Blueprints, you cannot use Blueprint objects in C++.
The AFieldPositionsCode class serves two purposes. First it stores all the waypoints in FieldPositions member. And second, it exposes GetFieldPosition method which you can use to find the current target for a given player.
Now, to assign the waypoints, you need to place AFieldPositionsCode in the level and go into property editor:
2. Creating behavior tree task to go to the current field position
We already had a behavior tree ready for our AI, so now it’s simple a matter of adding new task under proper state condition:
We care only about the items marked in red – rest is left over from the auto-serving feature, not really used right now. But I’m not getting rid of it – I will use it later.
Implementation of MoveToPosition task (click for browseable version):
3. Switching current team setting
To test if the players position themselves accordingly, we want to be able to force a team setting change manually. I store the integer value per team in GameMode blueprint. Actually, to be more function-friendly, I store it as a map, where the key is Team enum and the value is the setting integer. Changing the value is simple:
I hooked up this function to keys 1 and 2 on the keyboard, for TeamA and TeamB accordingly.
4. UI showing player role and current team setting
For the player role I went with 3D text, just above player head:
There is a blueprint function, which does exactly that – all I needed to do, is to add it to the Player blueprint Tick event:
For the team setting, I already had a full screen UMG widget, so I just added necessary fields:
And bound them to appropriate GameMode variables.
Here’s the end result:
Now, that we have two teams and players that can position themselves accordingly, we can start implementing more gameplay features! Next one will be serving to the other team and receiving serves! Also, we will add score counting, so it will finally be a proper GAME.
Let me know in the comments if you like this form of posts. Are you enjoying reading more details? Would you be interested in video tutorials that would cover literally every step required to get particular functionality done? Any feedback is welcome!