坦克大战:经典2D策略游戏的Unity实现

19.1 坦克大战游戏概述

坦克大战是一款经典的2D射击游戏,最早由日本南梦宫公司于1985年发行。游戏以俯视角视角展示战场,玩家控制坦克在地图上移动并射击敌方坦克,同时保护自己的基地不被摧毁。这款游戏曾风靡全球,深受玩家喜爱。

在本章中,我们将使用Unity 2021.3.8f1c1引擎来重新实现这款经典游戏,不仅复刻其核心玩法,还将引入现代游戏设计理念和一些扩展功能。通过实现坦克大战,我们将学习Unity中的2D游戏开发技术,包括精灵渲染、碰撞检测、物理系统、人工智能等多方面内容。

坦克大战虽然画面简单,但其游戏机制和编程逻辑却相当丰富,是初学者学习游戏开发的理想项目。本章将带领读者从零开始,一步步实现这款经典游戏的各个系统。

主要学习内容:

  1. 2D游戏场景和地图创建
  2. 精灵渲染和动画系统
  3. 角色控制与物理系统
  4. 敌人AI行为设计
  5. 游戏状态管理和UI实现
  6. 对象池优化与性能提升

19.2 坦克大战游戏系统设计

在开始编码之前,我们需要先明确游戏的基本规则和设计要点,为后续的开发打下基础。

19.2.1 核心游戏要素

坦克大战的基本要素主要包括以下几点:

  1. 玩家坦克:由玩家控制,可以上下左右移动,发射子弹攻击敌人。玩家坦克有限定的生命数量。

  2. 敌方坦克:由AI控制,会自动移动并攻击玩家和基地。游戏中有多种类型的敌方坦克,包括普通坦克、快速坦克、装甲坦克等,它们有不同的速度、装甲和攻击力。

  3. 地图元素

    • 砖墙:可以被子弹摧毁
    • 钢墙:不能被普通子弹摧毁,只能被强化子弹破坏
    • 水域:坦克不能通过
    • 草丛:可以隐藏坦克
    • 冰面:使坦克滑行
  4. 基地:玩家需要保护的目标,如果被敌方坦克摧毁则游戏结束。

  5. 道具系统:游戏中会随机出现各种道具,提供临时能力增强,如:

    • 星星:提升子弹威力
    • 坦克:增加生命
    • 计时器:暂时冻结所有敌人
    • 铲子:临时增强基地周围的墙

19.2.2 游戏胜利与失败条件

  • 胜利条件:消灭当前关卡的所有敌方坦克。
  • 失败条件:玩家坦克生命耗尽,或基地被摧毁。

19.2.3 游戏控制方式

  • 使用键盘方向键或WASD控制坦克移动
  • 空格键或鼠标左键发射子弹
  • ESC键暂停游戏

接下来,我们将详细讨论游戏的实现思路和代码结构。

19.3 游戏开发架构与思路

19.3.1 地图系统设计与生成

在坦克大战中,地图是游戏的重要组成部分。我们将使用Unity的Tilemap系统来构建游戏地图,它提供了高效的2D地图编辑和渲染功能。

地图设计原则
  1. 网格化设计:游戏地图采用13×13的网格结构,每个网格可以放置一种地形元素。
  2. 平衡性考虑:地图设计需考虑攻防平衡,为玩家提供足够的掩护,同时也要留出足够的机动空间。
  3. 多样性:设计多种不同布局的关卡,增加游戏的可玩性。
地形类型及属性

csharp

// 地形类型枚举
public enum TerrainType
{
    Empty,
    BrickWall,
    SteelWall,
    Water,
    Forest,
    Ice
}

// 地形属性类
public class TerrainProperties
{
    public bool IsPassable { get; set; }
    public bool IsDestructible { get; set; }
    public bool CanHideUnit { get; set; }
    public bool IsSlidable { get; set; }
    
    public TerrainProperties(bool isPassable, bool isDestructible, bool canHideUnit, bool isSlidable)
    {
        IsPassable = isPassable;
        IsDestructible = isDestructible;
        CanHideUnit = canHideUnit;
        IsSlidable = isSlidable;
    }
}

地图管理器的实现

csharp

public class MapManager : MonoBehaviour
{
    // 单例模式
    public static MapManager Instance { get; private set; }
    
    [SerializeField] private Tilemap groundTilemap;
    [SerializeField] private Tilemap obstacleTilemap;
    [SerializeField] private Tilemap decorationTilemap;
    
    [Header("Tile Assets")]
    [SerializeField] private TileBase brickTile;
    [SerializeField] private TileBase steelTile;
    [SerializeField] private TileBase waterTile;
    [SerializeField] private TileBase forestTile;
    [SerializeField] private TileBase iceTile;
    
    // 地图尺寸
    private const int MAP_WIDTH = 13;
    private const int MAP_HEIGHT = 13;
    
    // 地形属性字典
    private Dictionary<TerrainType, TerrainProperties> terrainProperties;
    
    // 当前地图数据
    private TerrainType[,] mapData;
    
    private void Awake()
    {
        // 单例初始化
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        
        // 初始化地形属性
        InitTerrainProperties();
        
        // 初始化地图数据
        mapData = new TerrainType[MAP_WIDTH, MAP_HEIGHT];
    }
    
    private void InitTerrainProperties()
    {
        terrainProperties = new Dictionary<TerrainType, TerrainProperties>
        {
            { TerrainType.Empty, new TerrainProperties(true, false, false, false) },
            { TerrainType.BrickWall, new TerrainProperties(false, true, false, false) },
            { TerrainType.SteelWall, new TerrainProperties(false, false, false, false) },
            { TerrainType.Water, new TerrainProperties(false, false, false, false) },
            { TerrainType.Forest, new TerrainProperties(true, false, true, false) },
            { TerrainType.Ice, new TerrainProperties(true, false, false, true) }
        };
    }
    
    // 加载指定关卡
    public void LoadLevel(int levelNumber)
    {
        // 清空现有地图
        ClearMap();
        
        // 加载关卡数据
        string levelData = LoadLevelData(levelNumber);
        
        // 解析关卡数据并生成地图
        GenerateMap(levelData);
        
        // 设置基地周围的墙
        SetupBaseWalls();
    }
    
    private string LoadLevelData(int levelNumber)
    {
        // 这里可以从文本文件、ScriptableObject或其他数据源加载关卡数据
        // 示例中使用硬编码的关卡数据
        TextAsset levelFile = Resources.Load<TextAsset>($"Levels/Level{levelNumber}");
        
        if (levelFile != null)
        {
            return levelFile.text;
        }
        else
        {
            Debug.LogError($"Level {levelNumber} data not found!");
            return GenerateDefaultLevel();
        }
    }
    
    private string GenerateDefaultLevel()
    {
        // 生成默认关卡数据
        StringBuilder sb = new StringBuilder();
        
        // 示例:创建简单的默认关卡布局
        for (int y = 0; y < MAP_HEIGHT; y++)
        {
            for (int x = 0; x < MAP_WIDTH; x++)
            {
                if (x == 0 || x == MAP_WIDTH - 1 || y == 0 || y == MAP_HEIGHT - 1)
                {
                    sb.Append('S'); // 外围用钢墙
                }
                else if ((x % 2 == 0 && y % 2 == 0) && !(x == 6 && y >= MAP_HEIGHT - 3))
                {
                    sb.Append('B'); // 交错的砖墙
                }
                else
                {
                    sb.Append('.'); // 空地
                }
            }
            sb.Append('\n');
        }
        
        return sb.ToString();
    }
    
    private void GenerateMap(string levelData)
    {
        string[] rows = levelData.Split('\n', StringSplitOptions.RemoveEmptyEntries);
        
        for (int y = 0; y < rows.Length && y < MAP_HEIGHT; y++)
        {
            string row = rows[y];
            
            for (int x = 0; x < row.Length && x < MAP_WIDTH; x++)
            {
                Vector3Int cellPosition = new Vector3Int(x, MAP_HEIGHT - y - 1, 0);
                TerrainType terrainType = CharToTerrainType(row[x]);
                
                mapData[x, MAP_HEIGHT - y - 1] = terrainType;
                PlaceTile(cellPosition, terrainType);
            }
        }
    }
    
    private TerrainType CharToTerrainType(char c)
    {
        switch (c)
        {
            case 'B': return TerrainType.BrickWall;
            case 'S': return TerrainType.SteelWall;
            case 'W': return TerrainType.Water;
            case 'F': return TerrainType.Forest;
            case 'I': return TerrainType.Ice;
            default: return TerrainType.Empty;
        }
    }
    
    private void PlaceTile(Vector3Int position, TerrainType terrainType)
    {
        switch (terrainType)
        {
            case TerrainType.BrickWall:
                obstacleTilemap.SetTile(position, brickTile);
                break;
            case TerrainType.SteelWall:
                obstacleTilemap.SetTile(position, steelTile);
                break;
            case TerrainType.Water:
                groundTilemap.SetTile(position, waterTile);
                break;
            case TerrainType.Forest:
                decorationTilemap.SetTile(position, forestTile);
                break;
            case TerrainType.Ice:
                groundTilemap.SetTile(position, iceTile);
                break;
        }
    }
    
    private void SetupBaseWalls()
    {
        // 基地位置(中心底部)
        int baseX = MAP_WIDTH / 2;
        int baseY = 0;
        
        // 在基地周围设置砖墙保护
        for (int x = baseX - 1; x <= baseX + 1; x++)
        {
            for (int y = baseY; y <= baseY + 1; y++)
            {
                // 跳过基地本身的位置
                if (x == baseX && y == baseY)
                    continue;
                
                Vector3Int pos = new Vector3Int(x, y, 0);
                obstacleTilemap.SetTile(pos, brickTile);
                mapData[x, y] = TerrainType.BrickWall;
            }
        }
    }
    
    private void ClearMap()
    {
        groundTilemap.ClearAllTiles();
        obstacleTilemap.ClearAllTiles();
        decorationTilemap.ClearAllTiles();
        
        // 重置地图数据
        for (int x = 0; x < MAP_WIDTH; x++)
        {
            for (int y = 0; y < MAP_HEIGHT; y++)
            {
                mapData[x, y] = TerrainType.Empty;
            }
        }
    }
    
    // 检查位置是否可通行
    public bool IsPositionPassable(Vector3 worldPosition)
    {
        Vector3Int cellPosition = groundTilemap.WorldToCell(worldPosition);
        
        // 检查是否在地图范围内
        if (cellPosition.x < 0 || cellPosition.x >= MAP_WIDTH ||
            cellPosition.y < 0 || cellPosition.y >= MAP_HEIGHT)
        {
            return false;
        }
        
        TerrainType terrainType = mapData[cellPosition.x, cellPosition.y];
        return terrainProperties[terrainType].IsPassable;
    }
    
    // 检查位置是否为可滑行地形
    public bool IsPositionSlidable(Vector3 worldPosition)
    {
        Vector3Int cellPosition = groundTilemap.WorldToCell(worldPosition);
        
        // 检查是否在地图范围内
        if (cellPosition.x < 0 || cellPosition.x >= MAP_WIDTH ||
            cellPosition.y < 0 || cellPosition.y >= MAP_HEIGHT)
        {
            return false;
        }
        
        TerrainType terrainType = mapData[cellPosition.x, cellPosition.y];
        return terrainProperties[terrainType].IsSlidable;
    }
    
    // 破坏指定位置的地形
    public void DestroyTerrain(Vector3 worldPosition)
    {
        Vector3Int cellPosition = obstacleTilemap.WorldToCell(worldPosition);
        
        // 检查是否在地图范围内
        if (cellPosition.x < 0 || cellPosition.x >= MAP_WIDTH ||
            cellPosition.y < 0 || cellPosition.y >= MAP_HEIGHT)
        {
            return;
        }
        
        TerrainType terrainType = mapData[cellPosition.x, cellPosition.y];
        
        // 只有可破坏的地形才能被摧毁
        if (terrainProperties[terrainType].IsDestructible)
        {
            obstacleTilemap.SetTile(cellPosition, null);
            mapData[cellPosition.x, cellPosition.y] = TerrainType.Empty;
        }
    }
}

通过以上代码,我们实现了一个功能完善的地图管理器,它可以加载关卡数据、生成地图、判断地形特性并支持地形破坏。

19.3.2 敌方坦克AI设计

敌人AI是坦克大战游戏中的核心元素之一。设计合理的AI行为可以增加游戏的挑战性和趣味性。

AI行为状态机设计

我们将使用状态机模式来管理敌方坦克的AI行为,主要包括以下几种状态:

  1. 巡逻状态:敌方坦克随机移动寻找目标
  2. 追击状态:发现玩家后向其移动并攻击
  3. 攻击基地状态:优先攻击玩家基地
  4. 规避状态:受到攻击时的躲避行为
敌方坦克类型设计

csharp

// 敌方坦克类型枚举
public enum EnemyTankType
{
    Basic,  // 基础型:速度和装甲均衡
    Fast,   // 快速型:高速但低装甲
    Heavy,  // 重型:低速高装甲
    Power   // 火力型:攻击力强但移动慢
}

// 敌方坦克属性类
[System.Serializable]
public class EnemyTankProperties
{
    public float MoveSpeed;
    public int Health;
    public float FireRate;
    public int ScoreValue;
    
    public EnemyTankProperties(float moveSpeed, int health, float fireRate, int scoreValue)
    {
        MoveSpeed = moveSpeed;
        Health = health;
        FireRate = fireRate;
        ScoreValue = scoreValue;
    }
}

敌方坦克AI状态机实现

csharp

// 敌方坦克AI状态枚举
public enum EnemyAIState
{
    Patrol,
    Chase,
    AttackBase,
    Evade
}

// 敌方坦克基类
public class EnemyTank : MonoBehaviour
{
    [SerializeField] protected EnemyTankType tankType;
    
    // 引用
    protected Rigidbody2D rb;
    protected SpriteRenderer spriteRenderer;
    
    // 状态
    protected EnemyAIState currentState;
    protected float stateTimer;
    protected Vector2 moveDirection;
    
    // 属性
    protected EnemyTankProperties properties;
    protected int currentHealth;
    protected float lastFireTime;
    
    // 目标
    protected Transform playerTransform;
    protected Transform baseTransform;
    
    // 公共属性访问器
    public EnemyTankType TankType => tankType;
    
    protected virtual void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        
        // 初始化状态
        currentState = EnemyAIState.Patrol;
        stateTimer = Random.Range(2f, 4f);
        
