炸弹人游戏开发进阶:从原理到实践

11.1 炸弹人游戏概述与数学模型

炸弹人是一款经典的策略动作游戏,玩家在迷宫中放置炸弹消灭敌人并寻找出口。这类游戏的核心机制涉及多种数学概念,包括离散网格系统、爆炸范围计算、碰撞检测和路径寻找算法。

从数学角度看,炸弹人游戏基于离散的网格系统,通常使用二维数组表示游戏地图。每个网格单元可以是空地、墙壁、可破坏障碍物或道具。炸弹的爆炸范围可以用十字形或曼哈顿距离来描述,这是一种特殊的距离度量方式,定义为两点在标准坐标系上的绝对差值之和。

在网格系统中,若有两个点P1(x1, y1)和P2(x2, y2),它们之间的曼哈顿距离为:

distance = |x1 - x2| + |y1 - y2|

这种距离计算在炸弹爆炸范围确定中特别有用,因为爆炸通常沿着水平和垂直方向传播,而不是对角线方向。

让我们从一个基本的地图表示开始:

csharp

// 地图数据结构
public class MapData
{
    // 地图图块类型枚举
    public enum TileType
    {
        Empty = 0,
        Wall = 1,
        Breakable = 2,
        PowerUp = 3
    }

    // 地图尺寸
    public int Width { get; private set; }
    public int Height { get; private set; }
    
    // 地图数据 - 二维数组存储每个位置的图块类型
    private TileType[,] mapGrid;
    
    public MapData(int width, int height)
    {
        Width = width;
        Height = height;
        mapGrid = new TileType[width, height];
    }
    
    // 获取指定位置的图块类型
    public TileType GetTile(int x, int y)
    {
        // 边界检查
        if (x < 0 || x >= Width || y < 0 || y >= Height)
        {
            return TileType.Wall; // 超出边界视为墙壁
        }
        
        return mapGrid[x, y];
    }
    
    // 设置指定位置的图块类型
    public void SetTile(int x, int y, TileType tileType)
    {
        // 边界检查
        if (x < 0 || x >= Width || y < 0 || y >= Height)
        {
            return;
        }
        
        mapGrid[x, y] = tileType;
    }
    
    // 计算炸弹爆炸范围(使用曼哈顿距离)
    public List<Vector2Int> CalculateExplosionRange(int centerX, int centerY, int explosionPower)
    {
        List<Vector2Int> affectedTiles = new List<Vector2Int>();
        
        // 添加中心点
        affectedTiles.Add(new Vector2Int(centerX, centerY));
        
        // 四个方向:上、右、下、左
        Vector2Int[] directions = 
        {
            new Vector2Int(0, 1),   // 上
            new Vector2Int(1, 0),   // 右
            new Vector2Int(0, -1),  // 下
            new Vector2Int(-1, 0)   // 左
        };
        
        // 对每个方向计算爆炸范围
        foreach (Vector2Int dir in directions)
        {
            for (int i = 1; i <= explosionPower; i++)
            {
                int newX = centerX + dir.x * i;
                int newY = centerY + dir.y * i;
                
                // 检查是否越界
                if (newX < 0 || newX >= Width || newY < 0 || newY >= Height)
                {
                    break; // 碰到边界,爆炸停止
                }
                
                // 添加当前格子
                affectedTiles.Add(new Vector2Int(newX, newY));
                
                // 如果碰到墙壁或可破坏物,爆炸停止传播
                if (mapGrid[newX, newY] == TileType.Wall)
                {
                    break;
                }
                
                // 如果是可破坏物,添加后停止
                if (mapGrid[newX, newY] == TileType.Breakable)
                {
                    break;
                }
            }
        }
        
        return affectedTiles;
    }
}

此代码实现了地图数据结构和爆炸范围计算的核心逻辑。使用曼哈顿距离作为爆炸传播模型,可以很好地模拟炸弹人游戏中的爆炸效果,同时考虑了墙壁和可破坏障碍物对爆炸传播的阻挡作用。

11.2 炸弹人游戏规则与平衡性设计

设计一款优秀的炸弹人游戏,需要精心调整游戏规则和平衡性,确保游戏既有挑战性又不会过于困难。下面介绍一些关键规则设计和数学平衡考量。

11.2.1 核心游戏元素与数值平衡

炸弹人游戏的核心元素包括:

  • 玩家属性:移动速度、炸弹数量、爆炸范围
  • 炸弹属性:引爆时间、爆炸范围、持续时间
  • 敌人属性:移动速度、智能程度、伤害值
  • 道具系统:增加炸弹数量、增加爆炸范围、增加移动速度等

这些元素之间的平衡直接影响游戏体验。以下是一个基本的游戏配置系统:

csharp

public class GameSettings
{
    // 玩家初始设置
    public static class PlayerSettings
    {
        public const float MovementSpeed = 5.0f;
        public const int InitialBombCount = 1;
        public const int InitialExplosionRange = 2;
        public const float BombPlacementCooldown = 0.5f; // 放置炸弹的冷却时间
    }

    // 炸弹设置
    public static class BombSettings
    {
        public const float FuseTime = 3.0f; // 引爆时间
        public const float ExplosionDuration = 1.0f; // 爆炸效果持续时间
        public const float ChainReactionDelay = 0.1f; // 连锁反应延迟
    }

    // 敌人设置
    public static class EnemySettings
    {
        // 不同类型敌人的速度
        public static readonly Dictionary<EnemyType, float> MovementSpeeds = new Dictionary<EnemyType, float>
        {
            { EnemyType.Basic, 2.5f },
            { EnemyType.Fast, 4.0f },
            { EnemyType.Tank, 1.5f }
        };

        // 不同类型敌人的生命值
        public static readonly Dictionary<EnemyType, int> HealthPoints = new Dictionary<EnemyType, int>
        {
            { EnemyType.Basic, 1 },
            { EnemyType.Fast, 1 },
            { EnemyType.Tank, 2 }
        };
    }

    // 道具设置
    public static class PowerUpSettings
    {
        // 道具出现概率 (0-100%)
        public const float SpawnChance = 40.0f;

        // 各类道具的相对权重
        public static readonly Dictionary<PowerUpType, float> SpawnWeights = new Dictionary<PowerUpType, float>
        {
            { PowerUpType.ExtraBomb, 30.0f },
            { PowerUpType.ExplosionRange, 30.0f },
            { PowerUpType.SpeedBoost, 20.0f },
            { PowerUpType.PassThroughWalls, 10.0f },
            { PowerUpType.RemoteDetonator, 10.0f }
        };

        // 计算随机道具类型
        public static PowerUpType GetRandomPowerUpType()
        {
            float totalWeight = 0;
            foreach (var weight in SpawnWeights.Values)
            {
                totalWeight += weight;
            }
            
            float randomValue = UnityEngine.Random.Range(0, totalWeight);
            float currentSum = 0;
            
            foreach (var kvp in SpawnWeights)
            {
                currentSum += kvp.Value;
                if (randomValue <= currentSum)
                {
                    return kvp.Key;
                }
            }
            
            // 默认返回额外炸弹道具
            return PowerUpType.ExtraBomb;
        }
    }

    // 难度曲线设置
    public static class DifficultySettings
    {
        // 随关卡递增的敌人数量
        public static int GetEnemyCountForLevel(int level)
        {
            // 基础敌人数 + 每关递增量 * (关卡数-1)
            return 3 + 1 * (level - 1);
        }
        
        // 随关卡递增的敌人速度倍率
        public static float GetEnemySpeedMultiplierForLevel(int level)
        {
            // 每5关敌人速度提高10%,但最多增加50%
            float speedIncrease = Mathf.Min(1.0f + (level / 5) * 0.1f, 1.5f);
            return speedIncrease;
        }
    }
}

// 敌人类型枚举
public enum EnemyType
{
    Basic,  // 基本敌人
    Fast,   // 快速敌人
    Tank    // 坦克敌人(生命值更多)
}

// 道具类型枚举
public enum PowerUpType
{
    ExtraBomb,         // 增加炸弹数量
    ExplosionRange,    // 增加爆炸范围
    SpeedBoost,        // 提升移动速度
    PassThroughWalls,  // 穿墙能力
    RemoteDetonator    // 遥控引爆器
}

11.2.2 游戏难度曲线设计

难度曲线是游戏设计的重要方面,一个良好的难度曲线应该随着玩家进度缓慢提升,给予足够的挑战但不至于令人沮丧。

在炸弹人游戏中,我们可以使用数学公式来表示难度随关卡的变化:

csharp

public static class DifficultyCalculator
{
    // 计算特定关卡的综合难度值
    public static float CalculateLevelDifficulty(int level)
    {
        // 基础难度值
        float baseDifficulty = 1.0f;
        
        // 难度递增公式:基础难度 * (1 + 关卡系数 * log(关卡数))
        // 使用对数函数使难度增长逐渐放缓
        float difficulty = baseDifficulty * (1.0f + 0.5f * Mathf.Log(level, 2));
        
        return difficulty;
    }
    
    // 根据难度值计算敌人数量
    public static int CalculateEnemyCount(float difficulty, int mapArea)
    {
        // 基于地图面积和难度值计算敌人数量
        // 地图越大,敌人越多;难度越高,敌人越多
        float enemyDensity = 0.05f * difficulty;
        int maxEnemies = Mathf.FloorToInt(mapArea * enemyDensity);
        
        // 设置上限和下限
        return Mathf.Clamp(maxEnemies, 2, 15);
    }
    
    // 计算敌人类型分布
    public static Dictionary<EnemyType, int> CalculateEnemyDistribution(int totalEnemies, float difficulty)
    {
        Dictionary<EnemyType, int> distribution = new Dictionary<EnemyType, int>();
        
        // 基础敌人占比随难度减少
        float basicRatio = 1.0f - 0.2f * (difficulty - 1.0f);
        basicRatio = Mathf.Clamp(basicRatio, 0.3f, 0.9f);
        
        // 快速敌人占比随难度增加
        float fastRatio = 0.1f + 0.15f * (difficulty - 1.0f);
        fastRatio = Mathf.Clamp(fastRatio, 0.1f, 0.5f);
        
        // 坦克敌人占比随难度增加
        float tankRatio = 0.0f + 0.1f * (difficulty - 1.0f);
        tankRatio = Mathf.Clamp(tankRatio, 0.0f, 0.3f);
        
        // 标准化比例
        float sum = basicRatio + fastRatio + tankRatio;
        basicRatio /= sum;
        fastRatio /= sum;
        tankRatio /= sum;
        
        // 计算实际数量
        int basicCount = Mathf.FloorToInt(totalEnemies * basicRatio);
        int fastCount = Mathf.FloorToInt(totalEnemies * fastRatio);
        int tankCount = totalEnemies - basicCount - fastCount;
        
        // 确保至少有一个基本敌人
        if (totalEnemies > 0 && basicCount == 0)
        {
            basicCount = 1;
            if (fastCount > 0) fastCount--;
            else if (tankCount > 0) tankCount--;
        }
        
        distribution[EnemyType.Basic] = basicCount;
        distribution[EnemyType.Fast] = fastCount;
        distribution[EnemyType.Tank] = tankCount;
        
        return distribution;
    }
}

这段代码展示了如何使用数学函数(特别是对数函数)来创建平滑的难度曲线,并计算每个关卡的敌人分布。对数函数在游戏设计中非常有用,因为它可以创造先快后慢的增长模式,这正是理想的游戏难度曲线。

11.3 炸弹人游戏架构设计

良好的架构设计对于构建可维护、可扩展的游戏至关重要。下面我们将探讨炸弹人游戏的架构设计,从地图生成到游戏管理。

11.3.1 地图生成系统

地图生成是炸弹人游戏的核心部分。一个好的地图生成器应该能够创建平衡且有趣的地图布局。下面是一个基于程序化生成的地图系统:

csharp

using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class MapGenerator
{
    // 地图图块类型
    public enum TileType
    {
        Empty,
        Wall,
        Breakable,
        PowerUp
    }
    
    // 地图配置参数
    public class MapConfig
    {
        public int Width = 15;
        public int Height = 13;
        public float BreakableWallDensity = 0.5f; // 可破坏墙的密度
        public float PowerUpSpawnChance = 0.2f;   // 道具生成概率
        public int MinPathLength = 3;             // 最小路径长度
        public bool EnsureFairStart = true;       // 确保玩家起点公平
    }
    
    private MapConfig config;
    
    public MapGenerator(MapConfig config = null)
    {
        this.config = config ?? new MapConfig();
    }
    
    // 生成地图数据
    public TileType[,] GenerateMap()
    {
        TileType[,] map = new TileType[config.Width, config.Height];
        
        // 1. 初始化边界和固定墙壁
        InitializeBasicStructure(map);
        
        // 2. 确保起点区域空旷
        CreateStartingAreas(map);
        
        // 3. 放置可破坏的墙壁
        PlaceBreakableWalls(map);
        
        // 4. 添加道具
        PlacePowerUps(map);
        
        // 5. 确保地图可通行
        EnsureMapPathExists(map);
        
        return map;
    }
    
    // 初始化基本结构:边界和固定墙壁
    private void InitializeBasicStructure(TileType[,] map)
    {
        // 首先将所有格子设为空
        for (int x = 0; x < config.Width; x++)
        {
            for (int y = 0; y < config.Height; y++)
            {
                map[x, y] = TileType.Empty;
            }
        }
        
        // 设置边界墙
        for (int x = 0; x < config.Width; x++)
        {
            map[x, 0] = TileType.Wall;
            map[x, config.Height - 1] = TileType.Wall;
        }
        
        for (int y = 0; y < config.Height; y++)
        {
            map[0, y] = TileType.Wall;
            map[config.Width - 1, y] = TileType.Wall;
        }
        
        // 创建固定的格子状墙壁(每隔一格放一个墙)
        for (int x = 2; x < config.Width - 1; x += 2)
        {
            for (int y = 2; y < config.Height - 1; y += 2)
            {
                map[x, y] = TileType.Wall;
            }
        }
    }
    
    // 确保起点区域空旷
    private void CreateStartingAreas(TileType[,] map)
    {
        // 玩家1起点(左上角)
        map[1, 1] = TileType.Empty;
        map[1, 2] = TileType.Empty;
        map[2, 1] = TileType.Empty;
        
        // 玩家2起点(右下角)
        map[config.Width - 2, config.Height - 2] = TileType.Empty;
        map[config.Width - 2, config.Height - 3] = TileType.Empty;
        map[config.Width - 3, config.Height - 2] = TileType.Empty;
        
        // 如果是多人游戏,可以添加更多起点
        if (config.Width >= 9 && config.Height >= 9)
        {
            // 玩家3起点(右上角)
            map[config.Width - 2, 1] = TileType.Empty;
            map[config.Width - 2, 2] = TileType.Empty;
            map[config.Width - 3, 1] = TileType.Empty;
            
            // 玩家4起点(左下角)
            map[1, config.Height - 2] = TileType.Empty;
            map[1, config.Height - 3] = TileType.Empty;
            map[2, config.Height - 2] = TileType.Empty;
        }
    }
    
    // 放置可破坏的墙壁
    private void PlaceBreakableWalls(TileType[,] map)
    {
        List<Vector2Int> emptyCells = new List<Vector2Int>();
        
        // 找出所有空单元格
        for (int x = 1; x < config.Width - 1; x++)
        {
            for (int y = 1; y < config.Height - 1; y++)
            {
                if (map[x, y] == TileType.Empty)
                {
                    emptyCells.Add(new Vector2Int(x, y));
                }
            }
        }
        
        // 随机打乱空单元格列表
        ShuffleList(emptyCells);
        
        // 确定要放置的可破坏墙数量
        int totalEmptyCells = emptyCells.Count;
        int breakableWallCount = Mathf.FloorToInt(totalEmptyCells * config.BreakableWallDensity);
        
        // 放置可破坏墙
        for (int i = 0; i < breakableWallCount && i < emptyCells.Count; i++)
        {
            Vector2Int cell = emptyCells[i];
            map[cell.x, cell.y] = TileType.Breakable;
        }
    }
    
    // 添加道具
    private void PlacePowerUps(TileType[,] map)
    {
        // 在可破坏墙内添加道具
        for (int x = 1; x < config.Width - 1; x++)
        {
            for (int y = 1; y < config.Height - 1; y++)
            {
                if (map[x, y] == TileType.Breakable && Random.value < config.PowerUpSpawnChance)
                {
                    // 标记这个位置有道具
                    // 实际道具会在墙被破坏时生成
                    // 这里我们用一个特殊值来表示含有道具的可破坏墙
                    map[x, y] = TileType.PowerUp;
                }
            }
        }
    }
    
    // 确保地图中存在至少一条通路
    private void EnsureMapPathExists(TileType[,] map)
    {
        // 使用简化的路径查找算法,确保玩家之间有通路
        // 这里简化处理,仅确保没有完全封闭的区域
        
        // 如果需要更复杂的路径确认,可以使用A*或Dijkstra算法
    }
    
    // 辅助方法:打乱列表
    private void ShuffleList<T>(List<T> list)
    {
        int n = list.Count;
        for (int i = 0; i < n; i++)
        {
            int r = i + Random.Range(0, n - i);
            T temp = list[i];
            list[i] = list[r];
            list[r] = temp;
        }
    }
    
    // 辅助方法:检查位置是否在地图内
    private bool IsInBounds(int x, int y)
    {
        return x >= 0 && x < config.Width && y >= 0 && y < config.Height;
    }
}

