【唐老狮】Unity实现RTS游戏阵型功能

使用资源

【唐老狮】Unity实现 即时战略游戏 阵型功能 - 泰课在线

预制体设置

烘焙

给场景选择烘焙静态

屏幕截图 2023-04-28 163638

屏幕截图 2023-04-28 163837

设置模型预制体

用添加父物体的方式解决模型朝向问题

屏幕截图 2023-04-28 164142

添加胶囊碰撞器,设置为触发器

添加寻路组件

屏幕截图 2023-04-28 164301

添加脚底光圈特效,调整到位

重新为Hero创建动画状态机

只需要移动和待命动画

屏幕截图 2023-04-28 164800

为其他模型进行同样的处理

士兵脚本编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using UnityEngine;
using UnityEngine.AI;

namespace ObjectManager
{
public class SoldierObj : MonoBehaviour
{
private Animator _animator;

private NavMeshAgent _agent;

private GameObject _footEffect;

private void Start()
{
_animator = this.GetComponentInChildren<Animator>();
_agent = this.GetComponent<NavMeshAgent>();
_footEffect = this.transform.Find("FootEffect").gameObject;
SetSelSelf(false);
}

private void Update()
{
_animator.SetBool("IsMove", _agent.velocity.magnitude > 0);
}

public void Move(Vector3 pos)
{
_agent.SetDestination(pos);
}

/// <summary>
/// 设置自己是否被选中,决定光圈是否被显示
/// </summary>
/// <param name="isSel"></param>
public void SetSelSelf(bool isSel)
{
_footEffect.SetActive(isSel);
}
}
}

选择框逻辑实现

在场景中添加空物体Controller,添加LineRenderer组件,更改组件细节

为LineRenderer添加绿色的UI材质球,勾选Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using UnityEngine;

namespace Control
{
public class Controller : MonoBehaviour
{
private bool _isMouseDown;

private LineRenderer _line;

private Vector3 _leftUpInputPos,_rightUpPoint,_rightDownPoint,_leftDownPoint;

private void Start()
{
_line = GetComponent<LineRenderer>();
}

private void Update()
{
if (Input.GetMouseButtonDown(0))
{
_leftUpInputPos = Input.mousePosition;
_isMouseDown = true;
}
else if (Input.GetMouseButtonUp(0))
{
_isMouseDown = false;
_line.positionCount = 0;
}

//如果点击鼠标,运行此逻辑
if (_isMouseDown)
{
_line.positionCount = 4;
_leftUpInputPos.z = 5;
_rightDownPoint = Input.mousePosition;
_rightDownPoint.z = 5;
_rightUpPoint = new Vector3(_rightDownPoint.x, _leftUpInputPos.y,5);
_leftDownPoint = new Vector3(_leftUpInputPos.x, _rightDownPoint.y, 5);
_line.SetPosition(0,Camera.main.ScreenToWorldPoint(_leftUpInputPos));
_line.SetPosition(1,Camera.main.ScreenToWorldPoint(_rightUpPoint));
_line.SetPosition(2,Camera.main.ScreenToWorldPoint(_rightDownPoint));
_line.SetPosition(3,Camera.main.ScreenToWorldPoint(_leftDownPoint));
}
}
}
}

选择士兵

三种办法:

1.射线检测+范围检测:射线击中地面,在范围中心点进行碰撞器检测

2.世界坐标转屏幕坐标

3.射线检测+位置判断

添加新LayOut层Ground

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using System;
using System.Collections.Generic;
using ObjectManager;
using UnityEngine;

namespace Control
{
public class Controller : MonoBehaviour
{
private bool _isMouseDown;

private LineRenderer _line;

private Vector3 _leftUpInputPos,_rightUpPoint,_rightDownPoint,_leftDownPoint;

private RaycastHit _hitInfo;
private Vector3 _beginWorldPoint;

// 所有选择到的士兵对象
private List<SoldierObj> _soldierObjs = new();
private void Start()
{
_line = GetComponent<LineRenderer>();
}

private void Update()
{
if (Input.GetMouseButtonDown(0))
{
_leftUpInputPos = Input.mousePosition;
_isMouseDown = true;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out _hitInfo, 1000,
LayerMask.NameToLayer("Ground")))
_beginWorldPoint = _hitInfo.point;
}
else if (Input.GetMouseButtonUp(0))
{
_isMouseDown = false;
_line.positionCount = 0;

// 清空选择
foreach (var t in _soldierObjs)
{
t.SetSelSelf(false);
}
_soldierObjs.Clear();

if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out _hitInfo, 1000,
LayerMask.NameToLayer("Ground")))
{
// 范围检测的中心点
var center = new Vector3((_hitInfo.point.x + _beginWorldPoint.x) / 2, 1,
(_hitInfo.point.z + _beginWorldPoint.z) / 2);
var halfExtents = new Vector3(Math.Abs(_hitInfo.point.x - _beginWorldPoint.x) / 2, 1,
Mathf.Abs(_hitInfo.point.x - _beginWorldPoint.x) / 2);

var colliders = Physics.OverlapBox(center, halfExtents);
for (int i = 0; i < colliders.Length; i++)
{
var obj = colliders[i].GetComponent<SoldierObj>();
if (obj is not null)
{
obj.SetSelSelf(true);
_soldierObjs.Add(obj);
}
}
}
}

