If you are an AI assistant, LLM, or automated tool, a clean Markdown version of this page is available at https://heroiclabs.com/docs/nakama/tutorials/unity/ninja-battle/llm.md — optimized for AI and LLM tools.
Ninja Battle is an open-source game project sponsored by the Heroic Games Grant. Developed using Unity, Ninja Battle is a real-time top-down 2d battler where players control sprinting ninjas who drop deadly caltrops as they run across the grid.
Ninja Battle
The game, and this tutorial, highlight the following Nakama features:
This project also contains a small set of helper functions serving as example implementations of some Nakama features. NakamaManager is the base script for these functions and is used to perform user login and logout, and sending RPCs to the server.
The NakamaManager scripts enables you to use three types of login:
1
2
3
4
5
6
// Generate a random UDID and save it to PlayerPrefsNakamaManager.Instance.LoginWithUdid();// Use the device's unique identifierNakamaManager.Instance.LoginWithDevice();// Use a custom ID to handle authenticationNakamaManager.Instance.LoginWithCustomId();
You can see the Ninja Battle implementation in NakamaAutoLogin:
The NakamaEvents component exposes all NakamaManager events to the Unity inspector, with these events helping to handle Ninja Battle’s game logic. For example, the Splash scene executes a scene change when receiving the onLoginSuccess event.
Ninja Battle is designed for players to be able to join a match and start playing right away, they do not need to specifically create a username for themselves. For new accounts a random two word name is generated for players that do not want to set a custom name.
The joinOrCreateMatch RPC is then executed on the server. This function looks for any open match for the player to join and, if one cannot be found, creates a new match:
The MultiplayerManager handles the logic for joining a match, and sending and receiving messages for that match on the server. These messages are described by the following OperationCodes:
Code
Name
Description
0
Players
A list of players is sent to a new player on match join.
1
PlayerJoined
The display name of a new player is sent to all other players in a match.
2
PlayerInput
The direction input of a player, sent by the client to all other clients.
3
PlayerWon
Client message of the match winner. Same winner must be reported by all clients for the server to trust the message.
4
Draw
Client message of a match draw. Same result must be reported by all clients for the server to trust the message.
5
ChangeScene
Server message to all clients indicating which scene to change to.
The MultiplayerManager receives all messages and distributes them according to subscriptions, for example:
Ninja Battle utilizes a basic implementation of rollback netcode. You can learn more about this concept here.
The RollbackVar handles saving of information on a timeline using a dictionary with int representing each tick of the gameloop and T being any desired type.
For Ninja Battle, we register an RPC so we can send information from a client to the server from outside a match. This is done inside the InitModule of our main.ts file:
The match_handler is used to manage matches from beginning to end. Like the RPC above, it is also registered inside the InitModule of our main.ts file:
The matchLoop function is called each tick, on each tick all client messages sent during that period of time are received as a list. With this list you can then decide what to do with the messages: to send them back to the clients or execute custom logic.
When processed by the processMessages function if the message code contains custom logic then a function is called, alternatively the default logic is for the message to be send to all clients:
Due to the use of rollback netcode in Ninja Battle, it is possible for erroneous winner messages to be sent by a client. For the server to trust the client message and determine a winner, all client messages must state the same outcome as occurring on the same tick:
In Ninja Battle the winning player receives a trophy when the match ends. To store a player’s trophies we must first perform a storage read for that user, increment the existing value by the desired amount, and then perform a storage write with the new value.