        // 获取属性
        properties = GetTankProperties(tankType);
        currentHealth = properties.Health;
    }
    
    protected virtual void Start()
    {
        // 查找玩家和基地
        playerTransform = GameObject.FindWithTag("Player")?.transform;
        baseTransform = GameObject.FindWithTag("Base")?.transform;
    }
    
    protected virtual void Update()
    {
        // 更新状态计时器
        stateTimer -= Time.deltaTime;
        
        if (stateTimer <= 0)
        {
            // 状态持续时间结束,决定下一个状态
            DecideNextState();
        }
        
        // 根据当前状态执行行为
        ExecuteStateAction();
        
        // 尝试射击
        TryFire();
    }
    
    protected virtual void FixedUpdate()
    {
        // 移动坦克
        Move();
    }
    
    protected virtual void DecideNextState()
    {
        // 根据各种条件决定下一个状态
        
        // 更新目标引用
        if (playerTransform == null)
            playerTransform = GameObject.FindWithTag("Player")?.transform;
        
        if (baseTransform == null)
            baseTransform = GameObject.FindWithTag("Base")?.transform;
        
        // 检查是否可以看到玩家
        bool canSeePlayer = CheckLineOfSight(playerTransform);
        
        // 检查是否可以看到基地
        bool canSeeBase = CheckLineOfSight(baseTransform);
        
        // 随机决策,但有一定的优先级
        float decisionValue = Random.value;
        
        if (canSeePlayer && decisionValue < 0.7f)
        {
            // 70%的几率追击玩家
            ChangeState(EnemyAIState.Chase);
        }
        else if (canSeeBase && decisionValue < 0.5f)
        {
            // 50%的几率攻击基地
            ChangeState(EnemyAIState.AttackBase);
        }
        else if (currentHealth <= properties.Health / 2 && decisionValue < 0.4f)
        {
            // 血量低于一半时,有40%的几率规避
            ChangeState(EnemyAIState.Evade);
        }
        else
        {
            // 默认巡逻
            ChangeState(EnemyAIState.Patrol);
        }
    }
    
    protected virtual void ChangeState(EnemyAIState newState)
    {
        currentState = newState;
        
        // 设置状态持续时间
        switch (newState)
        {
            case EnemyAIState.Patrol:
                stateTimer = Random.Range(3f, 6f);
                break;
                
            case EnemyAIState.Chase:
                stateTimer = Random.Range(5f, 8f);
                break;
                
            case EnemyAIState.AttackBase:
                stateTimer = Random.Range(4f, 7f);
                break;
                
            case EnemyAIState.Evade:
                stateTimer = Random.Range(2f, 4f);
                break;
        }
    }
    
    protected virtual void ExecuteStateAction()
    {
        switch (currentState)
        {
            case EnemyAIState.Patrol:
                ExecutePatrolState();
                break;
                
            case EnemyAIState.Chase:
                ExecuteChaseState();
                break;
                
            case EnemyAIState.AttackBase:
                ExecuteAttackBaseState();
                break;
                
            case EnemyAIState.Evade:
                ExecuteEvadeState();
                break;
        }
    }
    
    protected virtual void ExecutePatrolState()
    {
        // 随机移动
        if (moveDirection == Vector2.zero || Random.value < 0.02f)
        {
            // 随机选择一个方向
            int directionIndex = Random.Range(0, 4);
            switch (directionIndex)
            {
                case 0:
                    moveDirection = Vector2.up;
                    break;
                case 1:
                    moveDirection = Vector2.right;
                    break;
                case 2:
                    moveDirection = Vector2.down;
                    break;
                case 3:
                    moveDirection = Vector2.left;
                    break;
            }
        }
        
        // 如果遇到障碍物,改变方向
        if (IsObstacleAhead())
        {
            // 选择一个新方向
            ChangeRandomDirection();
        }
    }
    
    protected virtual void ExecuteChaseState()
    {
        if (playerTransform != null)
        {
            // 计算到玩家的方向
            Vector2 directionToPlayer = (playerTransform.position - transform.position).normalized;
            
            // 只使用水平或垂直分量,不允许斜向移动
            if (Mathf.Abs(directionToPlayer.x) > Mathf.Abs(directionToPlayer.y))
            {
                moveDirection = new Vector2(Mathf.Sign(directionToPlayer.x), 0);
            }
            else
            {
                moveDirection = new Vector2(0, Mathf.Sign(directionToPlayer.y));
            }
            
            // 如果遇到障碍物,尝试绕过
            if (IsObstacleAhead())
            {
                // 尝试找到一条到玩家的路径
                TryFindPathToTarget(playerTransform.position);
            }
        }
        else
        {
            // 如果找不到玩家,切换到巡逻状态
            ChangeState(EnemyAIState.Patrol);
        }
    }
    
    protected virtual void ExecuteAttackBaseState()
    {
        if (baseTransform != null)
        {
            // 计算到基地的方向
            Vector2 directionToBase = (baseTransform.position - transform.position).normalized;
            
            // 只使用水平或垂直分量
            if (Mathf.Abs(directionToBase.x) > Mathf.Abs(directionToBase.y))
            {
                moveDirection = new Vector2(Mathf.Sign(directionToBase.x), 0);
            }
            else
            {
                moveDirection = new Vector2(0, Mathf.Sign(directionToBase.y));
            }
            
            // 如果遇到障碍物,尝试绕过或摧毁
            if (IsObstacleAhead())
            {
                // 尝试找到一条到基地的路径
                TryFindPathToTarget(baseTransform.position);
            }
        }
        else
        {
            // 如果找不到基地,切换到巡逻状态
            ChangeState(EnemyAIState.Patrol);
        }
    }
    
    protected virtual void ExecuteEvadeState()
    {
        if (playerTransform != null)
        {
            // 计算远离玩家的方向
            Vector2 directionAwayFromPlayer = ((Vector2)transform.position - (Vector2)playerTransform.position).normalized;
            
            // 只使用水平或垂直分量
            if (Mathf.Abs(directionAwayFromPlayer.x) > Mathf.Abs(directionAwayFromPlayer.y))
            {
                moveDirection = new Vector2(Mathf.Sign(directionAwayFromPlayer.x), 0);
            }
            else
            {
                moveDirection = new Vector2(0, Mathf.Sign(directionAwayFromPlayer.y));
            }
            
            // 如果遇到障碍物,随机改变方向
            if (IsObstacleAhead())
            {
                ChangeRandomDirection();
            }
        }
        else
        {
            // 如果找不到玩家,切换到巡逻状态
            ChangeState(EnemyAIState.Patrol);
        }
    }
    
    protected virtual void Move()
    {
        // 根据移动方向和速度移动坦克
        rb.velocity = moveDirection * properties.MoveSpeed;
        
        // 更新坦克朝向
        if (moveDirection != Vector2.zero)
        {
            float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
            transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
        }
    }
    
    protected virtual void TryFire()
    {
        // 检查是否可以射击
        if (Time.time >= lastFireTime + properties.FireRate)
        {
            // 检查前方是否有目标
            if (CheckTargetInFireLine())
            {
                Fire();
                lastFireTime = Time.time;
            }
            // 随机射击
            else if (Random.value < 0.05f)
            {
                Fire();
                lastFireTime = Time.time;
            }
        }
    }
    
    protected virtual void Fire()
    {
        // 在子类中实现具体的射击逻辑
    }
    
    protected bool IsObstacleAhead()
    {
        // 使用射线检测前方是否有障碍物
        float rayDistance = 1.0f;
        RaycastHit2D hit = Physics2D.Raycast(transform.position, moveDirection, rayDistance, LayerMask.GetMask("Obstacle"));
        return hit.collider != null;
    }
    
    protected void ChangeRandomDirection()
    {
        // 选择一个与当前方向垂直的方向
        Vector2[] possibleDirections = new Vector2[2];
        
        if (moveDirection.x != 0)
        {
            possibleDirections[0] = Vector2.up;
            possibleDirections[1] = Vector2.down;
        }
        else
        {
            possibleDirections[0] = Vector2.right;
            possibleDirections[1] = Vector2.left;
        }
        
        moveDirection = possibleDirections[Random.Range(0, 2)];
    }
    
    protected bool CheckLineOfSight(Transform target)
    {
        if (target == null)
            return false;
            
        // 检查是否可以直接看到目标
        Vector2 directionToTarget = (target.position - transform.position).normalized;
        float distanceToTarget = Vector2.Distance(transform.position, target.position);
        
        RaycastHit2D hit = Physics2D.Raycast(transform.position, directionToTarget, distanceToTarget, LayerMask.GetMask("Obstacle"));
        
        // 如果没有障碍物挡住视线
        return hit.collider == null;
    }
    
    protected bool CheckTargetInFireLine()
    {
        // 检查射击方向上是否有目标
        float rayDistance = 10.0f;
        RaycastHit2D hit = Physics2D.Raycast(transform.position, transform.right, rayDistance, LayerMask.GetMask("Player", "Base"));
        
        return hit.collider != null;
    }
    
    protected void TryFindPathToTarget(Vector3 targetPosition)
    {
        // 简单的寻路:尝试垂直于当前方向移动
        ChangeRandomDirection();
    }
    
    public virtual void TakeDamage()
    {
        currentHealth--;
        
        if (currentHealth <= 0)
        {
            // 坦克被摧毁
            Die();
        }
    }
    
    protected virtual void Die()
    {
        // 播放爆炸效果
        // 增加玩家分数
        // 通知游戏管理器
        GameManager.Instance.EnemyDestroyed(this);
        
        // 销毁游戏对象
        Destroy(gameObject);
    }
    
    private EnemyTankProperties GetTankProperties(EnemyTankType type)
    {
        // 根据坦克类型返回相应的属性
        switch (type)
        {
            case EnemyTankType.Basic:
                return new EnemyTankProperties(3.0f, 1, 2.0f, 100);
            case EnemyTankType.Fast:
                return new EnemyTankProperties(5.0f, 1, 1.5f, 200);
            case EnemyTankType.Heavy:
                return new EnemyTankProperties(2.0f, 3, 2.5f, 300);
            case EnemyTankType.Power:
                return new EnemyTankProperties(2.5f, 2, 1.0f, 400);
            default:
                return new EnemyTankProperties(3.0f, 1, 2.0f, 100);
        }
    }
}

以上代码实现了敌方坦克的基本AI行为。通过状态机模式,我们可以让敌方坦克表现出不同的行为模式,增加游戏的挑战性和趣味性。

19.3.3 玩家控制系统设计

玩家控制系统是游戏的核心交互部分,需要实现流畅的输入响应和坦克控制。

玩家坦克属性与控制

csharp

public class PlayerTank : MonoBehaviour
{
    [Header("Movement")]
    [SerializeField] private float moveSpeed = 4.0f;
    [SerializeField] private float rotationSpeed = 200.0f;
    
    [Header("Combat")]
    [SerializeField] private Transform firePoint;
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private float fireRate = 0.5f;
    
    [Header("Properties")]
    [SerializeField] private int maxLives = 3;
    [SerializeField] private int bulletPower = 1;
    [SerializeField] private int maxBullets = 1;
    
    // 引用
    private Rigidbody2D rb;
    private SpriteRenderer spriteRenderer;
    
    // 状态
    private Vector2 moveDirection;
    private float lastFireTime;
    private int currentLives;
    private int activeBulletCount;
    private bool isInvulnerable;
    private bool isOnIce;
    
    // 公共属性
    public int CurrentLives => currentLives;
    public int BulletPower => bulletPower;
    
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        
        currentLives = maxLives;
        activeBulletCount = 0;
    }
    
    private void Update()
    {
        // 处理玩家输入
        HandleInput();
        
        // 处理射击
        HandleShooting();
    }
    
    private void FixedUpdate()
    {
        // 移动坦克
        Move();
    }
    
    private void HandleInput()
    {
        // 读取移动输入
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        
        // 只允许水平或垂直移动,不能斜向移动
        if (Mathf.Abs(horizontal) > Mathf.Abs(vertical))
        {
            moveDirection = new Vector2(horizontal, 0).normalized;
        }
        else if (Mathf.Abs(vertical) > 0)
        {
            moveDirection = new Vector2(0, vertical).normalized;
        }
        else
        {
            // 如果不在冰面上,则停止移动
            if (!isOnIce)
            {
                moveDirection = Vector2.zero;
            }
        }
    }
    
    private void Move()
    {
        // 根据移动方向和速度移动坦克
        if (moveDirection != Vector2.zero)
        {
            // 更新坦克朝向
            float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
            transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
            
            // 应用移动
            rb.velocity = moveDirection * moveSpeed;
        }
        else if (!isOnIce)
        {
            // 不在冰面上时停止移动
            rb.velocity = Vector2.zero;
        }
    }
    
    private void HandleShooting()
    {
        // 检查射击输入
        if (Input.GetKeyDown(KeyCode.Space) && Time.time > lastFireTime + fireRate && activeBulletCount < maxBullets)
        {
            Fire();
        }
    }
    
    private void Fire()
    {
        // 创建子弹
        GameObject bullet = Instantiate(bulletPrefab, firePoint.position, transform.rotation);
        
        // 配置子弹
        PlayerBullet playerBullet = bullet.GetComponent<PlayerBullet>();
        if (playerBullet != null)
        {
            playerBullet.Initialize(bulletPower, this);
            activeBulletCount++;
        }
        
        // 更新射击时间
        lastFireTime = Time.time;
        
        // 播放射击音效
        AudioManager.Instance.PlaySound("PlayerShoot");
    }
    
    public void OnBulletDestroyed()
    {
        // 减少活动子弹计数
        activeBulletCount = Mathf.Max(0, activeBulletCount - 1);
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 检测是否在冰面上
        if (collision.CompareTag("Ice"))
        {
            isOnIce = true;
        }
    }
    
    private void OnTriggerExit2D(Collider2D collision)
    {
        // 检测是否离开冰面
        if (collision.CompareTag("Ice"))
        {
            isOnIce = false;
            
            // 如果没有输入,则停止移动
            if (moveDirection == Vector2.zero)
            {
                rb.velocity = Vector2.zero;
            }
        }
    }
    
    public void TakeDamage()
    {
        if (isInvulnerable)
            return;
        
        currentLives--;
        
        if (currentLives <= 0)
        {
            // 玩家死亡
            Die();
        }
        else
        {
            // 临时无敌并重生
            StartCoroutine(Respawn());
        }
    }
    
    private void Die()
    {
        // 播放死亡效果
        
        // 通知游戏管理器
        GameManager.Instance.PlayerDied();
        
        // 禁用玩家
        gameObject.SetActive(false);
    }
    
    private IEnumerator Respawn()
    {
        // 禁用碰撞和渲染
        GetComponent<Collider2D>().enabled = false;
        spriteRenderer.enabled = false;
        
        yield return new WaitForSeconds(1.0f);
        
        // 重置位置
        transform.position = GameManager.Instance.GetPlayerSpawnPosition();
        transform.rotation = Quaternion.identity;
        
        // 启用碰撞和渲染
        GetComponent<Collider2D>().enabled = true;
        spriteRenderer.enabled = true;
        
        // 设置临时无敌
        StartCoroutine(TemporaryInvulnerability(3.0f));
    }
    
    private IEnumerator TemporaryInvulnerability(float duration)
    {
        isInvulnerable = true;
        
        // 闪烁效果
        float blinkRate = 0.1f;
        for (float t = 0; t < duration; t += blinkRate)
        {
            spriteRenderer.enabled = !spriteRenderer.enabled;
            yield return new WaitForSeconds(blinkRate);
        }
        
        spriteRenderer.enabled = true;
        isInvulnerable = false;
    }
    
    public void PowerUpBullet()
    {
        // 提升子弹威力,最高为3
        bulletPower = Mathf.Min(bulletPower + 1, 3);
    }
    
    public void IncreaseMaxBullets()
    {
        // 提升最大子弹数量,最高为2
        maxBullets = Mathf.Min(maxBullets + 1, 2);
    }
    
    public void AddLife()
    {
        // 增加生命,最高为maxLives
        currentLives = Mathf.Min(currentLives + 1, maxLives);
    }
}

