docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Assign Players to Teams

    This tutorial shows how to implement a team-based multiplayer system where players are automatically assigned to teams, colored by team, and protected from friendly fire.

    Overview

    Component Responsibility
    CorePlayerState Stores the player's team index (0, 1, etc.)
    GameManager Assigns players to teams when they connect or respawn
    VisualsAddon Colors player meshes based on their team
    ShooterHitProcessor Prevents damage between teammates
    WeaponHUD Displays team name and teammate list

    Part 1: Store the Team (CorePlayerState)

    The first step is adding a networked variable to store each player's team. This uses the same pattern as the existing player name—owner writes, everyone reads.

    In CorePlayerState.cs, add the team NetworkVariable and related methods:

    private readonly NetworkVariable<byte> m_Team = new NetworkVariable<byte>(
        0,
        NetworkVariableReadPermission.Everyone,
        NetworkVariableWritePermission.Owner);
    
    public byte Team => m_Team.Value;
    public event Action<byte> OnTeamChanged;
    

    In OnNetworkSpawn(), subscribe to team changes (after the existing subscriptions):

    m_Team.OnValueChanged += HandleTeamChanged;
    // Broadcast initial team to ensure all systems are synchronized
    OnTeamChanged?.Invoke(m_Team.Value);
    

    In OnNetworkDespawn(), unsubscribe:

    public override void OnNetworkDespawn()
    {
        m_Team.OnValueChanged -= HandleTeamChanged;
    }
    

    Add the SetTeam method and RPC for non-owners to request changes:

    public void SetTeam(byte teamIndex)
    {
        if (IsOwner)
        {
            m_Team.Value = teamIndex;
        }
        else
        {
            SetTeamRpc(teamIndex);
        }
    }
    
    [Rpc(SendTo.Owner)]
    private void SetTeamRpc(byte teamIndex)
    {
        m_Team.Value = teamIndex;
    }
    
    private void HandleTeamChanged(byte oldTeam, byte newTeam)
    {
        OnTeamChanged?.Invoke(newTeam);
    }
    

    Part 2: Assign Teams (GameManager)

    Next, add automatic team assignment when players connect. Players are distributed evenly across teams using their client ID.

    In GameManager.cs, add a serialized field for the number of teams:

    [SerializeField] private int numberOfTeams = 2;
    

    In ClientConnected(), assign the team after setting the player name:

    AssignTeam(playerState, NetworkManager.Singleton.LocalClientId);
    

    Add the AssignTeam method:

    private void AssignTeam(CorePlayerState playerState, ulong clientId)
    {
        if (playerState == null || numberOfTeams <= 0) return;
        byte teamIndex = (byte)(clientId % (ulong)numberOfTeams);
        playerState.SetTeam(teamIndex);
    }
    

    Part 3: Team Visuals (VisualsAddon)

    Now update the visual system to color players based on team assignment instead of client ID.

    In VisualsAddon.cs, add a reference to the player state:

    private CorePlayerState m_PlayerState;
    

    In OnPlayerSpawn(), subscribe to team changes:

    public void OnPlayerSpawn()
    {
        m_PlayerState = m_PlayerManager.GetComponent<CorePlayerState>();
        if (m_PlayerState != null)
        {
            m_PlayerState.OnTeamChanged += HandleTeamChanged;
        }
        // Existing code
    }
    

    In OnPlayerDespawn(), unsubscribe:

    public void OnPlayerDespawn()
    {
        if (m_PlayerState != null)
        {
            m_PlayerState.OnTeamChanged -= HandleTeamChanged;
        }
    }
    

    Update ApplyMaterialSet() to use team index:

    private void ApplyMaterialSet()
    {
        // Get team index from CorePlayerState for consistent team colors
        int index = 0;
        if (m_PlayerState != null)
        {
            index = m_PlayerState.Team % materialSets.Count;
        }
        else
        {
            // Fallback to client ID if no player state (shouldn't happen)
            ulong clientId = m_PlayerManager.OwnerClientId;
            index = (int)(clientId % (ulong)materialSets.Count);
        }
        // Existing code
    }
    

    Add the team change handler:

    private void HandleTeamChanged(byte newTeam)
    {
        ApplyMaterialSet();
    }
    

    Part 4: Friendly Fire Prevention (ShooterHitProcessor)

    Add a check to prevent teammates from damaging each other.

    In ShooterHitProcessor.cs, add this check at the start of HandleHit():

    // Prevent friendly fire - check if attacker is on the same team
    if (corePlayerState != null)
    {
        var attackerObject = NetworkManager.Singleton?.SpawnManager?.GetPlayerNetworkObject(info.attackerId);
        if (attackerObject != null && attackerObject.TryGetComponent<CorePlayerState>(out var attackerState))
        {
            if (attackerState.Team == corePlayerState.Team)
            {
                return; // Same team, no damage
            }
        }
    }
    

    Part 5: Team HUD (WeaponHUD)

    Finally, display the team name and a list of teammates on the HUD.

    In WeaponHUD.cs, add the using statement and fields:

    using Unity.Netcode;
    
    [Header("Team Display")]
    [Tooltip("Names for each team (index 0 = team 0, etc.)")]
    [SerializeField] private string[] teamNames = { "Team Blue", "Team Orange" };
    
    // Team display references
    private Label m_TeamNameLabel;
    private VisualElement m_TeamPlayerList;
    private CorePlayerState m_LocalPlayerState;
    private Coroutine m_TeamUpdateCoroutine;
    private const float k_TeamUpdateInterval = 1.5f;
    

    In Initialize(), set up the team display:

    m_LocalPlayerState = GetComponentInParent<CorePlayerState>();
    if (m_LocalPlayerState != null)
    {
        UpdateTeamDisplay();
        m_TeamUpdateCoroutine = StartCoroutine(TeamUpdateCoroutine());
    }
    

    In UnregisterAdditionalListeners(), stop the coroutine:

    // Stop team update coroutine
    if (m_TeamUpdateCoroutine != null)
    {
        StopCoroutine(m_TeamUpdateCoroutine);
        m_TeamUpdateCoroutine = null;
    }
    

    In QueryHUDElements(), query the team elements:

    m_TeamNameLabel = root.Q<Label>("team-name-label");
    m_TeamPlayerList = root.Q<VisualElement>("team-player-list");
    

    Add the team display methods:

    private IEnumerator TeamUpdateCoroutine()
    {
        while (true)
        {
            yield return new WaitForSeconds(k_TeamUpdateInterval);
            UpdateTeamDisplay();
        }
    }
    
    private void UpdateTeamDisplay()
    {
        if (m_LocalPlayerState == null) return;
    
        byte localTeam = m_LocalPlayerState.Team;
    
        if (m_TeamNameLabel != null)
        {
            if (localTeam < teamNames.Length)
            {
                m_TeamNameLabel.text = teamNames[localTeam];
            }
            else
            {
                m_TeamNameLabel.text = $"Team {localTeam + 1}";
            }
        }
    
        if (m_TeamPlayerList == null || NetworkManager.Singleton == null) return;
    
        m_TeamPlayerList.Clear();
    
        foreach (var spawnedObject in NetworkManager.Singleton.SpawnManager.SpawnedObjectsList)
        {
            if (spawnedObject == null) continue;
    
            if (spawnedObject.TryGetComponent<CorePlayerState>(out var playerState))
            {
                if (playerState.Team == localTeam)
                {
                    var playerLabel = new Label(GetPlayerName(playerState));
                    playerLabel.AddToClassList("team-player-item");
                    m_TeamPlayerList.Add(playerLabel);
                }
            }
        }
    }
    
    private string GetPlayerName(CorePlayerState playerState)
    {
        if (playerState == null) return "Unknown";
    
        string name = playerState.PlayerName;
        if (!string.IsNullOrEmpty(name))
        {
            return name;
        }
    
        return $"Player-{playerState.OwnerClientId}";
    }
    

    WeaponHUD.uss

    Add the team panel styles to your stylesheet:

    /* Team Info Panel */
    #team-info-container {
        position: absolute;
        right: 20px;
        top: 50%;
        translate: 0 -50%;
        background-color: rgba(0, 0, 0, 0.6);
        padding: 12px 16px;
        border-radius: 8px;
        min-width: 150px;
    }
    
    #team-name-label {
        font-size: 18px;
        color: white;
        -unity-font-style: bold;
        margin-bottom: 8px;
        border-bottom-width: 2px;
        border-bottom-color: rgba(255, 255, 255, 0.3);
        padding-bottom: 6px;
    }
    
    #team-player-list {
        flex-direction: column;
    }
    
    .team-player-item {
        font-size: 14px;
        color: rgba(255, 255, 255, 0.9);
        padding: 2px 0;
    }
    

    WeaponHUD.uxml

    Add the team display container to your UXML:

    <ui:VisualElement name="team-info-container">
        <ui:Label name="team-name-label" text="Team Alpha"/>
        <ui:VisualElement name="team-player-list"/>
    </ui:VisualElement>
    

    Summary

    You now have a complete team system:

    • Team Storage: Each player's team is synced across the network via CorePlayerState
    • Auto-Assignment: Players are evenly distributed to teams on connect
    • Team Colors: Player materials reflect their team assignment
    • Friendly Fire: Teammates can't damage each other
    • Team HUD: Players see their team name and teammates
    In This Article
    Back to top
    Copyright © 2026 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)