11.即时战略阵型总结

11.总结


11.1 知识点

主要讲解内容

完成目标

关于RTS游戏的阵型功能

小实践


11.2 核心要点速览

项目概述

项目名称:即时战略阵型系统

核心玩法:框选士兵 → 右键点击目标点 → 士兵按阵型移动到目标位置

实现目标

  • 鼠标左键拖拽绘制选择框,框选范围内的士兵
  • 选中士兵显示脚下光圈特效
  • 鼠标右键点击地面,士兵按阵型排列移动
  • 支持不同兵种的前后排站位
  • 大角度转向时士兵不穿插

前置知识:Unity 基础、C# 基础、Transform、Vector3、射线检测、NavMesh 寻路、协程

场景与预设体设置

场景设置

  1. 导入地形资源,设置为寻路静态(Navigation Static)
  2. 烘焙寻路数据(Navigation 面板 → Bake)

士兵预设体制作

步骤 操作 说明
1 模型父对象 用空对象包裹模型,解决模型朝向问题
2 胶囊碰撞器 添加 CapsuleCollider,用于范围检测
3 寻路组件 添加 NavMeshAgent,实现自动寻路
4 选中光圈 作为子对象,控制选中状态显示
5 动画控制器 仅包含 Idle 和 Move 两个状态

动画控制器:新建 AnimatorController,包含站立(Idle)和移动(Move)两个状态,通过 IsMove 参数切换。

士兵逻辑

SoldierObj 类核心成员

public class SoldierObj : MonoBehaviour
{
    private Animator animator;      // 动画控制
    private NavMeshAgent agent;     // 寻路组件
    private GameObject footEffect;  // 选中光圈
    public SoldierType type;        // 兵种类型

    // 移动到目标点
    public void Move(Vector3 pos) => agent.SetDestination(pos);

    // 设置选中状态(光圈显隐)
    public void SetSelSelf(bool isSel) => footEffect.SetActive(isSel);
}

动画切换逻辑:在 Update 中根据寻路组件速度判断播放站立或移动动画:

void Update()
{
    animator.SetBool("IsMove", agent.velocity.magnitude > 0);
}

选择框绘制

实现思路:记录鼠标按下点和当前点,用 LineRenderer 绘制矩形框。

核心变量

变量 类型 作用
isMouseDown bool 鼠标左键是否按下
line LineRenderer 画线组件
leftUpPoint Vector3 左上角屏幕坐标
rightDownPoint Vector3 右下角屏幕坐标

绘制流程

if (isMouseDown)
{
    // 计算四个角点(屏幕坐标,z=5 是摄像机距离)
    leftUpPoint.z = 5;
    rightDownPoint = Input.mousePosition;
    rightDownPoint.z = 5;
    // 右上角 = 右下角的 x + 左上角的 y
    rightUpPoint = new Vector3(rightDownPoint.x, leftUpPoint.y, 5);
    // 左下角 = 左上角的 x + 右下角的 y
    leftDownPoint = new Vector3(leftUpPoint.x, rightDownPoint.y, 5);

    // 屏幕坐标转世界坐标并绘制
    line.positionCount = 4;
    line.SetPosition(0, Camera.main.ScreenToWorldPoint(leftUpPoint));
    line.SetPosition(1, Camera.main.ScreenToWorldPoint(rightUpPoint));
    line.SetPosition(2, Camera.main.ScreenToWorldPoint(rightDownPoint));
    line.SetPosition(3, Camera.main.ScreenToWorldPoint(leftDownPoint));
}

LineRenderer 设置要点

  • Use World Space:勾选
  • Loop:勾选(闭合矩形)
  • Width:调细(如 0.05)

选中士兵

实现方案对比

方案 思路 优缺点
射线检测 + 范围检测 从选择框两角发射射线到地面,用 OverlapBox 检测 推荐,效率高
世界坐标转屏幕坐标 遍历所有士兵,判断屏幕坐标是否在选择框内 代码繁琐,需遍历所有对象
射线检测 + 位置判断 手写判断士兵位置是否在范围内 重复造轮子

推荐方案实现

// 1. 鼠标按下时记录起始世界坐标
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), 
    out hitInfo, 1000, 1 << LayerMask.NameToLayer("Ground")))
    beginWorldPos = hitInfo.point;

// 2. 鼠标抬起时计算范围检测参数
Vector3 center = new Vector3(
    (hitInfo.point.x + beginWorldPos.x) / 2, 
    1, 
    (hitInfo.point.z + beginWorldPos.z) / 2);