玩家子弹类

csharp

public class PlayerBullet : MonoBehaviour
{
    [SerializeField] private float speed = 10.0f;
    [SerializeField] private float lifetime = 3.0f;
    
    private int power;
    private PlayerTank owner;
    private Rigidbody2D rb;
    
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
    }
    
    public void Initialize(int bulletPower, PlayerTank bulletOwner)
    {
        power = bulletPower;
        owner = bulletOwner;
        
        // 设置生命周期
        Destroy(gameObject, lifetime);
    }
    
    private void FixedUpdate()
    {
        // 移动子弹
        rb.velocity = transform.right * speed;
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 处理碰撞
        if (collision.CompareTag("Enemy"))
        {
            // 击中敌人
            EnemyTank enemyTank = collision.GetComponent<EnemyTank>();
            if (enemyTank != null)
            {
                enemyTank.TakeDamage();
            }
            
            DestroyBullet();
        }
        else if (collision.CompareTag("BrickWall"))
        {
            // 击中砖墙
            MapManager.Instance.DestroyTerrain(collision.transform.position);
            
            DestroyBullet();
        }
        else if (collision.CompareTag("SteelWall"))
        {
            // 击中钢墙,只有高级子弹才能摧毁
            if (power >= 3)
            {
                MapManager.Instance.DestroyTerrain(collision.transform.position);
            }
            
            DestroyBullet();
        }
        else if (collision.CompareTag("EnemyBullet"))
        {
            // 子弹相撞
            Destroy(collision.gameObject);
            DestroyBullet();
        }
    }
    
    private void DestroyBullet()
    {
        // 通知拥有者
        if (owner != null)
        {
            owner.OnBulletDestroyed();
        }
        
        // 播放爆炸效果
        
        // 销毁子弹
        Destroy(gameObject);
    }
}

通过以上代码,我们实现了完整的玩家控制系统,包括坦克移动、射击和生命管理。

19.3.4 障碍物系统设计

在坦克大战中,障碍物是游戏地图的重要组成部分,它们影响坦克的移动和子弹的传播。

障碍物基类

csharp

public abstract class Obstacle : MonoBehaviour
{
    [Header("Properties")]
    [SerializeField] protected bool isDestructible;
    [SerializeField] protected bool isPassable;
    
    // 公共属性访问器
    public bool IsDestructible => isDestructible;
    public bool IsPassable => isPassable;
    
    public virtual void HandleBulletCollision(int bulletPower)
    {
        // 如果障碍物可被摧毁且子弹威力足够
        if (isDestructible && ShouldDestroyByBulletPower(bulletPower))
        {
            // 播放破坏特效
            PlayDestructionEffect();
            
            // 销毁障碍物
            Destroy(gameObject);
        }
    }
    
    protected virtual bool ShouldDestroyByBulletPower(int bulletPower)
    {
        // 默认实现:任何子弹都可以摧毁
        return true;
    }
    
    protected virtual void PlayDestructionEffect()
    {
        // 在子类中实现具体的特效
    }
}

具体障碍物实现

csharp

// 砖墙
public class BrickWall : Obstacle
{
    [SerializeField] private GameObject destructionEffectPrefab;
    
    private void Awake()
    {
        isDestructible = true;
        isPassable = false;
    }
    
    protected override void PlayDestructionEffect()
    {
        if (destructionEffectPrefab != null)
        {
            Instantiate(destructionEffectPrefab, transform.position, Quaternion.identity);
        }
        
        // 播放音效
        AudioManager.Instance.PlaySound("BrickDestroy");
    }
}

// 钢墙
public class SteelWall : Obstacle
{
    [SerializeField] private GameObject destructionEffectPrefab;
    
    private void Awake()
    {
        isDestructible = true;
        isPassable = false;
    }
    
    protected override bool ShouldDestroyByBulletPower(int bulletPower)
    {
        // 只有威力大于等于3的子弹才能摧毁钢墙
        return bulletPower >= 3;
    }
    
    protected override void PlayDestructionEffect()
    {
        if (destructionEffectPrefab != null)
        {
            Instantiate(destructionEffectPrefab, transform.position, Quaternion.identity);
        }
        
        // 播放音效
        AudioManager.Instance.PlaySound("SteelDestroy");
    }
}

// 水域
public class Water : Obstacle
{
    private void Awake()
    {
        isDestructible = false;
        isPassable = false;
    }
}

// 草丛
public class Forest : Obstacle
{
    private void Awake()
    {
        isDestructible = false;
        isPassable = true;
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 当坦克进入草丛时,降低其Sprite的渲染顺序
        if (collision.CompareTag("Player") || collision.CompareTag("Enemy"))
        {
            SpriteRenderer tankRenderer = collision.GetComponent<SpriteRenderer>();
            if (tankRenderer != null)
            {
                tankRenderer.sortingOrder = -1;
            }
        }
    }
    
    private void OnTriggerExit2D(Collider2D collision)
    {
        // 当坦克离开草丛时,恢复其Sprite的渲染顺序
        if (collision.CompareTag("Player") || collision.CompareTag("Enemy"))
        {
            SpriteRenderer tankRenderer = collision.GetComponent<SpriteRenderer>();
            if (tankRenderer != null)
            {
                tankRenderer.sortingOrder = 1;
            }
        }
    }
}

// 冰面
public class Ice : Obstacle
{
    private void Awake()
    {
        isDestructible = false;
        isPassable = true;
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 冰面逻辑在坦克控制器中处理
    }
}

通过以上的障碍物系统设计,我们可以创建多样化的游戏地图,增加游戏的趣味性和策略性。

19.3.5 道具系统设计

道具系统可以为游戏增加更多变化和策略元素。在坦克大战中,道具会随机出现,玩家可以通过收集它们获得各种临时能力。

道具基类

csharp

public abstract class PowerUp : MonoBehaviour
{
    [SerializeField] protected float lifetime = 10.0f;
    [SerializeField] protected GameObject collectEffectPrefab;
    
    protected virtual void Start()
    {
        // 道具自动销毁计时
        Destroy(gameObject, lifetime);
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            // 获取玩家坦克
            PlayerTank playerTank = collision.GetComponent<PlayerTank>();
            if (playerTank != null)
            {
                // 应用道具效果
                ApplyEffect(playerTank);
                
                // 播放收集特效
                if (collectEffectPrefab != null)
                {
                    Instantiate(collectEffectPrefab, transform.position, Quaternion.identity);
                }
                
                // 播放音效
                AudioManager.Instance.PlaySound("PowerUp");
                
                // 销毁道具
                Destroy(gameObject);
            }
        }
    }
    
    // 道具效果的抽象方法,由子类实现
    protected abstract void ApplyEffect(PlayerTank playerTank);
}

具体道具实现

csharp

// 星星道具 - 提升子弹威力
public class StarPowerUp : PowerUp
{
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        playerTank.PowerUpBullet();
    }
}

// 坦克道具 - 增加一条生命
public class TankPowerUp : PowerUp
{
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        playerTank.AddLife();
    }
}

// 计时器道具 - 冻结所有敌人
public class TimerPowerUp : PowerUp
{
    [SerializeField] private float freezeDuration = 10.0f;
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        // 通知游戏管理器冻结所有敌人
        GameManager.Instance.FreezeEnemies(freezeDuration);
    }
}

// 铲子道具 - 加固基地周围的墙
public class ShovelPowerUp : PowerUp
{
    [SerializeField] private float fortifyDuration = 15.0f;
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        // 通知游戏管理器加固基地
        GameManager.Instance.FortifyBase(fortifyDuration);
    }
}

// 炸弹道具 - 摧毁所有敌人
public class BombPowerUp : PowerUp
{
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        // 通知游戏管理器摧毁所有敌人
        GameManager.Instance.DestroyAllEnemies();
    }
}

道具生成器

csharp

public class PowerUpSpawner : MonoBehaviour
{
    [System.Serializable]
    public class PowerUpData
    {
        public GameObject powerUpPrefab;
        public float weight = 1.0f;
    }
    
    [Header("Spawning Settings")]
    [SerializeField] private List<PowerUpData> availablePowerUps = new List<PowerUpData>();
    [SerializeField] private float spawnChance = 0.3f;
    
    // 当敌人被摧毁时调用
    public void TrySpawnPowerUp(Vector3 position)
    {
        // 根据概率决定是否生成道具
        if (Random.value <= spawnChance)
        {
            SpawnRandomPowerUp(position);
        }
    }
    
    private void SpawnRandomPowerUp(Vector3 position)
    {
        // 如果没有可用的道具,直接返回
        if (availablePowerUps.Count == 0)
            return;
            
        // 计算总权重
        float totalWeight = 0;
        foreach (var powerUpData in availablePowerUps)
        {
            totalWeight += powerUpData.weight;
        }
        
        // 随机选择一个道具
        float randomValue = Random.value * totalWeight;
        float weightSum = 0;
        
        foreach (var powerUpData in availablePowerUps)
        {
            weightSum += powerUpData.weight;
            if (randomValue <= weightSum)
            {
                // 生成选中的道具
                Instantiate(powerUpData.powerUpPrefab, position, Quaternion.identity);
                break;
            }
        }
    }
}

通过这个道具系统,我们可以在游戏中添加更多的变化和策略元素,提升游戏的可玩性。

19.3.6 基地系统设计

基地是玩家需要保护的核心目标,如果基地被摧毁,游戏就会结束。

csharp

public class Base : MonoBehaviour
{
    [SerializeField] private GameObject destructionEffectPrefab;
    [SerializeField] private Sprite defaultSprite;
    [SerializeField] private Sprite destroyedSprite;
    
    private SpriteRenderer spriteRenderer;
    private bool isDestroyed = false;
    
    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = defaultSprite;
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (isDestroyed)
            return;
            
        // 检查是否被敌方子弹击中
        if (collision.CompareTag("EnemyBullet"))
        {
            DestroyBase();
        }
    }
    
    public void DestroyBase()
    {
        if (isDestroyed)
            return;
            
        isDestroyed = true;
        
        // 更改外观
        spriteRenderer.sprite = destroyedSprite;
        
        // 播放爆炸效果
        if (destructionEffectPrefab != null)
        {
            Instantiate(destructionEffectPrefab, transform.position, Quaternion.identity);
        }
        
        // 播放音效
        AudioManager.Instance.PlaySound("BaseDestroy");
        
        // 通知游戏管理器游戏结束
        GameManager.Instance.GameOver(false);
    }
    
    // 用于铲子道具的基地加固功能
    public void Fortify(float duration)
    {
        StartCoroutine(FortifyRoutine(duration));
    }
    
    private IEnumerator FortifyRoutine(float duration)
    {
        // 获取基地周围的墙
        List<GameObject> surroundingWalls = GetSurroundingWalls();
        
        // 将周围的墙升级为钢墙
        foreach (GameObject wall in surroundingWalls)
        {
            BrickWall brickWall = wall.GetComponent<BrickWall>();
            if (brickWall != null)
            {
                // 替换为钢墙
                SpriteRenderer wallRenderer = wall.GetComponent<SpriteRenderer>();
                if (wallRenderer != null)
                {
                    wallRenderer.color = Color.gray;
                }
                
                // 暂时将碰撞层改为钢墙
                wall.layer = LayerMask.NameToLayer("SteelWall");
            }
        }
        
        // 等待持续时间
        yield return new WaitForSeconds(duration);
        
        // 恢复墙的原始状态
        foreach (GameObject wall in surroundingWalls)
        {
            // 检查墙是否还存在
            if (wall != null)
            {
                SpriteRenderer wallRenderer = wall.GetComponent<SpriteRenderer>();
                if (wallRenderer != null)
                {
                    wallRenderer.color = Color.white;
                }
                
                // 恢复原始碰撞层
                wall.layer = LayerMask.NameToLayer("BrickWall");
            }
        }
    }
    
    private List<GameObject> GetSurroundingWalls()
    {
        List<GameObject> walls = new List<GameObject>();
        
        // 获取基地周围的墙(通常是4个位置)
        float wallDistance = 1.0f;
        Vector2[] directions = new Vector2[] { Vector2.up, Vector2.right, Vector2.down, Vector2.left };
        
        foreach (Vector2 direction in directions)
        {
            RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, wallDistance, LayerMask.GetMask("BrickWall"));
            if (hit.collider != null)
            {
                walls.Add(hit.collider.gameObject);
            }
        }
        
        return walls;
    }
}

通过基地系统,我们实现了游戏的核心保护目标,同时还加入了基地加固的功能,增加了游戏的策略性。

19.3.7 游戏流程控制

游戏流程控制是管理整个游戏状态和进程的核心系统。

csharp

// 游戏状态枚举
public enum GameState
{
    MainMenu,
    Playing,
    Paused,
    GameOver,
    LevelComplete
}

public class GameManager : MonoBehaviour
{
    // 单例模式
    public static GameManager Instance { get; private set; }
    
    [Header("Game Settings")]
    [SerializeField] private int startingLives = 3;
    [SerializeField] private int currentLevel = 1;
    [SerializeField] private int maxLevel = 10;
    
    [Header("Prefabs")]
    [SerializeField] private GameObject playerTankPrefab;
    [SerializeField] private GameObject[] enemyTankPrefabs;
    
    [Header("Spawn Points")]
    [SerializeField] private Transform playerSpawnPoint;
    [SerializeField] private Transform[] enemySpawnPoints;
    
    [Header("UI References")]
    [SerializeField] private UIManager uiManager;
    
    // 游戏状态
    private GameState currentState;
    private int playerLives;
    private int playerScore;
    private int enemiesRemaining;
    private int enemiesActive;
    private int maxConcurrentEnemies = 4;
    private bool isBaseDestroyed = false;
    
    // 引用
    private PlayerTank playerTank;
    private Base playerBase;
    private List<EnemyTank> activeEnemies = new List<EnemyTank>();
    private PowerUpSpawner powerUpSpawner;
    
    // 公共属性
    public GameState CurrentState => currentState;
    public int PlayerLives => playerLives;
    public int PlayerScore => playerScore;
    public int EnemiesRemaining => enemiesRemaining;
    
    private void Awake()
    {
        // 单例初始化
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        
        // 获取引用
        playerBase = FindObjectOfType<Base>();
        powerUpSpawner = GetComponent<PowerUpSpawner>();
        
        // 初始化游戏状态
        currentState = GameState.MainMenu;
        playerLives = startingLives;
        playerScore = 0;
    }
    
