---
title: "Entities in action: Tanks"
content_type: tutorial
url: "https://learn.unity.com/tutorial/entities-in-action-tanks?language=en"
language: en
difficulty: intermediate
duration_minutes: 30
unity_version:
  - "6.0"
  - "6 Preview"
  - "6 Beta"
  - "2023.2"
  - "2023.1"
  - "2022.3"
supported_unity_versions:
  - name: "6.0"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en&version=6.0"
  - name: "6 Preview"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en&version=6+Preview"
  - name: "6 Beta"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en&version=6+Beta"
  - name: "2023.2"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en&version=2023.2"
  - name: "2023.1"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en&version=2023.1"
  - name: "2022.3"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en&version=2022.3"
available_languages:
  - name: "en"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks.md?language=en"
  - name: "ko"
    url: "https://learn.unity.com/tutorial/entities-in-action-tanks-kr.md?language=ko"
last_updated: "2026-08-04T14:24:02.000Z"
---

# Entities in action: Tanks

In this tutorial, you use jobs and entities to spawn, move, and control tanks and cannonballs with code and user input.

In this tutorial, you'll put your new knowledge of jobs and entities into action in a sample project that will demonstrate how to implement common features of games. Your objective is to spawn moving tanks that shoot cannonballs from a spinning turret. Tanks are destroyed when hit by a cannonball. The player controls the movement of one tank.

By the end of this tutorial, you'll be able to do the following:

- Make an entity move randomly using code.
- Spawn entities from a prefab using code.
- Control the behavior of spawned entities using code.
- Add a user input system to an entities project.

## Materials

