坦克大战:经典2D策略游戏的Unity实现
本文详细介绍了使用Unity 2021.3.8f1c1引擎开发经典2D坦克大战游戏的全过程。主要内容包括: 游戏系统设计:构建了包含玩家坦克、敌方坦克、多种地形元素和道具系统的核心玩法框架,定义了游戏胜利/失败条件。 关键技术实现: 采用Tilemap系统构建网格化地图 使用状态机模式实现敌方AI行为 开发流畅的玩家控制系统 设计子弹物理交互和碰撞检测系统 实现包含多种增益效果的道具系统 扩展功能
坦克大战:经典2D策略游戏的Unity实现
19.1 坦克大战游戏概述
坦克大战是一款经典的2D射击游戏,最早由日本南梦宫公司于1985年发行。游戏以俯视角视角展示战场,玩家控制坦克在地图上移动并射击敌方坦克,同时保护自己的基地不被摧毁。这款游戏曾风靡全球,深受玩家喜爱。
在本章中,我们将使用Unity 2021.3.8f1c1引擎来重新实现这款经典游戏,不仅复刻其核心玩法,还将引入现代游戏设计理念和一些扩展功能。通过实现坦克大战,我们将学习Unity中的2D游戏开发技术,包括精灵渲染、碰撞检测、物理系统、人工智能等多方面内容。
坦克大战虽然画面简单,但其游戏机制和编程逻辑却相当丰富,是初学者学习游戏开发的理想项目。本章将带领读者从零开始,一步步实现这款经典游戏的各个系统。
主要学习内容:
- 2D游戏场景和地图创建
- 精灵渲染和动画系统
- 角色控制与物理系统
- 敌人AI行为设计
- 游戏状态管理和UI实现
- 对象池优化与性能提升
19.2 坦克大战游戏系统设计
在开始编码之前,我们需要先明确游戏的基本规则和设计要点,为后续的开发打下基础。
19.2.1 核心游戏要素
坦克大战的基本要素主要包括以下几点:
-
玩家坦克:由玩家控制,可以上下左右移动,发射子弹攻击敌人。玩家坦克有限定的生命数量。
-
敌方坦克:由AI控制,会自动移动并攻击玩家和基地。游戏中有多种类型的敌方坦克,包括普通坦克、快速坦克、装甲坦克等,它们有不同的速度、装甲和攻击力。
-
地图元素:
- 砖墙:可以被子弹摧毁
- 钢墙:不能被普通子弹摧毁,只能被强化子弹破坏
- 水域:坦克不能通过
- 草丛:可以隐藏坦克
- 冰面:使坦克滑行
-
基地:玩家需要保护的目标,如果被敌方坦克摧毁则游戏结束。
-
道具系统:游戏中会随机出现各种道具,提供临时能力增强,如:
- 星星:提升子弹威力
- 坦克:增加生命
- 计时器:暂时冻结所有敌人
- 铲子:临时增强基地周围的墙
19.2.2 游戏胜利与失败条件
- 胜利条件:消灭当前关卡的所有敌方坦克。
- 失败条件:玩家坦克生命耗尽,或基地被摧毁。
19.2.3 游戏控制方式
- 使用键盘方向键或WASD控制坦克移动
- 空格键或鼠标左键发射子弹
- ESC键暂停游戏
接下来,我们将详细讨论游戏的实现思路和代码结构。
19.3 游戏开发架构与思路
19.3.1 地图系统设计与生成
在坦克大战中,地图是游戏的重要组成部分。我们将使用Unity的Tilemap系统来构建游戏地图,它提供了高效的2D地图编辑和渲染功能。
地图设计原则
- 网格化设计:游戏地图采用13×13的网格结构,每个网格可以放置一种地形元素。
- 平衡性考虑:地图设计需考虑攻防平衡,为玩家提供足够的掩护,同时也要留出足够的机动空间。
- 多样性:设计多种不同布局的关卡,增加游戏的可玩性。
地形类型及属性
csharp
// 地形类型枚举
public enum TerrainType
{
Empty,
BrickWall,
SteelWall,
Water,
Forest,
Ice
}
// 地形属性类
public class TerrainProperties
{
public bool IsPassable { get; set; }
public bool IsDestructible { get; set; }
public bool CanHideUnit { get; set; }
public bool IsSlidable { get; set; }
public TerrainProperties(bool isPassable, bool isDestructible, bool canHideUnit, bool isSlidable)
{
IsPassable = isPassable;
IsDestructible = isDestructible;
CanHideUnit = canHideUnit;
IsSlidable = isSlidable;
}
}
地图管理器的实现
csharp
public class MapManager : MonoBehaviour
{
// 单例模式
public static MapManager Instance { get; private set; }
[SerializeField] private Tilemap groundTilemap;
[SerializeField] private Tilemap obstacleTilemap;
[SerializeField] private Tilemap decorationTilemap;
[Header("Tile Assets")]
[SerializeField] private TileBase brickTile;
[SerializeField] private TileBase steelTile;
[SerializeField] private TileBase waterTile;
[SerializeField] private TileBase forestTile;
[SerializeField] private TileBase iceTile;
// 地图尺寸
private const int MAP_WIDTH = 13;
private const int MAP_HEIGHT = 13;
// 地形属性字典
private Dictionary<TerrainType, TerrainProperties> terrainProperties;
// 当前地图数据
private TerrainType[,] mapData;
private void Awake()
{
// 单例初始化
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
return;
}
// 初始化地形属性
InitTerrainProperties();
// 初始化地图数据
mapData = new TerrainType[MAP_WIDTH, MAP_HEIGHT];
}
private void InitTerrainProperties()
{
terrainProperties = new Dictionary<TerrainType, TerrainProperties>
{
{ TerrainType.Empty, new TerrainProperties(true, false, false, false) },
{ TerrainType.BrickWall, new TerrainProperties(false, true, false, false) },
{ TerrainType.SteelWall, new TerrainProperties(false, false, false, false) },
{ TerrainType.Water, new TerrainProperties(false, false, false, false) },
{ TerrainType.Forest, new TerrainProperties(true, false, true, false) },
{ TerrainType.Ice, new TerrainProperties(true, false, false, true) }
};
}
// 加载指定关卡
public void LoadLevel(int levelNumber)
{
// 清空现有地图
ClearMap();
// 加载关卡数据
string levelData = LoadLevelData(levelNumber);
// 解析关卡数据并生成地图
GenerateMap(levelData);
// 设置基地周围的墙
SetupBaseWalls();
}
private string LoadLevelData(int levelNumber)
{
// 这里可以从文本文件、ScriptableObject或其他数据源加载关卡数据
// 示例中使用硬编码的关卡数据
TextAsset levelFile = Resources.Load<TextAsset>($"Levels/Level{levelNumber}");
if (levelFile != null)
{
return levelFile.text;
}
else
{
Debug.LogError($"Level {levelNumber} data not found!");
return GenerateDefaultLevel();
}
}
private string GenerateDefaultLevel()
{
// 生成默认关卡数据
StringBuilder sb = new StringBuilder();
// 示例:创建简单的默认关卡布局
for (int y = 0; y < MAP_HEIGHT; y++)
{
for (int x = 0; x < MAP_WIDTH; x++)
{
if (x == 0 || x == MAP_WIDTH - 1 || y == 0 || y == MAP_HEIGHT - 1)
{
sb.Append('S'); // 外围用钢墙
}
else if ((x % 2 == 0 && y % 2 == 0) && !(x == 6 && y >= MAP_HEIGHT - 3))
{
sb.Append('B'); // 交错的砖墙
}
else
{
sb.Append('.'); // 空地
}
}
sb.Append('\n');
}
return sb.ToString();
}
private void GenerateMap(string levelData)
{
string[] rows = levelData.Split('\n', StringSplitOptions.RemoveEmptyEntries);
for (int y = 0; y < rows.Length && y < MAP_HEIGHT; y++)
{
string row = rows[y];
for (int x = 0; x < row.Length && x < MAP_WIDTH; x++)
{
Vector3Int cellPosition = new Vector3Int(x, MAP_HEIGHT - y - 1, 0);
TerrainType terrainType = CharToTerrainType(row[x]);
mapData[x, MAP_HEIGHT - y - 1] = terrainType;
PlaceTile(cellPosition, terrainType);
}
}
}
private TerrainType CharToTerrainType(char c)
{
switch (c)
{
case 'B': return TerrainType.BrickWall;
case 'S': return TerrainType.SteelWall;
case 'W': return TerrainType.Water;
case 'F': return TerrainType.Forest;
case 'I': return TerrainType.Ice;
default: return TerrainType.Empty;
}
}
private void PlaceTile(Vector3Int position, TerrainType terrainType)
{
switch (terrainType)
{
case TerrainType.BrickWall:
obstacleTilemap.SetTile(position, brickTile);
break;
case TerrainType.SteelWall:
obstacleTilemap.SetTile(position, steelTile);
break;
case TerrainType.Water:
groundTilemap.SetTile(position, waterTile);
break;
case TerrainType.Forest:
decorationTilemap.SetTile(position, forestTile);
break;
case TerrainType.Ice:
groundTilemap.SetTile(position, iceTile);
break;
}
}
private void SetupBaseWalls()
{
// 基地位置(中心底部)
int baseX = MAP_WIDTH / 2;
int baseY = 0;
// 在基地周围设置砖墙保护
for (int x = baseX - 1; x <= baseX + 1; x++)
{
for (int y = baseY; y <= baseY + 1; y++)
{
// 跳过基地本身的位置
if (x == baseX && y == baseY)
continue;
Vector3Int pos = new Vector3Int(x, y, 0);
obstacleTilemap.SetTile(pos, brickTile);
mapData[x, y] = TerrainType.BrickWall;
}
}
}
private void ClearMap()
{
groundTilemap.ClearAllTiles();
obstacleTilemap.ClearAllTiles();
decorationTilemap.ClearAllTiles();
// 重置地图数据
for (int x = 0; x < MAP_WIDTH; x++)
{
for (int y = 0; y < MAP_HEIGHT; y++)
{
mapData[x, y] = TerrainType.Empty;
}
}
}
// 检查位置是否可通行
public bool IsPositionPassable(Vector3 worldPosition)
{
Vector3Int cellPosition = groundTilemap.WorldToCell(worldPosition);
// 检查是否在地图范围内
if (cellPosition.x < 0 || cellPosition.x >= MAP_WIDTH ||
cellPosition.y < 0 || cellPosition.y >= MAP_HEIGHT)
{
return false;
}
TerrainType terrainType = mapData[cellPosition.x, cellPosition.y];
return terrainProperties[terrainType].IsPassable;
}
// 检查位置是否为可滑行地形
public bool IsPositionSlidable(Vector3 worldPosition)
{
Vector3Int cellPosition = groundTilemap.WorldToCell(worldPosition);
// 检查是否在地图范围内
if (cellPosition.x < 0 || cellPosition.x >= MAP_WIDTH ||
cellPosition.y < 0 || cellPosition.y >= MAP_HEIGHT)
{
return false;
}
TerrainType terrainType = mapData[cellPosition.x, cellPosition.y];
return terrainProperties[terrainType].IsSlidable;
}
// 破坏指定位置的地形
public void DestroyTerrain(Vector3 worldPosition)
{
Vector3Int cellPosition = obstacleTilemap.WorldToCell(worldPosition);
// 检查是否在地图范围内
if (cellPosition.x < 0 || cellPosition.x >= MAP_WIDTH ||
cellPosition.y < 0 || cellPosition.y >= MAP_HEIGHT)
{
return;
}
TerrainType terrainType = mapData[cellPosition.x, cellPosition.y];
// 只有可破坏的地形才能被摧毁
if (terrainProperties[terrainType].IsDestructible)
{
obstacleTilemap.SetTile(cellPosition, null);
mapData[cellPosition.x, cellPosition.y] = TerrainType.Empty;
}
}
}
通过以上代码,我们实现了一个功能完善的地图管理器,它可以加载关卡数据、生成地图、判断地形特性并支持地形破坏。
19.3.2 敌方坦克AI设计
敌人AI是坦克大战游戏中的核心元素之一。设计合理的AI行为可以增加游戏的挑战性和趣味性。
AI行为状态机设计
我们将使用状态机模式来管理敌方坦克的AI行为,主要包括以下几种状态:
- 巡逻状态:敌方坦克随机移动寻找目标
- 追击状态:发现玩家后向其移动并攻击
- 攻击基地状态:优先攻击玩家基地
- 规避状态:受到攻击时的躲避行为
敌方坦克类型设计
csharp
// 敌方坦克类型枚举
public enum EnemyTankType
{
Basic, // 基础型:速度和装甲均衡
Fast, // 快速型:高速但低装甲
Heavy, // 重型:低速高装甲
Power // 火力型:攻击力强但移动慢
}
// 敌方坦克属性类
[System.Serializable]
public class EnemyTankProperties
{
public float MoveSpeed;
public int Health;
public float FireRate;
public int ScoreValue;
public EnemyTankProperties(float moveSpeed, int health, float fireRate, int scoreValue)
{
MoveSpeed = moveSpeed;
Health = health;
FireRate = fireRate;
ScoreValue = scoreValue;
}
}
敌方坦克AI状态机实现
csharp
// 敌方坦克AI状态枚举
public enum EnemyAIState
{
Patrol,
Chase,
AttackBase,
Evade
}
// 敌方坦克基类
public class EnemyTank : MonoBehaviour
{
[SerializeField] protected EnemyTankType tankType;
// 引用
protected Rigidbody2D rb;
protected SpriteRenderer spriteRenderer;
// 状态
protected EnemyAIState currentState;
protected float stateTimer;
protected Vector2 moveDirection;
// 属性
protected EnemyTankProperties properties;
protected int currentHealth;
protected float lastFireTime;
// 目标
protected Transform playerTransform;
protected Transform baseTransform;
// 公共属性访问器
public EnemyTankType TankType => tankType;
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
// 初始化状态
currentState = EnemyAIState.Patrol;
stateTimer = Random.Range(2f, 4f);
// 获取属性
properties = GetTankProperties(tankType);
currentHealth = properties.Health;
}
protected virtual void Start()
{
// 查找玩家和基地
playerTransform = GameObject.FindWithTag("Player")?.transform;
baseTransform = GameObject.FindWithTag("Base")?.transform;
}
protected virtual void Update()
{
// 更新状态计时器
stateTimer -= Time.deltaTime;
if (stateTimer <= 0)
{
// 状态持续时间结束,决定下一个状态
DecideNextState();
}
// 根据当前状态执行行为
ExecuteStateAction();
// 尝试射击
TryFire();
}
protected virtual void FixedUpdate()
{
// 移动坦克
Move();
}
protected virtual void DecideNextState()
{
// 根据各种条件决定下一个状态
// 更新目标引用
if (playerTransform == null)
playerTransform = GameObject.FindWithTag("Player")?.transform;
if (baseTransform == null)
baseTransform = GameObject.FindWithTag("Base")?.transform;
// 检查是否可以看到玩家
bool canSeePlayer = CheckLineOfSight(playerTransform);
// 检查是否可以看到基地
bool canSeeBase = CheckLineOfSight(baseTransform);
// 随机决策,但有一定的优先级
float decisionValue = Random.value;
if (canSeePlayer && decisionValue < 0.7f)
{
// 70%的几率追击玩家
ChangeState(EnemyAIState.Chase);
}
else if (canSeeBase && decisionValue < 0.5f)
{
// 50%的几率攻击基地
ChangeState(EnemyAIState.AttackBase);
}
else if (currentHealth <= properties.Health / 2 && decisionValue < 0.4f)
{
// 血量低于一半时,有40%的几率规避
ChangeState(EnemyAIState.Evade);
}
else
{
// 默认巡逻
ChangeState(EnemyAIState.Patrol);
}
}
protected virtual void ChangeState(EnemyAIState newState)
{
currentState = newState;
// 设置状态持续时间
switch (newState)
{
case EnemyAIState.Patrol:
stateTimer = Random.Range(3f, 6f);
break;
case EnemyAIState.Chase:
stateTimer = Random.Range(5f, 8f);
break;
case EnemyAIState.AttackBase:
stateTimer = Random.Range(4f, 7f);
break;
case EnemyAIState.Evade:
stateTimer = Random.Range(2f, 4f);
break;
}
}
protected virtual void ExecuteStateAction()
{
switch (currentState)
{
case EnemyAIState.Patrol:
ExecutePatrolState();
break;
case EnemyAIState.Chase:
ExecuteChaseState();
break;
case EnemyAIState.AttackBase:
ExecuteAttackBaseState();
break;
case EnemyAIState.Evade:
ExecuteEvadeState();
break;
}
}
protected virtual void ExecutePatrolState()
{
// 随机移动
if (moveDirection == Vector2.zero || Random.value < 0.02f)
{
// 随机选择一个方向
int directionIndex = Random.Range(0, 4);
switch (directionIndex)
{
case 0:
moveDirection = Vector2.up;
break;
case 1:
moveDirection = Vector2.right;
break;
case 2:
moveDirection = Vector2.down;
break;
case 3:
moveDirection = Vector2.left;
break;
}
}
// 如果遇到障碍物,改变方向
if (IsObstacleAhead())
{
// 选择一个新方向
ChangeRandomDirection();
}
}
protected virtual void ExecuteChaseState()
{
if (playerTransform != null)
{
// 计算到玩家的方向
Vector2 directionToPlayer = (playerTransform.position - transform.position).normalized;
// 只使用水平或垂直分量,不允许斜向移动
if (Mathf.Abs(directionToPlayer.x) > Mathf.Abs(directionToPlayer.y))
{
moveDirection = new Vector2(Mathf.Sign(directionToPlayer.x), 0);
}
else
{
moveDirection = new Vector2(0, Mathf.Sign(directionToPlayer.y));
}
// 如果遇到障碍物,尝试绕过
if (IsObstacleAhead())
{
// 尝试找到一条到玩家的路径
TryFindPathToTarget(playerTransform.position);
}
}
else
{
// 如果找不到玩家,切换到巡逻状态
ChangeState(EnemyAIState.Patrol);
}
}
protected virtual void ExecuteAttackBaseState()
{
if (baseTransform != null)
{
// 计算到基地的方向
Vector2 directionToBase = (baseTransform.position - transform.position).normalized;
// 只使用水平或垂直分量
if (Mathf.Abs(directionToBase.x) > Mathf.Abs(directionToBase.y))
{
moveDirection = new Vector2(Mathf.Sign(directionToBase.x), 0);
}
else
{
moveDirection = new Vector2(0, Mathf.Sign(directionToBase.y));
}
// 如果遇到障碍物,尝试绕过或摧毁
if (IsObstacleAhead())
{
// 尝试找到一条到基地的路径
TryFindPathToTarget(baseTransform.position);
}
}
else
{
// 如果找不到基地,切换到巡逻状态
ChangeState(EnemyAIState.Patrol);
}
}
protected virtual void ExecuteEvadeState()
{
if (playerTransform != null)
{
// 计算远离玩家的方向
Vector2 directionAwayFromPlayer = ((Vector2)transform.position - (Vector2)playerTransform.position).normalized;
// 只使用水平或垂直分量
if (Mathf.Abs(directionAwayFromPlayer.x) > Mathf.Abs(directionAwayFromPlayer.y))
{
moveDirection = new Vector2(Mathf.Sign(directionAwayFromPlayer.x), 0);
}
else
{
moveDirection = new Vector2(0, Mathf.Sign(directionAwayFromPlayer.y));
}
// 如果遇到障碍物,随机改变方向
if (IsObstacleAhead())
{
ChangeRandomDirection();
}
}
else
{
// 如果找不到玩家,切换到巡逻状态
ChangeState(EnemyAIState.Patrol);
}
}
protected virtual void Move()
{
// 根据移动方向和速度移动坦克
rb.velocity = moveDirection * properties.MoveSpeed;
// 更新坦克朝向
if (moveDirection != Vector2.zero)
{
float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}
protected virtual void TryFire()
{
// 检查是否可以射击
if (Time.time >= lastFireTime + properties.FireRate)
{
// 检查前方是否有目标
if (CheckTargetInFireLine())
{
Fire();
lastFireTime = Time.time;
}
// 随机射击
else if (Random.value < 0.05f)
{
Fire();
lastFireTime = Time.time;
}
}
}
protected virtual void Fire()
{
// 在子类中实现具体的射击逻辑
}
protected bool IsObstacleAhead()
{
// 使用射线检测前方是否有障碍物
float rayDistance = 1.0f;
RaycastHit2D hit = Physics2D.Raycast(transform.position, moveDirection, rayDistance, LayerMask.GetMask("Obstacle"));
return hit.collider != null;
}
protected void ChangeRandomDirection()
{
// 选择一个与当前方向垂直的方向
Vector2[] possibleDirections = new Vector2[2];
if (moveDirection.x != 0)
{
possibleDirections[0] = Vector2.up;
possibleDirections[1] = Vector2.down;
}
else
{
possibleDirections[0] = Vector2.right;
possibleDirections[1] = Vector2.left;
}
moveDirection = possibleDirections[Random.Range(0, 2)];
}
protected bool CheckLineOfSight(Transform target)
{
if (target == null)
return false;
// 检查是否可以直接看到目标
Vector2 directionToTarget = (target.position - transform.position).normalized;
float distanceToTarget = Vector2.Distance(transform.position, target.position);
RaycastHit2D hit = Physics2D.Raycast(transform.position, directionToTarget, distanceToTarget, LayerMask.GetMask("Obstacle"));
// 如果没有障碍物挡住视线
return hit.collider == null;
}
protected bool CheckTargetInFireLine()
{
// 检查射击方向上是否有目标
float rayDistance = 10.0f;
RaycastHit2D hit = Physics2D.Raycast(transform.position, transform.right, rayDistance, LayerMask.GetMask("Player", "Base"));
return hit.collider != null;
}
protected void TryFindPathToTarget(Vector3 targetPosition)
{
// 简单的寻路:尝试垂直于当前方向移动
ChangeRandomDirection();
}
public virtual void TakeDamage()
{
currentHealth--;
if (currentHealth <= 0)
{
// 坦克被摧毁
Die();
}
}
protected virtual void Die()
{
// 播放爆炸效果
// 增加玩家分数
// 通知游戏管理器
GameManager.Instance.EnemyDestroyed(this);
// 销毁游戏对象
Destroy(gameObject);
}
private EnemyTankProperties GetTankProperties(EnemyTankType type)
{
// 根据坦克类型返回相应的属性
switch (type)
{
case EnemyTankType.Basic:
return new EnemyTankProperties(3.0f, 1, 2.0f, 100);
case EnemyTankType.Fast:
return new EnemyTankProperties(5.0f, 1, 1.5f, 200);
case EnemyTankType.Heavy:
return new EnemyTankProperties(2.0f, 3, 2.5f, 300);
case EnemyTankType.Power:
return new EnemyTankProperties(2.5f, 2, 1.0f, 400);
default:
return new EnemyTankProperties(3.0f, 1, 2.0f, 100);
}
}
}
以上代码实现了敌方坦克的基本AI行为。通过状态机模式,我们可以让敌方坦克表现出不同的行为模式,增加游戏的挑战性和趣味性。
19.3.3 玩家控制系统设计
玩家控制系统是游戏的核心交互部分,需要实现流畅的输入响应和坦克控制。
玩家坦克属性与控制
csharp
public class PlayerTank : MonoBehaviour
{
[Header("Movement")]
[SerializeField] private float moveSpeed = 4.0f;
[SerializeField] private float rotationSpeed = 200.0f;
[Header("Combat")]
[SerializeField] private Transform firePoint;
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private float fireRate = 0.5f;
[Header("Properties")]
[SerializeField] private int maxLives = 3;
[SerializeField] private int bulletPower = 1;
[SerializeField] private int maxBullets = 1;
// 引用
private Rigidbody2D rb;
private SpriteRenderer spriteRenderer;
// 状态
private Vector2 moveDirection;
private float lastFireTime;
private int currentLives;
private int activeBulletCount;
private bool isInvulnerable;
private bool isOnIce;
// 公共属性
public int CurrentLives => currentLives;
public int BulletPower => bulletPower;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
currentLives = maxLives;
activeBulletCount = 0;
}
private void Update()
{
// 处理玩家输入
HandleInput();
// 处理射击
HandleShooting();
}
private void FixedUpdate()
{
// 移动坦克
Move();
}
private void HandleInput()
{
// 读取移动输入
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 只允许水平或垂直移动,不能斜向移动
if (Mathf.Abs(horizontal) > Mathf.Abs(vertical))
{
moveDirection = new Vector2(horizontal, 0).normalized;
}
else if (Mathf.Abs(vertical) > 0)
{
moveDirection = new Vector2(0, vertical).normalized;
}
else
{
// 如果不在冰面上,则停止移动
if (!isOnIce)
{
moveDirection = Vector2.zero;
}
}
}
private void Move()
{
// 根据移动方向和速度移动坦克
if (moveDirection != Vector2.zero)
{
// 更新坦克朝向
float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
// 应用移动
rb.velocity = moveDirection * moveSpeed;
}
else if (!isOnIce)
{
// 不在冰面上时停止移动
rb.velocity = Vector2.zero;
}
}
private void HandleShooting()
{
// 检查射击输入
if (Input.GetKeyDown(KeyCode.Space) && Time.time > lastFireTime + fireRate && activeBulletCount < maxBullets)
{
Fire();
}
}
private void Fire()
{
// 创建子弹
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, transform.rotation);
// 配置子弹
PlayerBullet playerBullet = bullet.GetComponent<PlayerBullet>();
if (playerBullet != null)
{
playerBullet.Initialize(bulletPower, this);
activeBulletCount++;
}
// 更新射击时间
lastFireTime = Time.time;
// 播放射击音效
AudioManager.Instance.PlaySound("PlayerShoot");
}
public void OnBulletDestroyed()
{
// 减少活动子弹计数
activeBulletCount = Mathf.Max(0, activeBulletCount - 1);
}
private void OnTriggerEnter2D(Collider2D collision)
{
// 检测是否在冰面上
if (collision.CompareTag("Ice"))
{
isOnIce = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
// 检测是否离开冰面
if (collision.CompareTag("Ice"))
{
isOnIce = false;
// 如果没有输入,则停止移动
if (moveDirection == Vector2.zero)
{
rb.velocity = Vector2.zero;
}
}
}
public void TakeDamage()
{
if (isInvulnerable)
return;
currentLives--;
if (currentLives <= 0)
{
// 玩家死亡
Die();
}
else
{
// 临时无敌并重生
StartCoroutine(Respawn());
}
}
private void Die()
{
// 播放死亡效果
// 通知游戏管理器
GameManager.Instance.PlayerDied();
// 禁用玩家
gameObject.SetActive(false);
}
private IEnumerator Respawn()
{
// 禁用碰撞和渲染
GetComponent<Collider2D>().enabled = false;
spriteRenderer.enabled = false;
yield return new WaitForSeconds(1.0f);
// 重置位置
transform.position = GameManager.Instance.GetPlayerSpawnPosition();
transform.rotation = Quaternion.identity;
// 启用碰撞和渲染
GetComponent<Collider2D>().enabled = true;
spriteRenderer.enabled = true;
// 设置临时无敌
StartCoroutine(TemporaryInvulnerability(3.0f));
}
private IEnumerator TemporaryInvulnerability(float duration)
{
isInvulnerable = true;
// 闪烁效果
float blinkRate = 0.1f;
for (float t = 0; t < duration; t += blinkRate)
{
spriteRenderer.enabled = !spriteRenderer.enabled;
yield return new WaitForSeconds(blinkRate);
}
spriteRenderer.enabled = true;
isInvulnerable = false;
}
public void PowerUpBullet()
{
// 提升子弹威力,最高为3
bulletPower = Mathf.Min(bulletPower + 1, 3);
}
public void IncreaseMaxBullets()
{
// 提升最大子弹数量,最高为2
maxBullets = Mathf.Min(maxBullets + 1, 2);
}
public void AddLife()
{
// 增加生命,最高为maxLives
currentLives = Mathf.Min(currentLives + 1, maxLives);
}
}
玩家子弹类
csharp
public class PlayerBullet : MonoBehaviour
{
[SerializeField] private float speed = 10.0f;
[SerializeField] private float lifetime = 3.0f;
private int power;
private PlayerTank owner;
private Rigidbody2D rb;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
public void Initialize(int bulletPower, PlayerTank bulletOwner)
{
power = bulletPower;
owner = bulletOwner;
// 设置生命周期
Destroy(gameObject, lifetime);
}
private void FixedUpdate()
{
// 移动子弹
rb.velocity = transform.right * speed;
}
private void OnTriggerEnter2D(Collider2D collision)
{
// 处理碰撞
if (collision.CompareTag("Enemy"))
{
// 击中敌人
EnemyTank enemyTank = collision.GetComponent<EnemyTank>();
if (enemyTank != null)
{
enemyTank.TakeDamage();
}
DestroyBullet();
}
else if (collision.CompareTag("BrickWall"))
{
// 击中砖墙
MapManager.Instance.DestroyTerrain(collision.transform.position);
DestroyBullet();
}
else if (collision.CompareTag("SteelWall"))
{
// 击中钢墙,只有高级子弹才能摧毁
if (power >= 3)
{
MapManager.Instance.DestroyTerrain(collision.transform.position);
}
DestroyBullet();
}
else if (collision.CompareTag("EnemyBullet"))
{
// 子弹相撞
Destroy(collision.gameObject);
DestroyBullet();
}
}
private void DestroyBullet()
{
// 通知拥有者
if (owner != null)
{
owner.OnBulletDestroyed();
}
// 播放爆炸效果
// 销毁子弹
Destroy(gameObject);
}
}
通过以上代码,我们实现了完整的玩家控制系统,包括坦克移动、射击和生命管理。
19.3.4 障碍物系统设计
在坦克大战中,障碍物是游戏地图的重要组成部分,它们影响坦克的移动和子弹的传播。
障碍物基类
csharp
public abstract class Obstacle : MonoBehaviour
{
[Header("Properties")]
[SerializeField] protected bool isDestructible;
[SerializeField] protected bool isPassable;
// 公共属性访问器
public bool IsDestructible => isDestructible;
public bool IsPassable => isPassable;
public virtual void HandleBulletCollision(int bulletPower)
{
// 如果障碍物可被摧毁且子弹威力足够
if (isDestructible && ShouldDestroyByBulletPower(bulletPower))
{
// 播放破坏特效
PlayDestructionEffect();
// 销毁障碍物
Destroy(gameObject);
}
}
protected virtual bool ShouldDestroyByBulletPower(int bulletPower)
{
// 默认实现:任何子弹都可以摧毁
return true;
}
protected virtual void PlayDestructionEffect()
{
// 在子类中实现具体的特效
}
}
具体障碍物实现
csharp
// 砖墙
public class BrickWall : Obstacle
{
[SerializeField] private GameObject destructionEffectPrefab;
private void Awake()
{
isDestructible = true;
isPassable = false;
}
protected override void PlayDestructionEffect()
{
if (destructionEffectPrefab != null)
{
Instantiate(destructionEffectPrefab, transform.position, Quaternion.identity);
}
// 播放音效
AudioManager.Instance.PlaySound("BrickDestroy");
}
}
// 钢墙
public class SteelWall : Obstacle
{
[SerializeField] private GameObject destructionEffectPrefab;
private void Awake()
{
isDestructible = true;
isPassable = false;
}
protected override bool ShouldDestroyByBulletPower(int bulletPower)
{
// 只有威力大于等于3的子弹才能摧毁钢墙
return bulletPower >= 3;
}
protected override void PlayDestructionEffect()
{
if (destructionEffectPrefab != null)
{
Instantiate(destructionEffectPrefab, transform.position, Quaternion.identity);
}
// 播放音效
AudioManager.Instance.PlaySound("SteelDestroy");
}
}
// 水域
public class Water : Obstacle
{
private void Awake()
{
isDestructible = false;
isPassable = false;
}
}
// 草丛
public class Forest : Obstacle
{
private void Awake()
{
isDestructible = false;
isPassable = true;
}
private void OnTriggerEnter2D(Collider2D collision)
{
// 当坦克进入草丛时,降低其Sprite的渲染顺序
if (collision.CompareTag("Player") || collision.CompareTag("Enemy"))
{
SpriteRenderer tankRenderer = collision.GetComponent<SpriteRenderer>();
if (tankRenderer != null)
{
tankRenderer.sortingOrder = -1;
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
// 当坦克离开草丛时,恢复其Sprite的渲染顺序
if (collision.CompareTag("Player") || collision.CompareTag("Enemy"))
{
SpriteRenderer tankRenderer = collision.GetComponent<SpriteRenderer>();
if (tankRenderer != null)
{
tankRenderer.sortingOrder = 1;
}
}
}
}
// 冰面
public class Ice : Obstacle
{
private void Awake()
{
isDestructible = false;
isPassable = true;
}
private void OnTriggerEnter2D(Collider2D collision)
{
// 冰面逻辑在坦克控制器中处理
}
}
通过以上的障碍物系统设计,我们可以创建多样化的游戏地图,增加游戏的趣味性和策略性。
19.3.5 道具系统设计
道具系统可以为游戏增加更多变化和策略元素。在坦克大战中,道具会随机出现,玩家可以通过收集它们获得各种临时能力。
道具基类
csharp
public abstract class PowerUp : MonoBehaviour
{
[SerializeField] protected float lifetime = 10.0f;
[SerializeField] protected GameObject collectEffectPrefab;
protected virtual void Start()
{
// 道具自动销毁计时
Destroy(gameObject, lifetime);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
// 获取玩家坦克
PlayerTank playerTank = collision.GetComponent<PlayerTank>();
if (playerTank != null)
{
// 应用道具效果
ApplyEffect(playerTank);
// 播放收集特效
if (collectEffectPrefab != null)
{
Instantiate(collectEffectPrefab, transform.position, Quaternion.identity);
}
// 播放音效
AudioManager.Instance.PlaySound("PowerUp");
// 销毁道具
Destroy(gameObject);
}
}
}
// 道具效果的抽象方法,由子类实现
protected abstract void ApplyEffect(PlayerTank playerTank);
}
具体道具实现
csharp
// 星星道具 - 提升子弹威力
public class StarPowerUp : PowerUp
{
protected override void ApplyEffect(PlayerTank playerTank)
{
playerTank.PowerUpBullet();
}
}
// 坦克道具 - 增加一条生命
public class TankPowerUp : PowerUp
{
protected override void ApplyEffect(PlayerTank playerTank)
{
playerTank.AddLife();
}
}
// 计时器道具 - 冻结所有敌人
public class TimerPowerUp : PowerUp
{
[SerializeField] private float freezeDuration = 10.0f;
protected override void ApplyEffect(PlayerTank playerTank)
{
// 通知游戏管理器冻结所有敌人
GameManager.Instance.FreezeEnemies(freezeDuration);
}
}
// 铲子道具 - 加固基地周围的墙
public class ShovelPowerUp : PowerUp
{
[SerializeField] private float fortifyDuration = 15.0f;
protected override void ApplyEffect(PlayerTank playerTank)
{
// 通知游戏管理器加固基地
GameManager.Instance.FortifyBase(fortifyDuration);
}
}
// 炸弹道具 - 摧毁所有敌人
public class BombPowerUp : PowerUp
{
protected override void ApplyEffect(PlayerTank playerTank)
{
// 通知游戏管理器摧毁所有敌人
GameManager.Instance.DestroyAllEnemies();
}
}
道具生成器
csharp
public class PowerUpSpawner : MonoBehaviour
{
[System.Serializable]
public class PowerUpData
{
public GameObject powerUpPrefab;
public float weight = 1.0f;
}
[Header("Spawning Settings")]
[SerializeField] private List<PowerUpData> availablePowerUps = new List<PowerUpData>();
[SerializeField] private float spawnChance = 0.3f;
// 当敌人被摧毁时调用
public void TrySpawnPowerUp(Vector3 position)
{
// 根据概率决定是否生成道具
if (Random.value <= spawnChance)
{
SpawnRandomPowerUp(position);
}
}
private void SpawnRandomPowerUp(Vector3 position)
{
// 如果没有可用的道具,直接返回
if (availablePowerUps.Count == 0)
return;
// 计算总权重
float totalWeight = 0;
foreach (var powerUpData in availablePowerUps)
{
totalWeight += powerUpData.weight;
}
// 随机选择一个道具
float randomValue = Random.value * totalWeight;
float weightSum = 0;
foreach (var powerUpData in availablePowerUps)
{
weightSum += powerUpData.weight;
if (randomValue <= weightSum)
{
// 生成选中的道具
Instantiate(powerUpData.powerUpPrefab, position, Quaternion.identity);
break;
}
}
}
}
通过这个道具系统,我们可以在游戏中添加更多的变化和策略元素,提升游戏的可玩性。
19.3.6 基地系统设计
基地是玩家需要保护的核心目标,如果基地被摧毁,游戏就会结束。
csharp
public class Base : MonoBehaviour
{
[SerializeField] private GameObject destructionEffectPrefab;
[SerializeField] private Sprite defaultSprite;
[SerializeField] private Sprite destroyedSprite;
private SpriteRenderer spriteRenderer;
private bool isDestroyed = false;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
spriteRenderer.sprite = defaultSprite;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (isDestroyed)
return;
// 检查是否被敌方子弹击中
if (collision.CompareTag("EnemyBullet"))
{
DestroyBase();
}
}
public void DestroyBase()
{
if (isDestroyed)
return;
isDestroyed = true;
// 更改外观
spriteRenderer.sprite = destroyedSprite;
// 播放爆炸效果
if (destructionEffectPrefab != null)
{
Instantiate(destructionEffectPrefab, transform.position, Quaternion.identity);
}
// 播放音效
AudioManager.Instance.PlaySound("BaseDestroy");
// 通知游戏管理器游戏结束
GameManager.Instance.GameOver(false);
}
// 用于铲子道具的基地加固功能
public void Fortify(float duration)
{
StartCoroutine(FortifyRoutine(duration));
}
private IEnumerator FortifyRoutine(float duration)
{
// 获取基地周围的墙
List<GameObject> surroundingWalls = GetSurroundingWalls();
// 将周围的墙升级为钢墙
foreach (GameObject wall in surroundingWalls)
{
BrickWall brickWall = wall.GetComponent<BrickWall>();
if (brickWall != null)
{
// 替换为钢墙
SpriteRenderer wallRenderer = wall.GetComponent<SpriteRenderer>();
if (wallRenderer != null)
{
wallRenderer.color = Color.gray;
}
// 暂时将碰撞层改为钢墙
wall.layer = LayerMask.NameToLayer("SteelWall");
}
}
// 等待持续时间
yield return new WaitForSeconds(duration);
// 恢复墙的原始状态
foreach (GameObject wall in surroundingWalls)
{
// 检查墙是否还存在
if (wall != null)
{
SpriteRenderer wallRenderer = wall.GetComponent<SpriteRenderer>();
if (wallRenderer != null)
{
wallRenderer.color = Color.white;
}
// 恢复原始碰撞层
wall.layer = LayerMask.NameToLayer("BrickWall");
}
}
}
private List<GameObject> GetSurroundingWalls()
{
List<GameObject> walls = new List<GameObject>();
// 获取基地周围的墙(通常是4个位置)
float wallDistance = 1.0f;
Vector2[] directions = new Vector2[] { Vector2.up, Vector2.right, Vector2.down, Vector2.left };
foreach (Vector2 direction in directions)
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, wallDistance, LayerMask.GetMask("BrickWall"));
if (hit.collider != null)
{
walls.Add(hit.collider.gameObject);
}
}
return walls;
}
}
通过基地系统,我们实现了游戏的核心保护目标,同时还加入了基地加固的功能,增加了游戏的策略性。
19.3.7 游戏流程控制
游戏流程控制是管理整个游戏状态和进程的核心系统。
csharp
// 游戏状态枚举
public enum GameState
{
MainMenu,
Playing,
Paused,
GameOver,
LevelComplete
}
public class GameManager : MonoBehaviour
{
// 单例模式
public static GameManager Instance { get; private set; }
[Header("Game Settings")]
[SerializeField] private int startingLives = 3;
[SerializeField] private int currentLevel = 1;
[SerializeField] private int maxLevel = 10;
[Header("Prefabs")]
[SerializeField] private GameObject playerTankPrefab;
[SerializeField] private GameObject[] enemyTankPrefabs;
[Header("Spawn Points")]
[SerializeField] private Transform playerSpawnPoint;
[SerializeField] private Transform[] enemySpawnPoints;
[Header("UI References")]
[SerializeField] private UIManager uiManager;
// 游戏状态
private GameState currentState;
private int playerLives;
private int playerScore;
private int enemiesRemaining;
private int enemiesActive;
private int maxConcurrentEnemies = 4;
private bool isBaseDestroyed = false;
// 引用
private PlayerTank playerTank;
private Base playerBase;
private List<EnemyTank> activeEnemies = new List<EnemyTank>();
private PowerUpSpawner powerUpSpawner;
// 公共属性
public GameState CurrentState => currentState;
public int PlayerLives => playerLives;
public int PlayerScore => playerScore;
public int EnemiesRemaining => enemiesRemaining;
private void Awake()
{
// 单例初始化
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
return;
}
// 获取引用
playerBase = FindObjectOfType<Base>();
powerUpSpawner = GetComponent<PowerUpSpawner>();
// 初始化游戏状态
currentState = GameState.MainMenu;
playerLives = startingLives;
playerScore = 0;
}
private void Start()
{
// 显示主菜单
uiManager.ShowMainMenu();
}
private void Update()
{
// 处理暂停
if (Input.GetKeyDown(KeyCode.Escape) && currentState == GameState.Playing)
{
PauseGame();
}
// 游戏进行中的逻辑
if (currentState == GameState.Playing)
{
// 检查是否需要生成更多敌人
if (enemiesActive < maxConcurrentEnemies && enemiesRemaining > 0)
{
SpawnEnemy();
}
// 检查关卡是否完成
if (enemiesRemaining == 0 && activeEnemies.Count == 0)
{
LevelComplete();
}
}
}
public void StartGame()
{
// 初始化游戏参数
playerLives = startingLives;
playerScore = 0;
currentLevel = 1;
// 开始第一关
StartLevel(currentLevel);
}
private void StartLevel(int level)
{
// 清理场景
ClearLevel();
// 加载地图
MapManager.Instance.LoadLevel(level);
// 初始化关卡敌人数量(随关卡增加)
enemiesRemaining = 10 + (level * 2);
enemiesActive = 0;
// 重置基地状态
isBaseDestroyed = false;
// 生成玩家坦克
SpawnPlayer();
// 更新UI
uiManager.UpdateLevelText(level);
uiManager.UpdateLivesText(playerLives);
uiManager.UpdateScoreText(playerScore);
uiManager.UpdateEnemiesText(enemiesRemaining);
// 显示关卡开始UI
uiManager.ShowLevelStart(level);
// 延迟一段时间后开始游戏
StartCoroutine(DelayedGameStart());
}
private IEnumerator DelayedGameStart()
{
yield return new WaitForSeconds(2.0f);
// 切换到游戏状态
currentState = GameState.Playing;
// 隐藏关卡开始UI
uiManager.HideLevelStart();
// 开始生成敌人
SpawnInitialEnemies();
}
private void SpawnInitialEnemies()
{
// 初始生成几个敌人
for (int i = 0; i < Mathf.Min(maxConcurrentEnemies, enemiesRemaining); i++)
{
SpawnEnemy();
}
}
private void SpawnPlayer()
{
// 在玩家出生点生成坦克
GameObject playerObj = Instantiate(playerTankPrefab, playerSpawnPoint.position, Quaternion.identity);
playerTank = playerObj.GetComponent<PlayerTank>();
}
private void SpawnEnemy()
{
if (enemiesRemaining <= 0)
return;
// 随机选择一个敌人类型(随关卡增加难度)
int maxEnemyType = Mathf.Min(enemyTankPrefabs.Length, 1 + (currentLevel / 2));
int enemyType = Random.Range(0, maxEnemyType);
// 随机选择一个生成点
Transform spawnPoint = enemySpawnPoints[Random.Range(0, enemySpawnPoints.Length)];
// 生成敌人
GameObject enemyObj = Instantiate(enemyTankPrefabs[enemyType], spawnPoint.position, Quaternion.identity);
EnemyTank enemy = enemyObj.GetComponent<EnemyTank>();
// 添加到活动敌人列表
activeEnemies.Add(enemy);
// 更新计数
enemiesRemaining--;
enemiesActive++;
// 更新UI
uiManager.UpdateEnemiesText(enemiesRemaining);
}
public void EnemyDestroyed(EnemyTank enemy)
{
// 从活动敌人列表中移除
activeEnemies.Remove(enemy);
enemiesActive--;
// 增加玩家分数
AddScore(enemy.TankType switch
{
EnemyTankType.Basic => 100,
EnemyTankType.Fast => 200,
EnemyTankType.Heavy => 300,
EnemyTankType.Power => 400,
_ => 100
});
// 尝试生成道具
if (powerUpSpawner != null)
{
powerUpSpawner.TrySpawnPowerUp(enemy.transform.position);
}
}
public void PlayerDied()
{
playerLives--;
// 更新UI
uiManager.UpdateLivesText(playerLives);
if (playerLives <= 0)
{
// 游戏结束
GameOver(false);
}
else
{
// 延迟重生
StartCoroutine(RespawnPlayerAfterDelay());
}
}
private IEnumerator RespawnPlayerAfterDelay()
{
yield return new WaitForSeconds(2.0f);
// 重新生成玩家
SpawnPlayer();
}
public void GameOver(bool victory)
{
currentState = GameState.GameOver;
// 显示游戏结束UI
uiManager.ShowGameOver(victory, playerScore);
}
private void LevelComplete()
{
currentState = GameState.LevelComplete;
// 显示关卡完成UI
uiManager.ShowLevelComplete();
// 检查是否完成了所有关卡
if (currentLevel >= maxLevel)
{
// 游戏胜利
StartCoroutine(DelayedGameVictory());
}
else
{
// 进入下一关
StartCoroutine(DelayedNextLevel());
}
}
private IEnumerator DelayedGameVictory()
{
yield return new WaitForSeconds(3.0f);
// 游戏胜利
GameOver(true);
}
private IEnumerator DelayedNextLevel()
{
yield return new WaitForSeconds(3.0f);
// 进入下一关
currentLevel++;
StartLevel(currentLevel);
}
private void PauseGame()
{
currentState = GameState.Paused;
Time.timeScale = 0;
// 显示暂停菜单
uiManager.ShowPauseMenu();
}
public void ResumeGame()
{
currentState = GameState.Playing;
Time.timeScale = 1;
// 隐藏暂停菜单
uiManager.HidePauseMenu();
}
public void ReturnToMainMenu()
{
currentState = GameState.MainMenu;
Time.timeScale = 1;
// 清理场景
ClearLevel();
// 显示主菜单
uiManager.ShowMainMenu();
}
private void ClearLevel()
{
// 销毁所有敌人
foreach (EnemyTank enemy in activeEnemies)
{
if (enemy != null)
{
Destroy(enemy.gameObject);
}
}
activeEnemies.Clear();
// 销毁玩家坦克
if (playerTank != null)
{
Destroy(playerTank.gameObject);
playerTank = null;
}
// 销毁所有子弹
GameObject[] bullets = GameObject.FindGameObjectsWithTag("Bullet");
foreach (GameObject bullet in bullets)
{
Destroy(bullet);
}
// 销毁所有道具
GameObject[] powerUps = GameObject.FindGameObjectsWithTag("PowerUp");
foreach (GameObject powerUp in powerUps)
{
Destroy(powerUp);
}
}
private void AddScore(int score)
{
playerScore += score;
uiManager.UpdateScoreText(playerScore);
}
public Vector3 GetPlayerSpawnPosition()
{
return playerSpawnPoint.position;
}
// 道具功能实现
public void FreezeEnemies(float duration)
{
StartCoroutine(FreezeEnemiesRoutine(duration));
}
private IEnumerator FreezeEnemiesRoutine(float duration)
{
// 冻结所有敌人
foreach (EnemyTank enemy in activeEnemies)
{
if (enemy != null)
{
enemy.gameObject.SetActive(false);
}
}
// 等待持续时间
yield return new WaitForSeconds(duration);
// 解冻所有敌人
foreach (EnemyTank enemy in activeEnemies)
{
if (enemy != null)
{
enemy.gameObject.SetActive(true);
}
}
}
public void FortifyBase(float duration)
{
if (playerBase != null)
{
playerBase.Fortify(duration);
}
}
public void DestroyAllEnemies()
{
// 创建临时列表以避免修改迭代器问题
List<EnemyTank> enemiesToDestroy = new List<EnemyTank>(activeEnemies);
foreach (EnemyTank enemy in enemiesToDestroy)
{
if (enemy != null)
{
// 销毁敌人并增加分数
AddScore(enemy.TankType switch
{
EnemyTankType.Basic => 100,
EnemyTankType.Fast => 200,
EnemyTankType.Heavy => 300,
EnemyTankType.Power => 400,
_ => 100
});
Destroy(enemy.gameObject);
activeEnemies.Remove(enemy);
enemiesActive--;
}
}
}
}
这个游戏管理器负责控制整个游戏的流程,包括关卡加载、敌人生成、游戏状态转换等核心功能。
19.4 项目实现详解
19.4.1 开发环境准备
在开始项目开发之前,我们需要做好以下准备工作:
-
创建Unity项目:
- 打开Unity Hub,创建一个新的2D项目。
- 选择Unity 2021.3.8f1c1版本。
-
设置项目结构:
mipsasm
Assets/ ├── Animations/ # 存放动画相关资源 ├── Audio/ # 音频文件 ├── Prefabs/ # 预制体 ├── Resources/ # 动态加载的资源 ├── Scenes/ # 场景文件 ├── Scripts/ # 脚本 ├── Sprites/ # 2D精灵图 └── Tiles/ # Tilemap瓦片资源 -
导入基本资源:
- 坦克精灵图
- 地形瓦片图
- 特效精灵图
- 音效和背景音乐
-
设置项目质量与分辨率:
- 将游戏分辨率设置为固定的宽高比,如16:9或4:3
- 设置合适的像素密度以保持像素游戏的风格
-
配置输入系统:
- 设置键盘输入映射:方向键/WASD用于移动,空格键用于射击
19.4.2 场景构建实现
游戏场景是玩家直接与之互动的环境,我们需要合理地设计和构建游戏场景。
主场景构建
csharp
public class SceneBuilder : MonoBehaviour
{
[SerializeField] private Transform gamePlayArea;
[SerializeField] private Transform uiCanvas;
[SerializeField] private GameObject mapManagerPrefab;
[SerializeField] private GameObject gameManagerPrefab;
[SerializeField] private GameObject audioManagerPrefab;
[SerializeField] private GameObject basePrefab;
private void Awake()
{
// 设置相机正交大小以适应游戏场景
Camera.main.orthographicSize = 6.5f;
// 初始化核心管理器
if (FindObjectOfType<GameManager>() == null)
{
Instantiate(gameManagerPrefab);
}
if (FindObjectOfType<MapManager>() == null)
{
Instantiate(mapManagerPrefab, gamePlayArea);
}
if (FindObjectOfType<AudioManager>() == null)
{
Instantiate(audioManagerPrefab);
}
// 如果没有基地,则创建一个
if (FindObjectOfType<Base>() == null)
{
Vector3 basePosition = new Vector3(0, -5, 0);
Instantiate(basePrefab, basePosition, Quaternion.identity, gamePlayArea);
}
// 设置UI缩放以适应不同分辨率
CanvasScaler canvasScaler = uiCanvas.GetComponent<CanvasScaler>();
if (canvasScaler != null)
{
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
canvasScaler.referenceResolution = new Vector2(800, 600);
canvasScaler.matchWidthOrHeight = 0.5f; // 混合宽度和高度的缩放
}
}
}
UI管理器实现
csharp
public class UIManager : MonoBehaviour
{
[Header("UI Panels")]
[SerializeField] private GameObject mainMenuPanel;
[SerializeField] private GameObject gameplayPanel;
[SerializeField] private GameObject pauseMenuPanel;
[SerializeField] private GameObject gameOverPanel;
[SerializeField] private GameObject levelCompletePanel;
[SerializeField] private GameObject levelStartPanel;
[Header("Gameplay UI Elements")]
[SerializeField] private TextMeshProUGUI levelText;
[SerializeField] private TextMeshProUGUI livesText;
[SerializeField] private TextMeshProUGUI scoreText;
[SerializeField] private TextMeshProUGUI enemiesText;
[Header("Game Over UI Elements")]
[SerializeField] private TextMeshProUGUI gameOverTitleText;
[SerializeField] private TextMeshProUGUI finalScoreText;
[Header("Level Start UI Elements")]
[SerializeField] private TextMeshProUGUI levelStartText;
// 隐藏所有面板
private void HideAllPanels()
{
mainMenuPanel.SetActive(false);
gameplayPanel.SetActive(false);
pauseMenuPanel.SetActive(false);
gameOverPanel.SetActive(false);
levelCompletePanel.SetActive(false);
levelStartPanel.SetActive(false);
}
// 显示主菜单
public void ShowMainMenu()
{
HideAllPanels();
mainMenuPanel.SetActive(true);
}
// 显示游戏界面
public void ShowGameplay()
{
HideAllPanels();
gameplayPanel.SetActive(true);
}
// 显示暂停菜单
public void ShowPauseMenu()
{
pauseMenuPanel.SetActive(true);
}
// 隐藏暂停菜单
public void HidePauseMenu()
{
pauseMenuPanel.SetActive(false);
}
// 显示游戏结束界面
public void ShowGameOver(bool victory, int finalScore)
{
HideAllPanels();
gameOverPanel.SetActive(true);
gameOverTitleText.text = victory ? "Victory!" : "Game Over";
finalScoreText.text = $"Final Score: {finalScore}";
}
// 显示关卡完成界面
public void ShowLevelComplete()
{
levelCompletePanel.SetActive(true);
}
// 显示关卡开始界面
public void ShowLevelStart(int level)
{
levelStartPanel.SetActive(true);
levelStartText.text = $"Level {level}";
}
// 隐藏关卡开始界面
public void HideLevelStart()
{
levelStartPanel.SetActive(false);
}
// 更新关卡文本
public void UpdateLevelText(int level)
{
levelText.text = $"Level: {level}";
}
// 更新生命文本
public void UpdateLivesText(int lives)
{
livesText.text = $"Lives: {lives}";
}
// 更新分数文本
public void UpdateScoreText(int score)
{
scoreText.text = $"Score: {score}";
}
// 更新敌人数量文本
public void UpdateEnemiesText(int enemies)
{
enemiesText.text = $"Enemies: {enemies}";
}
// UI按钮事件处理
public void OnStartGameButton()
{
GameManager.Instance.StartGame();
ShowGameplay();
}
public void OnResumeButton()
{
GameManager.Instance.ResumeGame();
}
public void OnMainMenuButton()
{
GameManager.Instance.ReturnToMainMenu();
}
public void OnQuitButton()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
public void OnRestartButton()
{
GameManager.Instance.StartGame();
ShowGameplay();
}
}
19.4.3 角色控制系统实现
玩家控制系统是游戏的核心交互部分。我们已经在前面的章节中实现了基本的坦克控制系统,下面我们来看一些更详细的实现细节。
坦克移动优化
为了使坦克移动更加平滑和真实,我们可以添加一些物理效果和动画:
csharp
public class TankController : MonoBehaviour
{
[SerializeField] private float accelerationRate = 10.0f;
[SerializeField] private float decelerationRate = 15.0f;
[SerializeField] private float maxMoveSpeed = 4.0f;
// 履带动画
[SerializeField] private Animator leftTrackAnimator;
[SerializeField] private Animator rightTrackAnimator;
// 移动粒子效果
[SerializeField] private ParticleSystem moveDustParticles;
// 引用
private Rigidbody2D rb;
// 状态
private Vector2 moveDirection;
private Vector2 targetVelocity;
private Vector2 currentVelocity;
private bool isOnIce;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
// 更新动画和特效
UpdateVisuals();
}
private void FixedUpdate()
{
// 计算目标速度
targetVelocity = moveDirection * maxMoveSpeed;
// 计算实际速度(考虑加速度和减速度)
if (moveDirection != Vector2.zero)
{
// 加速
currentVelocity = Vector2.MoveTowards(
currentVelocity,
targetVelocity,
accelerationRate * Time.fixedDeltaTime
);
}
else if (!isOnIce)
{
// 减速(如果不在冰面上)
currentVelocity = Vector2.MoveTowards(
currentVelocity,
Vector2.zero,
decelerationRate * Time.fixedDeltaTime
);
}
// 应用速度
rb.velocity = currentVelocity;
}
public void SetMoveDirection(Vector2 direction)
{
moveDirection = direction;
// 更新朝向
if (direction != Vector2.zero)
{
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}
private void UpdateVisuals()
{
// 更新履带动画
if (leftTrackAnimator != null && rightTrackAnimator != null)
{
bool isMoving = currentVelocity.magnitude > 0.1f;
leftTrackAnimator.SetBool("IsMoving", isMoving);
rightTrackAnimator.SetBool("IsMoving", isMoving);
}
// 更新移动粒子效果
if (moveDustParticles != null)
{
if (currentVelocity.magnitude > 0.1f && !isOnIce)
{
if (!moveDustParticles.isPlaying)
{
moveDustParticles.Play();
}
}
else
{
if (moveDustParticles.isPlaying)
{
moveDustParticles.Stop();
}
}
}
}
// 碰撞检测
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Ice"))
{
isOnIce = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.CompareTag("Ice"))
{
isOnIce = false;
}
}
}
坦克射击系统优化
为了使射击系统更加精确和有反馈,我们可以添加一些特效和后坐力效果:
csharp
public class TankWeaponSystem : MonoBehaviour
{
[SerializeField] private Transform firePoint;
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private float fireRate = 0.5f;
[SerializeField] private int maxBullets = 1;
[SerializeField] private ParticleSystem muzzleFlashEffect;
[SerializeField] private float recoilForce = 3.0f;
// 引用
private Rigidbody2D rb;
// 状态
private float lastFireTime;
private int activeBulletCount;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
activeBulletCount = 0;
}
public bool TryFire()
{
if (Time.time > lastFireTime + fireRate && activeBulletCount < maxBullets)
{
FireBullet();
return true;
}
return false;
}
private void FireBullet()
{
// 创建子弹
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
// 配置子弹
Bullet bulletComponent = bullet.GetComponent<Bullet>();
if (bulletComponent != null)
{
bulletComponent.Initialize(GetBulletPower(), gameObject);
bulletComponent.OnDestroyed += OnBulletDestroyed;
activeBulletCount++;
}
// 播放特效
if (muzzleFlashEffect != null)
{
muzzleFlashEffect.Play();
}
// 应用后坐力
ApplyRecoil();
// 更新射击时间
lastFireTime = Time.time;
// 播放音效
AudioManager.Instance.PlaySound("TankShoot");
}
private void ApplyRecoil()
{
// 计算后坐力方向(与射击方向相反)
Vector2 recoilDirection = -transform.right;
// 应用后坐力冲量
rb.AddForce(recoilDirection * recoilForce, ForceMode2D.Impulse);
}
private void OnBulletDestroyed()
{
activeBulletCount = Mathf.Max(0, activeBulletCount - 1);
}
protected virtual int GetBulletPower()
{
// 子类可以覆盖此方法以提供不同的子弹威力
return 1;
}
}
通过以上优化,我们使坦克的移动和射击系统更加真实和有反馈感,提升了游戏的手感。
19.4.4 子弹系统与物理交互
子弹系统是坦克大战游戏中非常重要的一部分,它需要处理子弹的移动、碰撞检测和伤害计算。
子弹基类
csharp
public abstract class Bullet : MonoBehaviour
{
[SerializeField] protected float speed = 10.0f;
[SerializeField] protected float lifetime = 3.0f;
[SerializeField] protected GameObject hitEffectPrefab;
protected int power;
protected GameObject owner;
protected Rigidbody2D rb;
protected TrailRenderer trailRenderer;
// 事件
public event Action OnDestroyed;
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
trailRenderer = GetComponent<TrailRenderer>();
// 设置生命周期
Destroy(gameObject, lifetime);
}
public virtual void Initialize(int bulletPower, GameObject bulletOwner)
{
power = bulletPower;
owner = bulletOwner;
// 根据威力调整外观
transform.localScale = Vector3.one * (0.8f + power * 0.1f);
// 调整拖尾颜色
if (trailRenderer != null && power > 1)
{
Color trailColor = (power == 2) ? Color.yellow : Color.red;
trailRenderer.startColor = trailColor;
trailRenderer.endColor = new Color(trailColor.r, trailColor.g, trailColor.b, 0);
}
}
protected virtual void FixedUpdate()
{
// 移动子弹
rb.velocity = transform.right * speed;
}
protected virtual void OnTriggerEnter2D(Collider2D collision)
{
// 忽略与拥有者的碰撞
if (collision.gameObject == owner)
return;
// 处理碰撞
HandleCollision(collision);
}
protected virtual void HandleCollision(Collider2D collision)
{
// 创建击中特效
CreateHitEffect();
// 子类实现具体的碰撞逻辑
// 销毁子弹
DestroyBullet();
}
protected virtual void CreateHitEffect()
{
if (hitEffectPrefab != null)
{
Instantiate(hitEffectPrefab, transform.position, Quaternion.identity);
}
}
protected virtual void DestroyBullet()
{
// 触发销毁事件
OnDestroyed?.Invoke();
// 销毁子弹
Destroy(gameObject);
}
}
玩家子弹实现
csharp
public class PlayerBullet : Bullet
{
protected override void HandleCollision(Collider2D collision)
{
if (collision.CompareTag("Enemy"))
{
// 击中敌人
EnemyTank enemyTank = collision.GetComponent<EnemyTank>();
if (enemyTank != null)
{
enemyTank.TakeDamage();
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("BrickWall"))
{
// 击中砖墙
BrickWall brickWall = collision.GetComponent<BrickWall>();
if (brickWall != null)
{
brickWall.HandleBulletCollision(power);
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("SteelWall"))
{
// 击中钢墙
SteelWall steelWall = collision.GetComponent<SteelWall>();
if (steelWall != null)
{
steelWall.HandleBulletCollision(power);
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("EnemyBullet"))
{
// 子弹相撞
Destroy(collision.gameObject);
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("Water") || collision.CompareTag("Ice") || collision.CompareTag("Forest"))
{
// 穿过这些地形
return;
}
else
{
// 其他碰撞
CreateHitEffect();
DestroyBullet();
}
}
}
敌人子弹实现
csharp
public class EnemyBullet : Bullet
{
protected override void HandleCollision(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
// 击中玩家
PlayerTank playerTank = collision.GetComponent<PlayerTank>();
if (playerTank != null)
{
playerTank.TakeDamage();
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("Base"))
{
// 击中基地
Base playerBase = collision.GetComponent<Base>();
if (playerBase != null)
{
playerBase.DestroyBase();
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("BrickWall"))
{
// 击中砖墙
BrickWall brickWall = collision.GetComponent<BrickWall>();
if (brickWall != null)
{
brickWall.HandleBulletCollision(power);
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("SteelWall"))
{
// 击中钢墙
SteelWall steelWall = collision.GetComponent<SteelWall>();
if (steelWall != null)
{
steelWall.HandleBulletCollision(power);
}
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("PlayerBullet"))
{
// 子弹相撞
Destroy(collision.gameObject);
CreateHitEffect();
DestroyBullet();
}
else if (collision.CompareTag("Water") || collision.CompareTag("Ice") || collision.CompareTag("Forest"))
{
// 穿过这些地形
return;
}
else
{
// 其他碰撞
CreateHitEffect();
DestroyBullet();
}
}
}
通过这种设计,我们可以区分玩家子弹和敌人子弹的行为,使它们对不同的目标产生不同的效果。
19.4.5 地图元素与基地防御
地图元素和基地是游戏的重要组成部分,它们决定了游戏的地形和胜负条件。
砖墙的分段破坏
为了实现更逼真的破坏效果,我们可以将砖墙分成四个部分,使其可以被逐步破坏:
csharp
public class BrickWallSegmented : Obstacle
{
[SerializeField] private GameObject[] wallSegments;
[SerializeField] private GameObject destructionEffectPrefab;
// 墙体状态 (四个部分的存在状态)
private bool[] segmentExists = new bool[4] { true, true, true, true };
private void Awake()
{
isDestructible = true;
isPassable = false;
}
public override void HandleBulletCollision(int bulletPower, Vector2 hitPoint)
{
// 计算击中了哪个部分
int hitSegmentIndex = CalculateHitSegment(hitPoint);
if (hitSegmentIndex >= 0 && hitSegmentIndex < 4 && segmentExists[hitSegmentIndex])
{
// 销毁被击中的部分
DestroySegment(hitSegmentIndex);
// 检查墙是否完全被摧毁
CheckWallDestroyed();
}
}
private int CalculateHitSegment(Vector2 hitPoint)
{
// 将世界坐标转换为局部坐标
Vector2 localHitPoint = transform.InverseTransformPoint(hitPoint);
// 将局部坐标映射到0-1范围
float normalizedX = (localHitPoint.x + 0.5f) / 1.0f;
float normalizedY = (localHitPoint.y + 0.5f) / 1.0f;
// 确定击中的象限
int quadrantX = normalizedX < 0.5f ? 0 : 1;
int quadrantY = normalizedY < 0.5f ? 0 : 1;
// 转换为段索引 (左下为0,右下为1,左上为2,右上为3)
return quadrantX + (quadrantY * 2);
}
private void DestroySegment(int segmentIndex)
{
if (segmentIndex < 0 || segmentIndex >= 4)
return;
// 标记段为已销毁
segmentExists[segmentIndex] = false;
// 禁用相应的视觉表示
if (wallSegments[segmentIndex] != null)
{
wallSegments[segmentIndex].SetActive(false);
}
// 播放破坏特效
if (destructionEffectPrefab != null)
{
Vector3 segmentPosition = wallSegments[segmentIndex].transform.position;
Instantiate(destructionEffectPrefab, segmentPosition, Quaternion.identity);
}
// 播放音效
AudioManager.Instance.PlaySound("BrickDestroy");
}
private void CheckWallDestroyed()
{
bool anySegmentExists = false;
foreach (bool exists in segmentExists)
{
if (exists)
{
anySegmentExists = true;
break;
}
}
if (!anySegmentExists)
{
// 墙已完全摧毁
Destroy(gameObject);
}
else
{
// 更新碰撞体以反映新的形状
UpdateCollider();
}
}
private void UpdateCollider()
{
// 实际应用中,可能需要更复杂的碰撞体更新逻辑
// 这里简化处理,仅根据现有段的数量调整碰撞体大小
BoxCollider2D boxCollider = GetComponent<BoxCollider2D>();
if (boxCollider != null)
{
int remainingSegments = 0;
foreach (bool exists in segmentExists)
{
if (exists)
remainingSegments++;
}
// 根据剩余段数调整碰撞体大小
float sizeMultiplier = remainingSegments / 4.0f;
boxCollider.size = new Vector2(sizeMultiplier, sizeMultiplier);
}
}
}
基地防御系统
基地是玩家需要保护的最重要目标,我们可以添加一些额外的防御机制:
csharp
public class BaseDefenseSystem : MonoBehaviour
{
[SerializeField] private Base playerBase;
[SerializeField] private float protectionRadius = 3.0f;
[SerializeField] private GameObject shieldEffectPrefab;
[SerializeField] private GameObject[] baseWalls;
private bool isShieldActive = false;
private GameObject activeShieldEffect = null;
private Coroutine fortificationCoroutine = null;
// 检测到敌人接近时触发警报
private void Update()
{
if (isShieldActive)
return;
// 检查是否有敌人接近基地
Collider2D[] colliders = Physics2D.OverlapCircleAll(
playerBase.transform.position,
protectionRadius,
LayerMask.GetMask("Enemy")
);
if (colliders.Length > 0)
{
// 敌人接近,触发警报
TriggerAlert();
}
}
// 触发警报
private void TriggerAlert()
{
// 播放警报音效
AudioManager.Instance.PlaySound("BaseAlert");
// 显示视觉提示(可以是UI元素或特效)
StartCoroutine(FlashBaseWarning());
}
// 闪烁基地警告
private IEnumerator FlashBaseWarning()
{
SpriteRenderer baseRenderer = playerBase.GetComponent<SpriteRenderer>();
Color originalColor = baseRenderer.color;
for (int i = 0; i < 3; i++)
{
baseRenderer.color = Color.red;
yield return new WaitForSeconds(0.2f);
baseRenderer.color = originalColor;
yield return new WaitForSeconds(0.2f);
}
}
// 激活基地防护罩(由道具触发)
public void ActivateShield(float duration)
{
if (fortificationCoroutine != null)
{
StopCoroutine(fortificationCoroutine);
}
fortificationCoroutine = StartCoroutine(ShieldRoutine(duration));
}
// 基地防护罩协程
private IEnumerator ShieldRoutine(float duration)
{
isShieldActive = true;
// 创建防护罩视觉效果
if (shieldEffectPrefab != null)
{
activeShieldEffect = Instantiate(
shieldEffectPrefab,
playerBase.transform.position,
Quaternion.identity,
transform
);
}
// 将基地周围的墙升级为钢墙
UpgradeBaseWalls(true);
// 等待持续时间
yield return new WaitForSeconds(duration);
// 恢复墙的原始状态
UpgradeBaseWalls(false);
// 销毁防护罩效果
if (activeShieldEffect != null)
{
Destroy(activeShieldEffect);
activeShieldEffect = null;
}
isShieldActive = false;
}
// 升级/降级基地周围的墙
private void UpgradeBaseWalls(bool upgrade)
{
foreach (GameObject wall in baseWalls)
{
if (wall == null)
continue;
SpriteRenderer wallRenderer = wall.GetComponent<SpriteRenderer>();
if (wallRenderer != null)
{
wallRenderer.color = upgrade ? Color.gray : Color.white;
}
// 修改碰撞层
wall.layer = LayerMask.NameToLayer(upgrade ? "SteelWall" : "BrickWall");
// 修改墙的属性
BrickWall brickWall = wall.GetComponent<BrickWall>();
if (brickWall != null)
{
if (upgrade)
{
// 临时保存原始状态并修改为钢墙
brickWall.enabled = false;
wall.AddComponent<SteelWall>();
}
else
{
// 移除钢墙组件并恢复砖墙
SteelWall steelWall = wall.GetComponent<SteelWall>();
if (steelWall != null)
{
Destroy(steelWall);
}
brickWall.enabled = true;
}
}
}
}
}
19.4.6 敌人AI行为树实现
为了创建更智能的敌人AI,我们可以使用行为树模式来组织和管理敌人的决策逻辑:
csharp
// 行为节点基类
public abstract class BehaviorNode
{
public enum Status
{
Success,
Failure,
Running
}
public abstract Status Execute(EnemyTank context);
}
// 选择节点 - 按顺序执行子节点,直到一个成功或所有失败
public class SelectorNode : BehaviorNode
{
private List<BehaviorNode> children = new List<BehaviorNode>();
public SelectorNode(params BehaviorNode[] nodes)
{
children.AddRange(nodes);
}
public override Status Execute(EnemyTank context)
{
foreach (var child in children)
{
Status status = child.Execute(context);
if (status != Status.Failure)
return status;
}
return Status.Failure;
}
}
// 序列节点 - 按顺序执行所有子节点,直到一个失败或所有成功
public class SequenceNode : BehaviorNode
{
private List<BehaviorNode> children = new List<BehaviorNode>();
public SequenceNode(params BehaviorNode[] nodes)
{
children.AddRange(nodes);
}
public override Status Execute(EnemyTank context)
{
foreach (var child in children)
{
Status status = child.Execute(context);
if (status != Status.Success)
return status;
}
return Status.Success;
}
}
// 条件节点 - 检查条件是否满足
public class ConditionNode : BehaviorNode
{
private System.Func<EnemyTank, bool> condition;
public ConditionNode(System.Func<EnemyTank, bool> condition)
{
this.condition = condition;
}
public override Status Execute(EnemyTank context)
{
return condition(context) ? Status.Success : Status.Failure;
}
}
// 动作节点 - 执行具体的行为
public class ActionNode : BehaviorNode
{
private System.Func<EnemyTank, Status> action;
public ActionNode(System.Func<EnemyTank, Status> action)
{
this.action = action;
}
public override Status Execute(EnemyTank context)
{
return action(context);
}
}
// 敌人AI控制器
public class EnemyAIController : MonoBehaviour
{
private EnemyTank enemyTank;
private BehaviorNode behaviorTree;
private void Awake()
{
enemyTank = GetComponent<EnemyTank>();
// 构建行为树
behaviorTree = new SelectorNode(
// 如果血量低,尝试逃跑
new SequenceNode(
new ConditionNode(HasLowHealth),
new ActionNode(Flee)
),
// 如果可以看到基地且足够近,攻击基地
new SequenceNode(
new ConditionNode(CanSeeBase),
new ConditionNode(IsCloseToBase),
new ActionNode(AttackBase)
),
// 如果可以看到玩家,追击并攻击
new SequenceNode(
new ConditionNode(CanSeePlayer),
new SelectorNode(
// 如果足够近,直接攻击
new SequenceNode(
new ConditionNode(IsCloseToPlayer),
new ActionNode(AttackPlayer)
),
// 否则追击
new ActionNode(ChasePlayer)
)
),
// 默认行为:巡逻
new ActionNode(Patrol)
);
}
private void Update()
{
if (GameManager.Instance.CurrentState != GameState.Playing)
return;
// 执行行为树
behaviorTree.Execute(enemyTank);
}
// 条件函数
private bool HasLowHealth(EnemyTank tank)
{
return tank.CurrentHealth <= tank.MaxHealth / 3;
}
private bool CanSeeBase(EnemyTank tank)
{
return tank.CanSeeTarget(tank.BaseTransform);
}
private bool IsCloseToBase(EnemyTank tank)
{
if (tank.BaseTransform == null)
return false;
float distance = Vector2.Distance(tank.transform.position, tank.BaseTransform.position);
return distance < 5.0f;
}
private bool CanSeePlayer(EnemyTank tank)
{
return tank.CanSeeTarget(tank.PlayerTransform);
}
private bool IsCloseToPlayer(EnemyTank tank)
{
if (tank.PlayerTransform == null)
return false;
float distance = Vector2.Distance(tank.transform.position, tank.PlayerTransform.position);
return distance < 3.0f;
}
// 动作函数
private BehaviorNode.Status Flee(EnemyTank tank)
{
// 实现逃跑逻辑
if (tank.PlayerTransform == null)
return BehaviorNode.Status.Failure;
Vector2 fleeDirection = (tank.transform.position - tank.PlayerTransform.position).normalized;
tank.SetMoveDirection(fleeDirection);
return BehaviorNode.Status.Running;
}
private BehaviorNode.Status AttackBase(EnemyTank tank)
{
// 实现攻击基地逻辑
if (tank.BaseTransform == null)
return BehaviorNode.Status.Failure;
Vector2 directionToBase = (tank.BaseTransform.position - tank.transform.position).normalized;
tank.SetMoveDirection(directionToBase);
// 尝试射击
tank.TryFire();
return BehaviorNode.Status.Running;
}
private BehaviorNode.Status AttackPlayer(EnemyTank tank)
{
// 实现攻击玩家逻辑
if (tank.PlayerTransform == null)
return BehaviorNode.Status.Failure;
// 面向玩家但不移动
Vector2 directionToPlayer = (tank.PlayerTransform.position - tank.transform.position).normalized;
tank.SetMoveDirection(directionToPlayer);
tank.StopMoving();
// 尝试射击
tank.TryFire();
return BehaviorNode.Status.Running;
}
private BehaviorNode.Status ChasePlayer(EnemyTank tank)
{
// 实现追击玩家逻辑
if (tank.PlayerTransform == null)
return BehaviorNode.Status.Failure;
Vector2 directionToPlayer = (tank.PlayerTransform.position - tank.transform.position).normalized;
tank.SetMoveDirection(directionToPlayer);
return BehaviorNode.Status.Running;
}
private BehaviorNode.Status Patrol(EnemyTank tank)
{
// 实现巡逻逻辑
tank.UpdatePatrolBehavior();
return BehaviorNode.Status.Running;
}
}
通过行为树模式,我们可以创建更复杂和智能的敌人AI,使敌人能够根据不同的情况做出合适的决策。
19.4.7 敌人生成器实现
敌人生成器负责在游戏中按照一定规则和节奏生成敌人,以控制游戏的难度和节奏。
csharp
public class EnemySpawner : MonoBehaviour
{
[System.Serializable]
public class EnemyWave
{
public int basicEnemyCount;
public int fastEnemyCount;
public int heavyEnemyCount;
public int powerEnemyCount;
public float timeBetweenSpawns = 3.0f;
}
[SerializeField] private GameObject basicEnemyPrefab;
[SerializeField] private GameObject fastEnemyPrefab;
[SerializeField] private GameObject heavyEnemyPrefab;
[SerializeField] private GameObject powerEnemyPrefab;
[SerializeField] private Transform[] spawnPoints;
[SerializeField] private EnemyWave[] waves;
[SerializeField] private int maxConcurrentEnemies = 4;
[SerializeField] private float initialDelay = 2.0f;
// 状态
private int currentWave = 0;
private int activeEnemies = 0;
private int totalEnemiesSpawned = 0;
private int totalEnemiesInLevel;
private Coroutine spawnCoroutine;
// 事件
public event Action<int> OnEnemySpawned;
public event Action OnAllEnemiesSpawned;
private void Start()
{
// 计算关卡中的总敌人数量
CalculateTotalEnemies();
}
private void CalculateTotalEnemies()
{
totalEnemiesInLevel = 0;
foreach (EnemyWave wave in waves)
{
totalEnemiesInLevel += wave.basicEnemyCount +
wave.fastEnemyCount +
wave.heavyEnemyCount +
wave.powerEnemyCount;
}
}
public void StartSpawning()
{
// 重置状态
currentWave = 0;
activeEnemies = 0;
totalEnemiesSpawned = 0;
// 开始生成敌人
if (spawnCoroutine != null)
{
StopCoroutine(spawnCoroutine);
}
spawnCoroutine = StartCoroutine(SpawnWaves());
}
public void StopSpawning()
{
if (spawnCoroutine != null)
{
StopCoroutine(spawnCoroutine);
spawnCoroutine = null;
}
}
private IEnumerator SpawnWaves()
{
// 初始延迟
yield return new WaitForSeconds(initialDelay);
while (currentWave < waves.Length)
{
// 获取当前波次
EnemyWave wave = waves[currentWave];
// 生成这一波中的所有敌人
int enemiesInWave = wave.basicEnemyCount +
wave.fastEnemyCount +
wave.heavyEnemyCount +
wave.powerEnemyCount;
int enemiesSpawnedInWave = 0;
// 生成基础敌人
for (int i = 0; i < wave.basicEnemyCount; i++)
{
yield return StartCoroutine(WaitForSpawnSlot());
SpawnEnemy(basicEnemyPrefab);
enemiesSpawnedInWave++;
yield return new WaitForSeconds(wave.timeBetweenSpawns);
}
// 生成快速敌人
for (int i = 0; i < wave.fastEnemyCount; i++)
{
yield return StartCoroutine(WaitForSpawnSlot());
SpawnEnemy(fastEnemyPrefab);
enemiesSpawnedInWave++;
yield return new WaitForSeconds(wave.timeBetweenSpawns);
}
// 生成重型敌人
for (int i = 0; i < wave.heavyEnemyCount; i++)
{
yield return StartCoroutine(WaitForSpawnSlot());
SpawnEnemy(heavyEnemyPrefab);
enemiesSpawnedInWave++;
yield return new WaitForSeconds(wave.timeBetweenSpawns);
}
// 生成火力敌人
for (int i = 0; i < wave.powerEnemyCount; i++)
{
yield return StartCoroutine(WaitForSpawnSlot());
SpawnEnemy(powerEnemyPrefab);
enemiesSpawnedInWave++;
yield return new WaitForSeconds(wave.timeBetweenSpawns);
}
// 等待这一波的所有敌人被消灭
while (activeEnemies > 0)
{
yield return new WaitForSeconds(0.5f);
}
// 进入下一波
currentWave++;
// 波次之间的延迟
if (currentWave < waves.Length)
{
yield return new WaitForSeconds(5.0f);
}
}
// 所有波次都已生成
OnAllEnemiesSpawned?.Invoke();
}
private IEnumerator WaitForSpawnSlot()
{
// 等待,直到活动敌人数量低于上限
while (activeEnemies >= maxConcurrentEnemies)
{
yield return new WaitForSeconds(0.5f);
}
}
private void SpawnEnemy(GameObject enemyPrefab)
{
// 选择一个随机生成点
Transform spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];
// 生成敌人
GameObject enemy = Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity);
// 配置敌人
EnemyTank enemyTank = enemy.GetComponent<EnemyTank>();
if (enemyTank != null)
{
enemyTank.OnDestroyed += OnEnemyDestroyed;
}
// 更新计数
activeEnemies++;
totalEnemiesSpawned++;
// 触发事件
OnEnemySpawned?.Invoke(totalEnemiesInLevel - totalEnemiesSpawned);
}
private void OnEnemyDestroyed()
{
activeEnemies--;
}
}
通过敌人生成器,我们可以按照设计的波次和节奏生成不同类型的敌人,增加游戏的挑战性和变化性。
19.4.8 道具系统完整实现
道具系统是坦克大战中增加变化和策略性的重要元素,下面是一个更完整的道具系统实现:
csharp
// 道具类型枚举
public enum PowerUpType
{
Star, // 提升子弹威力
Tank, // 增加生命
Clock, // 冻结敌人
Shovel, // 加固基地
Bomb, // 摧毁所有敌人
Shield // 临时护盾
}
// 道具基类
public abstract class PowerUp : MonoBehaviour
{
[SerializeField] protected PowerUpType type;
[SerializeField] protected float lifetime = 10.0f;
[SerializeField] protected GameObject collectEffectPrefab;
[SerializeField] protected GameObject powerUpVisual;
private float blinkStartTime;
private bool isBlinking = false;
protected virtual void Start()
{
// 设置闪烁开始时间(生命周期的后1/3)
blinkStartTime = Time.time + (lifetime * 2 / 3);
// 设置自动销毁
Destroy(gameObject, lifetime);
}
protected virtual void Update()
{
// 道具即将消失时闪烁
if (Time.time >= blinkStartTime && !isBlinking)
{
isBlinking = true;
StartCoroutine(BlinkEffect());
}
}
private IEnumerator BlinkEffect()
{
SpriteRenderer renderer = powerUpVisual.GetComponent<SpriteRenderer>();
// 闪烁直到销毁
while (true)
{
renderer.enabled = !renderer.enabled;
yield return new WaitForSeconds(0.15f);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
PlayerTank playerTank = collision.GetComponent<PlayerTank>();
if (playerTank != null)
{
// 应用道具效果
ApplyEffect(playerTank);
// 播放收集特效
if (collectEffectPrefab != null)
{
Instantiate(collectEffectPrefab, transform.position, Quaternion.identity);
}
// 播放音效
AudioManager.Instance.PlaySound("PowerUp");
// 通知游戏管理器
GameManager.Instance.PowerUpCollected(type);
// 销毁道具
Destroy(gameObject);
}
}
}
// 道具效果的抽象方法,由子类实现
protected abstract void ApplyEffect(PlayerTank playerTank);
}
// 星星道具实现
public class StarPowerUp : PowerUp
{
protected override void Start()
{
base.Start();
type = PowerUpType.Star;
}
protected override void ApplyEffect(PlayerTank playerTank)
{
playerTank.PowerUpBullet();
}
}
// 坦克道具实现
public class TankPowerUp : PowerUp
{
protected override void Start()
{
base.Start();
type = PowerUpType.Tank;
}
protected override void ApplyEffect(PlayerTank playerTank)
{
playerTank.AddLife();
}
}
// 计时器道具实现
public class ClockPowerUp : PowerUp
{
[SerializeField] private float freezeDuration = 10.0f;
protected override void Start()
{
base.Start();
type = PowerUpType.Clock;
}
protected override void ApplyEffect(PlayerTank playerTank)
{
GameManager.Instance.FreezeEnemies(freezeDuration);
}
}
// 铲子道具实现
public class ShovelPowerUp : PowerUp
{
[SerializeField] private float fortifyDuration = 15.0f;
protected override void Start()
{
base.Start();
type = PowerUpType.Shovel;
}
protected override void ApplyEffect(PlayerTank playerTank)
{
GameManager.Instance.FortifyBase(fortifyDuration);
}
}
// 炸弹道具实现
public class BombPowerUp : PowerUp
{
protected override void Start()
{
base.Start();
type = PowerUpType.Bomb;
}
protected override void ApplyEffect(PlayerTank playerTank)
{
GameManager.Instance.DestroyAllEnemies();
}
}
// 护盾道具实现
public class ShieldPowerUp : PowerUp
{
[SerializeField] private float shieldDuration = 10.0f;
protected override void Start()
{
base.Start();
type = PowerUpType.Shield;
}
protected override void ApplyEffect(PlayerTank playerTank)
{
playerTank.ActivateShield(shieldDuration);
}
}
// 道具生成器
public class PowerUpManager : MonoBehaviour
{
[System.Serializable]
public class PowerUpData
{
public PowerUpType type;
public GameObject prefab;
public float spawnWeight = 1.0f;
}
[SerializeField] private List<PowerUpData> powerUpData = new List<PowerUpData>();
[SerializeField] private float spawnChance = 0.3f;
[SerializeField] private float minSpawnInterval = 30.0f;
private float lastSpawnTime = -999f;
// 当敌人被摧毁时尝试生成道具
public void TrySpawnPowerUp(Vector3 position)
{
// 检查最小生成间隔
if (Time.time - lastSpawnTime < minSpawnInterval)
{
return;
}
// 根据概率决定是否生成道具
if (Random.value <= spawnChance)
{
SpawnRandomPowerUp(position);
lastSpawnTime = Time.time;
}
}
// 生成随机道具
private void SpawnRandomPowerUp(Vector3 position)
{
if (powerUpData.Count == 0)
return;
// 计算总权重
float totalWeight = 0;
foreach (var data in powerUpData)
{
totalWeight += data.spawnWeight;
}
// 随机选择一个道具
float randomValue = Random.value * totalWeight;
float currentWeight = 0;
foreach (var data in powerUpData)
{
currentWeight += data.spawnWeight;
if (randomValue <= currentWeight)
{
// 生成选中的道具
Instantiate(data.prefab, position, Quaternion.identity);
break;
}
}
}
// 强制生成特定类型的道具(用于关卡设计或特定事件)
public void SpawnSpecificPowerUp(PowerUpType type, Vector3 position)
{
foreach (var data in powerUpData)
{
if (data.type == type)
{
Instantiate(data.prefab, position, Quaternion.identity);
return;
}
}
}
}
通过这个完整的道具系统,我们可以为游戏增加更多的变化和策略元素,提升游戏的可玩性和乐趣。
19.5 扩展功能与优化
19.5.1 双人模式实现
坦克大战的一个经典功能是支持两个玩家同时游戏。下面是实现双人模式的关键代码:
csharp
// 玩家控制器基类
public abstract class PlayerController : MonoBehaviour
{
[SerializeField] protected float moveSpeed = 4.0f;
[SerializeField] protected float fireRate = 0.5f;
[SerializeField] protected int maxBullets = 1;
[SerializeField] protected int bulletPower = 1;
[SerializeField] protected GameObject shieldEffectPrefab;
// 引用
protected Rigidbody2D rb;
protected SpriteRenderer spriteRenderer;
protected GameObject activeShieldEffect;
// 状态
protected int activeBulletCount = 0;
protected float lastFireTime = -999f;
protected bool isInvulnerable = false;
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
protected virtual void Update()
{
// 处理输入
HandleInput();
}
protected virtual void FixedUpdate()
{
// 移动坦克
Move();
}
// 子类必须实现的输入处理方法
protected abstract void HandleInput();
// 移动逻辑
protected virtual void Move()
{
// 共享的移动逻辑...
}
// 射击方法
protected virtual void Fire()
{
// 共享的射击逻辑...
}
// 子弹销毁回调
public void OnBulletDestroyed()
{
activeBulletCount = Mathf.Max(0, activeBulletCount - 1);
}
// 受伤方法
public virtual void TakeDamage()
{
// 共享的受伤逻辑...
}
// 激活护盾
public void ActivateShield(float duration)
{
StartCoroutine(ShieldRoutine(duration));
}
private IEnumerator ShieldRoutine(float duration)
{
// 护盾逻辑...
yield return null;
}
// 增强子弹
public void PowerUpBullet()
{
bulletPower = Mathf.Min(bulletPower + 1, 3);
}
// 增加最大子弹数
public void IncreaseMaxBullets()
{
maxBullets = Mathf.Min(maxBullets + 1, 2);
}
}
// 玩家1控制器
public class Player1Controller : PlayerController
{
protected override void HandleInput()
{
// 使用方向键和空格键
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 处理移动输入...
// 处理射击输入
if (Input.GetKeyDown(KeyCode.Space) && Time.time >= lastFireTime + fireRate && activeBulletCount < maxBullets)
{
Fire();
}
}
}
// 玩家2控制器
public class Player2Controller : PlayerController
{
protected override void HandleInput()
{
// 使用WASD和左Ctrl键
float horizontal = 0;
float vertical = 0;
if (Input.GetKey(KeyCode.A)) horizontal = -1;
if (Input.GetKey(KeyCode.D)) horizontal = 1;
if (Input.GetKey(KeyCode.W)) vertical = 1;
if (Input.GetKey(KeyCode.S)) vertical = -1;
// 处理移动输入...
// 处理射击输入
if (Input.GetKeyDown(KeyCode.LeftControl) && Time.time >= lastFireTime + fireRate && activeBulletCount < maxBullets)
{
Fire();
}
}
}
// 多人游戏管理器
public class MultiplayerManager : MonoBehaviour
{
[SerializeField] private GameObject player1Prefab;
[SerializeField] private GameObject player2Prefab;
[SerializeField] private Transform player1SpawnPoint;
[SerializeField] private Transform player2SpawnPoint;
private PlayerController player1;
private PlayerController player2;
private bool isMultiplayerMode = false;
// 公共属性
public bool IsMultiplayerMode => isMultiplayerMode;
public void EnableMultiplayerMode(bool enable)
{
isMultiplayerMode = enable;
}
public void SpawnPlayers()
{
// 生成玩家1
GameObject p1Obj = Instantiate(player1Prefab, player1SpawnPoint.position, Quaternion.identity);
player1 = p1Obj.GetComponent<PlayerController>();
// 如果是多人模式,生成玩家2
if (isMultiplayerMode)
{
GameObject p2Obj = Instantiate(player2Prefab, player2SpawnPoint.position, Quaternion.identity);
player2 = p2Obj.GetComponent<PlayerController>();
}
}
public bool AreAllPlayersDead()
{
if (!isMultiplayerMode)
{
return player1 == null || !player1.gameObject.activeSelf;
}
else
{
bool p1Dead = player1 == null || !player1.gameObject.activeSelf;
bool p2Dead = player2 == null || !player2.gameObject.activeSelf;
return p1Dead && p2Dead;
}
}
public void RespawnPlayers()
{
// 重生玩家的逻辑...
}
}
19.5.2 性能优化:对象池实现
对象池是一种常用的优化技术,可以减少频繁创建和销毁对象(如子弹、爆炸效果)带来的性能开销:
csharp
public class ObjectPool : MonoBehaviour
{
// 单例
public static ObjectPool Instance { get; private set; }
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
[SerializeField] private List<Pool> pools;
private Dictionary<string, Queue<GameObject>> poolDictionary;
private void Awake()
{
// 单例初始化
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
return;
}
// 初始化对象池
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
objectPool.Enqueue(obj);
obj.transform.parent = transform;
}
poolDictionary.Add(pool.tag, objectPool);
}
}
public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning($"Pool with tag {tag} doesn't exist!");
return null;
}
// 获取一个对象
Queue<GameObject> objectPool = poolDictionary[tag];
// 如果队列为空,创建一个新对象
if (objectPool.Count == 0)
{
foreach (Pool pool in pools)
{
if (pool.tag == tag)
{
GameObject newObj = Instantiate(pool.prefab, position, rotation);
return newObj;
}
}
}
// 获取池中的对象
GameObject obj = objectPool.Dequeue();
// 重新激活对象
obj.SetActive(true);
obj.transform.position = position;
obj.transform.rotation = rotation;
// 如果对象实现了IPoolable接口,调用OnSpawn方法
IPoolable poolable = obj.GetComponent<IPoolable>();
if (poolable != null)
{
poolable.OnSpawn();
}
// 将对象重新加入队列末尾(循环使用)
objectPool.Enqueue(obj);
return obj;
}
// 直接返回对象到池(可选)
public void ReturnToPool(string tag, GameObject obj)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning($"Pool with tag {tag} doesn't exist!");
return;
}
// 如果对象实现了IPoolable接口,调用OnDespawn方法
IPoolable poolable = obj.GetComponent<IPoolable>();
if (poolable != null)
{
poolable.OnDespawn();
}
obj.SetActive(false);
}
}
// 可池化接口
public interface IPoolable
{
void OnSpawn();
void OnDespawn();
}
// 子弹示例实现
public class PooledBullet : Bullet, IPoolable
{
private string poolTag = "Bullet";
public void OnSpawn()
{
// 重置子弹状态
// ...
}
public void OnDespawn()
{
// 清理子弹状态
// ...
}
protected override void DestroyBullet()
{
// 触发销毁事件
OnDestroyed?.Invoke();
// 返回到对象池而不是销毁
ObjectPool.Instance.ReturnToPool(poolTag, gameObject);
}
}
19.5.3 视觉效果增强
为了提升游戏的视觉效果,我们可以添加各种粒子特效和后处理效果:
csharp
public class EffectsManager : MonoBehaviour
{
// 单例
public static EffectsManager Instance { get; private set; }
[System.Serializable]
public class EffectData
{
public string tag;
public GameObject effectPrefab;
public float duration;
}
[SerializeField] private List<EffectData> effects;
// 屏幕震动
[SerializeField] private float shakeAmount = 0.1f;
[SerializeField] private float shakeDecreaseFactor = 1.0f;
private Dictionary<string, EffectData> effectsDictionary;
private Camera mainCamera;
private Vector3 originalCameraPosition;
private float shakeIntensity = 0;
private void Awake()
{
// 单例初始化
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
return;
}
// 初始化字典
effectsDictionary = new Dictionary<string, EffectData>();
foreach (EffectData effect in effects)
{
effectsDictionary.Add(effect.tag, effect);
}
// 获取相机引用
mainCamera = Camera.main;
originalCameraPosition = mainCamera.transform.position;
}
private void Update()
{
// 处理相机震动
if (shakeIntensity > 0)
{
mainCamera.transform.position = originalCameraPosition + Random.insideUnitSphere * shakeIntensity;
shakeIntensity -= shakeDecreaseFactor * Time.deltaTime;
if (shakeIntensity <= 0)
{
shakeIntensity = 0;
mainCamera.transform.position = originalCameraPosition;
}
}
}
public GameObject PlayEffect(string effectTag, Vector3 position, Quaternion rotation)
{
if (!effectsDictionary.ContainsKey(effectTag))
{
Debug.LogWarning($"Effect with tag {effectTag} doesn't exist!");
return null;
}
EffectData data = effectsDictionary[effectTag];
// 创建特效
GameObject effect = ObjectPool.Instance.SpawnFromPool(effectTag, position, rotation);
if (effect == null)
{
effect = Instantiate(data.effectPrefab, position, rotation);
}
// 如果有持续时间,设置自动销毁
if (data.duration > 0)
{
StartCoroutine(DestroyEffectAfterDelay(effect, data.duration, effectTag));
}
return effect;
}
private IEnumerator DestroyEffectAfterDelay(GameObject effect, float delay, string poolTag)
{
yield return new WaitForSeconds(delay);
// 返回到对象池
ObjectPool.Instance.ReturnToPool(poolTag, effect);
}
// 播放坦克爆炸效果
public void PlayTankExplosion(Vector3 position)
{
PlayEffect("TankExplosion", position, Quaternion.identity);
// 触发相机震动
ShakeCamera(0.2f);
}
// 播放基地爆炸效果
public void PlayBaseExplosion(Vector3 position)
{
PlayEffect("BaseExplosion", position, Quaternion.identity);
// 触发更强的相机震动
ShakeCamera(0.5f);
}
// 播放子弹击中效果
public void PlayBulletHit(Vector3 position)
{
PlayEffect("BulletHit", position, Quaternion.identity);
}
// 播放墙体破坏效果
public void PlayWallDestruction(Vector3 position, bool isSteel)
{
string effectTag = isSteel ? "SteelDestruction" : "BrickDestruction";
PlayEffect(effectTag, position, Quaternion.identity);
}
// 触发相机震动
public void ShakeCamera(float intensity)
{
shakeIntensity = intensity;
originalCameraPosition = mainCamera.transform.position;
}
}
19.5.4 关卡编辑器实现
为了方便设计关卡,我们可以实现一个简单的关卡编辑器:
csharp
#if UNITY_EDITOR
using UnityEditor;
public class LevelEditorWindow : EditorWindow
{
private enum BrushType
{
Empty,
BrickWall,
SteelWall,
Water,
Forest,
Ice
}
[System.Serializable]
public class LevelData
{
public int width = 13;
public int height = 13;
public string mapData;
public int basicEnemyCount = 10;
public int fastEnemyCount = 4;
public int heavyEnemyCount = 3;
public int powerEnemyCount = 2;
}
private BrushType selectedBrush = BrushType.BrickWall;
private LevelData currentLevel = new LevelData();
private char[,] map;
private Vector2 scrollPosition;
private int cellSize = 20;
private Texture2D brickTexture;
private Texture2D steelTexture;
private Texture2D waterTexture;
private Texture2D forestTexture;
private Texture2D iceTexture;
[MenuItem("Tools/Tank Battle/Level Editor")]
public static void ShowWindow()
{
GetWindow<LevelEditorWindow>("Level Editor");
}
private void OnEnable()
{
// 加载纹理
brickTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/BrickWall.png");
steelTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/SteelWall.png");
waterTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/Water.png");
forestTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/Forest.png");
iceTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Sprites/Ice.png");
// 初始化地图
InitializeMap();
}
private void InitializeMap()
{
map = new char[currentLevel.width, currentLevel.height];
// 填充空地
for (int x = 0; x < currentLevel.width; x++)
{
for (int y = 0; y < currentLevel.height; y++)
{
map[x, y] = '.';
}
}
// 如果有现有数据,解析它
if (!string.IsNullOrEmpty(currentLevel.mapData))
{
for (int i = 0; i < currentLevel.mapData.Length && i < currentLevel.width * currentLevel.height; i++)
{
int x = i % currentLevel.width;
int y = i / currentLevel.width;
map[x, y] = currentLevel.mapData[i];
}
}
}
private void OnGUI()
{
DrawToolbar();
DrawMapSettings();
DrawEnemySettings();
EditorGUILayout.Space(10);
DrawMapEditor();
DrawSaveButtons();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
// 刷子选择
selectedBrush = (BrushType)EditorGUILayout.EnumPopup("Brush: ", selectedBrush);
EditorGUILayout.EndHorizontal();
}
private void DrawMapSettings()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Map Settings", EditorStyles.boldLabel);
// 地图尺寸设置
EditorGUI.BeginChangeCheck();
currentLevel.width = EditorGUILayout.IntSlider("Width: ", currentLevel.width, 10, 20);
currentLevel.height = EditorGUILayout.IntSlider("Height: ", currentLevel.height, 10, 20);
if (EditorGUI.EndChangeCheck())
{
// 地图尺寸改变,重新初始化
InitializeMap();
}
EditorGUILayout.EndVertical();
}
private void DrawEnemySettings()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Enemy Settings", EditorStyles.boldLabel);
// 敌人数量设置
currentLevel.basicEnemyCount = EditorGUILayout.IntSlider("Basic Enemies: ", currentLevel.basicEnemyCount, 0, 20);
currentLevel.fastEnemyCount = EditorGUILayout.IntSlider("Fast Enemies: ", currentLevel.fastEnemyCount, 0, 10);
currentLevel.heavyEnemyCount = EditorGUILayout.IntSlider("Heavy Enemies: ", currentLevel.heavyEnemyCount, 0, 10);
currentLevel.powerEnemyCount = EditorGUILayout.IntSlider("Power Enemies: ", currentLevel.powerEnemyCount, 0, 10);
EditorGUILayout.EndVertical();
}
private void DrawMapEditor()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Map Editor", EditorStyles.boldLabel);
// 计算地图编辑器的大小
float mapWidth = currentLevel.width * cellSize;
float mapHeight = currentLevel.height * cellSize;
// 滚动视图
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
Rect mapRect = GUILayoutUtility.GetRect(mapWidth, mapHeight);
// 绘制网格背景
DrawGrid(mapRect);
// 绘制地图内容
for (int x = 0; x < currentLevel.width; x++)
{
for (int y = 0; y < currentLevel.height; y++)
{
Rect cellRect = new Rect(
mapRect.x + x * cellSize,
mapRect.y + y * cellSize,
cellSize,
cellSize
);
char cellType = map[x, y];
DrawCell(cellRect, cellType);
// 处理鼠标点击
HandleCellInteraction(cellRect, x, y);
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void DrawGrid(Rect mapRect)
{
// 绘制背景
EditorGUI.DrawRect(mapRect, new Color(0.2f, 0.2f, 0.2f));
// 绘制网格线
Handles.color = Color.gray;
for (int x = 0; x <= currentLevel.width; x++)
{
float xPos = mapRect.x + x * cellSize;
Handles.DrawLine(
new Vector3(xPos, mapRect.y, 0),
new Vector3(xPos, mapRect.y + mapRect.height, 0)
);
}
for (int y = 0; y <= currentLevel.height; y++)
{
float yPos = mapRect.y + y * cellSize;
Handles.DrawLine(
new Vector3(mapRect.x, yPos, 0),
new Vector3(mapRect.x + mapRect.width, yPos, 0)
);
}
}
private void DrawCell(Rect cellRect, char cellType)
{
// 根据单元格类型绘制内容
Texture2D texture = null;
switch (cellType)
{
case 'B':
texture = brickTexture;
break;
case 'S':
texture = steelTexture;
break;
case 'W':
texture = waterTexture;
break;
case 'F':
texture = forestTexture;
break;
case 'I':
texture = iceTexture;
break;
}
if (texture != null)
{
GUI.DrawTexture(cellRect, texture);
}
}
private void HandleCellInteraction(Rect cellRect, int x, int y)
{
Event currentEvent = Event.current;
if (currentEvent.type == EventType.MouseDown && cellRect.Contains(currentEvent.mousePosition))
{
// 左键绘制
if (currentEvent.button == 0)
{
// 设置单元格类型
map[x, y] = GetBrushChar();
currentEvent.Use();
Repaint();
}
// 右键擦除
else if (currentEvent.button == 1)
{
map[x, y] = '.';
currentEvent.Use();
Repaint();
}
}
}
private char GetBrushChar()
{
switch (selectedBrush)
{
case BrushType.BrickWall: return 'B';
case BrushType.SteelWall: return 'S';
case BrushType.Water: return 'W';
case BrushType.Forest: return 'F';
case BrushType.Ice: return 'I';
default: return '.';
}
}
private void DrawSaveButtons()
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("New Level"))
{
if (EditorUtility.DisplayDialog("New Level", "Create a new level? Any unsaved changes will be lost.", "Yes", "Cancel"))
{
currentLevel = new LevelData();
InitializeMap();
}
}
if (GUILayout.Button("Save Level"))
{
SaveLevel();
}
if (GUILayout.Button("Load Level"))
{
LoadLevel();
}
EditorGUILayout.EndHorizontal();
}
private void SaveLevel()
{
// 将地图转换为字符串
UpdateLevelData();
// 选择保存路径
string path = EditorUtility.SaveFilePanelInProject("Save Level", "Level", "json", "Save level data");
if (string.IsNullOrEmpty(path))
return;
// 序列化并保存
string json = JsonUtility.ToJson(currentLevel, true);
System.IO.File.WriteAllText(path, json);
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("Save Level", "Level saved successfully!", "OK");
}
private void LoadLevel()
{
// 选择要加载的文件
string path = EditorUtility.OpenFilePanel("Load Level", Application.dataPath, "json");
if (string.IsNullOrEmpty(path))
return;
// 读取并反序列化
string json = System.IO.File.ReadAllText(path);
currentLevel = JsonUtility.FromJson<LevelData>(json);
// 重新初始化地图
InitializeMap();
}
private void UpdateLevelData()
{
// 将地图数组转换为字符串
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int y = 0; y < currentLevel.height; y++)
{
for (int x = 0; x < currentLevel.width; x++)
{
sb.Append(map[x, y]);
}
}
currentLevel.mapData = sb.ToString();
}
}
#endif
19.6 项目总结
在本章中,我们详细介绍了如何使用Unity 2021.3.8f1c1开发一款经典的坦克大战游戏。我们从游戏设计、架构规划到代码实现,全面覆盖了游戏开发的各个方面。
主要实现的功能
-
完整的地图系统:使用Tilemap实现了多种地形类型,包括砖墙、钢墙、水域、草丛和冰面,每种地形都有不同的游戏效果。
-
玩家控制系统:实现了流畅的坦克移动和射击控制,支持不同的子弹威力和生命系统。
-
敌人AI:设计了具有多种行为模式的敌人AI,能够在不同情况下做出合理的决策,包括追击玩家、攻击基地和规避危险。
-
道具系统:实现了多种类型的道具,如星星、坦克、计时器、铲子等,为游戏增加了更多变化。
-
多人模式:支持两名玩家同时游戏,增加了游戏的社交性和乐趣。
-
性能优化:通过对象池、合理的碰撞检测和资源管理,确保游戏运行流畅。
-
视觉效果:添加了各种粒子效果、相机震动和特效,增强游戏的视觉表现。
-
关卡编辑器:开发了一个简单但功能完整的关卡编辑器,方便设计和测试游戏关卡。
开发心得
-
架构设计的重要性:良好的架构设计可以使游戏开发更加清晰和可维护,特别是在实现复杂系统如AI和碰撞检测时。
-
组件化开发:将游戏功能拆分为不同的组件,如地图管理器、敌人生成器、道具系统等,可以使代码更加模块化和易于理解。
-
性能优化:在开发过程中就考虑性能优化,如使用对象池管理频繁创建和销毁的对象,可以避免后期遇到性能瓶颈。
-
游戏体验:游戏开发不仅仅是功能实现,还需要考虑游戏的平衡性、难度曲线和整体体验,通过反复测试和调整来优化这些方面。
通过本章的学习,读者应该能够掌握2D游戏开发的核心技术和设计理念,不仅可以应用于坦克大战游戏,还可以扩展到其他类型的2D游戏开发中。
希望这个详细的教程能够帮助读者更好地理解游戏开发的过程,并在自己的游戏开发之路上取得进步!
更多推荐


所有评论(0)