    private void Start()
    {
        // 显示主菜单
        uiManager.ShowMainMenu();
    }
    
    private void Update()
    {
        // 处理暂停
        if (Input.GetKeyDown(KeyCode.Escape) && currentState == GameState.Playing)
        {
            PauseGame();
        }
        
        // 游戏进行中的逻辑
        if (currentState == GameState.Playing)
        {
            // 检查是否需要生成更多敌人
            if (enemiesActive < maxConcurrentEnemies && enemiesRemaining > 0)
            {
                SpawnEnemy();
            }
            
            // 检查关卡是否完成
            if (enemiesRemaining == 0 && activeEnemies.Count == 0)
            {
                LevelComplete();
            }
        }
    }
    
    public void StartGame()
    {
        // 初始化游戏参数
        playerLives = startingLives;
        playerScore = 0;
        currentLevel = 1;
        
        // 开始第一关
        StartLevel(currentLevel);
    }
    
    private void StartLevel(int level)
    {
        // 清理场景
        ClearLevel();
        
        // 加载地图
        MapManager.Instance.LoadLevel(level);
        
        // 初始化关卡敌人数量(随关卡增加)
        enemiesRemaining = 10 + (level * 2);
        enemiesActive = 0;
        
        // 重置基地状态
        isBaseDestroyed = false;
        
        // 生成玩家坦克
        SpawnPlayer();
        
        // 更新UI
        uiManager.UpdateLevelText(level);
        uiManager.UpdateLivesText(playerLives);
        uiManager.UpdateScoreText(playerScore);
        uiManager.UpdateEnemiesText(enemiesRemaining);
        
        // 显示关卡开始UI
        uiManager.ShowLevelStart(level);
        
        // 延迟一段时间后开始游戏
        StartCoroutine(DelayedGameStart());
    }
    
    private IEnumerator DelayedGameStart()
    {
        yield return new WaitForSeconds(2.0f);
        
        // 切换到游戏状态
        currentState = GameState.Playing;
        
        // 隐藏关卡开始UI
        uiManager.HideLevelStart();
        
        // 开始生成敌人
        SpawnInitialEnemies();
    }
    
    private void SpawnInitialEnemies()
    {
        // 初始生成几个敌人
        for (int i = 0; i < Mathf.Min(maxConcurrentEnemies, enemiesRemaining); i++)
        {
            SpawnEnemy();
        }
    }
    
    private void SpawnPlayer()
    {
        // 在玩家出生点生成坦克
        GameObject playerObj = Instantiate(playerTankPrefab, playerSpawnPoint.position, Quaternion.identity);
        playerTank = playerObj.GetComponent<PlayerTank>();
    }
    
    private void SpawnEnemy()
    {
        if (enemiesRemaining <= 0)
            return;
        
        // 随机选择一个敌人类型(随关卡增加难度)
        int maxEnemyType = Mathf.Min(enemyTankPrefabs.Length, 1 + (currentLevel / 2));
        int enemyType = Random.Range(0, maxEnemyType);
        
        // 随机选择一个生成点
        Transform spawnPoint = enemySpawnPoints[Random.Range(0, enemySpawnPoints.Length)];
        
        // 生成敌人
        GameObject enemyObj = Instantiate(enemyTankPrefabs[enemyType], spawnPoint.position, Quaternion.identity);
        EnemyTank enemy = enemyObj.GetComponent<EnemyTank>();
        
        // 添加到活动敌人列表
        activeEnemies.Add(enemy);
        
        // 更新计数
        enemiesRemaining--;
        enemiesActive++;
        
        // 更新UI
        uiManager.UpdateEnemiesText(enemiesRemaining);
    }
    
    public void EnemyDestroyed(EnemyTank enemy)
    {
        // 从活动敌人列表中移除
        activeEnemies.Remove(enemy);
        enemiesActive--;
        
        // 增加玩家分数
        AddScore(enemy.TankType switch
        {
            EnemyTankType.Basic => 100,
            EnemyTankType.Fast => 200,
            EnemyTankType.Heavy => 300,
            EnemyTankType.Power => 400,
            _ => 100
        });
        
        // 尝试生成道具
        if (powerUpSpawner != null)
        {
            powerUpSpawner.TrySpawnPowerUp(enemy.transform.position);
        }
    }
    
    public void PlayerDied()
    {
        playerLives--;
        
        // 更新UI
        uiManager.UpdateLivesText(playerLives);
        
        if (playerLives <= 0)
        {
            // 游戏结束
            GameOver(false);
        }
        else
        {
            // 延迟重生
            StartCoroutine(RespawnPlayerAfterDelay());
        }
    }
    
    private IEnumerator RespawnPlayerAfterDelay()
    {
        yield return new WaitForSeconds(2.0f);
        
        // 重新生成玩家
        SpawnPlayer();
    }
    
    public void GameOver(bool victory)
    {
        currentState = GameState.GameOver;
        
        // 显示游戏结束UI
        uiManager.ShowGameOver(victory, playerScore);
    }
    
    private void LevelComplete()
    {
        currentState = GameState.LevelComplete;
        
        // 显示关卡完成UI
        uiManager.ShowLevelComplete();
        
        // 检查是否完成了所有关卡
        if (currentLevel >= maxLevel)
        {
            // 游戏胜利
            StartCoroutine(DelayedGameVictory());
        }
        else
        {
            // 进入下一关
            StartCoroutine(DelayedNextLevel());
        }
    }
    
    private IEnumerator DelayedGameVictory()
    {
        yield return new WaitForSeconds(3.0f);
        
        // 游戏胜利
        GameOver(true);
    }
    
    private IEnumerator DelayedNextLevel()
    {
        yield return new WaitForSeconds(3.0f);
        
        // 进入下一关
        currentLevel++;
        StartLevel(currentLevel);
    }
    
    private void PauseGame()
    {
        currentState = GameState.Paused;
        Time.timeScale = 0;
        
        // 显示暂停菜单
        uiManager.ShowPauseMenu();
    }
    
    public void ResumeGame()
    {
        currentState = GameState.Playing;
        Time.timeScale = 1;
        
        // 隐藏暂停菜单
        uiManager.HidePauseMenu();
    }
    
    public void ReturnToMainMenu()
    {
        currentState = GameState.MainMenu;
        Time.timeScale = 1;
        
        // 清理场景
        ClearLevel();
        
        // 显示主菜单
        uiManager.ShowMainMenu();
    }
    
    private void ClearLevel()
    {
        // 销毁所有敌人
        foreach (EnemyTank enemy in activeEnemies)
        {
            if (enemy != null)
            {
                Destroy(enemy.gameObject);
            }
        }
        
        activeEnemies.Clear();
        
        // 销毁玩家坦克
        if (playerTank != null)
        {
            Destroy(playerTank.gameObject);
            playerTank = null;
        }
        
        // 销毁所有子弹
        GameObject[] bullets = GameObject.FindGameObjectsWithTag("Bullet");
        foreach (GameObject bullet in bullets)
        {
            Destroy(bullet);
        }
        
        // 销毁所有道具
        GameObject[] powerUps = GameObject.FindGameObjectsWithTag("PowerUp");
        foreach (GameObject powerUp in powerUps)
        {
            Destroy(powerUp);
        }
    }
    
    private void AddScore(int score)
    {
        playerScore += score;
        uiManager.UpdateScoreText(playerScore);
    }
    
    public Vector3 GetPlayerSpawnPosition()
    {
        return playerSpawnPoint.position;
    }
    
    // 道具功能实现
    
    public void FreezeEnemies(float duration)
    {
        StartCoroutine(FreezeEnemiesRoutine(duration));
    }
    
    private IEnumerator FreezeEnemiesRoutine(float duration)
    {
        // 冻结所有敌人
        foreach (EnemyTank enemy in activeEnemies)
        {
            if (enemy != null)
            {
                enemy.gameObject.SetActive(false);
            }
        }
        
        // 等待持续时间
        yield return new WaitForSeconds(duration);
        
        // 解冻所有敌人
        foreach (EnemyTank enemy in activeEnemies)
        {
            if (enemy != null)
            {
                enemy.gameObject.SetActive(true);
            }
        }
    }
    
    public void FortifyBase(float duration)
    {
        if (playerBase != null)
        {
            playerBase.Fortify(duration);
        }
    }
    
    public void DestroyAllEnemies()
    {
        // 创建临时列表以避免修改迭代器问题
        List<EnemyTank> enemiesToDestroy = new List<EnemyTank>(activeEnemies);
        
        foreach (EnemyTank enemy in enemiesToDestroy)
        {
            if (enemy != null)
            {
                // 销毁敌人并增加分数
                AddScore(enemy.TankType switch
                {
                    EnemyTankType.Basic => 100,
                    EnemyTankType.Fast => 200,
                    EnemyTankType.Heavy => 300,
                    EnemyTankType.Power => 400,
                    _ => 100
                });
                
                Destroy(enemy.gameObject);
                activeEnemies.Remove(enemy);
                enemiesActive--;
            }
        }
    }
}

这个游戏管理器负责控制整个游戏的流程,包括关卡加载、敌人生成、游戏状态转换等核心功能。

19.4 项目实现详解

19.4.1 开发环境准备

在开始项目开发之前,我们需要做好以下准备工作:

  1. 创建Unity项目

    • 打开Unity Hub,创建一个新的2D项目。
    • 选择Unity 2021.3.8f1c1版本。
  2. 设置项目结构

    mipsasm

    Assets/
    ├── Animations/      # 存放动画相关资源
    ├── Audio/           # 音频文件
    ├── Prefabs/         # 预制体
    ├── Resources/       # 动态加载的资源
    ├── Scenes/          # 场景文件
    ├── Scripts/         # 脚本
    ├── Sprites/         # 2D精灵图
    └── Tiles/           # Tilemap瓦片资源
    

  3. 导入基本资源

    • 坦克精灵图
    • 地形瓦片图
    • 特效精灵图
    • 音效和背景音乐
  4. 设置项目质量与分辨率

    • 将游戏分辨率设置为固定的宽高比,如16:9或4:3
    • 设置合适的像素密度以保持像素游戏的风格
  5. 配置输入系统

    • 设置键盘输入映射:方向键/WASD用于移动,空格键用于射击

19.4.2 场景构建实现

游戏场景是玩家直接与之互动的环境,我们需要合理地设计和构建游戏场景。

主场景构建

csharp

public class SceneBuilder : MonoBehaviour
{
    [SerializeField] private Transform gamePlayArea;
    [SerializeField] private Transform uiCanvas;
    
    [SerializeField] private GameObject mapManagerPrefab;
    [SerializeField] private GameObject gameManagerPrefab;
    [SerializeField] private GameObject audioManagerPrefab;
    [SerializeField] private GameObject basePrefab;
    
    private void Awake()
    {
        // 设置相机正交大小以适应游戏场景
        Camera.main.orthographicSize = 6.5f;
        
        // 初始化核心管理器
        if (FindObjectOfType<GameManager>() == null)
        {
            Instantiate(gameManagerPrefab);
        }
        
        if (FindObjectOfType<MapManager>() == null)
        {
            Instantiate(mapManagerPrefab, gamePlayArea);
        }
        
        if (FindObjectOfType<AudioManager>() == null)
        {
            Instantiate(audioManagerPrefab);
        }
        
        // 如果没有基地,则创建一个
        if (FindObjectOfType<Base>() == null)
        {
            Vector3 basePosition = new Vector3(0, -5, 0);
            Instantiate(basePrefab, basePosition, Quaternion.identity, gamePlayArea);
        }
        
        // 设置UI缩放以适应不同分辨率
        CanvasScaler canvasScaler = uiCanvas.GetComponent<CanvasScaler>();
        if (canvasScaler != null)
        {
            canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
            canvasScaler.referenceResolution = new Vector2(800, 600);
            canvasScaler.matchWidthOrHeight = 0.5f; // 混合宽度和高度的缩放
        }
    }
}

UI管理器实现

csharp

public class UIManager : MonoBehaviour
{
    [Header("UI Panels")]
    [SerializeField] private GameObject mainMenuPanel;
    [SerializeField] private GameObject gameplayPanel;
    [SerializeField] private GameObject pauseMenuPanel;
    [SerializeField] private GameObject gameOverPanel;
    [SerializeField] private GameObject levelCompletePanel;
    [SerializeField] private GameObject levelStartPanel;
    
    [Header("Gameplay UI Elements")]
    [SerializeField] private TextMeshProUGUI levelText;
    [SerializeField] private TextMeshProUGUI livesText;
    [SerializeField] private TextMeshProUGUI scoreText;
    [SerializeField] private TextMeshProUGUI enemiesText;
    
    [Header("Game Over UI Elements")]
    [SerializeField] private TextMeshProUGUI gameOverTitleText;
    [SerializeField] private TextMeshProUGUI finalScoreText;
    
    [Header("Level Start UI Elements")]
    [SerializeField] private TextMeshProUGUI levelStartText;
    
    // 隐藏所有面板
    private void HideAllPanels()
    {
        mainMenuPanel.SetActive(false);
        gameplayPanel.SetActive(false);
        pauseMenuPanel.SetActive(false);
        gameOverPanel.SetActive(false);
        levelCompletePanel.SetActive(false);
        levelStartPanel.SetActive(false);
    }
    
    // 显示主菜单
    public void ShowMainMenu()
    {
        HideAllPanels();
        mainMenuPanel.SetActive(true);
    }
    
    // 显示游戏界面
    public void ShowGameplay()
    {
        HideAllPanels();
        gameplayPanel.SetActive(true);
    }
    
    // 显示暂停菜单
    public void ShowPauseMenu()
    {
        pauseMenuPanel.SetActive(true);
    }
    
    // 隐藏暂停菜单
    public void HidePauseMenu()
    {
        pauseMenuPanel.SetActive(false);
    }
    
    // 显示游戏结束界面
    public void ShowGameOver(bool victory, int finalScore)
    {
        HideAllPanels();
        gameOverPanel.SetActive(true);
        
        gameOverTitleText.text = victory ? "Victory!" : "Game Over";
        finalScoreText.text = $"Final Score: {finalScore}";
    }
    
    // 显示关卡完成界面
    public void ShowLevelComplete()
    {
        levelCompletePanel.SetActive(true);
    }
    
    // 显示关卡开始界面
    public void ShowLevelStart(int level)
    {
        levelStartPanel.SetActive(true);
        levelStartText.text = $"Level {level}";
    }
    
    // 隐藏关卡开始界面
    public void HideLevelStart()
    {
        levelStartPanel.SetActive(false);
    }
    
    // 更新关卡文本
    public void UpdateLevelText(int level)
    {
        levelText.text = $"Level: {level}";
    }
    
    // 更新生命文本
    public void UpdateLivesText(int lives)
    {
        livesText.text = $"Lives: {lives}";
    }
    