- [tanks_tutorial.zip](https://unity-connect-prd.storage.googleapis.com/20240214/19eff8a4-45e1-4866-a73e-6658024b0cb5/tanks_tutorial.zip)

## 1. Overview

This tutorial demonstrates the essentials of the Entities package and is intended as a follow-up to the HelloCube and job system tutorials.

Your objective in this tutorial is to spawn moving tanks that shoot cannonballs from a spinning turret. Tanks are destroyed when hit by a cannonball. The player controls the movement of one tank.

The tutorial uses a few DOTS features: entities, systems, entity queries, entity command buffers, entity jobs (IJobEntity), baking, and subscenes.

(This tutorial is a variation of [the Tanks tutorial](https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/EntitiesSamples/Assets/Tutorials/Tanks/README.md) in the Entities samples repository.)

## 2. Before you begin

This tutorial assumes that you have a basic understanding of C# and GameObjects. It also assumes you have gone through the HelloCube tutorial (which covers basics of Entities, such as entity creation and subscenes) and the jobs tutorial.

## Set up your Unity project

To set up a Unity project for this tutorial, follow these instructions:

**1.**  [Create a new Unity project](https://learn.unity.com/tutorial/project-setup-processes#60f6aedeedbc2a7e96802196) using the **3D (URP) Template**.

**2.**  [Install the **Entities Graphics package** via the Package Manager](https://learn.unity.com/tutorial/project-setup-processes#60ed7a6aedbc2a002520b6ec).

You’ll need the Entities Graphics package to render entities, and installing Entities Graphics will also automatically install the Entities package itself as a dependency.

## 3. Create a single, immobile tank

First you’ll create the tank that will be used throughout this tutorial. To create a **Tank** GameObject, follow these instructions:

**1.**   In the **Main** scene, create a new subscene (check out the HelloCube tutorial for a refresher on how to do this). The name of the subscene is not important, so the default name "New Sub Scene" is fine.

**2.**   In the subscene, create a 3D cube GameObject and name it “Tank”.

**3.**   Create a 3D sphere GameObject as a child GameObject of **Tank** and name it “Turret”.

**4.**   Set the position of the **Turret** GameObject to (0, 0.5, 0).

**5.**   Create a 3D cylinder GameObject as a child GameObject of **Turret** and name it “Cannon”.

**6.**   Set the scale of the **Cannon** GameObject to (0.2, 0.5, 0.2).

**7.**   Set the position of the **Cannon** GameObject to (0, 0.5, 0).

**8.**  Set the rotation of the **Turret** GameObject to (45, 0, 0).

Your tank should now look something like this:

![](https://connect-mediagw.unity.com/h1/20240214/learn/images/e5f4f235-99f2-401b-bc4d-da745fe38839_image.png)

As explained in the HelloCube tutorial, baking will serialize one entity for each of these GameObjects and give each entity **Transform** and **Rendering** components. When the scene is loaded at runtime, the entities are loaded, not the GameObjects, so the tank rendered in Play mode will be composed of three entities.

## 4. Move the tank along a random curvy path

First, let’s add a **Tank** component to each tank entity in baking (as explained in the HelloCube tutorial). Create a new script file named **TankAuthoring.cs** and add the following code:

```csharp
using Unity.Entities;
using UnityEngine;

public class TankAuthoring : MonoBehaviour
{
    public GameObject Turret;
    public GameObject Cannon;
    
    class Baker : Baker<TankAuthoring>
    {
        public override void Bake(TankAuthoring authoring)
        {
            // GetEntity returns the Entity baked from the GameObject
            var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
            AddComponent(entity, new Tank
            {
                Turret = GetEntity(authoring.Turret, TransformUsageFlags.Dynamic),
                Cannon = GetEntity(authoring.Cannon, TransformUsageFlags.Dynamic)
            });
        }
    }
}

// A component that will be added to the root entity of every tank.
public struct Tank : IComponentData
{
    public Entity Turret;
    public Entity Cannon;
} 
```

Add a **TankAuthoring** component to the **Tank** GameObject, set the Turret property to the Turret GameObject, and set the Cannon property to the Cannon GameObject. In baking, the **Tank** component will be added to the baked tank root entity, the turret entity will be assigned to its "Turret" field, and the cannon entity will be assigned to its "Cannon" field. (These Turret and Cannon fields will be used in a later step).

![](https://connect-mediagw.unity.com/h1/20240214/learn/images/b9573976-7e70-43db-b5f3-1bc65b43f086_image.png)

Now let’s create a system to move the tank along a random curvy path. Create a new script file named "TankMovementSystem.cs" and add the following lines of code:

```csharp
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

public partial struct TankMovementSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var dt = SystemAPI.Time.DeltaTime;

        // For each entity having a LocalTransform and Tank component, 
        // we access the LocalTransform and entity ID.
        foreach (var (transform, entity) in
                 SystemAPI.Query<RefRW<LocalTransform>>()
                     .WithAll<Tank>()
                     .WithEntityAccess())
        {
            var pos = transform.ValueRO.Position;

            // This does not modify the actual position of the tank, only the point at
            // which we sample the 3D noise function. This way, every tank is using a
            // different slice and will move along its own different random flow field.
            pos.y = (float)entity.Index;

            var angle = (0.5f + noise.cnoise(pos / 10f)) * 4.0f * math.PI;
            var dir = float3.zero;
            math.sincos(angle, out dir.x, out dir.z);

            // Update the LocalTransform.
            transform.ValueRW.Position += dir * dt * 5.0f;
            transform.ValueRW.Rotation = quaternion.RotateY(angle);
        }
    }
}
```

The **MovementSystem** will update every frame. In the update, the system queries for all entities that have a **LocalTransform** and **Tank** component, and it updates the **LocalTransform** to move each tank along a random, curvy path.

## 5. Spawn many tanks with random colors

To spawn many tanks, first you’ll turn your tank into a baked prefab. First, make the **Tank** GameObject and its child GameObjects into a new prefab named “Tank”, then delete the original tank from the subscene.

Next, add the **URPMateiralPropertyBaseColorAuthoring** component to all three GameObjects in the **Tank** prefab (the **Tank**, **Turret**, and **Cannon** GameObjects). This adds the **Entities.Rendering.URPMaterialPropertyBaseColor** component to the entities. Setting the value of this component at runtime dynamically changes the color of the entity.

To bake the prefab as an entity, follow these instructions:

**1.**  Add a new GameObject in the subscene and name it “Config”.

**2.**  Create a new script file named "ConfigAuthoring.cs" with the following code:

```csharp
using Unity.Entities;
using UnityEngine;

public class ConfigAuthoring : MonoBehaviour
{
    public GameObject TankPrefab;
    public GameObject CannonBallPrefab;
    public int TankCount;

    class Baker : Baker<ConfigAuthoring>
    {
        public override void Bake(ConfigAuthoring authoring)
        {
            // The config entity itself doesn’t need transform components,
            // so we use TransformUsageFlags.None
            var entity = GetEntity(authoring, TransformUsageFlags.None);
            AddComponent(entity, new Config
            {
                // Bake the prefab into entities. GetEntity will return the 
                // root entity of the prefab hierarchy.
                TankPrefab = GetEntity(authoring.TankPrefab, TransformUsageFlags.Dynamic),
                CannonBallPrefab = GetEntity(authoring.CannonBallPrefab, TransformUsageFlags.Dynamic),
                TankCount = authoring.TankCount,
            });
        }
    }
}
public struct Config : IComponentData
{
    public Entity TankPrefab;
    public Entity CannonBallPrefab;
    public int TankCount;
}
```

 **3.**  Add an instance of ConfigAuthoring to the **Config** GameObject, set its "Tank Prefab" field to the Tank prefab, and set its "Tank Count" to 10. (The "CannonBallPrefab" field will be set in a later step.)

![](https://connect-mediagw.unity.com/h1/20240214/learn/images/d102feed-a747-4b08-8451-a1d39a1eb4b6_image.png)

Now, to spawn the tanks and set their colors, create a new script file named **TankSpawnSystem.cs** with the following code:

```csharp
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;
using Random = Unity.Mathematics.Random;

public partial struct TankSpawnSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        // This RequireForUpdate means the system only updates if at 
        // least one entity with the Config component exists.
        // Effectively, this system will not update until the 
        // subscene with the Config has been loaded.
        state.RequireForUpdate<Config>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // Disabling a system will stop further updates.
        // By disabling this system in its first update, 
        // it will effectively update only once.
        state.Enabled = false;

        // A "singleton" is a component type which only has a single instance.
        // GetSingleton<T> throws an exception if zero entities or 
        // more than one entity has a component of type T.
        // In this case, there should only ever be one instance of Config.
        var config = SystemAPI.GetSingleton<Config>();

        // Random numbers from a hard-coded seed.
        var random = new Random(123);

        for (int i = 0; i < config.TankCount; i++)
        {
            var tankEntity = state.EntityManager.Instantiate(config.TankPrefab);

            // URPMaterialPropertyBaseColor is a component from the Entities.Graphics package 
            // that lets us set the rendered base color of a rendered entity.
            var color = new URPMaterialPropertyBaseColor { Value = RandomColor(ref random) };
           
            // Every root entity instantiated from a prefab has a LinkedEntityGroup component, which
            // is a list of all the entities that make up the prefab hierarchy (including the root).

            // (LinkedEntityGroup is a special kind of component called a "DynamicBuffer", which is
            // a resizable array of struct values instead of just a single struct.)
            var linkedEntities = state.EntityManager.GetBuffer<LinkedEntityGroup>(tankEntity);
            foreach (var entity in linkedEntities)
            {
                // We want to set the URPMaterialPropertyBaseColor component only on the
                // entities that have it, so we first check.
                if (state.EntityManager.HasComponent<URPMaterialPropertyBaseColor>(entity.Value))
                {
                    // Set the color of each entity that makes up the tank.
                    state.EntityManager.SetComponentData(entity.Value, color);    
                }
            }
        }
    }

    // Return a random color that is visually distinct.
    // (Naive randomness would produce a distribution of colors clustered 
    // around a narrow range of hues. See https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ )
    static float4 RandomColor(ref Random random)
    {
        // 0.618034005f is inverse of the golden ratio
        var hue = (random.NextFloat() + 0.618034005f) % 1;
        return (Vector4)Color.HSVToRGB(hue, 1.0f, 1.0f);
    }
}
```

Now if you enter Play mode, you should see 10 random-colored tanks wandering in different directions:

![](https://connect-mediagw.unity.com/h1/20240214/learn/images/076a1c8a-d680-4979-96cb-22834cad1da0_image.png)

Note that you might see the tanks in the **Game** view but not the **Scene** view. To make the tanks visible in the **Scene** view, select **Edit** > **Preferences** > **Entities** > **Scene View Mode** to "Runtime Data":

![](https://connect-mediagw.unity.com/h1/20240214/learn/images/2bdc99df-1d6e-4a2d-9ad1-06cf2db768ef_image.png)

## 6. Move one tank with player input and have the camera follow

If you want one tank to be controlled by the player, you first need a way to distinguish the player’s tank entity from the other tanks, so let’s create a new **Player** component add it to one of the spawned tanks:

**1.**  Add the following code to to a new file **Player.cs**:

```csharp
using Unity.Entities;

public struct Player : IComponentData
{
}
```

**Note**: As a matter of convention, entity component types are often defined in the same file as a related authoring component, but in this case the **Player** component doesn't need an authoring component.

**2.**  In the **TankSpawnSystem**, add the following code in the loop that creates the tanks (after the line that instantiates the prefab):

```csharp
// Add the Player component to the first tank
if (i == 0)
{
    state.EntityManager.AddComponent<Player>(tankEntity);
}
```

**3.**  To stop the player tank from moving along a random path like the other tanks, modify the header of the loop in **TankMovementSystem** to match the following code:

```csharp
foreach (var (transform, entity) in
           SystemAPI.Query<RefRW<LocalTransform>>()
                .WithAll<Tank>()
                .WithNone<Player>()  // exclude the player tank from the query
                .WithEntityAccess())
```

The **WithNone\<Player>** call specifies that the query should exclude any entity that has a **Player** component, so the player tank will no longer be moved in this loop.

**4.**  To move the player and the camera, create a new script file named **PlayerSystem.cs** with the following code:

```csharp
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

public partial struct PlayerSystem : ISystem
{
    // Because OnUpdate accesses a managed object (the camera), we cannot Burst compile 
    // this method, so we don't use the [BurstCompile] attribute here.
    public void OnUpdate(ref SystemState state)
    {
        var movement = new float3(
            Input.GetAxis("Horizontal"),
            0,
            Input.GetAxis("Vertical")
        );
        movement *= SystemAPI.Time.DeltaTime;

        foreach (var playerTransform in 
                SystemAPI.Query<RefRW<LocalTransform>>()
                    .WithAll<Player>())
        {
            // move the player tank
            playerTransform.ValueRW.Position += movement;

            // move the camera to follow the player
            var cameraTransform = Camera.main.transform;
            cameraTransform.position = playerTransform.ValueRO.Position;
            cameraTransform.position -= 10.0f * (Vector3)playerTransform.ValueRO.Forward();  // move the camera back from the player
            cameraTransform.position += new Vector3(0, 5f, 0);  // raise the camera by an offset
            cameraTransform.LookAt(playerTransform.ValueRO.Position);  // look at the player
        }
    }
}
```

Now in Play mode, you should be able to control the first tank, and the camera will follow. Because the camera moves with you, it may be hard to tell in the **Game** view that you're moving.

## 7. Spawn cannonballs at the tip of the turret

To have the tank fire cannonballs, you first need a **CannonBall** component:

**1.**  Create a new script named **CannonBallAuthoring.cs** with the following code:

```csharp
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;

public class CannonBallAuthoring : MonoBehaviour
{
    class Baker : Baker<CannonBallAuthoring>
    {
        public override void Bake(CannonBallAuthoring authoring)
        {
            var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);

            // By default, components are zero-initialized,
            // so the Velocity field of CannonBall will be float3.zero.
            AddComponent<CannonBall>(entity);
            AddComponent<URPMaterialPropertyBaseColor>(entity);
        }
    }
}

public struct CannonBall : IComponentData
{
    public float3 Velocity;
}
```

**2.**  Create a new prefab named **CannonBall** with a single GameObject that renders a 3D sphere and set its scale to (0.4, 0.4, 0.4).

**3.**  Add the **CannonBallAuthoring** component to the **CannonBall** prefab's root GameObject.

**4.**  In the **Config** GameObject in the subscene, set the "CannonBallPrefab" field in **ConfigAuthoring** to the CannonBall prefab.

**5.**  To spawn the cannonballs, create a new script file named **ShootingSystem.cs** with the following code:

```csharp
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;

// This attribute puts this system before TransformSystemGroup in the update order.
// ShootingSystem only sets the local transforms of the cannonballs, but the transform systems
// in TransformSystemGroup will set their world transforms (LocalToWorld).
// If the ShootingSystem were to update after TransformSystemGroup in the frame, the cannonballs
// it spawned would render at the origin for a single frame.
[UpdateBefore(typeof(TransformSystemGroup))]
public partial struct ShootingSystem : ISystem
{
    private float timer;
    
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // Only shoot in frames where timer has expired
        timer -= SystemAPI.Time.DeltaTime;
        if (timer > 0)
        {
            return; 
        } 
        timer = 0.3f;   // reset timer

        var config = SystemAPI.GetSingleton<Config>();
        
        var ballTransform = state.EntityManager.GetComponentData<LocalTransform>(config.CannonBallPrefab);
        
        // For each turret of every tank, spawn a cannonball and set its initial velocity
        foreach (var (tank, transform, color) in
                 SystemAPI.Query<RefRO<Tank>, RefRO<LocalToWorld>, RefRO<URPMaterialPropertyBaseColor>>())
        {
            Entity cannonBallEntity = state.EntityManager.Instantiate(config.CannonBallPrefab);
            
            // Set color of the cannonball to match the tank that shot it.
            state.EntityManager.SetComponentData(cannonBallEntity, color.ValueRO);
            
            // We need the transform of the cannon in world space, so we get its LocalToWorld instead of LocalTransform.
            var cannonTransform = state.EntityManager.GetComponentData<LocalToWorld>(tank.ValueRO.Cannon);
            ballTransform.Position =  cannonTransform.Position;
            
            // Set position of the new cannonball to match the spawn point
            state.EntityManager.SetComponentData(cannonBallEntity, ballTransform);

            // Set velocity of the cannonball to shoot out of the cannon.
            state.EntityManager.SetComponentData(cannonBallEntity, new CannonBall
            {
                Velocity = math.normalize(cannonTransform.Up) * 12.0f
            });
        }
    }
}
```

Now in Play mode, the tanks fire cannon balls at regular intervals, but the cannonballs don't yet move, so they stay where they spawn:

![](https://connect-mediagw.unity.com/h1/20240214/learn/images/8451814c-ae56-4dc0-ba8f-bd8aaec46f4e_image.png)

## 8. Rotate the turret

To rotate the turrets, you can create another system, but it’s simpler to add some additional code to the bottom of the **OnUpdate** method of the **TankMovementSystem**:

```csharp
var spin = quaternion.RotateY(SystemAPI.Time.DeltaTime * math.PI);

foreach (var tank in
         SystemAPI.Query<RefRW<Tank>>())
{
    var trans = SystemAPI.GetComponentRW<LocalTransform>(tank.ValueRO.Turret);
    
    // Add a rotation around the Y axis (relative to the parent).
    trans.ValueRW.Rotation = math.mul(spin, trans.ValueRO.Rotation);
}
```

## 9. Move the cannonballs

To move the cannonballs and to destroy them when they hit the ground, create a new script named **CannonBallSystem.cs** and add the following lines of code:

```csharp
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

public partial struct CannonBallSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();

        var cannonBallJob = new CannonBallJob
        {
            ECB = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged),
            DeltaTime = SystemAPI.Time.DeltaTime
        };

        cannonBallJob.Schedule();
    }
}

// IJobEntity relies on source generation to implicitly define a 
// query from the signature of the Execute method.
// In this case, the implicit query will look for all entities that
// have the CannonBall and LocalTransform components. 
[BurstCompile]
public partial struct CannonBallJob : IJobEntity
{
    public EntityCommandBuffer ECB;
    public float DeltaTime;

    // Execute will be called once for every entity that 
    // has a CannonBall and LocalTransform component.
    void Execute(Entity entity, ref CannonBall cannonBall, ref LocalTransform transform)
    {
        var gravity = new float3(0.0f, -9.82f, 0.0f);

        transform.Position += cannonBall.Velocity * DeltaTime;

        // if hit the ground
        if (transform.Position.y <= 0.0f)
        {
            ECB.DestroyEntity(entity);
        }

        cannonBall.Velocity += gravity * DeltaTime;
    }
}
```

By putting the work into a job, the work is moved from the main thread to the worker threads.

## 10. More things to try

 Here are some gameplay tweaks you might implement:

- Give the player direct control of their turret.
- Destroy tanks when they are hit by a cannonball.
- Respawn destroyed tanks after a timer.
- Display a victory message when the player destroys all enemy tanks.
- Respawn the player but give them a limited number of lives.

## 11. Next steps

For more tutorials and samples demonstrating all the features of the Entities package, see the [Entities Components Samples repository](https://github.com/Unity-Technologies/EntityComponentSystemSamples/) on GitHub.

Then, as you explore DOTS further, be sure to try our [**DOTS Best Practices**](https://learn.unity.com/course/dots-best-practices) course.
