Tales of Fortune

Tales of Fortune is a multiplayer PvP/PvE games. The gameplay is to competitively and strategically plan out moves to beat your online enemies in a 20-30 minute long session game. Prove your worth!

  • Role: Network Programmer
  • Team Size: 2
  • Development Time: Continuous
  • Engine: Unity, C#

About

Tales of Fortune is a multiplayer online naval combat turn based game. The gameplay is to competitively and strategically plan out moves to beat your online enemies in a 20-30 minute long session game. The game will mainly be oriented around PvP but will also feature PvE in the form of AI enemies and bosses.

My Tasks

This entire game is a two man project which my co worker and I have pair programmed and combined our efforts instead of working on seperate elements of the game. This is mainly due to the fact that we are still coding the main framework for the game and we have considered splitting the work up for efficiency in the future. Since this is a stand-alone two man project we both function as jack of all trades working on all different aspects of the game such as graphics, shaders, post processing and lighting, camera, audio etc.

Multiplayer

Gameplay

The main gameplay loop is every player (ship) gets to cash in 4 moves every round, moves include cannon shots. So prediction is the main strategic element in the game. We aim to keep the gameplay fluent and give room for the players to use their creativity to edge out the other players. The players will also play vs AI of different sorts to gather resources throughout different stages of the game, because we are still working on the core framework most of the gameplay is currently missing, we will shift focus to the actual game once the building pillars for the game are done.

MVC (Model-View-Controller)

The game is built using the MVC (Model-View-Controller) pattern and serves as a structural framework. The Model manages core game data and logic, handling player actions and enforcing rules. Views present this data to individual players, rendering the game from their perspectives. Controllers coordinate player input, processing it to update the Model and ensuring synchronized updates to all connected Views. This separation facilitates organized code, helping manage the complexities of online interactions, synchronize game states across players, and maintain a consistent gaming experience for everyone involved. The usage of the module is shared between the server and the client.

Server and Client

Tales of fortune is an online multiplayer game and therefore the entire gameplay framework is designed to work around that concept. The connection between server and client is that every player is a client communicating their messages to the server and back. This is done using the riptide networking solution by Tom Weiland.

Server

The server is the global place where data is collected and then distributed to the clients in order to effectively sync anything from logic to animations. The server is responsible for time, handling the different states of the game, and performing all the gameplay logic (without any animations, just the logic). The server is basically the meeting point for all clients.

Client

Every player is a client with an ID and their build is where all the heavy assets are located. To minimize data sent to the server we simply receive data from the server and act accordingly on all clients. The client's main responsibility is to retrieve all the gameplay data from the server and produce the visuals of it on your device. For example all the game animations happen only locally on the client.

Data Oriented Programming

Big parts of the project were designed in a data oriented way because handling data packets this way was very optimal for us and simplified our code and communication between server and client immensely. We extracted most data out and used player IDs to identify which data belonged to what player, all data were modified using these indexes. This firstly made the code run much faster as we didn’t need to reference big object classes to modify one thing and made our messages way smaller. Finding what part of the code to change became also much easier as all we needed to know was what index was being modified, and change everything using that. This taught us a lot about how data oriented programming can be very useful to simplify code.

Message Handling

Every single multiplayer logic happens through messages getting sent from either the server to the clients or a client sending a message to the server. Using the message ID riptide uses reflection to call functions when messages have been sent from either side using the message handler attribute, however the IDs must match where you create the message and where you retrieve it and all data needs to be collected in the correct order.

Sending messages

 
private void SetRoundState()
{
    Message _message = Message.Create(MessageSendMode.reliable, ServerToClientId.sendRoundState);

    _message.AddString(roundState.ToString());
    NetworkManager.Singleton.Server.SendToAll(_message);
}
 

Receiving messages

 
[MessageHandler((ushort)ServerToClientId.sendRoundState)]
static void SetRoundState(Message message)
{
    string roundState = message.GetString();
    switch (roundState)
    {
        case "PlanningPhase":
            currentState = RoundState.PlanningPhase;
            break;
        case "PlayingPhase":
            currentState = RoundState.PlayingPhase;
            break;
    }
    
    changedRound = true;
}
     

Matchmaking

Players can join a game together through the matchmaking system implemented in the game. When the minimum required players needed for a match has been met the lobby owner can start the game.

Chat

For the multiplayer in-game chat we took mostly inspiration from League of Legends chat system. The chat shows allies as green and all enemies and foes as red. The chat also shows a timestamp for when the message was sent in game time. The client responsible for the message sends the message to the server, the server then resends that message to all other clients, using the ID each client checks if the message sent is local or not, that is how the client determines which message belongs to it and what messages belong to other clients (rendering them as enemies). The server also has the feature to send Server messages to clients rendering them as yellow. To prevent any client from crashing the server, all messages have a length limit and the server can start deleting messages if it exceeds a certain max value.

Chat - Source Code
 
        private static int chatLength = 0;
        
        [MessageHandler((ushort)ClientToServerId.sendChatText)]
        private static void UpdateChat(ushort fromClientId, Message message)
        {
            string text = message.GetString();
            string timeText = "[" + TOFTimeHandler.GetTimeAsMinutes() + "]";
        
            string time = timeText;
            string username = TOFPlayer.players[fromClientId].username;
            string chatMessage = text;
            
            Message _message = Message.Create(MessageSendMode.reliable, ServerToClientId.sendChatText);
            _message.AddUShort(fromClientId);
            _message.AddString(time);
            _message.AddString(username);
            _message.AddString(chatMessage);
            
            NetworkManager.Singleton.Server.SendToAll(_message);
            
            chatLength++;
        }

Intractable Grid

The game features an highly advanced grid system where players can visually draw out their moves, the grid uses an algorithm that checks which tiles around the player have been clicked and determines the move based on that. The moves are then translated into 4 inputs that are then translated into data and sent to the server. Players can even interact and place out abilities using the grid. The grid has helper functions for converting world position to grid position, for creating and attaching smaller grids to the main grid etc.

Cards and Zone

The game features a fully networked card and perk system that alters the players stats and game behaviors. The cards work similar to games like Teamfight Tactics where they add a subtle change to the game without completely dictating the gameplay. Cards are meant to add a sense of adaptation and strategy where players have to constantly play around the cards they get since they will always be random every new round.

The game also features a battle royale like zone that slowly gathers players to one point in the map, this helps keep the gameplay intense and mitigates issues like players hiding or running too far away and completely evading player interaction. The server uses a circle algorithm to determine the size of the radius for the zone, this is then sent to the client's shader to mask out the safe area.