Vector3 halfExtents = new Vector3(
    Mathf.Abs(hitInfo.point.x - beginWorldPos.x) / 2, 
    1, 
    Mathf.Abs(hitInfo.point.z - beginWorldPos.z) / 2);

// 3. 盒型范围检测
Collider[] colliders = Physics.OverlapBox(center, halfExtents);

选择上限:最多选择 12 个士兵,通过循环条件 soldierObjs.Count < 12 控制。

阵型点位计算

核心思路:根据士兵数量,计算每个士兵在阵型中的目标位置。

方向向量计算

// 面朝向:目标点 - 上一次点击点(或第一个士兵位置)
Vector3 nowForward = (targetPos - frontPos).normalized;
// 右朝向:面朝向绕 Y 轴旋转 90 度
Vector3 nowRight = Quaternion.Euler(0, 90, 0) * nowForward;

阵型布局规则(以 4 人为例):

阵型示意(点击点为中心):
    前
  [1][2]    ← nowForward 方向偏移 +soldierOffset/2
  [3][4]    ← nowForward 方向偏移 -soldierOffset/2
    后
  左  右

点位计算示例(4 人阵型):

case 4:
    targetsPos.Add(targetPos + nowForward * offset/2 - nowRight * offset/2);  // 左前
    targetsPos.Add(targetPos + nowForward * offset/2 + nowRight * offset/2);  // 右前
    targetsPos.Add(targetPos - nowForward * offset/2 - nowRight * offset/2);  // 左后
    targetsPos.Add(targetPos - nowForward * offset/2 + nowRight * offset/2);  // 右后
    break;

关键点:返回目标点时,从前排往后返回,方便后续按兵种排序分配位置。

阵型布局优化

兵种类型定义

public enum SoldierType
{
    Hero,       // 英雄 - 前排
    Warrior,    // 战士 - 前排
    Archer,     // 猎人 - 后排
    Magician,   // 魔法师 - 后排
    Loong,      // 龙 - 最后排
}

排序规则:枚举值越小越靠前,分配到阵型前排位置。

soldierObjs.Sort((a, b) => {
    if (a.type < b.type) return -1;   // a 排前面
    else if (a.type == b.type) return 0;
    else return 1;                     // b 排前面
});

实现效果:英雄和战士站前排,远程单位站后排,保护脆皮单位。

寻路优化

问题:大角度转向(如 180 度掉头)时,士兵会穿插穿过彼此。

解决思路:当新朝向与老朝向夹角 > 60 度时,重新排序士兵列表。

排序规则

  1. 优先按兵种排序(前排优先)
  2. 同兵种按距离排序(离目标点近的优先)
if (Vector3.Angle(newForward, oldForward) > 60)
{
    soldierObjs.Sort((a, b) => {
        // 优先兵种排序
        if (a.type != b.type)
            return a.type < b.type ? -1 : 1;
        // 同兵种按距离排序
        return Vector3.Distance(a.transform.position, targetPos)
             <= Vector3.Distance(b.transform.position, targetPos) ? -1 : 1;
    });
}

效果:后阵变前阵时,后排士兵自动分配到前排目标点,避免穿插。

制作流程建议

  1. 场景搭建:导入地形 → 设置寻路静态 → 烘焙 NavMesh
  2. 预设体制作:创建士兵父对象 → 添加组件 → 制作预制体
  3. 士兵逻辑:SoldierObj 脚本 → 动画切换 → 移动方法
  4. 选择框:LineRenderer 绘制 → 屏幕坐标转世界坐标
  5. 选中士兵:射线检测 → OverlapBox 范围检测 → 选中列表管理
  6. 阵型移动:方向向量计算 → 阵型点位分配 → NavMeshAgent 移动
  7. 布局优化:兵种枚举 → 排序算法
  8. 寻路优化:夹角判断 → 复合排序

11.3 面试题精选

进阶题

1. RTS 游戏中选择框选中单位有哪几种实现方案?各有什么优缺点?

题目

请列举 RTS 游戏中框选单位的常见实现方案,并分析各自的优缺点。

深入解析

常见的三种实现方案:

方案一:射线检测 + 范围检测(推荐)

  • 从选择框的左上角和右下角分别发射射线到地面
  • 得到两个世界坐标点后,使用 Physics.OverlapBox 进行盒型范围检测
  • 优点:效率高,只检测框选范围内的对象
  • 缺点:需要地面有碰撞体,且选择框必须与地面有交点