    // 更新分数文本
    public void UpdateScoreText(int score)
    {
        scoreText.text = $"Score: {score}";
    }
    
    // 更新敌人数量文本
    public void UpdateEnemiesText(int enemies)
    {
        enemiesText.text = $"Enemies: {enemies}";
    }
    
    // UI按钮事件处理
    
    public void OnStartGameButton()
    {
        GameManager.Instance.StartGame();
        ShowGameplay();
    }
    
    public void OnResumeButton()
    {
        GameManager.Instance.ResumeGame();
    }
    
    public void OnMainMenuButton()
    {
        GameManager.Instance.ReturnToMainMenu();
    }
    
    public void OnQuitButton()
    {
        #if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
        #else
        Application.Quit();
        #endif
    }
    
    public void OnRestartButton()
    {
        GameManager.Instance.StartGame();
        ShowGameplay();
    }
}

19.4.3 角色控制系统实现

玩家控制系统是游戏的核心交互部分。我们已经在前面的章节中实现了基本的坦克控制系统,下面我们来看一些更详细的实现细节。

坦克移动优化

为了使坦克移动更加平滑和真实,我们可以添加一些物理效果和动画:

csharp

public class TankController : MonoBehaviour
{
    [SerializeField] private float accelerationRate = 10.0f;
    [SerializeField] private float decelerationRate = 15.0f;
    [SerializeField] private float maxMoveSpeed = 4.0f;
    
    // 履带动画
    [SerializeField] private Animator leftTrackAnimator;
    [SerializeField] private Animator rightTrackAnimator;
    
    // 移动粒子效果
    [SerializeField] private ParticleSystem moveDustParticles;
    
    // 引用
    private Rigidbody2D rb;
    
    // 状态
    private Vector2 moveDirection;
    private Vector2 targetVelocity;
    private Vector2 currentVelocity;
    private bool isOnIce;
    
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
    }
    
    private void Update()
    {
        // 更新动画和特效
        UpdateVisuals();
    }
    
    private void FixedUpdate()
    {
        // 计算目标速度
        targetVelocity = moveDirection * maxMoveSpeed;
        
        // 计算实际速度(考虑加速度和减速度)
        if (moveDirection != Vector2.zero)
        {
            // 加速
            currentVelocity = Vector2.MoveTowards(
                currentVelocity,
                targetVelocity,
                accelerationRate * Time.fixedDeltaTime
            );
        }
        else if (!isOnIce)
        {
            // 减速(如果不在冰面上)
            currentVelocity = Vector2.MoveTowards(
                currentVelocity,
                Vector2.zero,
                decelerationRate * Time.fixedDeltaTime
            );
        }
        
        // 应用速度
        rb.velocity = currentVelocity;
    }
    
    public void SetMoveDirection(Vector2 direction)
    {
        moveDirection = direction;
        
        // 更新朝向
        if (direction != Vector2.zero)
        {
            float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
            transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
        }
    }
    
    private void UpdateVisuals()
    {
        // 更新履带动画
        if (leftTrackAnimator != null && rightTrackAnimator != null)
        {
            bool isMoving = currentVelocity.magnitude > 0.1f;
            leftTrackAnimator.SetBool("IsMoving", isMoving);
            rightTrackAnimator.SetBool("IsMoving", isMoving);
        }
        
        // 更新移动粒子效果
        if (moveDustParticles != null)
        {
            if (currentVelocity.magnitude > 0.1f && !isOnIce)
            {
                if (!moveDustParticles.isPlaying)
                {
                    moveDustParticles.Play();
                }
            }
            else
            {
                if (moveDustParticles.isPlaying)
                {
                    moveDustParticles.Stop();
                }
            }
        }
    }
    
    // 碰撞检测
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Ice"))
        {
            isOnIce = true;
        }
    }
    
    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.CompareTag("Ice"))
        {
            isOnIce = false;
        }
    }
}

坦克射击系统优化

为了使射击系统更加精确和有反馈,我们可以添加一些特效和后坐力效果:

csharp

public class TankWeaponSystem : MonoBehaviour
{
    [SerializeField] private Transform firePoint;
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private float fireRate = 0.5f;
    [SerializeField] private int maxBullets = 1;
    
    [SerializeField] private ParticleSystem muzzleFlashEffect;
    [SerializeField] private float recoilForce = 3.0f;
    
    // 引用
    private Rigidbody2D rb;
    
    // 状态
    private float lastFireTime;
    private int activeBulletCount;
    
    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        activeBulletCount = 0;
    }
    
    public bool TryFire()
    {
        if (Time.time > lastFireTime + fireRate && activeBulletCount < maxBullets)
        {
            FireBullet();
            return true;
        }
        
        return false;
    }
    
    private void FireBullet()
    {
        // 创建子弹
        GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
        
        // 配置子弹
        Bullet bulletComponent = bullet.GetComponent<Bullet>();
        if (bulletComponent != null)
        {
            bulletComponent.Initialize(GetBulletPower(), gameObject);
            bulletComponent.OnDestroyed += OnBulletDestroyed;
            activeBulletCount++;
        }
        
        // 播放特效
        if (muzzleFlashEffect != null)
        {
            muzzleFlashEffect.Play();
        }
        
        // 应用后坐力
        ApplyRecoil();
        
        // 更新射击时间
        lastFireTime = Time.time;
        
        // 播放音效
        AudioManager.Instance.PlaySound("TankShoot");
    }
    
    private void ApplyRecoil()
    {
        // 计算后坐力方向(与射击方向相反)
        Vector2 recoilDirection = -transform.right;
        
        // 应用后坐力冲量
        rb.AddForce(recoilDirection * recoilForce, ForceMode2D.Impulse);
    }
    
    private void OnBulletDestroyed()
    {
        activeBulletCount = Mathf.Max(0, activeBulletCount - 1);
    }
    
    protected virtual int GetBulletPower()
    {
        // 子类可以覆盖此方法以提供不同的子弹威力
        return 1;
    }
}

通过以上优化,我们使坦克的移动和射击系统更加真实和有反馈感,提升了游戏的手感。

19.4.4 子弹系统与物理交互

子弹系统是坦克大战游戏中非常重要的一部分,它需要处理子弹的移动、碰撞检测和伤害计算。

子弹基类

csharp

public abstract class Bullet : MonoBehaviour
{
    [SerializeField] protected float speed = 10.0f;
    [SerializeField] protected float lifetime = 3.0f;
    [SerializeField] protected GameObject hitEffectPrefab;
    
    protected int power;
    protected GameObject owner;
    protected Rigidbody2D rb;
    protected TrailRenderer trailRenderer;
    
    // 事件
    public event Action OnDestroyed;
    
    protected virtual void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        trailRenderer = GetComponent<TrailRenderer>();
        
        // 设置生命周期
        Destroy(gameObject, lifetime);
    }
    
    public virtual void Initialize(int bulletPower, GameObject bulletOwner)
    {
        power = bulletPower;
        owner = bulletOwner;
        
        // 根据威力调整外观
        transform.localScale = Vector3.one * (0.8f + power * 0.1f);
        
        // 调整拖尾颜色
        if (trailRenderer != null && power > 1)
        {
            Color trailColor = (power == 2) ? Color.yellow : Color.red;
            trailRenderer.startColor = trailColor;
            trailRenderer.endColor = new Color(trailColor.r, trailColor.g, trailColor.b, 0);
        }
    }
    
    protected virtual void FixedUpdate()
    {
        // 移动子弹
        rb.velocity = transform.right * speed;
    }
    
    protected virtual void OnTriggerEnter2D(Collider2D collision)
    {
        // 忽略与拥有者的碰撞
        if (collision.gameObject == owner)
            return;
            
        // 处理碰撞
        HandleCollision(collision);
    }
    
    protected virtual void HandleCollision(Collider2D collision)
    {
        // 创建击中特效
        CreateHitEffect();
        
        // 子类实现具体的碰撞逻辑
        
        // 销毁子弹
        DestroyBullet();
    }
    
    protected virtual void CreateHitEffect()
    {
        if (hitEffectPrefab != null)
        {
            Instantiate(hitEffectPrefab, transform.position, Quaternion.identity);
        }
    }
    
    protected virtual void DestroyBullet()
    {
        // 触发销毁事件
        OnDestroyed?.Invoke();
        
        // 销毁子弹
        Destroy(gameObject);
    }
}

玩家子弹实现

csharp

