炸弹人游戏开发进阶:从原理到实践
本文介绍了炸弹人游戏的开发实现,重点讲解了游戏的核心机制、数学模型和架构设计。文章从离散网格系统、曼哈顿距离计算等数学基础出发,详细阐述了地图生成、炸弹管理、角色控制等核心系统的实现方法。通过模块化设计,将游戏分解为地图生成、炸弹管理、角色控制和UI系统等独立模块,提高了代码的可维护性。文章还探讨了游戏平衡性设计、性能优化策略以及测试方法,并提出了可能的扩展方向,如多人联网对战和AI优化等。这套完
炸弹人游戏开发进阶:从原理到实践
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 项目总结
-
游戏架构:我们采用了模块化设计,将游戏分解为地图生成、炸弹管理、角色控制和UI系统等独立模块,提高了代码的可维护性和可扩展性。
-
数学应用:应用曼哈顿距离计算爆炸范围,使用对数函数设计难度曲线,以及随机算法生成平衡的游戏地图。
-
性能优化:实现了对象池系统,减少运行时内存分配,优化了渲染管线,确保游戏在各种设备上流畅运行。
-
游戏平衡:通过数据收集和分析,调整游戏难度,确保玩家获得适当的挑战和进步感。
11.6.2 可能的扩展方向
-
多人联网对战:实现网络同步,允许多名玩家在线对战。
-
关卡编辑器:开发一个可视化编辑器,让玩家创建和分享自定义关卡。
-
AI优化:使用更先进的算法如行为树或神经网络,提高敌人AI的智能性和自然性。
-
更多游戏模式:添加故事模式、生存模式或团队合作模式,增加游戏多样性。
-
移动平台适配:优化触摸控制和界面布局,使游戏在移动设备上更易使用。
11.6.3 结语
炸弹人游戏虽然概念简单,但要实现一个高质量的商业版本,需要考虑众多细节和技术挑战。通过本章的学习,我们不仅掌握了炸弹人游戏的实现技术,还了解了游戏开发的一般原则和最佳实践。这些知识和经验可以应用到各种类型的游戏开发中,为你的游戏开发之路打下坚实基础。
希望本章内容能够帮助你成功开发出自己的炸弹人游戏,或者将这些技术应用到其他游戏项目中。游戏开发是一个不断学习和探索的过程,保持好奇心和创新精神,你将能创造出更多令人惊叹的游戏体验。
更多推荐
所有评论(0)