地图生成器使用程序化生成方法,确保地图具有适当的结构和平衡性。它首先创建基本的墙壁布局,然后添加可破坏墙壁和道具,最后确保地图中存在通路,让玩家可以互相接近。

11.3.2 炸弹管理系统

炸弹系统是游戏的核心机制之一,需要处理炸弹的放置、计时、爆炸和连锁反应等。

csharp

using System;
using System.Collections.Generic;
using UnityEngine;

public class BombManager : MonoBehaviour
{
    // 炸弹预制体
    [SerializeField] private GameObject bombPrefab;
    [SerializeField] private GameObject explosionPrefab;
    
    // 地图引用
    private MapData mapData;
    
    // 当前活跃的炸弹列表
    private List<BombData> activeBombs = new List<BombData>();
    
    // 爆炸事件
    public event Action<Vector2Int, int> OnBombExplode;
    
    // 设置地图数据
    public void SetMapData(MapData map)
    {
        this.mapData = map;
    }
    
    // 更新所有炸弹状态
    private void Update()
    {
        for (int i = activeBombs.Count - 1; i >= 0; i--)
        {
            BombData bomb = activeBombs[i];
            
            // 更新计时器
            bomb.RemainingTime -= Time.deltaTime;
            
            // 检查是否应该爆炸
            if (bomb.RemainingTime <= 0)
            {
                // 触发爆炸
                ExplodeBomb(bomb);
                
                // 从列表中移除
                activeBombs.RemoveAt(i);
            }
        }
    }
    
    // 放置炸弹
    public bool PlaceBomb(int x, int y, int playerId, int explosionRange)
    {
        // 检查坐标有效性
        if (!IsValidBombPosition(x, y))
        {
            return false;
        }
        
        // 创建炸弹数据
        BombData newBomb = new BombData
        {
            Position = new Vector2Int(x, y),
            OwnerId = playerId,
            ExplosionRange = explosionRange,
            RemainingTime = GameSettings.BombSettings.FuseTime,
            IsRemoteControlled = false // 默认非遥控炸弹
        };
        
        // 添加到活跃炸弹列表
        activeBombs.Add(newBomb);
        
        // 实例化炸弹对象
        Vector3 worldPosition = GridToWorldPosition(x, y);
        GameObject bombObject = Instantiate(bombPrefab, worldPosition, Quaternion.identity);
        
        // 设置炸弹的视觉表示
        BombView bombView = bombObject.GetComponent<BombView>();
        if (bombView != null)
        {
            bombView.Initialize(newBomb);
            newBomb.View = bombView;
        }
        
        return true;
    }
    
    // 爆炸炸弹
    private void ExplodeBomb(BombData bomb)
    {
        // 计算爆炸范围
        List<Vector2Int> explosionTiles = mapData.CalculateExplosionRange(
            bomb.Position.x, bomb.Position.y, bomb.ExplosionRange);
        
        // 创建爆炸效果
        foreach (Vector2Int tile in explosionTiles)
        {
            Vector3 worldPos = GridToWorldPosition(tile.x, tile.y);
            Instantiate(explosionPrefab, worldPos, Quaternion.identity);
            
            // 处理爆炸影响(破坏墙壁,伤害玩家/敌人等)
            HandleExplosionEffect(tile);
        }
        
        // 检查连锁爆炸
        CheckChainReaction(explosionTiles);
        
        // 触发爆炸事件
        OnBombExplode?.Invoke(bomb.Position, bomb.ExplosionRange);
        
        // 销毁炸弹视图
        if (bomb.View != null)
        {
            Destroy(bomb.View.gameObject);
        }
    }
    
    // 处理爆炸效果
    private void HandleExplosionEffect(Vector2Int position)
    {
        // 获取该位置的图块类型
        MapData.TileType tileType = mapData.GetTile(position.x, position.y);
        
        // 根据图块类型处理效果
        switch (tileType)
        {
            case MapData.TileType.Breakable:
                // 破坏可破坏墙
                mapData.SetTile(position.x, position.y, MapData.TileType.Empty);
                break;
            
            case MapData.TileType.PowerUp:
                // 破坏包含道具的墙,并生成道具
                mapData.SetTile(position.x, position.y, MapData.TileType.Empty);
                SpawnPowerUp(position.x, position.y);
                break;
        }
        
        // 在实际游戏中,还需要检测是否有玩家或敌人在爆炸范围内
    }
    
    // 检查连锁反应
    private void CheckChainReaction(List<Vector2Int> explosionTiles)
    {
        for (int i = activeBombs.Count - 1; i >= 0; i--)
        {
            BombData bomb = activeBombs[i];
            
            // 检查该炸弹是否在爆炸范围内
            if (explosionTiles.Contains(bomb.Position))
            {
                // 设置较短的引爆时间以创建连锁效应
                bomb.RemainingTime = Mathf.Min(
                    bomb.RemainingTime, 
                    GameSettings.BombSettings.ChainReactionDelay);
            }
        }
    }
    
    // 生成道具
    private void SpawnPowerUp(int x, int y)
    {
        // 随机选择一个道具类型
        PowerUpType powerUpType = GameSettings.PowerUpSettings.GetRandomPowerUpType();
        
        // 在实际游戏中,这里会实例化道具对象
        Debug.Log($"在位置 ({x}, {y}) 生成道具: {powerUpType}");
    }
    
    // 检查位置是否有效(无墙、无炸弹)
    private bool IsValidBombPosition(int x, int y)
    {
        // 检查地图边界
        if (x < 0 || x >= mapData.Width || y < 0 || y >= mapData.Height)
        {
            return false;
        }
        
        // 检查是否是空地
        MapData.TileType tileType = mapData.GetTile(x, y);
        if (tileType != MapData.TileType.Empty)
        {
            return false;
        }
        
        // 检查是否已有炸弹
        foreach (BombData bomb in activeBombs)
        {
            if (bomb.Position.x == x && bomb.Position.y == y)
            {
                return false;
            }
        }
        
        return true;
    }
    
    // 触发遥控炸弹爆炸
    public void DetonateRemoteBombs(int playerId)
    {
        for (int i = activeBombs.Count - 1; i >= 0; i--)
        {
            BombData bomb = activeBombs[i];
            
            // 检查是否是该玩家的遥控炸弹
            if (bomb.OwnerId == playerId && bomb.IsRemoteControlled)
            {
                // 设置炸弹立即爆炸
                bomb.RemainingTime = 0;
            }
        }
    }
    
    // 辅助方法:将网格坐标转换为世界坐标
    private Vector3 GridToWorldPosition(int x, int y)
    {
        // 假设每个格子大小为1单位
        return new Vector3(x, y, 0);
    }
    
    // 炸弹数据类
    public class BombData
    {
        public Vector2Int Position;
        public int OwnerId;
        public int ExplosionRange;
        public float RemainingTime;
        public bool IsRemoteControlled;
        public BombView View;
    }
}

// 炸弹视图组件
public class BombView : MonoBehaviour
{
    private BombManager.BombData bombData;
    
    [SerializeField] private SpriteRenderer spriteRenderer;
    [SerializeField] private Animator animator;
    
    // 初始化炸弹视图
    public void Initialize(BombManager.BombData data)
    {
        bombData = data;
        
        // 启动动画
        if (animator != null)
        {
            animator.SetTrigger("Start");
        }
    }
    
    // 更新函数
    private void Update()
    {
        // 更新动画或视觉效果,例如闪烁频率随时间增加
        if (bombData != null && animator != null)
        {
            float normalizedTime = 1 - (bombData.RemainingTime / GameSettings.BombSettings.FuseTime);
            animator.SetFloat("FuseProgress", normalizedTime);
        }
    }
}

炸弹管理器处理炸弹的生命周期,包括放置、计时、爆炸和连锁反应。它还处理爆炸的影响,如破坏墙壁和生成道具。

11.3.3 怪物AI与寻路系统

敌人AI是炸弹人游戏的重要组成部分。下面的代码实现了基于网格的寻路系统和基本的敌人行为:

csharp

using System.Collections.Generic;
using UnityEngine;
using System.Linq;

// 敌人行为控制器
public class EnemyController : MonoBehaviour
{
    // 敌人数据
    private EnemyData enemyData;
    private MapData mapData;
    
    // 移动相关
    private Vector2Int currentGridPosition;
    private Vector2Int targetGridPosition;
    private float moveSpeed;
    private float movementProgress = 0;
    
    // 视觉组件
    [SerializeField] private SpriteRenderer spriteRenderer;
    [SerializeField] private Animator animator;
    
    // AI决策计时器
    private float decisionTimer = 0;
    private float decisionInterval = 0.5f; // 决策间隔
    
    // 初始化敌人
    public void Initialize(EnemyData data, MapData map)
    {
        enemyData = data;
        mapData = map;
        
        // 设置位置
        currentGridPosition = data.InitialPosition;
        targetGridPosition = currentGridPosition;
        transform.position = GridToWorldPosition(currentGridPosition);
        
        // 设置速度
        moveSpeed = GameSettings.EnemySettings.MovementSpeeds[data.Type];
        
        // 设置视觉效果
        UpdateVisuals();
    }
    
    // 更新函数
    private void Update()
    {
        // 处理移动
        if (currentGridPosition != targetGridPosition)
        {
            // 计算移动进度
            movementProgress += Time.deltaTime * moveSpeed;
            
            if (movementProgress >= 1.0f)
            {
                // 移动完成
                movementProgress = 0;
                currentGridPosition = targetGridPosition;
                transform.position = GridToWorldPosition(currentGridPosition);
            }
            else
            {
                // 插值移动
                Vector3 startPos = GridToWorldPosition(currentGridPosition);
                Vector3 endPos = GridToWorldPosition(targetGridPosition);
                transform.position = Vector3.Lerp(startPos, endPos, movementProgress);
            }
            
            // 更新动画
            UpdateMovementAnimation();
        }
        else
        {
            // 已到达目标位置,进行AI决策
            decisionTimer += Time.deltaTime;
            
            if (decisionTimer >= decisionInterval)
            {
                decisionTimer = 0;
                MakeMovementDecision();
            }
        }
    }
    
    // AI决策
    private void MakeMovementDecision()
    {
        // 根据敌人类型使用不同的AI策略
        switch (enemyData.Type)
        {
            case EnemyType.Basic:
                MakeBasicMovementDecision();
                break;
            
            case EnemyType.Fast:
                MakeFastMovementDecision();
                break;
            
            case EnemyType.Tank:
                MakeTankMovementDecision();
                break;
        }
    }
    
    // 基础敌人的移动决策 - 随机移动
    private void MakeBasicMovementDecision()
    {
        // 获取可能的移动方向
        List<Vector2Int> possibleMoves = GetPossibleMoves();
        
        // 如果没有可能的移动,就留在原地
        if (possibleMoves.Count == 0)
            return;
        
        // 随机选择一个方向
        Vector2Int moveDirection = possibleMoves[Random.Range(0, possibleMoves.Count)];
        
        // 设置新的目标位置
        targetGridPosition = currentGridPosition + moveDirection;
    }
    
    // 快速敌人的移动决策 - 朝玩家方向移动
    private void MakeFastMovementDecision()
    {
        // 获取最近玩家位置
        Vector2Int playerPosition = FindNearestPlayerPosition();
        
        // 如果找不到玩家,随机移动
        if (playerPosition == new Vector2Int(-1, -1))
        {
            MakeBasicMovementDecision();
            return;
        }
        
        // 尝试向玩家移动
        List<Vector2Int> possibleMoves = GetPossibleMoves();
        
        // 如果没有可能的移动,就留在原地
        if (possibleMoves.Count == 0)
            return;
        
        // 计算每个方向到玩家的距离
        Dictionary<Vector2Int, float> moveDistances = new Dictionary<Vector2Int, float>();
        foreach (Vector2Int move in possibleMoves)
        {
            Vector2Int newPos = currentGridPosition + move;
            float distance = Vector2Int.Distance(newPos, playerPosition);
            moveDistances.Add(move, distance);
        }
        
        // 选择最接近玩家的方向
        Vector2Int bestMove = moveDistances.OrderBy(kvp => kvp.Value).First().Key;
        targetGridPosition = currentGridPosition + bestMove;
    }
    