public class PlayerBullet : Bullet
{
    protected override void HandleCollision(Collider2D collision)
    {
        if (collision.CompareTag("Enemy"))
        {
            // 击中敌人
            EnemyTank enemyTank = collision.GetComponent<EnemyTank>();
            if (enemyTank != null)
            {
                enemyTank.TakeDamage();
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("BrickWall"))
        {
            // 击中砖墙
            BrickWall brickWall = collision.GetComponent<BrickWall>();
            if (brickWall != null)
            {
                brickWall.HandleBulletCollision(power);
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("SteelWall"))
        {
            // 击中钢墙
            SteelWall steelWall = collision.GetComponent<SteelWall>();
            if (steelWall != null)
            {
                steelWall.HandleBulletCollision(power);
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("EnemyBullet"))
        {
            // 子弹相撞
            Destroy(collision.gameObject);
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("Water") || collision.CompareTag("Ice") || collision.CompareTag("Forest"))
        {
            // 穿过这些地形
            return;
        }
        else
        {
            // 其他碰撞
            CreateHitEffect();
            DestroyBullet();
        }
    }
}

敌人子弹实现

csharp

public class EnemyBullet : Bullet
{
    protected override void HandleCollision(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            // 击中玩家
            PlayerTank playerTank = collision.GetComponent<PlayerTank>();
            if (playerTank != null)
            {
                playerTank.TakeDamage();
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("Base"))
        {
            // 击中基地
            Base playerBase = collision.GetComponent<Base>();
            if (playerBase != null)
            {
                playerBase.DestroyBase();
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("BrickWall"))
        {
            // 击中砖墙
            BrickWall brickWall = collision.GetComponent<BrickWall>();
            if (brickWall != null)
            {
                brickWall.HandleBulletCollision(power);
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("SteelWall"))
        {
            // 击中钢墙
            SteelWall steelWall = collision.GetComponent<SteelWall>();
            if (steelWall != null)
            {
                steelWall.HandleBulletCollision(power);
            }
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("PlayerBullet"))
        {
            // 子弹相撞
            Destroy(collision.gameObject);
            
            CreateHitEffect();
            DestroyBullet();
        }
        else if (collision.CompareTag("Water") || collision.CompareTag("Ice") || collision.CompareTag("Forest"))
        {
            // 穿过这些地形
            return;
        }
        else
        {
            // 其他碰撞
            CreateHitEffect();
            DestroyBullet();
        }
    }
}

通过这种设计,我们可以区分玩家子弹和敌人子弹的行为,使它们对不同的目标产生不同的效果。

19.4.5 地图元素与基地防御

地图元素和基地是游戏的重要组成部分,它们决定了游戏的地形和胜负条件。

砖墙的分段破坏

为了实现更逼真的破坏效果,我们可以将砖墙分成四个部分,使其可以被逐步破坏:

csharp

public class BrickWallSegmented : Obstacle
{
    [SerializeField] private GameObject[] wallSegments;
    [SerializeField] private GameObject destructionEffectPrefab;
    
    // 墙体状态 (四个部分的存在状态)
    private bool[] segmentExists = new bool[4] { true, true, true, true };
    
    private void Awake()
    {
        isDestructible = true;
        isPassable = false;
    }
    
    public override void HandleBulletCollision(int bulletPower, Vector2 hitPoint)
    {
        // 计算击中了哪个部分
        int hitSegmentIndex = CalculateHitSegment(hitPoint);
        
        if (hitSegmentIndex >= 0 && hitSegmentIndex < 4 && segmentExists[hitSegmentIndex])
        {
            // 销毁被击中的部分
            DestroySegment(hitSegmentIndex);
            
            // 检查墙是否完全被摧毁
            CheckWallDestroyed();
        }
    }
    
    private int CalculateHitSegment(Vector2 hitPoint)
    {
        // 将世界坐标转换为局部坐标
        Vector2 localHitPoint = transform.InverseTransformPoint(hitPoint);
        
        // 将局部坐标映射到0-1范围
        float normalizedX = (localHitPoint.x + 0.5f) / 1.0f;
        float normalizedY = (localHitPoint.y + 0.5f) / 1.0f;
        
        // 确定击中的象限
        int quadrantX = normalizedX < 0.5f ? 0 : 1;
        int quadrantY = normalizedY < 0.5f ? 0 : 1;
        
        // 转换为段索引 (左下为0,右下为1,左上为2,右上为3)
        return quadrantX + (quadrantY * 2);
    }
    
    private void DestroySegment(int segmentIndex)
    {
        if (segmentIndex < 0 || segmentIndex >= 4)
            return;
            
        // 标记段为已销毁
        segmentExists[segmentIndex] = false;
        
        // 禁用相应的视觉表示
        if (wallSegments[segmentIndex] != null)
        {
            wallSegments[segmentIndex].SetActive(false);
        }
        
        // 播放破坏特效
        if (destructionEffectPrefab != null)
        {
            Vector3 segmentPosition = wallSegments[segmentIndex].transform.position;
            Instantiate(destructionEffectPrefab, segmentPosition, Quaternion.identity);
        }
        
        // 播放音效
        AudioManager.Instance.PlaySound("BrickDestroy");
    }
    
    private void CheckWallDestroyed()
    {
        bool anySegmentExists = false;
        
        foreach (bool exists in segmentExists)
        {
            if (exists)
            {
                anySegmentExists = true;
                break;
            }
        }
        
        if (!anySegmentExists)
        {
            // 墙已完全摧毁
            Destroy(gameObject);
        }
        else
        {
            // 更新碰撞体以反映新的形状
            UpdateCollider();
        }
    }
    
    private void UpdateCollider()
    {
        // 实际应用中,可能需要更复杂的碰撞体更新逻辑
        // 这里简化处理,仅根据现有段的数量调整碰撞体大小
        
        BoxCollider2D boxCollider = GetComponent<BoxCollider2D>();
        if (boxCollider != null)
        {
            int remainingSegments = 0;
            foreach (bool exists in segmentExists)
            {
                if (exists)
                    remainingSegments++;
            }
            
            // 根据剩余段数调整碰撞体大小
            float sizeMultiplier = remainingSegments / 4.0f;
            boxCollider.size = new Vector2(sizeMultiplier, sizeMultiplier);
        }
    }
}

基地防御系统

基地是玩家需要保护的最重要目标,我们可以添加一些额外的防御机制:

csharp

public class BaseDefenseSystem : MonoBehaviour
{
    [SerializeField] private Base playerBase;
    [SerializeField] private float protectionRadius = 3.0f;
    [SerializeField] private GameObject shieldEffectPrefab;
    [SerializeField] private GameObject[] baseWalls;
    
    private bool isShieldActive = false;
    private GameObject activeShieldEffect = null;
    private Coroutine fortificationCoroutine = null;
    
    // 检测到敌人接近时触发警报
    private void Update()
    {
        if (isShieldActive)
            return;
            
        // 检查是否有敌人接近基地
        Collider2D[] colliders = Physics2D.OverlapCircleAll(
            playerBase.transform.position,
            protectionRadius,
            LayerMask.GetMask("Enemy")
        );
        
        if (colliders.Length > 0)
        {
            // 敌人接近,触发警报
            TriggerAlert();
        }
    }
    
    // 触发警报
    private void TriggerAlert()
    {
        // 播放警报音效
        AudioManager.Instance.PlaySound("BaseAlert");
        
        // 显示视觉提示(可以是UI元素或特效)
        StartCoroutine(FlashBaseWarning());
    }
    
    // 闪烁基地警告
    private IEnumerator FlashBaseWarning()
    {
        SpriteRenderer baseRenderer = playerBase.GetComponent<SpriteRenderer>();
        Color originalColor = baseRenderer.color;
        
        for (int i = 0; i < 3; i++)
        {
            baseRenderer.color = Color.red;
            yield return new WaitForSeconds(0.2f);
            baseRenderer.color = originalColor;
            yield return new WaitForSeconds(0.2f);
        }
    }
    
    // 激活基地防护罩(由道具触发)
    public void ActivateShield(float duration)
    {
        if (fortificationCoroutine != null)
        {
            StopCoroutine(fortificationCoroutine);
        }
        
        fortificationCoroutine = StartCoroutine(ShieldRoutine(duration));
    }
    
    // 基地防护罩协程
    private IEnumerator ShieldRoutine(float duration)
    {
        isShieldActive = true;
        
        // 创建防护罩视觉效果
        if (shieldEffectPrefab != null)
        {
            activeShieldEffect = Instantiate(
                shieldEffectPrefab,
                playerBase.transform.position,
                Quaternion.identity,
                transform
            );
        }
        
        // 将基地周围的墙升级为钢墙
        UpgradeBaseWalls(true);
        
        // 等待持续时间
        yield return new WaitForSeconds(duration);
        
        // 恢复墙的原始状态
        UpgradeBaseWalls(false);
        
        // 销毁防护罩效果
        if (activeShieldEffect != null)
        {
            Destroy(activeShieldEffect);
            activeShieldEffect = null;
        }
        
        isShieldActive = false;
    }
    
    // 升级/降级基地周围的墙
    private void UpgradeBaseWalls(bool upgrade)
    {
        foreach (GameObject wall in baseWalls)
        {
            if (wall == null)
                continue;
                
            SpriteRenderer wallRenderer = wall.GetComponent<SpriteRenderer>();
            if (wallRenderer != null)
            {
                wallRenderer.color = upgrade ? Color.gray : Color.white;
            }
            
            // 修改碰撞层
            wall.layer = LayerMask.NameToLayer(upgrade ? "SteelWall" : "BrickWall");
            
            // 修改墙的属性
            BrickWall brickWall = wall.GetComponent<BrickWall>();
            if (brickWall != null)
            {
                if (upgrade)
                {
                    // 临时保存原始状态并修改为钢墙
                    brickWall.enabled = false;
                    wall.AddComponent<SteelWall>();
                }
                else
                {
                    // 移除钢墙组件并恢复砖墙
                    SteelWall steelWall = wall.GetComponent<SteelWall>();
                    if (steelWall != null)
                    {
                        Destroy(steelWall);
                    }
                    brickWall.enabled = true;
                }
            }
        }
    }
}

19.4.6 敌人AI行为树实现

为了创建更智能的敌人AI,我们可以使用行为树模式来组织和管理敌人的决策逻辑:

csharp

// 行为节点基类
public abstract class BehaviorNode
{
    public enum Status
    {
        Success,
        Failure,
        Running
    }
    
    public abstract Status Execute(EnemyTank context);
}

// 选择节点 - 按顺序执行子节点,直到一个成功或所有失败
public class SelectorNode : BehaviorNode
{
    private List<BehaviorNode> children = new List<BehaviorNode>();
    
    public SelectorNode(params BehaviorNode[] nodes)
    {
        children.AddRange(nodes);
    }
    
    public override Status Execute(EnemyTank context)
    {
        foreach (var child in children)
        {
            Status status = child.Execute(context);
            
            if (status != Status.Failure)
                return status;
        }
        
        return Status.Failure;
    }
}

// 序列节点 - 按顺序执行所有子节点,直到一个失败或所有成功
public class SequenceNode : BehaviorNode
{
    private List<BehaviorNode> children = new List<BehaviorNode>();
    
    public SequenceNode(params BehaviorNode[] nodes)
    {
        children.AddRange(nodes);
    }
    
    public override Status Execute(EnemyTank context)
    {
        foreach (var child in children)
        {
            Status status = child.Execute(context);
            
            if (status != Status.Success)
                return status;
        }
        
        return Status.Success;
    }
}

// 条件节点 - 检查条件是否满足
public class ConditionNode : BehaviorNode
{
    private System.Func<EnemyTank, bool> condition;
    
    public ConditionNode(System.Func<EnemyTank, bool> condition)
    {
        this.condition = condition;
    }
    
    public override Status Execute(EnemyTank context)
    {
        return condition(context) ? Status.Success : Status.Failure;
    }
}

// 动作节点 - 执行具体的行为
public class ActionNode : BehaviorNode
{
    private System.Func<EnemyTank, Status> action;
    
    public ActionNode(System.Func<EnemyTank, Status> action)
    {
        this.action = action;
    }
    
    public override Status Execute(EnemyTank context)
    {
        return action(context);
    }
}

// 敌人AI控制器
public class EnemyAIController : MonoBehaviour
{
    private EnemyTank enemyTank;
    private BehaviorNode behaviorTree;
    
    private void Awake()
    {
        enemyTank = GetComponent<EnemyTank>();
        
        // 构建行为树
        behaviorTree = new SelectorNode(
            // 如果血量低,尝试逃跑
            new SequenceNode(
                new ConditionNode(HasLowHealth),
                new ActionNode(Flee)
            ),
            
            // 如果可以看到基地且足够近,攻击基地
            new SequenceNode(
                new ConditionNode(CanSeeBase),
                new ConditionNode(IsCloseToBase),
                new ActionNode(AttackBase)
            ),
            
            // 如果可以看到玩家,追击并攻击
            new SequenceNode(
                new ConditionNode(CanSeePlayer),
                new SelectorNode(
                    // 如果足够近,直接攻击
                    new SequenceNode(
                        new ConditionNode(IsCloseToPlayer),
                        new ActionNode(AttackPlayer)
                    ),
                    // 否则追击
                    new ActionNode(ChasePlayer)
                )
            ),
            
            // 默认行为:巡逻
            new ActionNode(Patrol)
        );
    }
    
    private void Update()
    {
        if (GameManager.Instance.CurrentState != GameState.Playing)
            return;
            
        // 执行行为树
        behaviorTree.Execute(enemyTank);
    }
    
    // 条件函数
    private bool HasLowHealth(EnemyTank tank)
    {
        return tank.CurrentHealth <= tank.MaxHealth / 3;
    }
    
    private bool CanSeeBase(EnemyTank tank)
    {
        return tank.CanSeeTarget(tank.BaseTransform);
    }
    
    private bool IsCloseToBase(EnemyTank tank)
    {
        if (tank.BaseTransform == null)
            return false;
            
        float distance = Vector2.Distance(tank.transform.position, tank.BaseTransform.position);
        return distance < 5.0f;
    }
    
    private bool CanSeePlayer(EnemyTank tank)
    {
        return tank.CanSeeTarget(tank.PlayerTransform);
    }
    
    private bool IsCloseToPlayer(EnemyTank tank)
    {
        if (tank.PlayerTransform == null)
            return false;
            
        float distance = Vector2.Distance(tank.transform.position, tank.PlayerTransform.position);
        return distance < 3.0f;
    }
    
    // 动作函数
    private BehaviorNode.Status Flee(EnemyTank tank)
    {
        // 实现逃跑逻辑
        if (tank.PlayerTransform == null)
            return BehaviorNode.Status.Failure;
            
        Vector2 fleeDirection = (tank.transform.position - tank.PlayerTransform.position).normalized;
        tank.SetMoveDirection(fleeDirection);
        
        return BehaviorNode.Status.Running;
    }
    
    private BehaviorNode.Status AttackBase(EnemyTank tank)
    {
        // 实现攻击基地逻辑
        if (tank.BaseTransform == null)
            return BehaviorNode.Status.Failure;
            
        Vector2 directionToBase = (tank.BaseTransform.position - tank.transform.position).normalized;
        tank.SetMoveDirection(directionToBase);
        
        // 尝试射击
        tank.TryFire();
        
        return BehaviorNode.Status.Running;
    }
    
    private BehaviorNode.Status AttackPlayer(EnemyTank tank)
    {
        // 实现攻击玩家逻辑
        if (tank.PlayerTransform == null)
            return BehaviorNode.Status.Failure;
            
        // 面向玩家但不移动
        Vector2 directionToPlayer = (tank.PlayerTransform.position - tank.transform.position).normalized;
        tank.SetMoveDirection(directionToPlayer);
        tank.StopMoving();
        
        // 尝试射击
        tank.TryFire();
        
        return BehaviorNode.Status.Running;
    }
    
    private BehaviorNode.Status ChasePlayer(EnemyTank tank)
    {
        // 实现追击玩家逻辑
        if (tank.PlayerTransform == null)
            return BehaviorNode.Status.Failure;
            
        Vector2 directionToPlayer = (tank.PlayerTransform.position - tank.transform.position).normalized;
        tank.SetMoveDirection(directionToPlayer);
        
        return BehaviorNode.Status.Running;
    }
    
    private BehaviorNode.Status Patrol(EnemyTank tank)
    {
        // 实现巡逻逻辑
        tank.UpdatePatrolBehavior();
        
        return BehaviorNode.Status.Running;
    }
}

通过行为树模式,我们可以创建更复杂和智能的敌人AI,使敌人能够根据不同的情况做出合适的决策。

19.4.7 敌人生成器实现

敌人生成器负责在游戏中按照一定规则和节奏生成敌人,以控制游戏的难度和节奏。

csharp

public class EnemySpawner : MonoBehaviour
{
    [System.Serializable]
    public class EnemyWave
    {
        public int basicEnemyCount;
        public int fastEnemyCount;
        public int heavyEnemyCount;
        public int powerEnemyCount;
        public float timeBetweenSpawns = 3.0f;
    }
    
    [SerializeField] private GameObject basicEnemyPrefab;
    [SerializeField] private GameObject fastEnemyPrefab;
    [SerializeField] private GameObject heavyEnemyPrefab;
    [SerializeField] private GameObject powerEnemyPrefab;
    
    [SerializeField] private Transform[] spawnPoints;
    [SerializeField] private EnemyWave[] waves;
    
    [SerializeField] private int maxConcurrentEnemies = 4;
    [SerializeField] private float initialDelay = 2.0f;
    
    // 状态
    private int currentWave = 0;
    private int activeEnemies = 0;
    private int totalEnemiesSpawned = 0;
    private int totalEnemiesInLevel;
    private Coroutine spawnCoroutine;
    
    // 事件
    public event Action<int> OnEnemySpawned;
    public event Action OnAllEnemiesSpawned;
    
    private void Start()
    {
        // 计算关卡中的总敌人数量
        CalculateTotalEnemies();
    }
    
    private void CalculateTotalEnemies()
    {
        totalEnemiesInLevel = 0;
        
        foreach (EnemyWave wave in waves)
        {
            totalEnemiesInLevel += wave.basicEnemyCount +
                                 wave.fastEnemyCount +
                                 wave.heavyEnemyCount +
                                 wave.powerEnemyCount;
        }
    }
    
    public void StartSpawning()
    {
        // 重置状态
        currentWave = 0;
        activeEnemies = 0;
        totalEnemiesSpawned = 0;
        
        // 开始生成敌人
        if (spawnCoroutine != null)
        {
            StopCoroutine(spawnCoroutine);
        }
        
        spawnCoroutine = StartCoroutine(SpawnWaves());
    }
    
    public void StopSpawning()
    {
        if (spawnCoroutine != null)
        {
            StopCoroutine(spawnCoroutine);
            spawnCoroutine = null;
        }
    }
    
    private IEnumerator SpawnWaves()
    {
        // 初始延迟
        yield return new WaitForSeconds(initialDelay);
        
        while (currentWave < waves.Length)
        {
            // 获取当前波次
            EnemyWave wave = waves[currentWave];
            
            // 生成这一波中的所有敌人
            int enemiesInWave = wave.basicEnemyCount +
                              wave.fastEnemyCount +
                              wave.heavyEnemyCount +
                              wave.powerEnemyCount;
            
            int enemiesSpawnedInWave = 0;
            
            // 生成基础敌人
            for (int i = 0; i < wave.basicEnemyCount; i++)
            {
                yield return StartCoroutine(WaitForSpawnSlot());
                SpawnEnemy(basicEnemyPrefab);
                enemiesSpawnedInWave++;
                yield return new WaitForSeconds(wave.timeBetweenSpawns);
            }
            
            // 生成快速敌人
            for (int i = 0; i < wave.fastEnemyCount; i++)
            {
                yield return StartCoroutine(WaitForSpawnSlot());
                SpawnEnemy(fastEnemyPrefab);
                enemiesSpawnedInWave++;
                yield return new WaitForSeconds(wave.timeBetweenSpawns);
            }
            
            // 生成重型敌人
            for (int i = 0; i < wave.heavyEnemyCount; i++)
            {
                yield return StartCoroutine(WaitForSpawnSlot());
                SpawnEnemy(heavyEnemyPrefab);
                enemiesSpawnedInWave++;
                yield return new WaitForSeconds(wave.timeBetweenSpawns);
            }
            
            // 生成火力敌人
            for (int i = 0; i < wave.powerEnemyCount; i++)
            {
                yield return StartCoroutine(WaitForSpawnSlot());
                SpawnEnemy(powerEnemyPrefab);
                enemiesSpawnedInWave++;
                yield return new WaitForSeconds(wave.timeBetweenSpawns);
            }
            
            // 等待这一波的所有敌人被消灭
            while (activeEnemies > 0)
            {
                yield return new WaitForSeconds(0.5f);
            }
            
            // 进入下一波
            currentWave++;
            
            // 波次之间的延迟
            if (currentWave < waves.Length)
            {
                yield return new WaitForSeconds(5.0f);
            }
        }
        
        // 所有波次都已生成
        OnAllEnemiesSpawned?.Invoke();
    }
    
    private IEnumerator WaitForSpawnSlot()
    {
        // 等待,直到活动敌人数量低于上限
        while (activeEnemies >= maxConcurrentEnemies)
        {
            yield return new WaitForSeconds(0.5f);
        }
    }
    
    private void SpawnEnemy(GameObject enemyPrefab)
    {
        // 选择一个随机生成点
        Transform spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];
        
        // 生成敌人
        GameObject enemy = Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity);
        
        // 配置敌人
        EnemyTank enemyTank = enemy.GetComponent<EnemyTank>();
        if (enemyTank != null)
        {
            enemyTank.OnDestroyed += OnEnemyDestroyed;
        }
        
        // 更新计数
        activeEnemies++;
        totalEnemiesSpawned++;
        
        // 触发事件
        OnEnemySpawned?.Invoke(totalEnemiesInLevel - totalEnemiesSpawned);
    }
    
    private void OnEnemyDestroyed()
    {
        activeEnemies--;
    }
}

通过敌人生成器,我们可以按照设计的波次和节奏生成不同类型的敌人,增加游戏的挑战性和变化性。

19.4.8 道具系统完整实现

道具系统是坦克大战中增加变化和策略性的重要元素,下面是一个更完整的道具系统实现:

csharp

// 道具类型枚举
public enum PowerUpType
{
    Star,       // 提升子弹威力
    Tank,       // 增加生命
    Clock,      // 冻结敌人
    Shovel,     // 加固基地
    Bomb,       // 摧毁所有敌人
    Shield      // 临时护盾
}

// 道具基类
public abstract class PowerUp : MonoBehaviour
{
    [SerializeField] protected PowerUpType type;
    [SerializeField] protected float lifetime = 10.0f;
    [SerializeField] protected GameObject collectEffectPrefab;
    [SerializeField] protected GameObject powerUpVisual;
    
    private float blinkStartTime;
    private bool isBlinking = false;
    
    protected virtual void Start()
    {
        // 设置闪烁开始时间(生命周期的后1/3)
        blinkStartTime = Time.time + (lifetime * 2 / 3);
        
        // 设置自动销毁
        Destroy(gameObject, lifetime);
    }
    
    protected virtual void Update()
    {
        // 道具即将消失时闪烁
        if (Time.time >= blinkStartTime && !isBlinking)
        {
            isBlinking = true;
            StartCoroutine(BlinkEffect());
        }
    }
    
    private IEnumerator BlinkEffect()
    {
        SpriteRenderer renderer = powerUpVisual.GetComponent<SpriteRenderer>();
        
        // 闪烁直到销毁
        while (true)
        {
            renderer.enabled = !renderer.enabled;
            yield return new WaitForSeconds(0.15f);
        }
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            PlayerTank playerTank = collision.GetComponent<PlayerTank>();
            if (playerTank != null)
            {
                // 应用道具效果
                ApplyEffect(playerTank);
                
                // 播放收集特效
                if (collectEffectPrefab != null)
                {
                    Instantiate(collectEffectPrefab, transform.position, Quaternion.identity);
                }
                
                // 播放音效
                AudioManager.Instance.PlaySound("PowerUp");
                
                // 通知游戏管理器
                GameManager.Instance.PowerUpCollected(type);
                
                // 销毁道具
                Destroy(gameObject);
            }
        }
    }
    
    // 道具效果的抽象方法,由子类实现
    protected abstract void ApplyEffect(PlayerTank playerTank);
}

// 星星道具实现
public class StarPowerUp : PowerUp
{
    protected override void Start()
    {
        base.Start();
        type = PowerUpType.Star;
    }
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        playerTank.PowerUpBullet();
    }
}

// 坦克道具实现
public class TankPowerUp : PowerUp
{
    protected override void Start()
    {
        base.Start();
        type = PowerUpType.Tank;
    }
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        playerTank.AddLife();
    }
}