// 如果点击鼠标,运行此逻辑
if (_isMouseDown)
{
_line.positionCount = 4;
_leftUpInputPos.z = 5;
_rightDownPoint = Input.mousePosition;
_rightDownPoint.z = 5;
_rightUpPoint = new Vector3(_rightDownPoint.x, _leftUpInputPos.y,5);
_leftDownPoint = new Vector3(_leftUpInputPos.x, _rightDownPoint.y, 5);

_line.SetPosition(0,Camera.main.ScreenToWorldPoint(_leftUpInputPos));
_line.SetPosition(1,Camera.main.ScreenToWorldPoint(_rightUpPoint));
_line.SetPosition(2,Camera.main.ScreenToWorldPoint(_rightDownPoint));
_line.SetPosition(3,Camera.main.ScreenToWorldPoint(_leftDownPoint));
}
}
}
}

阵型功能分析

魔兽争霸中最多只能选择12个单位

如果只选择了一个单位,鼠标指哪单位去哪

如果选择了两个单位,两个单位会分立鼠标两侧

以此类推

屏幕截图 2023-04-28 193921

屏幕截图 2023-04-28 193928

我们可以制定一个规则,将阵列中两个单位之间的距离设置为定值

我们不用总结一个通用的算法,而是可以通过选中的单位数分类讨论

通过两次鼠标点击位置,计算移动的方向向量,从而确定阵列的方向(右方向)

前往阵型目标点逻辑实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
using System;
using System.Collections.Generic;
using ObjectManager;
using UnityEngine;

namespace Control
{
public class Controller : MonoBehaviour
{
private bool _isMouseDown;
private LineRenderer _line;
private Vector3 _leftUpInputPos,_rightUpPoint,_rightDownPoint,_leftDownPoint;
private RaycastHit _hitInfo;
private Vector3 _beginWorldPoint;

// 所有选择到的士兵对象
private List<SoldierObj> _soldierObjs = new();

private Vector3 _frontPos;

[SerializeField]private float soldierOffset; // 士兵之间的间隔距离
private void Start()
{
_line = GetComponent<LineRenderer>();
}

private void Update()
{
SelSoldiers();

ControlSoldierMove();
}

private void SelSoldiers()
{
if (Input.GetMouseButtonDown(0))
{
_leftUpInputPos = Input.mousePosition;
_isMouseDown = true;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out _hitInfo, 1000,
LayerMask.NameToLayer("Ground")))
_beginWorldPoint = _hitInfo.point;
}
else if (Input.GetMouseButtonUp(0))
{
_isMouseDown = false;
_line.positionCount = 0;

// 每一次重新选择士兵,清空上一次的位置
_frontPos = Vector3.zero;

// 清空选择
foreach (var t in _soldierObjs)
{
t.SetSelSelf(false);
}
_soldierObjs.Clear();

if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out _hitInfo, 1000,
LayerMask.NameToLayer("Ground")))
{
// 范围检测的中心点
var center = new Vector3((_hitInfo.point.x + _beginWorldPoint.x) / 2, 1,
(_hitInfo.point.z + _beginWorldPoint.z) / 2);
var halfExtents = new Vector3(Math.Abs(_hitInfo.point.x - _beginWorldPoint.x) / 2, 1,
Mathf.Abs(_hitInfo.point.x - _beginWorldPoint.x) / 2);

var colliders = Physics.OverlapBox(center, halfExtents);

// 最多只能选择12个士兵
for (int i = 0; i < colliders.Length&&_soldierObjs.Count<12; i++)
{
var obj = colliders[i].GetComponent<SoldierObj>();
if (obj is not null)
{
obj.SetSelSelf(true);
_soldierObjs.Add(obj);
}
}
}
}