    // 坦克敌人的移动决策 - 追踪玩家但有惯性
    private void MakeTankMovementDecision()
    {
        // 基本上与快速敌人相似,但移动更慢且更有决心
        // 简化起见,这里使用相同的逻辑
        MakeFastMovementDecision();
    }
    
    // 获取可能的移动方向
    private List<Vector2Int> GetPossibleMoves()
    {
        List<Vector2Int> possibleMoves = new List<Vector2Int>();
        
        // 四个基本方向
        Vector2Int[] directions = 
        {
            new Vector2Int(0, 1),  // 上
            new Vector2Int(1, 0),  // 右
            new Vector2Int(0, -1), // 下
            new Vector2Int(-1, 0)  // 左
        };
        
        foreach (Vector2Int dir in directions)
        {
            Vector2Int newPos = currentGridPosition + dir;
            
            // 检查新位置是否有效
            if (IsValidMove(newPos))
            {
                possibleMoves.Add(dir);
            }
        }
        
        return possibleMoves;
    }
    
    // 检查移动是否有效
    private bool IsValidMove(Vector2Int position)
    {
        // 检查是否在地图边界内
        if (position.x < 0 || position.x >= mapData.Width ||
            position.y < 0 || position.y >= mapData.Height)
        {
            return false;
        }
        
        // 检查该位置是否有墙壁或可破坏物
        MapData.TileType tileType = mapData.GetTile(position.x, position.y);
        if (tileType == MapData.TileType.Wall || tileType == MapData.TileType.Breakable)
        {
            return false;
        }
        
        // 检查该位置是否有炸弹
        // 实际游戏中需要检查炸弹管理器
        
        return true;
    }
    
    // 寻找最近的玩家
    private Vector2Int FindNearestPlayerPosition()
    {
        // 在实际游戏中,这里会查询玩家管理器获取所有玩家位置
        // 简化起见,这里返回固定位置
        
        // 假设玩家位置
        Vector2Int playerPosition = new Vector2Int(1, 1);
        
        return playerPosition;
    }
    
    // 更新视觉效果
    private void UpdateVisuals()
    {
        if (spriteRenderer != null)
        {
            // 根据敌人类型设置颜色
            switch (enemyData.Type)
            {
                case EnemyType.Basic:
                    spriteRenderer.color = Color.red;
                    break;
                case EnemyType.Fast:
                    spriteRenderer.color = Color.blue;
                    break;
                case EnemyType.Tank:
                    spriteRenderer.color = Color.green;
                    break;
            }
        }
    }
    
    // 更新移动动画
    private void UpdateMovementAnimation()
    {
        if (animator != null)
        {
            // 计算移动方向
            Vector2Int direction = targetGridPosition - currentGridPosition;
            
            // 设置动画参数
            animator.SetFloat("DirectionX", direction.x);
            animator.SetFloat("DirectionY", direction.y);
            animator.SetBool("IsMoving", true);
        }
    }
    
    // 响应伤害
    public void TakeDamage()
    {
        enemyData.Health--;
        
        if (enemyData.Health <= 0)
        {
            Die();
        }
        else
        {
            // 播放受伤动画
            if (animator != null)
            {
                animator.SetTrigger("Hurt");
            }
        }
    }
    
    // 死亡处理
    private void Die()
    {
        // 播放死亡动画
        if (animator != null)
        {
            animator.SetTrigger("Die");
        }
        
        // 延迟销毁对象
        Destroy(gameObject, 1.0f);
    }
    
    // 网格坐标转世界坐标
    private Vector3 GridToWorldPosition(Vector2Int gridPos)
    {
        return new Vector3(gridPos.x, gridPos.y, 0);
    }
}

// 敌人数据类
public class EnemyData
{
    public EnemyType Type;
    public Vector2Int InitialPosition;
    public int Health;
    
    public EnemyData(EnemyType type, Vector2Int position)
    {
        Type = type;
        InitialPosition = position;
        Health = GameSettings.EnemySettings.HealthPoints[type];
    }
}

这段代码实现了敌人的基本AI行为,包括随机移动、追踪玩家和使用不同策略的敌人类型。它还包括了敌人的移动控制、碰撞检测和视觉更新。

11.3.4 游戏管理系统

游戏管理器负责协调各个系统,管理游戏状态和流程:

csharp

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    // 单例实例
    public static GameManager Instance { get; private set; }
    
    // 游戏状态
    public enum GameState
    {
        MainMenu,
        Loading,
        Playing,
        Paused,
        GameOver,
        Victory
    }
    
    // 当前游戏状态
    private GameState currentState;
    
    // 游戏组件引用
    [SerializeField] private MapGenerator mapGenerator;
    [SerializeField] private BombManager bombManager;
    [SerializeField] private PlayerManager playerManager;
    [SerializeField] private EnemySpawner enemySpawner;
    [SerializeField] private UIManager uiManager;
    
    // 游戏设置
    [SerializeField] private int currentLevel = 1;
    [SerializeField] private float gameTime = 0;
    [SerializeField] private int playerCount = 1;
    [SerializeField] private bool vsMode = false;
    
    // 游戏统计
    private int enemiesDefeated = 0;
    private int itemsCollected = 0;
    
    // 游戏状态变更事件
    public event Action<GameState> OnGameStateChanged;
    
    private void Awake()
    {
        // 单例模式
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
    
    private void Start()
    {
        // 设置初始状态
        SetGameState(GameState.MainMenu);
    }
    
    private void Update()
    {
        // 根据当前状态更新游戏
        switch (currentState)
        {
            case GameState.Playing:
                UpdateGameplay();
                break;
                
            case GameState.Paused:
                // 暂停状态不更新游戏逻辑
                break;
                
            case GameState.GameOver:
            case GameState.Victory:
                // 游戏结束状态,等待用户输入
                CheckRestartInput();
                break;
        }
    }
    
    // 更新游戏逻辑
    private void UpdateGameplay()
    {
        // 更新游戏时间
        gameTime += Time.deltaTime;
        
        // 更新UI
        if (uiManager != null)
        {
            uiManager.UpdateTime(gameTime);
        }
        
        // 检查游戏结束条件
        CheckGameEndConditions();
    }
    
    // 设置游戏状态
    public void SetGameState(GameState newState)
    {
        // 退出当前状态
        switch (currentState)
        {
            case GameState.Playing:
                Time.timeScale = 1f; // 恢复正常时间流
                break;
                
            case GameState.Paused:
                Time.timeScale = 1f; // 恢复正常时间流
                break;
        }
        
        // 设置新状态
        currentState = newState;
        
        // 进入新状态
        switch (newState)
        {
            case GameState.MainMenu:
                // 显示主菜单
                break;
                
            case GameState.Loading:
                // 启动加载流程
                StartCoroutine(LoadGameAsync());
                break;
                
            case GameState.Playing:
                // 确保正常时间流
                Time.timeScale = 1f;
                break;
                
            case GameState.Paused:
                // 暂停时间
                Time.timeScale = 0f;
                break;
                
            case GameState.GameOver:
                // 显示游戏结束画面
                if (uiManager != null)
                {
                    uiManager.ShowGameOver(false, currentLevel, enemiesDefeated, itemsCollected);
                }
                break;
                
            case GameState.Victory:
                // 显示胜利画面
                if (uiManager != null)
                {
                    uiManager.ShowGameOver(true, currentLevel, enemiesDefeated, itemsCollected);
                }
                break;
        }
        
        // 触发状态变更事件
        OnGameStateChanged?.Invoke(newState);
    }
    
    // 异步加载游戏
    private IEnumerator LoadGameAsync()
    {
        // 重置游戏统计
        gameTime = 0;
        enemiesDefeated = 0;
        itemsCollected = 0;
        
        // 显示加载画面
        if (uiManager != null)
        {
            uiManager.ShowLoading();
        }
        
        // 生成地图
        MapGenerator.MapConfig mapConfig = new MapGenerator.MapConfig
        {
            Width = 15,
            Height = 13,
            BreakableWallDensity = 0.5f + (currentLevel * 0.02f), // 随关卡增加密度
            PowerUpSpawnChance = 0.2f + (currentLevel * 0.01f),   // 随关卡增加道具概率
            EnsureFairStart = !vsMode  // 非对战模式确保公平起点
        };
        
        MapGenerator.TileType[,] mapData = mapGenerator.GenerateMap(mapConfig);
        
        // 模拟加载时间
        yield return new WaitForSeconds(1.5f);
        
        // 初始化玩家
        playerManager.InitializePlayers(playerCount, vsMode);
        
        // 初始化敌人
        int enemyCount = DifficultyCalculator.CalculateEnemyCount(
            DifficultyCalculator.CalculateLevelDifficulty(currentLevel),
            mapConfig.Width * mapConfig.Height);
            
        Dictionary<EnemyType, int> enemyDistribution = DifficultyCalculator.CalculateEnemyDistribution(
            enemyCount, 
            DifficultyCalculator.CalculateLevelDifficulty(currentLevel));
            
        enemySpawner.SpawnEnemies(enemyDistribution, mapData);
        
        // 更新UI
        if (uiManager != null)
        {
            uiManager.UpdateLevel(currentLevel);
            uiManager.HideLoading();
        }
        
        // 切换到游戏状态
        SetGameState(GameState.Playing);
    }
    
    // 暂停/恢复游戏
    public void TogglePause()
    {
        if (currentState == GameState.Playing)
        {
            SetGameState(GameState.Paused);
        }
        else if (currentState == GameState.Paused)
        {
            SetGameState(GameState.Playing);
        }
    }
    
    // 开始新游戏
    public void StartNewGame(int playerCount, bool vsMode)
    {
        this.playerCount = playerCount;
        this.vsMode = vsMode;
        currentLevel = 1;
        
        SetGameState(GameState.Loading);
    }
    
    // 开始下一关
    public void StartNextLevel()
    {
        currentLevel++;
        SetGameState(GameState.Loading);
    }
    
    // 返回主菜单
    public void ReturnToMainMenu()
    {
        SetGameState(GameState.MainMenu);
    }
    
    // 检查游戏结束条件
    private void CheckGameEndConditions()
    {
        // 非对战模式下,检查是否所有敌人都被消灭
        if (!vsMode)
        {
            if (enemySpawner.GetRemainingEnemyCount() == 0)
            {
                SetGameState(GameState.Victory);
            }
            
            // 检查是否所有玩家都死亡
            if (playerManager.AreAllPlayersDead())
            {
                SetGameState(GameState.GameOver);
            }
        }
        else
        {
            // 对战模式下,检查是否只剩一名玩家
            if (playerManager.GetAlivePlayerCount() <= 1)
            {
                if (playerManager.GetAlivePlayerCount() == 0)
                {
                    SetGameState(GameState.GameOver);
                }
                else
                {
                    SetGameState(GameState.Victory);
                }
            }
        }
    }
    
    // 检查重新开始输入
    private void CheckRestartInput()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            if (currentState == GameState.Victory)
            {
                StartNextLevel();
            }
            else
            {
                StartNewGame(playerCount, vsMode);
            }
        }
        
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            ReturnToMainMenu();
        }
    }
    
    // 敌人被击败回调
    public void OnEnemyDefeated()
    {
        enemiesDefeated++;
        
        // 更新UI
        if (uiManager != null)
        {
            uiManager.UpdateScore(enemiesDefeated);
        }
    }
    
    // 道具收集回调
    public void OnItemCollected(PowerUpType itemType)
    {
        itemsCollected++;
    }
    
    // 获取当前游戏时间
    public float GetGameTime()
    {
        return gameTime;
    }
    
    // 获取当前关卡
    public int GetCurrentLevel()
    {
        return currentLevel;
    }
}

游戏管理器作为中央控制器,协调各个子系统的工作,管理游戏状态转换,并处理游戏进程中的事件和条件判断。它实现了单例模式,确保在整个游戏中只有一个管理器实例。

11.3.5 游戏流程设计

游戏流程是指游戏从开始到结束的完整过程,包括关卡流转、胜负判定和资源管理。以下是游戏流程图的实现:

csharp

using System;
using System.Collections.Generic;
using UnityEngine;

// 游戏流程管理器
public class GameFlowManager : MonoBehaviour
{
    // 游戏阶段
    public enum GamePhase
    {
        Initialization,    // 初始化阶段
        MapGeneration,     // 地图生成阶段
        PlayerSpawning,    // 玩家生成阶段
        EnemySpawning,     // 敌人生成阶段
        Gameplay,          // 游戏进行阶段
        LevelTransition,   // 关卡过渡阶段
        GameOver           // 游戏结束阶段
    }
    
    // 当前游戏阶段
    private GamePhase currentPhase = GamePhase.Initialization;
    
    // 阶段时间跟踪
    private float phaseTimer = 0;
    
    // 事件回调
    public event Action<GamePhase> OnPhaseChange;
    public event Action<int> OnLevelStart;
    public event Action<bool> OnGameEnd; // true表示胜利,false表示失败
    
    // 游戏配置
    [SerializeField] private int maxLevel = 10;
    [SerializeField] private float levelTransitionDuration = 3f;
    
    // 游戏状态
    private int currentLevel = 1;
    private bool gameInProgress = false;
    
    // 依赖组件引用
    private GameManager gameManager;
    private MapGenerator mapGenerator;
    private PlayerManager playerManager;
    private EnemySpawner enemySpawner;
    
    private void Awake()
    {
        gameManager = GetComponent<GameManager>();
        
        // 寻找必要组件
        if (mapGenerator == null) mapGenerator = FindObjectOfType<MapGenerator>();
        if (playerManager == null) playerManager = FindObjectOfType<PlayerManager>();
        if (enemySpawner == null) enemySpawner = FindObjectOfType<EnemySpawner>();
        
        // 订阅游戏状态变更事件
        if (gameManager != null)
        {
            gameManager.OnGameStateChanged += OnGameStateChanged;
        }
    }
    
    private void Start()
    {
        // 初始设置
        currentPhase = GamePhase.Initialization;
        gameInProgress = false;
    }
    
    private void Update()
    {
        if (!gameInProgress)
            return;
            
        // 更新当前阶段
        UpdateCurrentPhase();
    }
    
    // 开始新游戏
    public void StartNewGame()
    {
        currentLevel = 1;
        gameInProgress = true;
        ChangePhase(GamePhase.MapGeneration);
    }
    