// 计时器道具实现
public class ClockPowerUp : PowerUp
{
    [SerializeField] private float freezeDuration = 10.0f;
    
    protected override void Start()
    {
        base.Start();
        type = PowerUpType.Clock;
    }
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        GameManager.Instance.FreezeEnemies(freezeDuration);
    }
}

// 铲子道具实现
public class ShovelPowerUp : PowerUp
{
    [SerializeField] private float fortifyDuration = 15.0f;
    
    protected override void Start()
    {
        base.Start();
        type = PowerUpType.Shovel;
    }
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        GameManager.Instance.FortifyBase(fortifyDuration);
    }
}

// 炸弹道具实现
public class BombPowerUp : PowerUp
{
    protected override void Start()
    {
        base.Start();
        type = PowerUpType.Bomb;
    }
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        GameManager.Instance.DestroyAllEnemies();
    }
}

// 护盾道具实现
public class ShieldPowerUp : PowerUp
{
    [SerializeField] private float shieldDuration = 10.0f;
    
    protected override void Start()
    {
        base.Start();
        type = PowerUpType.Shield;
    }
    
    protected override void ApplyEffect(PlayerTank playerTank)
    {
        playerTank.ActivateShield(shieldDuration);
    }
}

// 道具生成器
public class PowerUpManager : MonoBehaviour
{
    [System.Serializable]
    public class PowerUpData
    {
        public PowerUpType type;
        public GameObject prefab;
        public float spawnWeight = 1.0f;
    }
    
    [SerializeField] private List<PowerUpData> powerUpData = new List<PowerUpData>();
    [SerializeField] private float spawnChance = 0.3f;
    [SerializeField] private float minSpawnInterval = 30.0f;
    
    private float lastSpawnTime = -999f;
    
    // 当敌人被摧毁时尝试生成道具
    public void TrySpawnPowerUp(Vector3 position)
    {
        // 检查最小生成间隔
        if (Time.time - lastSpawnTime < minSpawnInterval)
        {
            return;
        }
        
        // 根据概率决定是否生成道具
        if (Random.value <= spawnChance)
        {
            SpawnRandomPowerUp(position);
            lastSpawnTime = Time.time;
        }
    }
    
    // 生成随机道具
    private void SpawnRandomPowerUp(Vector3 position)
    {
        if (powerUpData.Count == 0)
            return;
            
        // 计算总权重
        float totalWeight = 0;
        foreach (var data in powerUpData)
        {
            totalWeight += data.spawnWeight;
        }
        
        // 随机选择一个道具
        float randomValue = Random.value * totalWeight;
        float currentWeight = 0;
        
        foreach (var data in powerUpData)
        {
            currentWeight += data.spawnWeight;
            if (randomValue <= currentWeight)
            {
                // 生成选中的道具
                Instantiate(data.prefab, position, Quaternion.identity);
                break;
            }
        }
    }
    
    // 强制生成特定类型的道具(用于关卡设计或特定事件)
    public void SpawnSpecificPowerUp(PowerUpType type, Vector3 position)
    {
        foreach (var data in powerUpData)
        {
            if (data.type == type)
            {
                Instantiate(data.prefab, position, Quaternion.identity);
                return;
            }
        }
    }
}

通过这个完整的道具系统,我们可以为游戏增加更多的变化和策略元素,提升游戏的可玩性和乐趣。

19.5 扩展功能与优化

19.5.1 双人模式实现

坦克大战的一个经典功能是支持两个玩家同时游戏。下面是实现双人模式的关键代码:

csharp

// 玩家控制器基类
public abstract class PlayerController : MonoBehaviour
{
    [SerializeField] protected float moveSpeed = 4.0f;
    [SerializeField] protected float fireRate = 0.5f;
    [SerializeField] protected int maxBullets = 1;
    [SerializeField] protected int bulletPower = 1;
    [SerializeField] protected GameObject shieldEffectPrefab;
    
    // 引用
    protected Rigidbody2D rb;
    protected SpriteRenderer spriteRenderer;
    protected GameObject activeShieldEffect;
    
    // 状态
    protected int activeBulletCount = 0;
    protected float lastFireTime = -999f;
    protected bool isInvulnerable = false;
    
    protected virtual void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
    }
    
    protected virtual void Update()
    {
        // 处理输入
        HandleInput();
    }
    
    protected virtual void FixedUpdate()
    {
        // 移动坦克
        Move();
    }
    
    // 子类必须实现的输入处理方法
    protected abstract void HandleInput();
    
    // 移动逻辑
    protected virtual void Move()
    {
        // 共享的移动逻辑...
    }
    
    // 射击方法
    protected virtual void Fire()
    {
        // 共享的射击逻辑...
    }
    
    // 子弹销毁回调
    public void OnBulletDestroyed()
    {
        activeBulletCount = Mathf.Max(0, activeBulletCount - 1);
    }
    
    // 受伤方法
    public virtual void TakeDamage()
    {
        // 共享的受伤逻辑...
    }
    
    // 激活护盾
    public void ActivateShield(float duration)
    {
        StartCoroutine(ShieldRoutine(duration));
    }
    
    private IEnumerator ShieldRoutine(float duration)
    {
        // 护盾逻辑...
        yield return null;
    }
    
    // 增强子弹
    public void PowerUpBullet()
    {
        bulletPower = Mathf.Min(bulletPower + 1, 3);
    }
    
    // 增加最大子弹数
    public void IncreaseMaxBullets()
    {
        maxBullets = Mathf.Min(maxBullets + 1, 2);
    }
}

// 玩家1控制器
public class Player1Controller : PlayerController
{
    protected override void HandleInput()
    {
        // 使用方向键和空格键
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        
        // 处理移动输入...
        
        // 处理射击输入
        if (Input.GetKeyDown(KeyCode.Space) && Time.time >= lastFireTime + fireRate && activeBulletCount < maxBullets)
        {
            Fire();
        }
    }
}

// 玩家2控制器
public class Player2Controller : PlayerController
{
    protected override void HandleInput()
    {
        // 使用WASD和左Ctrl键
        float horizontal = 0;
        float vertical = 0;
        
        if (Input.GetKey(KeyCode.A)) horizontal = -1;
        if (Input.GetKey(KeyCode.D)) horizontal = 1;
        if (Input.GetKey(KeyCode.W)) vertical = 1;
        if (Input.GetKey(KeyCode.S)) vertical = -1;
        
        // 处理移动输入...
        
        // 处理射击输入
        if (Input.GetKeyDown(KeyCode.LeftControl) && Time.time >= lastFireTime + fireRate && activeBulletCount < maxBullets)
        {
            Fire();
        }
    }
}

// 多人游戏管理器
public class MultiplayerManager : MonoBehaviour
{
    [SerializeField] private GameObject player1Prefab;
    [SerializeField] private GameObject player2Prefab;
    [SerializeField] private Transform player1SpawnPoint;
    [SerializeField] private Transform player2SpawnPoint;
    
    private PlayerController player1;
    private PlayerController player2;
    private bool isMultiplayerMode = false;
    
    // 公共属性
    public bool IsMultiplayerMode => isMultiplayerMode;
    
    public void EnableMultiplayerMode(bool enable)
    {
        isMultiplayerMode = enable;
    }
    
    public void SpawnPlayers()
    {
        // 生成玩家1
        GameObject p1Obj = Instantiate(player1Prefab, player1SpawnPoint.position, Quaternion.identity);
        player1 = p1Obj.GetComponent<PlayerController>();
        
        // 如果是多人模式,生成玩家2
        if (isMultiplayerMode)
        {
            GameObject p2Obj = Instantiate(player2Prefab, player2SpawnPoint.position, Quaternion.identity);
            player2 = p2Obj.GetComponent<PlayerController>();
        }
    }
    
    public bool AreAllPlayersDead()
    {
        if (!isMultiplayerMode)
        {
            return player1 == null || !player1.gameObject.activeSelf;
        }
        else
        {
            bool p1Dead = player1 == null || !player1.gameObject.activeSelf;
            bool p2Dead = player2 == null || !player2.gameObject.activeSelf;
            return p1Dead && p2Dead;
        }
    }
    
    public void RespawnPlayers()
    {
        // 重生玩家的逻辑...
    }
}

19.5.2 性能优化:对象池实现

对象池是一种常用的优化技术,可以减少频繁创建和销毁对象(如子弹、爆炸效果)带来的性能开销:

csharp

public class ObjectPool : MonoBehaviour
{
    // 单例
    public static ObjectPool Instance { get; private set; }
    
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }
    
    [SerializeField] private List<Pool> pools;
    
    private Dictionary<string, Queue<GameObject>> poolDictionary;
    
    private void Awake()
    {
        // 单例初始化
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        
        // 初始化对象池
        poolDictionary = new Dictionary<string, Queue<GameObject>>();
        
        foreach (Pool pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();
            
            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
                obj.transform.parent = transform;
            }
            
            poolDictionary.Add(pool.tag, objectPool);
        }
    }
    
    public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            Debug.LogWarning($"Pool with tag {tag} doesn't exist!");
            return null;
        }
        
        // 获取一个对象
        Queue<GameObject> objectPool = poolDictionary[tag];
        
        // 如果队列为空,创建一个新对象
        if (objectPool.Count == 0)
        {
            foreach (Pool pool in pools)
            {
                if (pool.tag == tag)
                {
                    GameObject newObj = Instantiate(pool.prefab, position, rotation);
                    return newObj;
                }
            }
        }
        
        // 获取池中的对象
        GameObject obj = objectPool.Dequeue();
        
        // 重新激活对象
        obj.SetActive(true);
        obj.transform.position = position;
        obj.transform.rotation = rotation;
        
        // 如果对象实现了IPoolable接口,调用OnSpawn方法
        IPoolable poolable = obj.GetComponent<IPoolable>();
        if (poolable != null)
        {
            poolable.OnSpawn();
        }
        
        // 将对象重新加入队列末尾(循环使用)
        objectPool.Enqueue(obj);
        
        return obj;
    }
    
    // 直接返回对象到池(可选)
    public void ReturnToPool(string tag, GameObject obj)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            Debug.LogWarning($"Pool with tag {tag} doesn't exist!");
            return;
        }
        
        // 如果对象实现了IPoolable接口,调用OnDespawn方法
        IPoolable poolable = obj.GetComponent<IPoolable>();
        if (poolable != null)
        {
            poolable.OnDespawn();
        }
        
        obj.SetActive(false);
    }
}

// 可池化接口
public interface IPoolable
{
    void OnSpawn();
    void OnDespawn();
}

// 子弹示例实现
public class PooledBullet : Bullet, IPoolable
{
    private string poolTag = "Bullet";
    
    public void OnSpawn()
    {
        // 重置子弹状态
        // ...
    }
    
    public void OnDespawn()
    {
        // 清理子弹状态
        // ...
    }
    
    protected override void DestroyBullet()
    {
        // 触发销毁事件
        OnDestroyed?.Invoke();
        
        // 返回到对象池而不是销毁
        ObjectPool.Instance.ReturnToPool(poolTag, gameObject);
    }
}

19.5.3 视觉效果增强

为了提升游戏的视觉效果,我们可以添加各种粒子特效和后处理效果:

csharp

public class EffectsManager : MonoBehaviour
{
    // 单例
    public static EffectsManager Instance { get; private set; }
    
    [System.Serializable]
    public class EffectData
    {
        public string tag;
        public GameObject effectPrefab;
        public float duration;
    }
    
    [SerializeField] private List<EffectData> effects;
    
    // 屏幕震动
    [SerializeField] private float shakeAmount = 0.1f;
    [SerializeField] private float shakeDecreaseFactor = 1.0f;
    