方案二:世界坐标转屏幕坐标

  • 遍历所有可选单位,将世界坐标转换为屏幕坐标
  • 判断屏幕坐标是否在选择框矩形范围内
  • 优点:不依赖地面碰撞体
  • 缺点:需要遍历所有对象,对象多时性能差

方案三:射线检测 + 手写位置判断

  • 射线获取两点后,手动判断士兵位置是否在范围内
  • 优点:灵活可控
  • 缺点:需要自己实现几何判断逻辑,容易出错
答题示例

三种方案:射线检测 + 范围检测、世界坐标转屏幕坐标、射线检测 + 手写判断。

推荐方案一:从选择框两角发射射线到地面,用 OverlapBox 检测。优点是效率高,只检测框选范围内的对象;缺点是需要地面有碰撞体。方案二需要遍历所有对象,对象多时性能差。方案三需要自己实现几何判断,容易出错。

参考文章
  • 6.选中士兵

2. 如何实现士兵按兵种自动分配阵型前排后排?

题目

RTS 游戏中如何让不同兵种自动站到阵型的前排或后排?

深入解析

核心思路:通过枚举定义兵种优先级,排序后按顺序分配阵型位置。

步骤一:定义兵种枚举

public enum SoldierType
{
    Hero,       // 0 - 最前排
    Warrior,    // 1 - 前排
    Archer,     // 2 - 后排
    Magician,   // 3 - 后排
    Loong,      // 4 - 最后排
}

步骤二:选中士兵时按兵种排序

soldierObjs.Sort((a, b) => {
    if (a.type < b.type) return -1;
    else if (a.type == b.type) return 0;
    else return 1;
});

步骤三:阵型点位从前排往后返回

// 阵型点位列表:索引 0 是最前排位置
List<Vector3> targetsPos = GetTargetPos(targetPos);
// 排序后的士兵列表:索引 0 是前排兵种
for (int i = 0; i < soldierObjs.Count; i++)
    soldierObjs[i].Move(targetsPos[i]);

关键点

  • 枚举值越小越靠前,排序后前排兵种在列表前面
  • 阵型点位计算时,前排位置先返回
  • 两个列表按索引一一对应,实现前排兵种站前排位置
答题示例

通过兵种枚举定义优先级,选中士兵时按枚举值排序。枚举值小的兵种排到列表前面,分配阵型时从前排位置开始分配,实现前排兵种站前排、后排兵种站后排的效果。关键是排序顺序和阵型点位返回顺序要一致。

参考文章
  • 9.阵型布局优化

深度题

3. RTS 阵型移动时,大角度转向如何避免士兵穿插?

题目

RTS 游戏中,当阵型大角度转向(如 180 度掉头)时,士兵会互相穿插穿过。如何解决这个问题?

深入解析

问题分析

  • 正常移动时,前排士兵分配前排位置,后排士兵分配后排位置
  • 大角度转向时,原来的后排变成了前排,但士兵列表顺序没变
  • 导致后排士兵被分配到前排位置,需要穿过前排士兵,产生穿插

解决思路:检测转向角度,超过阈值时重新排序士兵列表。

实现步骤

  1. 计算新旧朝向夹角
Vector3 newForward = (targetPos - soldierObjs[0].transform.position).normalized;
Vector3 oldForward = soldierObjs[0].transform.forward;
float angle = Vector3.Angle(newForward, oldForward);
  1. 夹角超过阈值时重新排序
if (angle > 60)
{
    soldierObjs.Sort((a, b) => {
        // 优先兵种排序
        if (a.type != b.type)
            return a.type < b.type ? -1 : 1;
        // 同兵种按距离排序(离目标近的优先)
        return Vector3.Distance(a.pos, targetPos) 
             <= Vector3.Distance(b.pos, targetPos) ? -1 : 1;
    });
}

排序逻辑解析

  • 兵种优先:保证前排兵种仍然站前排
  • 距离次之:离目标点近的士兵优先分配前排位置
  • 效果:原来的后排士兵如果离新前排位置更近,会被分配到前排

为什么用 60 度作为阈值

  • 小角度转向时,士兵移动路径不交叉,无需重排
  • 大角度转向时,移动路径会交叉,需要重排
  • 60 度是经验值,可根据实际效果调整
答题示例

问题是转向时后排士兵被分配到前排位置,需要穿过前排士兵。

解决方案:计算新旧朝向夹角,超过 60 度时重新排序。排序规则是优先按兵种、其次按距离。这样离新前排位置近的士兵会被分配到前排,避免穿插。

核心代码是用 Vector3.Angle 计算夹角,超过阈值后用复合排序重排士兵列表。

参考文章
  • 10.寻路优化


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