    // 更新当前阶段
    private void UpdateCurrentPhase()
    {
        phaseTimer += Time.deltaTime;
        
        switch (currentPhase)
        {
            case GamePhase.Initialization:
                // 初始化完成后进入地图生成阶段
                if (phaseTimer > 0.5f) // 短暂延迟
                {
                    ChangePhase(GamePhase.MapGeneration);
                }
                break;
                
            case GamePhase.MapGeneration:
                // 地图生成完成后进入玩家生成阶段
                GenerateMap();
                ChangePhase(GamePhase.PlayerSpawning);
                break;
                
            case GamePhase.PlayerSpawning:
                // 玩家生成完成后进入敌人生成阶段
                SpawnPlayers();
                ChangePhase(GamePhase.EnemySpawning);
                break;
                
            case GamePhase.EnemySpawning:
                // 敌人生成完成后进入游戏进行阶段
                SpawnEnemies();
                ChangePhase(GamePhase.Gameplay);
                break;
                
            case GamePhase.Gameplay:
                // 游戏进行阶段,检查胜利/失败条件
                CheckGameEndConditions();
                break;
                
            case GamePhase.LevelTransition:
                // 关卡过渡阶段,完成后进入下一关
                if (phaseTimer > levelTransitionDuration)
                {
                    currentLevel++;
                    
                    if (currentLevel > maxLevel)
                    {
                        // 通关所有关卡,游戏胜利
                        OnGameEnd?.Invoke(true);
                        ChangePhase(GamePhase.GameOver);
                    }
                    else
                    {
                        // 进入下一关
                        ChangePhase(GamePhase.MapGeneration);
                    }
                }
                break;
                
            case GamePhase.GameOver:
                // 游戏结束阶段
                if (phaseTimer > 3f) // 3秒后返回主菜单
                {
                    gameInProgress = false;
                    if (gameManager != null)
                    {
                        gameManager.ReturnToMainMenu();
                    }
                }
                break;
        }
    }
    
    // 更改游戏阶段
    private void ChangePhase(GamePhase newPhase)
    {
        currentPhase = newPhase;
        phaseTimer = 0;
        
        // 触发阶段变更事件
        OnPhaseChange?.Invoke(newPhase);
        
        // 如果是游戏进行阶段,触发关卡开始事件
        if (newPhase == GamePhase.Gameplay)
        {
            OnLevelStart?.Invoke(currentLevel);
        }
    }
    
    // 生成地图
    private void GenerateMap()
    {
        if (mapGenerator != null)
        {
            // 创建地图配置
            MapGenerator.MapConfig mapConfig = new MapGenerator.MapConfig
            {
                // 根据关卡调整地图参数
                Width = 15,
                Height = 13,
                BreakableWallDensity = 0.5f + (currentLevel * 0.02f),
                PowerUpSpawnChance = 0.2f + (currentLevel * 0.01f)
            };
            
            // 生成地图
            mapGenerator.GenerateMap(mapConfig);
        }
        else
        {
            Debug.LogError("无法找到地图生成器!");
        }
    }
    
    // 生成玩家
    private void SpawnPlayers()
    {
        if (playerManager != null)
        {
            // 生成玩家
            playerManager.InitializePlayers(gameManager.GetPlayerCount(), gameManager.IsVsMode());
        }
        else
        {
            Debug.LogError("无法找到玩家管理器!");
        }
    }
    
    // 生成敌人
    private void SpawnEnemies()
    {
        if (enemySpawner != null && !gameManager.IsVsMode())
        {
            // 计算该关卡的敌人数量和分布
            float difficulty = DifficultyCalculator.CalculateLevelDifficulty(currentLevel);
            int totalEnemies = DifficultyCalculator.CalculateEnemyCount(difficulty, 15 * 13);
            
            Dictionary<EnemyType, int> distribution = 
                DifficultyCalculator.CalculateEnemyDistribution(totalEnemies, difficulty);
                
            // 生成敌人
            enemySpawner.SpawnEnemies(distribution, mapGenerator.GetMapData());
        }
    }
    
    // 检查游戏结束条件
    private void CheckGameEndConditions()
    {
        if (!gameManager.IsVsMode())
        {
            // 单人/合作模式
            
            // 检查是否所有敌人已消灭
            if (enemySpawner.GetRemainingEnemyCount() == 0)
            {
                // 关卡通关
                ChangePhase(GamePhase.LevelTransition);
            }
            
            // 检查是否所有玩家都死亡
            if (playerManager.AreAllPlayersDead())
            {
                // 游戏失败
                OnGameEnd?.Invoke(false);
                ChangePhase(GamePhase.GameOver);
            }
        }
        else
        {
            // 对战模式
            
            // 检查是否只剩一名玩家
            if (playerManager.GetAlivePlayerCount() <= 1)
            {
                // 决出胜负
                OnGameEnd?.Invoke(playerManager.GetAlivePlayerCount() == 1);
                ChangePhase(GamePhase.GameOver);
            }
        }
    }
    
    // 游戏状态变更处理
    private void OnGameStateChanged(GameManager.GameState newState)
    {
        switch (newState)
        {
            case GameManager.GameState.MainMenu:
                gameInProgress = false;
                break;
                
            case GameManager.GameState.Playing:
                if (!gameInProgress)
                {
                    StartNewGame();
                }
                break;
                
            case GameManager.GameState.GameOver:
            case GameManager.GameState.Victory:
                ChangePhase(GamePhase.GameOver);
                break;
        }
    }
    
    // 获取当前关卡
    public int GetCurrentLevel()
    {
        return currentLevel;
    }
    
    // 获取当前游戏阶段
    public GamePhase GetCurrentPhase()
    {
        return currentPhase;
    }
    
    // 判断游戏是否进行中
    public bool IsGameInProgress()
    {
        return gameInProgress;
    }
}

游戏流程管理器以状态机的方式实现了游戏的不同阶段,确保游戏按照预期的顺序从初始化到结束。这种设计可以清晰地分离不同阶段的逻辑,便于调试和扩展。

11.4 炸弹人游戏实现

在理解了炸弹人游戏的架构设计后,我们现在来实现具体的游戏功能。

11.4.1 游戏初始化与资源加载

首先,我们需要实现游戏的初始化逻辑,包括资源加载和基本设置:

csharp

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameInitializer : MonoBehaviour
{
    // 单例实例
    public static GameInitializer Instance { get; private set; }
    
    // 资源加载状态
    [SerializeField] private GameObject loadingScreenPrefab;
    [SerializeField] private string mainMenuSceneName = "MainMenu";
    
    // 加载状态
    private bool isInitialized = false;
    private bool isLoading = false;
    private GameObject loadingScreenInstance;
    
    // 初始化设置
    [System.Serializable]
    public class InitializationSettings
    {
        public bool enableTutorial = true;
        public int targetFrameRate = 60;
        public bool enableVSync = true;
        public float masterVolume = 1.0f;
        public float musicVolume = 0.7f;
        public float effectsVolume = 1.0f;
    }
    
    [SerializeField] private InitializationSettings settings;
    
    // 资源引用
    [System.Serializable]
    public class ResourceReferences
    {
        public GameObject playerPrefab;
        public GameObject bombPrefab;
        public GameObject explosionPrefab;
        public GameObject[] enemyPrefabs;
        public GameObject[] powerUpPrefabs;
        public GameObject[] tilesPrefabs;
        public AudioClip[] soundEffects;
        public AudioClip[] musicTracks;
    }
    
    [SerializeField] private ResourceReferences resources;
    
    private void Awake()
    {
        // 单例模式
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        Instance = this;
        DontDestroyOnLoad(gameObject);
        
        // 初始化游戏
        StartCoroutine(InitializeGameAsync());
    }
    
    // 异步初始化游戏
    private IEnumerator InitializeGameAsync()
    {
        isLoading = true;
        
        // 显示加载画面
        ShowLoadingScreen();
        
        // 应用游戏设置
        ApplyGameSettings();
        
        // 预加载资源
        yield return StartCoroutine(PreloadResourcesAsync());
        
        // 初始化音频系统
        InitializeAudioSystem();
        
        // 初始化输入系统
        InitializeInputSystem();
        
        // 初始化存档系统
        InitializeSaveSystem();
        
        // 完成初始化
        isInitialized = true;
        isLoading = false;
        
        // 隐藏加载画面
        HideLoadingScreen();
        
        // 加载主菜单场景
        SceneManager.LoadScene(mainMenuSceneName);
    }
    
    // 应用游戏设置
    private void ApplyGameSettings()
    {
        // 设置目标帧率
        Application.targetFrameRate = settings.targetFrameRate;
        
        // 设置垂直同步
        QualitySettings.vSyncCount = settings.enableVSync ? 1 : 0;
        
        // 设置音量
        AudioListener.volume = settings.masterVolume;
    }
    
    // 预加载资源
    private IEnumerator PreloadResourcesAsync()
    {
        // 模拟资源加载过程
        float totalLoadTime = 2.0f; // 假设加载需要2秒
        float elapsedTime = 0;
        
        while (elapsedTime < totalLoadTime)
        {
            elapsedTime += Time.deltaTime;
            
            // 更新加载进度
            float progress = elapsedTime / totalLoadTime;
            UpdateLoadingProgress(progress);
            
            yield return null;
        }
        
        // 确保进度显示为100%
        UpdateLoadingProgress(1.0f);
    }
    
    // 初始化音频系统
    private void InitializeAudioSystem()
    {
        // 创建音频管理器
        GameObject audioManagerObject = new GameObject("AudioManager");
        audioManagerObject.transform.SetParent(transform);
        AudioManager audioManager = audioManagerObject.AddComponent<AudioManager>();
        
        // 设置音频资源
        audioManager.Initialize(resources.soundEffects, resources.musicTracks, 
                               settings.musicVolume, settings.effectsVolume);
    }
    
    // 初始化输入系统
    private void InitializeInputSystem()
    {
        // 在实际项目中,这里会初始化输入系统
        // 可能使用Unity的新输入系统或自定义输入管理器
    }
    
    // 初始化存档系统
    private void InitializeSaveSystem()
    {
        // 在实际项目中,这里会初始化存档系统
        // 检查是否有存档文件,加载玩家进度等
    }
    
    // 显示加载画面
    private void ShowLoadingScreen()
    {
        if (loadingScreenPrefab != null && loadingScreenInstance == null)
        {
            loadingScreenInstance = Instantiate(loadingScreenPrefab);
            DontDestroyOnLoad(loadingScreenInstance);
        }
    }
    
    // 隐藏加载画面
    private void HideLoadingScreen()
    {
        if (loadingScreenInstance != null)
        {
            Destroy(loadingScreenInstance);
            loadingScreenInstance = null;
        }
    }
    
    // 更新加载进度
    private void UpdateLoadingProgress(float progress)
    {
        if (loadingScreenInstance != null)
        {
            LoadingScreen loadingScreen = loadingScreenInstance.GetComponent<LoadingScreen>();
            if (loadingScreen != null)
            {
                loadingScreen.UpdateProgress(progress);
            }
        }
    }
    
    // 获取资源引用
    public ResourceReferences GetResourceReferences()
    {
        return resources;
    }
    
    // 判断是否已初始化
    public bool IsInitialized()
    {
        return isInitialized;
    }
    
    // 判断是否正在加载
    public bool IsLoading()
    {
        return isLoading;
    }
    
    // 获取初始化设置
    public InitializationSettings GetSettings()
    {
        return settings;
    }
}

// 加载屏幕类
public class LoadingScreen : MonoBehaviour
{
    [SerializeField] private UnityEngine.UI.Slider progressBar;
    [SerializeField] private TMPro.TextMeshProUGUI progressText;
    
    public void UpdateProgress(float progress)
    {
        if (progressBar != null)
        {
            progressBar.value = progress;
        }
        
        if (progressText != null)
        {
            progressText.text = $"加载中... {Mathf.FloorToInt(progress * 100)}%";
        }
    }
}

游戏初始化器负责游戏的启动流程,包括设置应用程序参数、加载必要资源和初始化各种系统。它使用协程进行异步加载,确保游戏在加载过程中保持响应性。

11.4.2 游戏地图生成与渲染

接下来,我们实现游戏地图的生成和渲染逻辑:

csharp

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class MapRenderer : MonoBehaviour
{
    // 地图网格和图层引用
    [SerializeField] private Grid mapGrid;
    [SerializeField] private Tilemap groundTilemap;
    [SerializeField] private Tilemap wallsTilemap;
    [SerializeField] private Tilemap breakableTilemap;
    
    // 地图图块
    [SerializeField] private TileBase groundTile;
    [SerializeField] private TileBase wallTile;
    [SerializeField] private TileBase breakableTile;
    [SerializeField] private TileBase[] decorativeTiles;
    
    // 地图数据引用
    private MapGenerator.TileType[,] mapData;
    
    // 装饰元素管理
    [SerializeField] private GameObject[] decorationPrefabs;
    private List<GameObject> spawnedDecorations = new List<GameObject>();
    
    // 地图尺寸
    private int mapWidth;
    private int mapHeight;
    
    // 初始化渲染器
    public void Initialize(int width, int height)
    {
        mapWidth = width;
        mapHeight = height;
        
        // 清除现有地图
        ClearMap();
    }
    
    // 渲染地图
    public void RenderMap(MapGenerator.TileType[,] data)
    {
        // 保存地图数据引用
        mapData = data;
        mapWidth = data.GetLength(0);
        mapHeight = data.GetLength(1);
        
        // 清除现有地图
        ClearMap();
        
        // 绘制基本地面
        RenderGround();
        
        // 绘制墙壁和可破坏物
        RenderWallsAndBreakables();
        
        // 添加装饰元素
        AddDecorations();
    }
    
    // 绘制基本地面
    private void RenderGround()
    {
        // 绘制地面图块
        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                Vector3Int position = new Vector3Int(x, y, 0);
                groundTilemap.SetTile(position, groundTile);
                
                // 随机添加一些装饰性地面图块
                if (Random.value < 0.1f && decorativeTiles.Length > 0)
                {
                    TileBase decorTile = decorativeTiles[Random.Range(0, decorativeTiles.Length)];
                    groundTilemap.SetTile(position, decorTile);
                }
            }
        }
    }
    
    // 绘制墙壁和可破坏物
    private void RenderWallsAndBreakables()
    {
        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                Vector3Int position = new Vector3Int(x, y, 0);
                
                // 根据地图数据放置图块
                switch (mapData[x, y])
                {
                    case MapGenerator.TileType.Wall:
                        wallsTilemap.SetTile(position, wallTile);
                        break;
                        
                    case MapGenerator.TileType.Breakable:
                    case MapGenerator.TileType.PowerUp:  // 含道具的可破坏物
                        breakableTilemap.SetTile(position, breakableTile);
                        break;
                }
            }
        }
    }
    
    // 添加装饰元素
    private void AddDecorations()
    {
        // 清除现有装饰物
        ClearDecorations();
        
        // 边界处理
        const float decorationDensity = 0.2f; // 装饰物密度
        
        // 在地图边界添加装饰物
        for (int x = -1; x <= mapWidth; x++)
        {
            for (int y = -1; y <= mapHeight; y++)
            {
                // 只在地图边界外围添加装饰
                if (x >= 0 && x < mapWidth && y >= 0 && y < mapHeight)
                    continue;
                    
                // 随机决定是否添加装饰物
                if (Random.value > decorationDensity)
                    continue;
                    
                // 随机选择装饰物
                if (decorationPrefabs.Length == 0)
                    continue;
                    
                GameObject decorPrefab = decorationPrefabs[Random.Range(0, decorationPrefabs.Length)];
                
                // 计算世界坐标
                Vector3 worldPos = mapGrid.CellToWorld(new Vector3Int(x, y, 0)) + 
                                   new Vector3(0.5f, 0.5f, 0); // 居中放置
                
                // 实例化装饰物
                GameObject decoration = Instantiate(decorPrefab, worldPos, Quaternion.identity);
                decoration.transform.SetParent(transform);
                
                // 随机旋转和缩放
                decoration.transform.rotation = Quaternion.Euler(0, 0, Random.Range(0, 360));
                float scale = Random.Range(0.8f, 1.2f);
                decoration.transform.localScale = new Vector3(scale, scale, 1);
                
                // 添加到列表以便后续清理
                spawnedDecorations.Add(decoration);
            }
        }
    }
    
    // 清除地图
    private void ClearMap()
    {
        // 清除各图层
        groundTilemap.ClearAllTiles();
        wallsTilemap.ClearAllTiles();
        breakableTilemap.ClearAllTiles();
        
        // 清除装饰物
        ClearDecorations();
    }
    
    // 清除装饰物
    private void ClearDecorations()
    {
        foreach (GameObject decoration in spawnedDecorations)
        {
            if (decoration != null)
            {
                Destroy(decoration);
            }
        }
        
        spawnedDecorations.Clear();
    }
    
    // 更新可破坏墙
    public void UpdateBreakableTile(int x, int y, bool isDestroyed)
    {
        Vector3Int position = new Vector3Int(x, y, 0);
        
        if (isDestroyed)
        {
            breakableTilemap.SetTile(position, null);
        }
        else
        {
            breakableTilemap.SetTile(position, breakableTile);
        }
    }
    
    // 获取图块世界坐标
    public Vector3 GetTileWorldPosition(int x, int y)
    {
        Vector3Int cellPosition = new Vector3Int(x, y, 0);
        return mapGrid.CellToWorld(cellPosition) + new Vector3(0.5f, 0.5f, 0);
    }
    
    // 世界坐标转图块坐标
    public Vector2Int WorldToTilePosition(Vector3 worldPosition)
    {
        Vector3Int cellPosition = mapGrid.WorldToCell(worldPosition);
        return new Vector2Int(cellPosition.x, cellPosition.y);
    }
}

地图渲染器负责将地图数据转换为可视化表示。它利用Unity的Tilemap系统高效地渲染大量图块,并添加装饰元素增强视觉效果。

11.4.3 玩家角色实现

下面我们实现玩家角色系统,包括移动、放炸弹和收集道具的功能:

csharp

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    // 玩家数据
    [SerializeField] private int playerId = 0;
    [SerializeField] private string playerName = "Player 1";
    
    // 移动设置
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float acceleration = 50f;
    [SerializeField] private float deceleration = 30f;
    
    // 炸弹设置
    [SerializeField] private int bombCapacity = 1;
    [SerializeField] private int explosionRange = 2;
    [SerializeField] private float bombCooldown = 0.5f;
    
    // 组件引用
    [SerializeField] private Rigidbody2D rb;
    [SerializeField] private SpriteRenderer spriteRenderer;
    [SerializeField] private Animator animator;
    
    // 状态变量
    private Vector2 moveDirection;
    private Vector2 smoothedMovement;
    private bool canPlaceBomb = true;
    private float lastBombTime;
    private int currentBombCount = 0;
    private bool isAlive = true;
    
    // 特殊能力状态
    private bool canPassThroughBreakables = false;
    private bool hasRemoteDetonator = false;
    private float invincibilityTime = 0;
    
    // 事件
    public event Action<int> OnPlayerDeath;
    public event Action<PowerUpType> OnPowerUpCollected;
    
    // 外部组件引用
    private BombManager bombManager;
    private MapData mapData;
    private AudioManager audioManager;
    
    // 初始化玩家
    public void Initialize(int id, string name, Vector2 startPosition, BombManager bombMgr, MapData map)
    {
        // 设置基本属性
        playerId = id;
        playerName = name;
        bombManager = bombMgr;
        mapData = map;
        transform.position = new Vector3(startPosition.x, startPosition.y, 0);
        
        // 初始化状态
        isAlive = true;
        currentBombCount = 0;
        
        // 寻找音频管理器
        audioManager = FindObjectOfType<AudioManager>();
        
        // 重置能力状态
        canPassThroughBreakables = false;
        hasRemoteDetonator = false;
        invincibilityTime = 0;
        
        // 更新视觉效果
        UpdateVisuals();
    }
    
    // 更新函数
    private void Update()
    {
        if (!isAlive)
            return;
            
        // 更新无敌时间
        if (invincibilityTime > 0)
        {
            invincibilityTime -= Time.deltaTime;
            
            // 闪烁效果
            if (spriteRenderer != null)
            {
                spriteRenderer.enabled = Time.time % 0.2f < 0.1f;
            }
        }
        else
        {
            // 确保精灵可见
            if (spriteRenderer != null)
            {
                spriteRenderer.enabled = true;
            }
        }
        
        // 处理炸弹放置冷却
        if (!canPlaceBomb && Time.time > lastBombTime + bombCooldown)
        {
            canPlaceBomb = true;
        }
    }
    
    // 物理更新
    private void FixedUpdate()
    {
        if (!isAlive)
            return;
            
        // 平滑移动
        Vector2 targetVelocity = moveDirection * moveSpeed;
        
        // 应用加速度或减速度
        if (moveDirection.sqrMagnitude > 0.1f)
        {
            smoothedMovement = Vector2.MoveTowards(
                smoothedMovement, targetVelocity, acceleration * Time.fixedDeltaTime);
        }
        else
        {
            smoothedMovement = Vector2.MoveTowards(
                smoothedMovement, Vector2.zero, deceleration * Time.fixedDeltaTime);
        }
        
        // 应用移动
        rb.velocity = smoothedMovement;
        
        // 更新动画
        UpdateAnimation();
    }
    
    // 输入处理 - 移动
    public void OnMove(InputAction.CallbackContext context)
    {
        if (!isAlive)
            return;
            
        // 读取输入方向
        moveDirection = context.ReadValue<Vector2>();
    }
    
    // 输入处理 - 放置炸弹
    public void OnBombPlace(InputAction.CallbackContext context)
    {
        if (!isAlive || !canPlaceBomb || currentBombCount >= bombCapacity)
            return;
            
        if (context.performed)
        {
            PlaceBomb();
        }
    }
    
    // 输入处理 - 触发遥控炸弹
    public void OnBombTrigger(InputAction.CallbackContext context)
    {
        if (!isAlive || !hasRemoteDetonator)
            return;
            
        if (context.performed)
        {
            DetonateRemoteBombs();
        }
    }
    
    // 放置炸弹
    private void PlaceBomb()
    {
        // 获取当前网格位置
        Vector2Int gridPosition = GetCurrentGridPosition();
        
        // 检查该位置是否可以放炸弹
        if (bombManager.PlaceBomb(gridPosition.x, gridPosition.y, playerId, explosionRange))
        {
            // 放置成功,更新状态
            canPlaceBomb = false;
            lastBombTime = Time.time;
            currentBombCount++;
            
            // 播放音效
            if (audioManager != null)
            {
                audioManager.PlaySound("BombPlace");
            }
            
            // 订阅炸弹爆炸事件
            bombManager.OnBombExplode += HandleBombExploded;
        }
    }
    
    // 引爆遥控炸弹
    private void DetonateRemoteBombs()
    {
        bombManager.DetonateRemoteBombs(playerId);
    }
    
    // 处理炸弹爆炸事件
    private void HandleBombExploded(Vector2Int position, int explosionRange)
    {
        // 如果是玩家放的炸弹爆炸,减少计数
        if (currentBombCount > 0)
        {
            currentBombCount--;
        }
    }
    
    // 获取当前网格位置
    private Vector2Int GetCurrentGridPosition()
    {
        // 将世界坐标转为网格坐标
        Vector3 worldPos = transform.position;
        int x = Mathf.RoundToInt(worldPos.x);
        int y = Mathf.RoundToInt(worldPos.y);
        
        return new Vector2Int(x, y);
    }
    
    // 更新动画
    private void UpdateAnimation()
    {
        if (animator == null)
            return;
            
        // 设置移动动画参数
        animator.SetFloat("MoveX", moveDirection.x);
        animator.SetFloat("MoveY", moveDirection.y);
        animator.SetFloat("MoveMagnitude", moveDirection.magnitude);
        
        // 如果移动,保存最后朝向
        if (moveDirection.sqrMagnitude > 0.1f)
        {
            animator.SetFloat("LastMoveX", moveDirection.x);
            animator.SetFloat("LastMoveY", moveDirection.y);
        }
    }
    
    // 更新视觉效果
    private void UpdateVisuals()
    {
        // 设置颜色或外观,区分不同玩家
        if (spriteRenderer != null)
        {
            // 为不同玩家设置不同色调
            switch (playerId)
            {
                case 0: // 玩家1 - 白色
                    spriteRenderer.color = Color.white;
                    break;
                case 1: // 玩家2 - 蓝色
                    spriteRenderer.color = new Color(0.7f, 0.7f, 1.0f);
                    break;
                case 2: // 玩家3 - 绿色
                    spriteRenderer.color = new Color(0.7f, 1.0f, 0.7f);
                    break;
                case 3: // 玩家4 - 黄色
                    spriteRenderer.color = new Color(1.0f, 1.0f, 0.7f);
                    break;
            }
        }
    }
    
    // 碰撞处理
    private void OnTriggerEnter2D(Collider2D other)
    {
        // 处理道具收集
        if (other.CompareTag("PowerUp"))
        {
            PowerUp powerUp = other.GetComponent<PowerUp>();
            if (powerUp != null)
            {
                CollectPowerUp(powerUp);
            }
        }
        
        // 处理爆炸伤害
        if (other.CompareTag("Explosion"))
        {
            // 如果无敌,则忽略伤害
            if (invincibilityTime > 0)
                return;
                
            TakeDamage();
        }
    }
    
    // 收集道具
    private void CollectPowerUp(PowerUp powerUp)
    {
        // 应用道具效果
        switch (powerUp.Type)
        {
            case PowerUpType.ExtraBomb:
                bombCapacity++;
                break;
                
            case PowerUpType.ExplosionRange:
                explosionRange++;
                break;
                
            case PowerUpType.SpeedBoost:
                moveSpeed += 1.0f;
                moveSpeed = Mathf.Min(moveSpeed, 10f); // 最大速度上限
                break;
                
            case PowerUpType.PassThroughWalls:
                canPassThroughBreakables = true;
                StartCoroutine(DisablePassThroughAfterTime(15f)); // 15秒后禁用
                break;
                
            case PowerUpType.RemoteDetonator:
                hasRemoteDetonator = true;
                break;
        }
        
        // 播放音效
        if (audioManager != null)
        {
            audioManager.PlaySound("PowerUp");
        }
        
        // 通知收集事件
        OnPowerUpCollected?.Invoke(powerUp.Type);
        
        // 销毁道具对象
        Destroy(powerUp.gameObject);
    }
    
    // 禁用穿墙能力的协程
    private IEnumerator DisablePassThroughAfterTime(float time)
    {
        yield return new WaitForSeconds(time);
        canPassThroughBreakables = false;
    }
    
    // 受到伤害
    public void TakeDamage()
    {
        // 如果无敌,则忽略伤害
        if (invincibilityTime > 0)
            return;
            
        // 玩家死亡
        isAlive = false;
        
        // 播放死亡动画
        if (animator != null)
        {
            animator.SetTrigger("Die");
        }
        
        // 播放死亡音效
        if (audioManager != null)
        {
            audioManager.PlaySound("PlayerDeath");
        }
        
        // 停止移动
        rb.velocity = Vector2.zero;
        moveDirection = Vector2.zero;
        
        // 触发死亡事件
        OnPlayerDeath?.Invoke(playerId);
        
        // 延迟销毁或重生
        StartCoroutine(DeathSequence());
    }
    
    // 死亡序列
    private IEnumerator DeathSequence()
    {
        // 等待一段时间显示死亡动画
        yield return new WaitForSeconds(1.5f);
        
        // 禁用玩家对象
        gameObject.SetActive(false);
    }
    
    // 重生
    public void Respawn(Vector2 position)
    {
        // 重置位置
        transform.position = new Vector3(position.x, position.y, 0);
        
        // 重置状态
        isAlive = true;
        canPlaceBomb = true;
        currentBombCount = 0;
        
        // 启用玩家对象
        gameObject.SetActive(true);
        
        // 设置短暂无敌期
        invincibilityTime = 3.0f;
        
        // 重置动画
        if (animator != null)
        {
            animator.SetTrigger("Respawn");
        }
    }
    
    // 属性访问器
    public int GetPlayerId() => playerId;
    public string GetPlayerName() => playerName;
    public bool IsAlive() => isAlive;
    public int GetBombCapacity() => bombCapacity;
    public int GetExplosionRange() => explosionRange;
    public bool HasRemoteDetonator() => hasRemoteDetonator;
    
    // 设置爆炸范围
    public void SetExplosionRange(int range)
    {
        explosionRange = range;
    }
    
    // 设置炸弹容量
    public void SetBombCapacity(int capacity)
    {
        bombCapacity = capacity;
    }
}

玩家控制器实现了玩家的移动、炸弹放置和道具收集等核心功能。它使用物理系统实现平滑移动,并通过动画系统表现玩家状态。

11.4.4 炸弹和爆炸效果实现

下面我们实现炸弹系统和爆炸效果:

csharp

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bomb : MonoBehaviour
{
    // 炸弹属性
    [SerializeField] private float fuseTime = 3f;
    [SerializeField] private int explosionRange = 2;
    [SerializeField] private bool isRemoteControlled = false;
    
    // 组件引用
    [SerializeField] private Animator animator;
    [SerializeField] private CircleCollider2D bombCollider;
    
    // 状态变量
    private bool hasExploded = false;
    private float timer = 0;
    private int ownerId;
    private BombManager bombManager;
    
    // 视觉效果
    [SerializeField] private GameObject explosionPrefab;
    [SerializeField] private GameObject centerExplosionPrefab;
    [SerializeField] private GameObject horizontalExplosionPrefab;
    [SerializeField] private GameObject verticalExplosionPrefab;
    [SerializeField] private GameObject endExplosionPrefab;
    
    // 初始化炸弹
    public void Initialize(int owner, int range, BombManager manager, bool remote = false)
    {
        ownerId = owner;
        explosionRange = range;
        bombManager = manager;
        isRemoteControlled = remote;
        
        // 初始不可碰撞,避免卡住玩家
        StartCoroutine(EnableCollisionAfterDelay(0.5f));
        
        // 播放动画
        if (animator != null)
        {
            animator.SetBool("IsRemote", isRemoteControlled);
        }
    }
    
    // 启用碰撞的延迟
    private IEnumerator EnableCollisionAfterDelay(float delay)
    {
        if (bombCollider != null)
        {
            bombCollider.enabled = false;
            yield return new WaitForSeconds(delay);
            bombCollider.enabled = true;
        }
    }
    
    private void Update()
    {
        if (hasExploded || isRemoteControlled)
            return;
            
        // 更新计时器
        timer += Time.deltaTime;
        
        // 更新闪烁频率
        if (animator != null)
        {
            float normalizedTime = timer / fuseTime;
            animator.SetFloat("FuseProgress", normalizedTime);
        }
        
        // 检查是否应该爆炸
        if (timer >= fuseTime)
        {
            Explode();
        }
    }
    
    // 爆炸方法
    public void Explode()
    {
        if (hasExploded)
            return;
            
        hasExploded = true;
        
        // 计算爆炸范围内的格子
        List<Vector2Int> explosionTiles = CalculateExplosionTiles();
        
        // 创建爆炸效果
        CreateExplosionEffects(explosionTiles);
        
        // 通知炸弹管理器
        if (bombManager != null)
        {
            bombManager.OnBombExploded(ownerId, transform.position);
        }
        
        // 播放爆炸音效
        AudioSource audioSource = GetComponent<AudioSource>();
        if (audioSource != null && audioSource.clip != null)
        {
            audioSource.Play();
        }
        else
        {
            // 使用全局音频管理器
            AudioManager audioManager = FindObjectOfType<AudioManager>();
            if (audioManager != null)
            {
                audioManager.PlaySound("Explosion");
            }
        }
        
        // 延迟销毁炸弹对象
        StartCoroutine(DestroyAfterExplosion());
    }
    
    // 计算爆炸范围内的格子
    private List<Vector2Int> CalculateExplosionTiles()
    {
        List<Vector2Int> tiles = new List<Vector2Int>();
        Vector2Int center = GetGridPosition();
        
        // 添加中心点
        tiles.Add(center);
        
        // 四个方向:上、右、下、左
        Vector2Int[] directions = 
        {
            new Vector2Int(0, 1),   // 上
            new Vector2Int(1, 0),   // 右
            new Vector2Int(0, -1),  // 下
            new Vector2Int(-1, 0)   // 左
        };
        
        // 对每个方向计算爆炸范围
        foreach (Vector2Int dir in directions)
        {
            // 沿着方向追踪爆炸范围
            for (int i = 1; i <= explosionRange; i++)
            {
                Vector2Int checkPos = center + dir * i;
                
                // 检查该位置的类型
                MapGenerator.TileType tileType = GetTileTypeAt(checkPos);
                
                // 将该位置添加到爆炸范围
                tiles.Add(checkPos);
                
                // 如果遇到墙或可破坏物,爆炸停止
                if (tileType == MapGenerator.TileType.Wall || 
                    tileType == MapGenerator.TileType.Breakable || 
                    tileType == MapGenerator.TileType.PowerUp)
                {
                    break;
                }
            }
        }
        
        return tiles;
    }
    
    // 创建爆炸效果
    private void CreateExplosionEffects(List<Vector2Int> explosionTiles)
    {
        Vector2Int center = GetGridPosition();
        
        // 创建中心爆炸效果
        Vector3 centerPos = GridToWorldPosition(center);
        GameObject centerExplosion = Instantiate(centerExplosionPrefab, centerPos, Quaternion.identity);
        Destroy(centerExplosion, 1.0f);
        
        // 四个方向的爆炸
        Vector2Int[] directions = 
        {
            new Vector2Int(0, 1),   // 上
            new Vector2Int(1, 0),   // 右
            new Vector2Int(0, -1),  // 下
            new Vector2Int(-1, 0)   // 左
        };
        
        // 对每个方向创建爆炸效果
        foreach (Vector2Int dir in directions)
        {
            List<Vector2Int> directionTiles = new List<Vector2Int>();
            
            // 收集该方向的所有爆炸格子
            for (int i = 1; i <= explosionRange; i++)
            {
                Vector2Int checkPos = center + dir * i;
                
                if (explosionTiles.Contains(checkPos))
                {
                    directionTiles.Add(checkPos);
                    
                    // 如果遇到墙或可破坏物,这是该方向的最后一个格子
                    MapGenerator.TileType tileType = GetTileTypeAt(checkPos);
                    if (tileType == MapGenerator.TileType.Wall || 
                        tileType == MapGenerator.TileType.Breakable || 
                        tileType == MapGenerator.TileType.PowerUp)
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            
            // 为每个格子创建适当的爆炸效果
            for (int i = 0; i < directionTiles.Count; i++)
            {
                Vector3 tilePos = GridToWorldPosition(directionTiles[i]);
                GameObject explosionEffect;
                Quaternion rotation = Quaternion.identity;
                
                if (i == directionTiles.Count - 1)
                {
                    // 爆炸末端
                    explosionEffect = endExplosionPrefab;
                }
                else
                {
                    // 爆炸中间部分
                    if (dir.x != 0)
                    {
                        // 水平方向
                        explosionEffect = horizontalExplosionPrefab;
                    }
                    else
                    {
                        // 垂直方向
                        explosionEffect = verticalExplosionPrefab;
                    }
                }
                
                // 设置朝向
                if (dir.x > 0) rotation = Quaternion.Euler(0, 0, 0);
                else if (dir.x < 0) rotation = Quaternion.Euler(0, 0, 180);
                else if (dir.y > 0) rotation = Quaternion.Euler(0, 0, 90);
                else if (dir.y < 0) rotation = Quaternion.Euler(0, 0, 270);
                
                // 创建爆炸效果
                GameObject effect = Instantiate(explosionEffect, tilePos, rotation);
                Destroy(effect, 1.0f);
                
                // 添加爆炸碰撞体
                ExplosionDamage damage = effect.AddComponent<ExplosionDamage>();
                damage.Initialize(ownerId);
            }
        }
    }
    
    // 爆炸后销毁
    private IEnumerator DestroyAfterExplosion()
    {
        // 隐藏炸弹模型
        SpriteRenderer renderer = GetComponent<SpriteRenderer>();
        if (renderer != null)
        {
            renderer.enabled = false;
        }
        
        // 等待一段时间(爆炸效果持续时间)
        yield return new WaitForSeconds(1.0f);
        
        // 销毁炸弹对象
        Destroy(gameObject);
    }
    
    // 获取当前网格位置
    private Vector2Int GetGridPosition()
    {
        Vector3 worldPos = transform.position;
        return new Vector2Int(Mathf.RoundToInt(worldPos.x), Mathf.RoundToInt(worldPos.y));
    }
    
    // 网格坐标转世界坐标
    private Vector3 GridToWorldPosition(Vector2Int gridPos)
    {
        return new Vector3(gridPos.x, gridPos.y, 0);
    }
    
    // 获取指定位置的图块类型
    private MapGenerator.TileType GetTileTypeAt(Vector2Int position)
    {
        // 在实际游戏中,这里会查询地图数据
        // 简化实现:使用射线检测物理对象
        
        Vector3 worldPos = GridToWorldPosition(position);
        Collider2D collider = Physics2D.OverlapPoint(worldPos);
        
        if (collider != null)
        {
            if (collider.CompareTag("Wall"))
                return MapGenerator.TileType.Wall;
            if (collider.CompareTag("Breakable"))
                return MapGenerator.TileType.Breakable;
            if (collider.CompareTag("PowerUp"))
                return MapGenerator.TileType.PowerUp;
        }
        
        return MapGenerator.TileType.Empty;
    }
    
    // 设置为遥控炸弹
    public void SetRemoteControlled(bool isRemote)
    {
        isRemoteControlled = isRemote;
        
        // 更新动画状态
        if (animator != null)
        {
            animator.SetBool("IsRemote", isRemote);
        }
    }
}

// 爆炸伤害组件
public class ExplosionDamage : MonoBehaviour
{
    private int ownerId;
    
    public void Initialize(int owner)
    {
        ownerId = owner;
    }
    
    private void OnTriggerEnter2D(Collider2D other)
    {
        // 处理可破坏物
        if (other.CompareTag("Breakable"))
        {
            BreakableWall breakable = other.GetComponent<BreakableWall>();
            if (breakable != null)
            {
                breakable.Break();
            }
        }
        
        // 处理炸弹连锁反应
        if (other.CompareTag("Bomb"))
        {
            Bomb bomb = other.GetComponent<Bomb>();
            if (bomb != null)
            {
                StartCoroutine(TriggerBombAfterDelay(bomb, 0.1f));
            }
        }
        
        // 处理玩家伤害
        if (other.CompareTag("Player"))
        {
            PlayerController player = other.GetComponent<PlayerController>();
            if (player != null)
            {
                player.TakeDamage();
            }
        }
        
        // 处理敌人伤害
        if (other.CompareTag("Enemy"))
        {
            EnemyController enemy = other.GetComponent<EnemyController>();
            if (enemy != null)
            {
                enemy.TakeDamage();
            }
        }
    }
    
    // 延迟触发炸弹爆炸(制造连锁效果)
    private IEnumerator TriggerBombAfterDelay(Bomb bomb, float delay)
    {
        yield return new WaitForSeconds(delay);
        bomb.Explode();
    }
}

炸弹系统实现了炸弹的放置、计时、爆炸和伤害机制。它处理炸弹爆炸时的视觉效果、连锁反应和对不同游戏对象(玩家、敌人、可破坏墙壁)的影响。

11.4.5 游戏UI系统

最后,我们实现游戏UI系统,提供游戏信息和用户交互:

csharp

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;

public class UIManager : MonoBehaviour
{
    [Header("面板引用")]
    [SerializeField] private GameObject mainMenuPanel;
    [SerializeField] private GameObject gameplayPanel;
    [SerializeField] private GameObject pausePanel;
    [SerializeField] private GameObject gameOverPanel;
    [SerializeField] private GameObject loadingPanel;
    
    [Header("游戏信息UI")]
    [SerializeField] private TextMeshProUGUI levelText;
    [SerializeField] private TextMeshProUGUI timerText;
    [SerializeField] private TextMeshProUGUI scoreText;
    
    [Header("玩家状态UI")]
    [SerializeField] private GameObject playerStatusPrefab;
    [SerializeField] private Transform playerStatusContainer;
    [SerializeField] private Sprite bombCapacityIcon;
    [SerializeField] private Sprite explosionRangeIcon;
    [SerializeField] private Sprite speedIcon;
    
    [Header("结束画面UI")]
    [SerializeField] private TextMeshProUGUI gameOverTitleText;
    [SerializeField] private TextMeshProUGUI gameOverStatsText;
    [SerializeField] private Button nextLevelButton;
    [SerializeField] private Button retryButton;
    [SerializeField] private Button menuButton;
    
    [Header("淡入淡出效果")]
    [SerializeField] private CanvasGroup fadeCanvasGroup;
    [SerializeField] private float fadeDuration = 0.5f;
    
    // 状态变量
    private GameManager gameManager;
    private PlayerManager playerManager;
    private float gameTime = 0;
    private int score = 0;
    private PlayerStatusUI[] playerStatusUIs;
    
    // 初始化
    private void Awake()
    {
        // 查找游戏管理器
        gameManager = FindObjectOfType<GameManager>();
        playerManager = FindObjectOfType<PlayerManager>();
        
        if (gameManager != null)
        {
            // 订阅游戏状态变更事件
            gameManager.OnGameStateChanged += OnGameStateChanged;
        }
    }
    
    private void Start()
    {
        // 默认显示主菜单
        ShowMainMenu();
        
        // 初始化按钮事件
        InitializeButtons();
    }
    
    // 初始化按钮事件
    private void InitializeButtons()
    {
        // 主菜单按钮
        Transform startButton = mainMenuPanel.transform.Find("StartButton");
        if (startButton != null)
        {
            Button button = startButton.GetComponent<Button>();
            if (button != null)
            {
                button.onClick.AddListener(OnStartButtonClicked);
            }
        }
        
        // 暂停界面按钮
        if (pausePanel != null)
        {
            Transform resumeButton = pausePanel.transform.Find("ResumeButton");
            if (resumeButton != null)
            {
                Button button = resumeButton.GetComponent<Button>();
                if (button != null)
                {
                    button.onClick.AddListener(OnResumeButtonClicked);
                }
            }
            
            Transform exitButton = pausePanel.transform.Find("ExitButton");
            if (exitButton != null)
            {
                Button button = exitButton.GetComponent<Button>();
                if (button != null)
                {
                    button.onClick.AddListener(OnExitButtonClicked);
                }
            }
        }
        
        // 游戏结束界面按钮
        if (nextLevelButton != null)
        {
            nextLevelButton.onClick.AddListener(OnNextLevelButtonClicked);
        }
        
        if (retryButton != null)
        {
            retryButton.onClick.AddListener(OnRetryButtonClicked);
        }
        
        if (menuButton != null)
        {
            menuButton.onClick.AddListener(OnExitButtonClicked);
        }
    }
    
    // 游戏状态变更处理
    private void OnGameStateChanged(GameManager.GameState newState)
    {
        switch (newState)
        {
            case GameManager.GameState.MainMenu:
                ShowMainMenu();
                break;
                
            case GameManager.GameState.Loading:
                ShowLoading();
                break;
                
            case GameManager.GameState.Playing:
                ShowGameplay();
                break;
                
            case GameManager.GameState.Paused:
                ShowPauseMenu();
                break;
                
            case GameManager.GameState.GameOver:
                ShowGameOver(false);
                break;
                
            case GameManager.GameState.Victory:
                ShowGameOver(true);
                break;
        }
    }
    
    // 显示主菜单
    private void ShowMainMenu()
    {
        HideAllPanels();
        mainMenuPanel.SetActive(true);
        StartCoroutine(FadeIn());
    }
    
    // 显示游戏界面
    private void ShowGameplay()
    {
        HideAllPanels();
        gameplayPanel.SetActive(true);
        StartCoroutine(FadeIn());
        
        // 初始化玩家状态UI
        InitializePlayerStatusUI();
    }
    
    // 显示暂停菜单
    private void ShowPauseMenu()
    {
        pausePanel.SetActive(true);
    }
    
    // 显示加载界面
    public void ShowLoading()
    {
        HideAllPanels();
        loadingPanel.SetActive(true);
        StartCoroutine(FadeIn());
    }
    
    // 显示游戏结束画面
    public void ShowGameOver(bool isVictory, int level = 0, int enemiesDefeated = 0, int itemsCollected = 0)
    {
        HideAllPanels();
        gameOverPanel.SetActive(true);
        
        // 设置标题
        if (gameOverTitleText != null)
        {
            gameOverTitleText.text = isVictory ? "胜利!" : "游戏结束";
        }
        
        // 设置统计信息
        if (gameOverStatsText != null)
        {
            int minutes = Mathf.FloorToInt(gameTime / 60);
            int seconds = Mathf.FloorToInt(gameTime % 60);
            
            gameOverStatsText.text = $"关卡: {level}\n" +
                                     $"用时: {minutes:00}:{seconds:00}\n" +
                                     $"敌人消灭: {enemiesDefeated}\n" +
                                     $"道具收集: {itemsCollected}\n" +
                                     $"总分: {score}";
        }
        
        // 设置按钮可见性
        if (nextLevelButton != null)
        {
            nextLevelButton.gameObject.SetActive(isVictory);
        }
        
        StartCoroutine(FadeIn());
    }
    
    // 隐藏所有面板
    private void HideAllPanels()
    {
        mainMenuPanel.SetActive(false);
        gameplayPanel.SetActive(false);
        pausePanel.SetActive(false);
        gameOverPanel.SetActive(false);
        loadingPanel.SetActive(false);
    }
    
    // 初始化玩家状态UI
    private void InitializePlayerStatusUI()
    {
        // 清除现有状态UI
        foreach (Transform child in playerStatusContainer)
        {
            Destroy(child.gameObject);
        }
        
        // 获取玩家数量
        int playerCount = playerManager != null ? playerManager.GetPlayerCount() : 1;
        
        // 创建新的状态UI
        playerStatusUIs = new PlayerStatusUI[playerCount];
        
        for (int i = 0; i < playerCount; i++)
        {
            GameObject statusObject = Instantiate(playerStatusPrefab, playerStatusContainer);
            PlayerStatusUI statusUI = statusObject.GetComponent<PlayerStatusUI>();
            
            if (statusUI != null)
            {
                PlayerController player = playerManager.GetPlayer(i);
                if (player != null)
                {
                    statusUI.Initialize(player);
                }
                
                playerStatusUIs[i] = statusUI;
            }
        }
    }
    
    // 更新游戏时间
    public void UpdateTime(float time)
    {
        gameTime = time;
        
        int minutes = Mathf.FloorToInt(time / 60);
        int seconds = Mathf.FloorToInt(time % 60);
        
        if (timerText != null)
        {
            timerText.text = $"{minutes:00}:{seconds:00}";
        }
    }
    
    // 更新关卡信息
    public void UpdateLevel(int level)
    {
        if (levelText != null)
        {
            levelText.text = $"关卡 {level}";
        }
    }
    
    // 更新分数
    public void UpdateScore(int newScore)
    {
        score = newScore;
        
        if (scoreText != null)
        {
            scoreText.text = $"分数: {score}";
        }
    }
    
    // 更新加载进度
    public void UpdateLoadingProgress(float progress)
    {
        if (loadingPanel != null)
        {
            Slider progressBar = loadingPanel.GetComponentInChildren<Slider>();
            if (progressBar != null)
            {
                progressBar.value = progress;
            }
            
            TextMeshProUGUI progressText = loadingPanel.GetComponentInChildren<TextMeshProUGUI>();
            if (progressText != null)
            {
                progressText.text = $"加载中... {Mathf.FloorToInt(progress * 100)}%";
            }
        }
    }
    
    // 按钮事件处理
    private void OnStartButtonClicked()
    {
        if (gameManager != null)
        {
            StartCoroutine(FadeOutAndSwitchState(GameManager.GameState.Loading));
        }
    }
    
    private void OnResumeButtonClicked()
    {
        if (gameManager != null)
        {
            gameManager.SetGameState(GameManager.GameState.Playing);
        }
    }
    
    private void OnExitButtonClicked()
    {
        if (gameManager != null)
        {
            StartCoroutine(FadeOutAndSwitchState(GameManager.GameState.MainMenu));
        }
    }
    
    private void OnNextLevelButtonClicked()
    {
        if (gameManager != null)
        {
            gameManager.StartNextLevel();
        }
    }
    
    private void OnRetryButtonClicked()
    {
        if (gameManager != null)
        {
            int currentLevel = gameManager.GetCurrentLevel();
            gameManager.StartNewGame(gameManager.GetPlayerCount(), gameManager.IsVsMode());
        }
    }
    
    // 淡出并切换状态
    private IEnumerator FadeOutAndSwitchState(GameManager.GameState newState)
    {
        // 淡出
        yield return StartCoroutine(FadeOut());
        
        // 切换状态
        if (gameManager != null)
        {
            gameManager.SetGameState(newState);
        }
    }
    
    // 淡入效果
    private IEnumerator FadeIn()
    {
        if (fadeCanvasGroup != null)
        {
            fadeCanvasGroup.alpha = 1;
            
            float timer = 0;
            while (timer < fadeDuration)
            {
                timer += Time.deltaTime;
                fadeCanvasGroup.alpha = 1 - (timer / fadeDuration);
                yield return null;
            }
            
            fadeCanvasGroup.alpha = 0;
        }
    }
    
    // 淡出效果
    private IEnumerator FadeOut()
    {
        if (fadeCanvasGroup != null)
        {
            fadeCanvasGroup.alpha = 0;
            
            float timer = 0;
            while (timer < fadeDuration)
            {
                timer += Time.deltaTime;
                fadeCanvasGroup.alpha = timer / fadeDuration;
                yield return null;
            }
            
            fadeCanvasGroup.alpha = 1;
        }
    }
}

// 玩家状态UI类
public class PlayerStatusUI : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI playerNameText;
    [SerializeField] private Image playerIcon;
    [SerializeField] private TextMeshProUGUI bombCapacityText;
    [SerializeField] private TextMeshProUGUI explosionRangeText;
    [SerializeField] private GameObject aliveIndicator;
    [SerializeField] private GameObject deadIndicator;
    
    private PlayerController trackedPlayer;
    
    public void Initialize(PlayerController player)
    {
        trackedPlayer = player;
        
        if (player != null)
        {
            // 设置玩家名称
            if (playerNameText != null)
            {
                playerNameText.text = player.GetPlayerName();
            }
            
            // 设置玩家图标颜色以匹配玩家精灵
            if (playerIcon != null)
            {
                SpriteRenderer playerRenderer = player.GetComponent<SpriteRenderer>();
                if (playerRenderer != null)
                {
                    playerIcon.color = playerRenderer.color;
                }
            }
            
            // 订阅玩家事件
            player.OnPlayerDeath += OnPlayerDeathHandler;
            player.OnPowerUpCollected += OnPowerUpCollectedHandler;
            
            // 更新初始状态
            UpdateStatus();
        }
    }
    
    private void Update()
    {
        if (trackedPlayer != null)
        {
            // 定期更新状态
            UpdateStatus();
        }
    }
    
    // 更新玩家状态显示
    private void UpdateStatus()
    {
        // 更新炸弹容量
        if (bombCapacityText != null)
        {
            bombCapacityText.text = trackedPlayer.GetBombCapacity().ToString();
        }
        
        // 更新爆炸范围
        if (explosionRangeText != null)
        {
            explosionRangeText.text = trackedPlayer.GetExplosionRange().ToString();
        }
        
        // 更新生存状态
        bool isAlive = trackedPlayer.IsAlive();
        if (aliveIndicator != null) aliveIndicator.SetActive(isAlive);
        if (deadIndicator != null) deadIndicator.SetActive(!isAlive);
    }
    
    // 处理玩家死亡事件
    private void OnPlayerDeathHandler(int playerId)
    {
        UpdateStatus();
    }
    
    // 处理道具收集事件
    private void OnPowerUpCollectedHandler(PowerUpType powerUpType)
    {
        UpdateStatus();
    }
    
    // 当对象销毁时取消订阅事件
    private void OnDestroy()
    {
        if (trackedPlayer != null)
        {
            trackedPlayer.OnPlayerDeath -= OnPlayerDeathHandler;
            trackedPlayer.OnPowerUpCollected -= OnPowerUpCollectedHandler;
        }
    }
}

UI管理器实现了游戏的各种界面,包括主菜单、游戏界面、暂停菜单和结束画面。它处理界面切换、游戏信息显示和用户输入事件,并提供玩家状态UI显示玩家的能力和生存状态。

11.5 游戏测试与优化

开发完成后,我们需要进行游戏测试和性能优化,确保游戏能够在各种设备上流畅运行。

11.5.1 游戏性能优化

csharp

using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class PerformanceOptimizer : MonoBehaviour
{
    [Header("性能监控")]
    [SerializeField] private bool showFPS = true;
    [SerializeField] private bool logPerformanceStats = false;
    [SerializeField] private float logInterval = 5f;
    
    [Header("优化设置")]
    [SerializeField] private bool useObjectPooling = true;
    [SerializeField] private int defaultPoolSize = 20;
    [SerializeField] private bool optimizeRendering = true;
    [SerializeField] private bool useLOD = true;
    
    // 对象池字典
    private Dictionary<string, Queue<GameObject>> objectPools = new Dictionary<string, Queue<GameObject>>();
    
    // 预制体引用字典
    private Dictionary<string, GameObject> prefabReferences = new Dictionary<string, GameObject>();
    
    // 性能监控变量
    private float fpsUpdateInterval = 0.5f;
    private float fpsAccumulator = 0;
    private int fpsFrames = 0;
    private float currentFps = 0;
    private float logTimer = 0;
    
    // FPS显示样式
    private GUIStyle fpsStyle;
    
    private void Awake()
    {
        // 初始化FPS显示样式
        fpsStyle = new GUIStyle
        {
            alignment = TextAnchor.UpperLeft,
            fontSize = 20,
            fontStyle = FontStyle.Bold,
            normal = { textColor = Color.yellow }
        };
        
        // 如果使用对象池,初始化常用对象池
        if (useObjectPooling)
        {
            InitializeCommonPools();
        }
        
        // 应用渲染优化
        if (optimizeRendering)
        {
            ApplyRenderingOptimizations();
        }
    }
    
    private void Update()
    {
        // 更新FPS计数
        UpdateFPS();
        
        // 记录性能统计
        if (logPerformanceStats)
        {
            logTimer += Time.deltaTime;
            if (logTimer >= logInterval)
            {
                logTimer = 0;
                LogPerformanceStats();
            }
        }
    }
    
    private void OnGUI()
    {
        if (showFPS)
        {
            GUI.Label(new Rect(10, 10, 100, 25), $"FPS: {Mathf.RoundToInt(currentFps)}", fpsStyle);
        }
    }
    
    // 更新FPS计数
    private void UpdateFPS()
    {
        fpsAccumulator += Time.unscaledDeltaTime;
        fpsFrames++;
        
        if (fpsAccumulator >= fpsUpdateInterval)
        {
            currentFps = fpsFrames / fpsAccumulator;
            fpsFrames = 0;
            fpsAccumulator = 0;
        }
    }
    
    // 记录性能统计
    private void LogPerformanceStats()
    {
        Debug.Log($"[性能] FPS: {Mathf.RoundToInt(currentFps)}, " +
                  $"内存: {(System.GC.GetTotalMemory(false) / 1048576f):F2} MB, " +
                  $"渲染: {UnityEngine.Rendering.FrameTimingManager.GetLatestTimings(1, null) ? "支持" : "不支持"}, " +
                  $"对象池数量: {objectPools.Sum(p => p.Value.Count)}");
    }
    
    // 初始化常用对象池
    private void InitializeCommonPools()
    {
        // 加载常用预制体
        prefabReferences["Bomb"] = Resources.Load<GameObject>("Prefabs/Bomb");
        prefabReferences["Explosion"] = Resources.Load<GameObject>("Prefabs/Explosion");
        prefabReferences["ExplosionEnd"] = Resources.Load<GameObject>("Prefabs/ExplosionEnd");
        prefabReferences["PowerUp"] = Resources.Load<GameObject>("Prefabs/PowerUp");
        
        // 创建对象池
        CreatePool("Bomb", prefabReferences["Bomb"], defaultPoolSize);
        CreatePool("Explosion", prefabReferences["Explosion"], defaultPoolSize * 4);
        CreatePool("ExplosionEnd", prefabReferences["ExplosionEnd"], defaultPoolSize * 4);
        CreatePool("PowerUp", prefabReferences["PowerUp"], 10);
    }
    
    // 创建对象池
    private void CreatePool(string poolName, GameObject prefab, int size)
    {
        if (prefab == null)
        {
            Debug.LogWarning($"无法为空预制体创建对象池: {poolName}");
            return;
        }
        
        // 创建池队列
        Queue<GameObject> pool = new Queue<GameObject>();
        objectPools[poolName] = pool;
        
        // 创建对象并添加到池中
        for (int i = 0; i < size; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            obj.transform.SetParent(transform);
            pool.Enqueue(obj);
        }
    }
    
    // 从池中获取对象
    public GameObject GetObjectFromPool(string poolName)
    {
        if (!objectPools.ContainsKey(poolName))
        {
            Debug.LogWarning($"对象池不存在: {poolName},尝试创建新池");
            
            if (prefabReferences.ContainsKey(poolName))
            {
                CreatePool(poolName, prefabReferences[poolName], defaultPoolSize);
            }
            else
            {
                Debug.LogError($"无法找到预制体引用: {poolName}");
                return null;
            }
        }
        
        Queue<GameObject> pool = objectPools[poolName];
        
        // 如果池为空,扩展池
        if (pool.Count == 0)
        {
            Debug.LogWarning($"对象池 {poolName} 已耗尽,动态扩展");
            
            if (prefabReferences.ContainsKey(poolName))
            {
                for (int i = 0; i < 5; i++) // 添加5个新对象
                {
                    GameObject newObj = Instantiate(prefabReferences[poolName]);
                    newObj.SetActive(false);
                    newObj.transform.SetParent(transform);
                    pool.Enqueue(newObj);
                }
            }
            else
            {
                Debug.LogError($"无法找到预制体引用来扩展池: {poolName}");
                return null;
            }
        }
        
        // 从池中取出对象
        GameObject obj = pool.Dequeue();
        obj.SetActive(true);
        
        // 如果对象有PooledObject组件,通知它被激活
        IPooledObject pooledObject = obj.GetComponent<IPooledObject>();
        if (pooledObject != null)
        {
            pooledObject.OnObjectSpawn();
        }
        
        return obj;
    }
    
    // 返回对象到池
    public void ReturnObjectToPool(string poolName, GameObject obj)
    {
        if (obj == null) return;
        
        // 如果对象有PooledObject组件,通知它被回收
        IPooledObject pooledObject = obj.GetComponent<IPooledObject>();
        if (pooledObject != null)
        {
            pooledObject.OnObjectDespawn();
        }
        
        obj.SetActive(false);
        obj.transform.SetParent(transform);
        
        if (!objectPools.ContainsKey(poolName))
        {
            objectPools[poolName] = new Queue<GameObject>();
        }
        
        objectPools[poolName].Enqueue(obj);
    }
    
    // 应用渲染优化
    private void ApplyRenderingOptimizations()
    {
        // 设置固定帧率
        Application.targetFrameRate = 60;
        
        // 优化批处理
        if (useLOD)
        {
            // 查找并配置LOD组
            LODGroup[] lodGroups = FindObjectsOfType<LODGroup>();
            foreach (LODGroup group in lodGroups)
            {
                // 根据设备性能调整LOD级别
                float lodBias = Mathf.Clamp(QualitySettings.lodBias, 0.5f, 1.5f);
                QualitySettings.lodBias = lodBias;
            }
        }
    }
    
    // 获取对象池统计
    public Dictionary<string, int> GetPoolStats()
    {
        Dictionary<string, int> stats = new Dictionary<string, int>();
        
        foreach (var pool in objectPools)
        {
            stats[pool.Key] = pool.Value.Count;
        }
        
        return stats;
    }
    
    // 获取当前FPS
    public float GetCurrentFPS()
    {
        return currentFps;
    }
}

// 对象池接口
public interface IPooledObject
{
    void OnObjectSpawn();
    void OnObjectDespawn();
}

性能优化器实现了对象池系统,通过重用游戏对象减少内存分配和垃圾收集,提高游戏性能。它还包括FPS监控和性能日志记录功能,帮助开发者识别和解决性能瓶颈。

11.5.2 游戏平衡性测试

csharp

using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

public class GameBalanceAnalyzer : MonoBehaviour
{
    [Header("数据收集设置")]
    [SerializeField] private bool collectData = true;
    [SerializeField] private string dataFolder = "BalanceData";
    [SerializeField] private float recordInterval = 1.0f;
    
    // 游戏会话数据
    private class SessionData
    {
        public int levelNumber;
        public float totalTime;
        public int enemiesDefeated;
        public int powerUpsCollected;
        public int bombsPlaced;
        public int playerDeaths;
        public Dictionary<PowerUpType, int> powerUpDistribution = new Dictionary<PowerUpType, int>();
        public Dictionary<EnemyType, int> enemyDefeathDistribution = new Dictionary<EnemyType, int>();
    }
    
    private SessionData currentSession;
    private float recordTimer = 0;
    
    // 游戏组件引用
    private GameManager gameManager;
    private PlayerManager playerManager;
    private BombManager bombManager;
    private EnemySpawner enemySpawner;
    
    private void Awake()
    {
        // 查找游戏组件
        gameManager = FindObjectOfType<GameManager>();
        playerManager = FindObjectOfType<PlayerManager>();
        bombManager = FindObjectOfType<BombManager>();
        enemySpawner = FindObjectOfType<EnemySpawner>();
        
        // 创建数据文件夹
        if (collectData && !Directory.Exists(dataFolder))
        {
            Directory.CreateDirectory(dataFolder);
        }
    }
    
    private void Start()
    {
        // 订阅游戏事件
        if (collectData && gameManager != null)
        {
            gameManager.OnGameStateChanged += OnGameStateChanged;
        }
        
        // 订阅炸弹事件
        if (collectData && bombManager != null)
        {
            bombManager.OnBombPlaced += OnBombPlaced;
        }
        
        // 订阅玩家事件
        if (collectData && playerManager != null)
        {
            playerManager.OnPlayerDeath += OnPlayerDeath;
            playerManager.OnPowerUpCollected += OnPowerUpCollected;
        }
        
        // 订阅敌人事件
        if (collectData && enemySpawner != null)
        {
            enemySpawner.OnEnemyDefeated += OnEnemyDefeated;
        }
    }
    
    private void Update()
    {
        if (!collectData || currentSession == null)
            return;
            
        // 更新会话时间
        currentSession.totalTime += Time.deltaTime;
        
        // 定期记录数据
        recordTimer += Time.deltaTime;
        if (recordTimer >= recordInterval)
        {
            recordTimer = 0;
            RecordIntervalData();
        }
    }
    
    // 游戏状态变更处理
    private void OnGameStateChanged(GameManager.GameState newState)
    {
        switch (newState)
        {
            case GameManager.GameState.Loading:
                // 新关卡开始,创建新会话
                CreateNewSession();
                break;
                
            case GameManager.GameState.Victory:
            case GameManager.GameState.GameOver:
                // 游戏结束,保存会话数据
                SaveSessionData(newState == GameManager.GameState.Victory);
                break;
        }
    }
    
    // 创建新会话
    private void CreateNewSession()
    {
        currentSession = new SessionData
        {
            levelNumber = gameManager != null ? gameManager.GetCurrentLevel() : 1,
            totalTime = 0,
            enemiesDefeated = 0,
            powerUpsCollected = 0,
            bombsPlaced = 0,
            playerDeaths = 0
        };
        
        // 初始化分布字典
        foreach (PowerUpType type in Enum.GetValues(typeof(PowerUpType)))
        {
            currentSession.powerUpDistribution[type] = 0;
        }
        
        foreach (EnemyType type in Enum.GetValues(typeof(EnemyType)))
        {
            currentSession.enemyDefeathDistribution[type] = 0;
        }
    }
    
    // 记录间隔数据
    private void RecordIntervalData()
    {
        if (currentSession == null)
            return;
            
        // 在实际实现中,这里会记录更多数据点,如玩家位置、敌人密度等
    }
    
    // 保存会话数据
    private void SaveSessionData(bool isVictory)
    {
        if (currentSession == null || !collectData)
            return;
            
        try
        {
            string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
            string fileName = $"{dataFolder}/level{currentSession.levelNumber}_{timestamp}_{(isVictory ? "victory" : "defeat")}.json";
            
            // 构建JSON对象
            Dictionary<string, object> dataObject = new Dictionary<string, object>
            {
                { "levelNumber", currentSession.levelNumber },
                { "result", isVictory ? "victory" : "defeat" },
                { "totalTime", currentSession.totalTime },
                { "enemiesDefeated", currentSession.enemiesDefeated },
                { "powerUpsCollected", currentSession.powerUpsCollected },
                { "bombsPlaced", currentSession.bombsPlaced },
                { "playerDeaths", currentSession.playerDeaths },
                { "powerUpDistribution", currentSession.powerUpDistribution },
                { "enemyDefeathDistribution", currentSession.enemyDefeathDistribution }
            };
            
            // 序列化为JSON
            string json = JsonUtility.ToJson(dataObject, true);
            
            // 写入文件
            File.WriteAllText(fileName, json);
            
            Debug.Log($"会话数据已保存到 {fileName}");
        }
        catch (Exception ex)
        {
            Debug.LogError($"保存会话数据时出错: {ex.Message}");
        }
    }
    
    // 事件处理
    private void OnBombPlaced(int playerId, Vector2Int position)
    {
        if (currentSession != null)
        {
            currentSession.bombsPlaced++;
        }
    }
    
    private void OnPlayerDeath(int playerId)
    {
        if (currentSession != null)
        {
            currentSession.playerDeaths++;
        }
    }
    
    private void OnPowerUpCollected(int playerId, PowerUpType type)
    {
        if (currentSession != null)
        {
            currentSession.powerUpsCollected++;
            currentSession.powerUpDistribution[type]++;
        }
    }
    
    private void OnEnemyDefeated(EnemyType type)
    {
        if (currentSession != null)
        {
            currentSession.enemiesDefeated++;
            currentSession.enemyDefeathDistribution[type]++;
        }
    }
    
    // 分析多个会话数据,生成平衡报告
    public void GenerateBalanceReport()
    {
        try
        {
            if (!Directory.Exists(dataFolder))
                return;
                
            string[] files = Directory.GetFiles(dataFolder, "*.json");
            
            if (files.Length == 0)
            {
                Debug.Log("没有可分析的数据文件");
                return;
            }
            
            // 按关卡分组的数据
            Dictionary<int, List<Dictionary<string, object>>> levelData = 
                new Dictionary<int, List<Dictionary<string, object>>>();
                
            // 读取所有文件
            foreach (string file in files)
            {
                string json = File.ReadAllText(file);
                Dictionary<string, object> data = JsonUtility.FromJson<Dictionary<string, object>>(json);
                
                int level = (int)data["levelNumber"];
                
                if (!levelData.ContainsKey(level))
                {
                    levelData[level] = new List<Dictionary<string, object>>();
                }
                
                levelData[level].Add(data);
            }
            
            // 生成报告
            string reportPath = $"{dataFolder}/balance_report_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.txt";
            using (StreamWriter writer = new StreamWriter(reportPath))
            {
                writer.WriteLine("炸弹人游戏平衡分析报告");
                writer.WriteLine("====================");
                writer.WriteLine();
                
                // 按关卡分析
                foreach (var kvp in levelData)
                {
                    int level = kvp.Key;
                    List<Dictionary<string, object>> sessions = kvp.Value;
                    
                    writer.WriteLine($"关卡 {level} 分析");
                    writer.WriteLine("-----------------");
                    
                    // 计算胜率
                    int victories = sessions.Count(s => (string)s["result"] == "victory");
                    float winRate = (float)victories / sessions.Count;
                    writer.WriteLine($"会话数量: {sessions.Count}");
                    writer.WriteLine($"胜率: {winRate:P2}");
                    
                    // 计算平均时间
                    float avgTime = sessions.Average(s => (float)s["totalTime"]);
                    writer.WriteLine($"平均完成时间: {avgTime:F2}秒");
                    
                    // 分析道具收集
                    float avgPowerUps = sessions.Average(s => (int)s["powerUpsCollected"]);
                    writer.WriteLine($"平均道具收集: {avgPowerUps:F2}");
                    
                    // 分析敌人击杀
                    float avgEnemies = sessions.Average(s => (int)s["enemiesDefeated"]);
                    writer.WriteLine($"平均敌人消灭: {avgEnemies:F2}");
                    
                    // 分析玩家死亡
                    float avgDeaths = sessions.Average(s => (int)s["playerDeaths"]);
                    writer.WriteLine($"平均玩家死亡: {avgDeaths:F2}");
                    
                    writer.WriteLine();
                }
                
                writer.WriteLine("总体建议");
                writer.WriteLine("--------");
                
                // 根据数据提出平衡性建议
                // 这里会有更复杂的逻辑来生成建议
                
                writer.WriteLine("报告生成完毕");
            }
            
            Debug.Log($"平衡分析报告已生成: {reportPath}");
        }
        catch (Exception ex)
        {
            Debug.LogError($"生成平衡报告时出错: {ex.Message}");
        }
    }
    
    // 清理
    private void OnDestroy()
    {
        // 取消事件订阅
        if (gameManager != null)
        {
            gameManager.OnGameStateChanged -= OnGameStateChanged;
        }
        
        if (bombManager != null)
        {
            bombManager.OnBombPlaced -= OnBombPlaced;
        }
        
        if (playerManager != null)
        {
            playerManager.OnPlayerDeath -= OnPlayerDeath;
            playerManager.OnPowerUpCollected -= OnPowerUpCollected;
        }
        
        if (enemySpawner != null)
        {
            enemySpawner.OnEnemyDefeated -= OnEnemyDefeated;
        }
    }
}

游戏平衡性分析器收集和分析游戏数据,以帮助开发者评估游戏的难度曲线和平衡性。它记录各种游戏事件(如炸弹放置、道具收集和敌人击杀),并生成详细的平衡报告,提供改进建议。

11.6 总结与拓展

至此,我们已经完成了炸弹人游戏的全部核心功能实现。通过本章的学习,我们深入理解了炸弹人游戏的架构设计、核心机制和实现细节,涵盖了从数学模型到性能优化的方方面面。

11.6.1 项目总结

  1. 游戏架构:我们采用了模块化设计,将游戏分解为地图生成、炸弹管理、角色控制和UI系统等独立模块,提高了代码的可维护性和可扩展性。

  2. 数学应用:应用曼哈顿距离计算爆炸范围,使用对数函数设计难度曲线,以及随机算法生成平衡的游戏地图。

  3. 性能优化:实现了对象池系统,减少运行时内存分配,优化了渲染管线,确保游戏在各种设备上流畅运行。

  4. 游戏平衡:通过数据收集和分析,调整游戏难度,确保玩家获得适当的挑战和进步感。

11.6.2 可能的扩展方向

  1. 多人联网对战:实现网络同步,允许多名玩家在线对战。

  2. 关卡编辑器:开发一个可视化编辑器,让玩家创建和分享自定义关卡。

  3. AI优化:使用更先进的算法如行为树或神经网络,提高敌人AI的智能性和自然性。

  4. 更多游戏模式:添加故事模式、生存模式或团队合作模式,增加游戏多样性。

  5. 移动平台适配:优化触摸控制和界面布局,使游戏在移动设备上更易使用。

11.6.3 结语

炸弹人游戏虽然概念简单,但要实现一个高质量的商业版本,需要考虑众多细节和技术挑战。通过本章的学习,我们不仅掌握了炸弹人游戏的实现技术,还了解了游戏开发的一般原则和最佳实践。这些知识和经验可以应用到各种类型的游戏开发中,为你的游戏开发之路打下坚实基础。

希望本章内容能够帮助你成功开发出自己的炸弹人游戏,或者将这些技术应用到其他游戏项目中。游戏开发是一个不断学习和探索的过程,保持好奇心和创新精神,你将能创造出更多令人惊叹的游戏体验。

Logo

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

更多推荐