// 如果点击鼠标,运行此逻辑
if (_isMouseDown)
{
_line.positionCount = 4;
_leftUpInputPos.z = 5;
_rightDownPoint = Input.mousePosition;
_rightDownPoint.z = 5;
_rightUpPoint = new Vector3(_rightDownPoint.x, _leftUpInputPos.y,5);
_leftDownPoint = new Vector3(_leftUpInputPos.x, _rightDownPoint.y, 5);

_line.SetPosition(0,Camera.main.ScreenToWorldPoint(_leftUpInputPos));
_line.SetPosition(1,Camera.main.ScreenToWorldPoint(_rightUpPoint));
_line.SetPosition(2,Camera.main.ScreenToWorldPoint(_rightDownPoint));
_line.SetPosition(3,Camera.main.ScreenToWorldPoint(_leftDownPoint));
}
}

private void ControlSoldierMove()
{
if (Input.GetMouseButtonDown(1))
{
//没有士兵不移动
if (_soldierObjs.Count<=0)
{
return;
}

//获取目标点,通过射线检测
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out _hitInfo,
1000,1<<LayerMask.NameToLayer("Ground")))
{
List<Vector3> targetsPos = GetTargetPos(_hitInfo.point);
for (int i = 0; i < _soldierObjs.Count; i++)
{
_soldierObjs[i].Move(targetsPos[i]);
}
}
}
}

private List<Vector3> GetTargetPos(Vector3 targetPos)
{
// 需要绩目标点的面朝向和右朝向
var nowForward = Vector3.zero;
var nowRight = Vector3.zero;

// 上一次已经移动过了,有上一次的位置
if (_frontPos != Vector3.zero)
{
nowForward = (targetPos - _frontPos).normalized;

}
else// 没有上一个点,用第一个士兵的位置绩
{
nowForward = (targetPos - _soldierObjs[0].transform.position).normalized;
}
nowRight = Quaternion.Euler(0, 90, 0) * nowForward;// 根据面朝向,旋转Y轴90读

var targetsPos = new List<Vector3>();

switch (_soldierObjs.Count)
{
case 1:
targetsPos.Add(targetPos);
break;
case 2:
targetsPos.Add(targetPos+nowRight*soldierOffset/2);
targetsPos.Add(targetPos-nowRight*soldierOffset/2);
break;
case 3:
targetsPos.Add(targetPos);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
break;
case 4:
targetsPos.Add(targetPos + nowForward*soldierOffset/2 - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset/2 + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowForward*soldierOffset/2 - nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowForward*soldierOffset/2 + nowRight*soldierOffset/2);
break;
case 5:
targetsPos.Add(targetPos + nowForward* soldierOffset/2);
targetsPos.Add(targetPos + nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos + nowForward* soldierOffset/2+nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2+nowRight*soldierOffset);
break;
case 6:
targetsPos.Add(targetPos + nowForward* soldierOffset/2);
targetsPos.Add(targetPos + nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos + nowForward* soldierOffset/2+nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2+nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2);
break;
case 7:
targetsPos.Add(targetPos+nowForward*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
targetsPos.Add(targetPos);
targetsPos.Add(targetPos-nowForward*soldierOffset);
break;
case 8:
targetsPos.Add(targetPos+nowForward*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
targetsPos.Add(targetPos);
targetsPos.Add(targetPos-nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos-nowForward*soldierOffset+nowRight*soldierOffset);
break;
case 9:
targetsPos.Add(targetPos+nowForward*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
targetsPos.Add(targetPos);
targetsPos.Add(targetPos-nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos-nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowForward*soldierOffset);
break;
case 10:
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
break;
case 11:
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos-nowForward*soldierOffset);
break;
case 12:
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset/2);
break;
}
_frontPos = targetPos;
return targetsPos;
}
}
}

阵型布局优化

游戏设计师可能会希望将肉盾放在阵列前面,ADC放在阵列后面

Controller中添加点的位置有一定的顺序,前排先添加,后排后添加

将士兵列表按类型排序,即可符合肉盾放在阵列前面,ADC放在阵列后面的要求

更改SoldierObj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using UnityEngine;
using UnityEngine.AI;