    private Dictionary<string, EffectData> effectsDictionary;
    private Camera mainCamera;
    private Vector3 originalCameraPosition;
    private float shakeIntensity = 0;
    
    private void Awake()
    {
        // 单例初始化
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        
        // 初始化字典
        effectsDictionary = new Dictionary<string, EffectData>();
        foreach (EffectData effect in effects)
        {
            effectsDictionary.Add(effect.tag, effect);
        }
        
        // 获取相机引用
        mainCamera = Camera.main;
        originalCameraPosition = mainCamera.transform.position;
    }
    
    private void Update()
    {
        // 处理相机震动
        if (shakeIntensity > 0)
        {
            mainCamera.transform.position = originalCameraPosition + Random.insideUnitSphere * shakeIntensity;
            shakeIntensity -= shakeDecreaseFactor * Time.deltaTime;
            
            if (shakeIntensity <= 0)
            {
                shakeIntensity = 0;
                mainCamera.transform.position = originalCameraPosition;
            }
        }
    }
    
    public GameObject PlayEffect(string effectTag, Vector3 position, Quaternion rotation)
    {
        if (!effectsDictionary.ContainsKey(effectTag))
        {
            Debug.LogWarning($"Effect with tag {effectTag} doesn't exist!");
            return null;
        }
        
        EffectData data = effectsDictionary[effectTag];
        
        // 创建特效
        GameObject effect = ObjectPool.Instance.SpawnFromPool(effectTag, position, rotation);
        if (effect == null)
        {
            effect = Instantiate(data.effectPrefab, position, rotation);
        }
        
        // 如果有持续时间,设置自动销毁
        if (data.duration > 0)
        {
            StartCoroutine(DestroyEffectAfterDelay(effect, data.duration, effectTag));
        }
        
        return effect;
    }
    
    private IEnumerator DestroyEffectAfterDelay(GameObject effect, float delay, string poolTag)
    {
        yield return new WaitForSeconds(delay);
        
        // 返回到对象池
        ObjectPool.Instance.ReturnToPool(poolTag, effect);
    }
    
    // 播放坦克爆炸效果
    public void PlayTankExplosion(Vector3 position)
    {
        PlayEffect("TankExplosion", position, Quaternion.identity);
        
        // 触发相机震动
        ShakeCamera(0.2f);
    }
    
    // 播放基地爆炸效果
    public void PlayBaseExplosion(Vector3 position)
    {
        PlayEffect("BaseExplosion", position, Quaternion.identity);
        
        // 触发更强的相机震动
        ShakeCamera(0.5f);
    }
    
    // 播放子弹击中效果
    public void PlayBulletHit(Vector3 position)
    {
        PlayEffect("BulletHit", position, Quaternion.identity);
    }
    
    // 播放墙体破坏效果
    public void PlayWallDestruction(Vector3 position, bool isSteel)
    {
        string effectTag = isSteel ? "SteelDestruction" : "BrickDestruction";
        PlayEffect(effectTag, position, Quaternion.identity);
    }
    
    // 触发相机震动
    public void ShakeCamera(float intensity)
    {
        shakeIntensity = intensity;
        originalCameraPosition = mainCamera.transform.position;
    }
}

19.5.4 关卡编辑器实现

为了方便设计关卡,我们可以实现一个简单的关卡编辑器:

csharp

#if UNITY_EDITOR
using UnityEditor;

public class LevelEditorWindow : EditorWindow
{
    private enum BrushType
    {
        Empty,
        BrickWall,
        SteelWall,
        Water,
        Forest,
        Ice
    }
    
    [System.Serializable]
    public class LevelData
    {
        public int width = 13;
        public int height = 13;
        public string mapData;
        public int basicEnemyCount = 10;
        public int fastEnemyCount = 4;
        public int heavyEnemyCount = 3;
        public int powerEnemyCount = 2;
    }
    
    private BrushType selectedBrush = BrushType.BrickWall;
    private LevelData currentLevel = new LevelData();
    private char[,] map;
    private Vector2 scrollPosition;
    private int cellSize = 20;
    
    private Texture2D brickTexture;
    private Texture2D steelTexture;
    private Texture2D waterTexture;
    private Texture2D forestTexture;
    private Texture2D iceTexture;
    
    [MenuItem("Tools/Tank Battle/Level Editor")]
    public static void ShowWindow()
    {
        GetWindow<LevelEditorWindow>("Level Editor");
    }
    
    private void OnEnable()
    {
        // 加载纹理
        brickTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/BrickWall.png");
        steelTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/SteelWall.png");
        waterTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/Water.png");
        forestTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/Forest.png");
        iceTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/Ice.png");
        
        // 初始化地图
        InitializeMap();
    }
    
    private void InitializeMap()
    {
        map = new char[currentLevel.width, currentLevel.height];
        
        // 填充空地
        for (int x = 0; x < currentLevel.width; x++)
        {
            for (int y = 0; y < currentLevel.height; y++)
            {
                map[x, y] = '.';
            }
        }
        
        // 如果有现有数据,解析它
        if (!string.IsNullOrEmpty(currentLevel.mapData))
        {
            for (int i = 0; i < currentLevel.mapData.Length && i < currentLevel.width * currentLevel.height; i++)
            {
                int x = i % currentLevel.width;
                int y = i / currentLevel.width;
                
                map[x, y] = currentLevel.mapData[i];
            }
        }
    }
    
    private void OnGUI()
    {
        DrawToolbar();
        DrawMapSettings();
        DrawEnemySettings();
        EditorGUILayout.Space(10);
        DrawMapEditor();
        DrawSaveButtons();
    }
    
    private void DrawToolbar()
    {
        EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
        
        // 刷子选择
        selectedBrush = (BrushType)EditorGUILayout.EnumPopup("Brush: ", selectedBrush);
        
        EditorGUILayout.EndHorizontal();
    }
    
    private void DrawMapSettings()
    {
        EditorGUILayout.BeginVertical(EditorStyles.helpBox);
        EditorGUILayout.LabelField("Map Settings", EditorStyles.boldLabel);
        
        // 地图尺寸设置
        EditorGUI.BeginChangeCheck();
        currentLevel.width = EditorGUILayout.IntSlider("Width: ", currentLevel.width, 10, 20);
        currentLevel.height = EditorGUILayout.IntSlider("Height: ", currentLevel.height, 10, 20);
        
        if (EditorGUI.EndChangeCheck())
        {
            // 地图尺寸改变,重新初始化
            InitializeMap();
        }
        
        EditorGUILayout.EndVertical();
    }
    
    private void DrawEnemySettings()
    {
        EditorGUILayout.BeginVertical(EditorStyles.helpBox);
        EditorGUILayout.LabelField("Enemy Settings", EditorStyles.boldLabel);
        
        // 敌人数量设置
        currentLevel.basicEnemyCount = EditorGUILayout.IntSlider("Basic Enemies: ", currentLevel.basicEnemyCount, 0, 20);
        currentLevel.fastEnemyCount = EditorGUILayout.IntSlider("Fast Enemies: ", currentLevel.fastEnemyCount, 0, 10);
        currentLevel.heavyEnemyCount = EditorGUILayout.IntSlider("Heavy Enemies: ", currentLevel.heavyEnemyCount, 0, 10);
        currentLevel.powerEnemyCount = EditorGUILayout.IntSlider("Power Enemies: ", currentLevel.powerEnemyCount, 0, 10);
        
        EditorGUILayout.EndVertical();
    }
    
    private void DrawMapEditor()
    {
        EditorGUILayout.BeginVertical(EditorStyles.helpBox);
        EditorGUILayout.LabelField("Map Editor", EditorStyles.boldLabel);
        
        // 计算地图编辑器的大小
        float mapWidth = currentLevel.width * cellSize;
        float mapHeight = currentLevel.height * cellSize;
        
        // 滚动视图
        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
        Rect mapRect = GUILayoutUtility.GetRect(mapWidth, mapHeight);
        
        // 绘制网格背景
        DrawGrid(mapRect);
        
        // 绘制地图内容
        for (int x = 0; x < currentLevel.width; x++)
        {
            for (int y = 0; y < currentLevel.height; y++)
            {
                Rect cellRect = new Rect(
                    mapRect.x + x * cellSize,
                    mapRect.y + y * cellSize,
                    cellSize,
                    cellSize
                );
                
                char cellType = map[x, y];
                DrawCell(cellRect, cellType);
                
                // 处理鼠标点击
                HandleCellInteraction(cellRect, x, y);
            }
        }
        
        EditorGUILayout.EndScrollView();
        EditorGUILayout.EndVertical();
    }
    
    private void DrawGrid(Rect mapRect)
    {
        // 绘制背景
        EditorGUI.DrawRect(mapRect, new Color(0.2f, 0.2f, 0.2f));
        
        // 绘制网格线
        Handles.color = Color.gray;
        
        for (int x = 0; x <= currentLevel.width; x++)
        {
            float xPos = mapRect.x + x * cellSize;
            Handles.DrawLine(
                new Vector3(xPos, mapRect.y, 0),
                new Vector3(xPos, mapRect.y + mapRect.height, 0)
            );
        }
        
        for (int y = 0; y <= currentLevel.height; y++)
        {
            float yPos = mapRect.y + y * cellSize;
            Handles.DrawLine(
                new Vector3(mapRect.x, yPos, 0),
                new Vector3(mapRect.x + mapRect.width, yPos, 0)
            );
        }
    }
    
    private void DrawCell(Rect cellRect, char cellType)
    {
        // 根据单元格类型绘制内容
        Texture2D texture = null;
        
        switch (cellType)
        {
            case 'B':
                texture = brickTexture;
                break;
            case 'S':
                texture = steelTexture;
                break;
            case 'W':
                texture = waterTexture;
                break;
            case 'F':
                texture = forestTexture;
                break;
            case 'I':
                texture = iceTexture;
                break;
        }
        
        if (texture != null)
        {
            GUI.DrawTexture(cellRect, texture);
        }
    }
    
    private void HandleCellInteraction(Rect cellRect, int x, int y)
    {
        Event currentEvent = Event.current;
        
        if (currentEvent.type == EventType.MouseDown && cellRect.Contains(currentEvent.mousePosition))
        {
            // 左键绘制
            if (currentEvent.button == 0)
            {
                // 设置单元格类型
                map[x, y] = GetBrushChar();
                currentEvent.Use();
                Repaint();
            }
            // 右键擦除
            else if (currentEvent.button == 1)
            {
                map[x, y] = '.';
                currentEvent.Use();
                Repaint();
            }
        }
    }
    
    private char GetBrushChar()
    {
        switch (selectedBrush)
        {
            case BrushType.BrickWall: return 'B';
            case BrushType.SteelWall: return 'S';
            case BrushType.Water: return 'W';
            case BrushType.Forest: return 'F';
            case BrushType.Ice: return 'I';
            default: return '.';
        }
    }
    
    private void DrawSaveButtons()
    {
        EditorGUILayout.BeginHorizontal();
        
        if (GUILayout.Button("New Level"))
        {
            if (EditorUtility.DisplayDialog("New Level", "Create a new level? Any unsaved changes will be lost.", "Yes", "Cancel"))
            {
                currentLevel = new LevelData();
                InitializeMap();
            }
        }
        
        if (GUILayout.Button("Save Level"))
        {
            SaveLevel();
        }
        
        if (GUILayout.Button("Load Level"))
        {
            LoadLevel();
        }
        
        EditorGUILayout.EndHorizontal();
    }
    
    private void SaveLevel()
    {
        // 将地图转换为字符串
        UpdateLevelData();
        
        // 选择保存路径
        string path = EditorUtility.SaveFilePanelInProject("Save Level", "Level", "json", "Save level data");
        if (string.IsNullOrEmpty(path))
            return;
            
        // 序列化并保存
        string json = JsonUtility.ToJson(currentLevel, true);
        System.IO.File.WriteAllText(path, json);
        
        AssetDatabase.Refresh();
        EditorUtility.DisplayDialog("Save Level", "Level saved successfully!", "OK");
    }
    
    private void LoadLevel()
    {
        // 选择要加载的文件
        string path = EditorUtility.OpenFilePanel("Load Level", Application.dataPath, "json");
        if (string.IsNullOrEmpty(path))
            return;
            
        // 读取并反序列化
        string json = System.IO.File.ReadAllText(path);
        currentLevel = JsonUtility.FromJson<LevelData>(json);
        
        // 重新初始化地图
        InitializeMap();
    }
    
    private void UpdateLevelData()
    {
        // 将地图数组转换为字符串
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        
        for (int y = 0; y < currentLevel.height; y++)
        {
            for (int x = 0; x < currentLevel.width; x++)
            {
                sb.Append(map[x, y]);
            }
        }
        
        currentLevel.mapData = sb.ToString();
    }
}
#endif

19.6 项目总结

在本章中,我们详细介绍了如何使用Unity 2021.3.8f1c1开发一款经典的坦克大战游戏。我们从游戏设计、架构规划到代码实现,全面覆盖了游戏开发的各个方面。

主要实现的功能

  1. 完整的地图系统:使用Tilemap实现了多种地形类型,包括砖墙、钢墙、水域、草丛和冰面,每种地形都有不同的游戏效果。

  2. 玩家控制系统:实现了流畅的坦克移动和射击控制,支持不同的子弹威力和生命系统。

  3. 敌人AI:设计了具有多种行为模式的敌人AI,能够在不同情况下做出合理的决策,包括追击玩家、攻击基地和规避危险。

  4. 道具系统:实现了多种类型的道具,如星星、坦克、计时器、铲子等,为游戏增加了更多变化。

  5. 多人模式:支持两名玩家同时游戏,增加了游戏的社交性和乐趣。

  6. 性能优化:通过对象池、合理的碰撞检测和资源管理,确保游戏运行流畅。

  7. 视觉效果:添加了各种粒子效果、相机震动和特效,增强游戏的视觉表现。

  8. 关卡编辑器:开发了一个简单但功能完整的关卡编辑器,方便设计和测试游戏关卡。

开发心得

  1. 架构设计的重要性:良好的架构设计可以使游戏开发更加清晰和可维护,特别是在实现复杂系统如AI和碰撞检测时。

  2. 组件化开发:将游戏功能拆分为不同的组件,如地图管理器、敌人生成器、道具系统等,可以使代码更加模块化和易于理解。

  3. 性能优化:在开发过程中就考虑性能优化,如使用对象池管理频繁创建和销毁的对象,可以避免后期遇到性能瓶颈。

  4. 游戏体验:游戏开发不仅仅是功能实现,还需要考虑游戏的平衡性、难度曲线和整体体验,通过反复测试和调整来优化这些方面。

通过本章的学习,读者应该能够掌握2D游戏开发的核心技术和设计理念,不仅可以应用于坦克大战游戏,还可以扩展到其他类型的2D游戏开发中。

希望这个详细的教程能够帮助读者更好地理解游戏开发的过程,并在自己的游戏开发之路上取得进步!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