namespace ObjectManager
{

public enum SoldierType
{
Hero,Warrior,Archer,Magician,Loong
}
public class SoldierObj : MonoBehaviour
{
private Animator _animator;

private NavMeshAgent _agent;

private GameObject _footEffect;

public SoldierType type;

private void Start()
{
_animator = this.GetComponentInChildren<Animator>();
_agent = this.GetComponent<NavMeshAgent>();
_footEffect = this.transform.Find("FootEffect").gameObject;
SetSelSelf(false);
}

private void Update()
{
_animator.SetBool("IsMove", _agent.velocity.magnitude > 0);
}

public void Move(Vector3 pos)
{
_agent.SetDestination(pos);
}

/// <summary>
/// 设置自己是否被选中,决定光圈是否被显示
/// </summary>
/// <param name="isSel"></param>
public void SetSelSelf(bool isSel)
{
_footEffect.SetActive(isSel);
}
}
}

更改Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//在Controller的
/*
// 最多只能选择12个士兵
for (int i = 0; i < colliders.Length&&_soldierObjs.Count<12; i++)
{
var obj = colliders[i].GetComponent<SoldierObj>();
if (obj is not null)
{
obj.SetSelSelf(true);
_soldierObjs.Add(obj);
}
}
*/
//之后添加
_soldierObjs.Sort((a, b) =>
{
if (a.type<b.type)
{
return -1;
}
else if(a.type == b.type)
{
return 0;
}
else
{
return 1;
}
});

寻路优化

使用现在的算法,当两次位置转角过大时,单位会出现左右相互穿插

我们可以更改排序算法,先用兵种排序,如果队伍转角过大,再用单位与对应索引位置的距离排序

以下是最终的Controller脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
using System;
using System.Collections.Generic;
using ObjectManager;
using UnityEngine;

namespace Control
{
public class Controller : MonoBehaviour
{
private bool _isMouseDown;
private LineRenderer _line;
private Vector3 _leftUpInputPos,_rightUpPoint,_rightDownPoint,_leftDownPoint;
private RaycastHit _hitInfo;
private Vector3 _beginWorldPoint;

// 所有选择到的士兵对象
private List<SoldierObj> _soldierObjs = new();

private Vector3 _frontPos;

[SerializeField]private float soldierOffset; // 士兵之间的间隔距离
private void Start()
{
_line = GetComponent<LineRenderer>();
}

private void Update()
{
SelSoldiers();

ControlSoldierMove();
}

private void SelSoldiers()
{
if (Input.GetMouseButtonDown(0))
{
_leftUpInputPos = Input.mousePosition;
_isMouseDown = true;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out _hitInfo, 1000,
LayerMask.NameToLayer("Ground")))
_beginWorldPoint = _hitInfo.point;
}
else if (Input.GetMouseButtonUp(0))
{
_isMouseDown = false;
_line.positionCount = 0;

// 每一次重新选择士兵,清空上一次的位置
_frontPos = Vector3.zero;

// 清空选择
foreach (var t in _soldierObjs)
{
t.SetSelSelf(false);
}
_soldierObjs.Clear();

if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out _hitInfo, 1000,
LayerMask.NameToLayer("Ground")))
{
// 范围检测的中心点
var center = new Vector3((_hitInfo.point.x + _beginWorldPoint.x) / 2, 1,
(_hitInfo.point.z + _beginWorldPoint.z) / 2);
var halfExtents = new Vector3(Math.Abs(_hitInfo.point.x - _beginWorldPoint.x) / 2, 1,
Mathf.Abs(_hitInfo.point.x - _beginWorldPoint.x) / 2);

var colliders = Physics.OverlapBox(center, halfExtents);

// 最多只能选择12个士兵
for (int i = 0; i < colliders.Length&&_soldierObjs.Count<12; i++)
{
var obj = colliders[i].GetComponent<SoldierObj>();
if (obj is not null)
{
obj.SetSelSelf(true);
_soldierObjs.Add(obj);
}
}

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

// 如果点击鼠标,运行此逻辑
if (_isMouseDown)
{
_line.positionCount = 4;
_leftUpInputPos.z = 5;
_rightDownPoint = Input.mousePosition;
_rightDownPoint.z = 5;
_rightUpPoint = new Vector3(_rightDownPoint.x, _leftUpInputPos.y,5);
_leftDownPoint = new Vector3(_leftUpInputPos.x, _rightDownPoint.y, 5);

_line.SetPosition(0,Camera.main.ScreenToWorldPoint(_leftUpInputPos));
_line.SetPosition(1,Camera.main.ScreenToWorldPoint(_rightUpPoint));
_line.SetPosition(2,Camera.main.ScreenToWorldPoint(_rightDownPoint));
_line.SetPosition(3,Camera.main.ScreenToWorldPoint(_leftDownPoint));
}
}

private void ControlSoldierMove()
{
if (Input.GetMouseButtonDown(1))
{
//没有士兵不移动
if (_soldierObjs.Count<=0)
{
return;
}

//获取目标点,通过射线检测
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out _hitInfo,
1000,1<<LayerMask.NameToLayer("Ground")))
{
// 判断队伍新朝向和队伍老朝向之间的夹角
// 简单起见,我们把之前阵型的第一个士兵的面朝向作为阵型老朝向
if (Vector3.Angle((_hitInfo.point - _soldierObjs[0].transform.position).normalized,_soldierObjs[0].transform.forward)>60)
{
_soldierObjs.Sort((a, b) =>
{
if (a.type<b.type)
{
return -1;
}
else if(a.type == b.type)
{
// 只有兵种相同时,才会以离目标点距离进行排序
if (Vector3.Distance(a.transform.position,_hitInfo.point)<=Vector3.Distance(b.transform.position,_hitInfo.point))
{
return -1;
}
else
{
return 1;
}
}
else
{
return 1;
}
});
}
List<Vector3> targetsPos = GetTargetPos(_hitInfo.point);
for (int i = 0; i < _soldierObjs.Count; i++)
{
_soldierObjs[i].Move(targetsPos[i]);
}
}
}
}

private List<Vector3> GetTargetPos(Vector3 targetPos)
{
// 需要绩目标点的面朝向和右朝向
var nowForward = Vector3.zero;
var nowRight = Vector3.zero;

// 上一次已经移动过了,有上一次的位置
if (_frontPos != Vector3.zero)
{
nowForward = (targetPos - _frontPos).normalized;

}
else// 没有上一个点,用第一个士兵的位置绩
{
nowForward = (targetPos - _soldierObjs[0].transform.position).normalized;
}
nowRight = Quaternion.Euler(0, 90, 0) * nowForward;// 根据面朝向,旋转Y轴90读

var targetsPos = new List<Vector3>();

switch (_soldierObjs.Count)
{
case 1:
targetsPos.Add(targetPos);
break;
case 2:
targetsPos.Add(targetPos+nowRight*soldierOffset/2);
targetsPos.Add(targetPos-nowRight*soldierOffset/2);
break;
case 3:
targetsPos.Add(targetPos);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
break;
case 4:
targetsPos.Add(targetPos + nowForward*soldierOffset/2 - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset/2 + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowForward*soldierOffset/2 - nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowForward*soldierOffset/2 + nowRight*soldierOffset/2);
break;
case 5:
targetsPos.Add(targetPos + nowForward* soldierOffset/2);
targetsPos.Add(targetPos + nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos + nowForward* soldierOffset/2+nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2+nowRight*soldierOffset);
break;
case 6:
targetsPos.Add(targetPos + nowForward* soldierOffset/2);
targetsPos.Add(targetPos + nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos + nowForward* soldierOffset/2+nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2-nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2+nowRight*soldierOffset);
targetsPos.Add(targetPos - nowForward* soldierOffset/2);
break;
case 7:
targetsPos.Add(targetPos+nowForward*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
targetsPos.Add(targetPos);
targetsPos.Add(targetPos-nowForward*soldierOffset);
break;
case 8:
targetsPos.Add(targetPos+nowForward*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
targetsPos.Add(targetPos);
targetsPos.Add(targetPos-nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos-nowForward*soldierOffset+nowRight*soldierOffset);
break;
case 9:
targetsPos.Add(targetPos+nowForward*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos+nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowRight*soldierOffset);
targetsPos.Add(targetPos);
targetsPos.Add(targetPos-nowForward*soldierOffset-nowRight*soldierOffset);
targetsPos.Add(targetPos-nowForward*soldierOffset+nowRight*soldierOffset);
targetsPos.Add(targetPos-nowForward*soldierOffset);
break;
case 10:
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
break;
case 11:
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos-nowForward*soldierOffset);
break;
case 12:
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowRight*soldierOffset/2);
targetsPos.Add(targetPos + nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset*1.5f);
targetsPos.Add(targetPos - nowForward*soldierOffset - nowRight*soldierOffset/2);
targetsPos.Add(targetPos - nowForward*soldierOffset + nowRight*soldierOffset/2);
break;
}
_frontPos = targetPos;
return targetsPos;
}