Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
MingSnake_241120
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
SparkProjects
MingSnake_241120
Commits
b2d1c691
Commit
b2d1c691
authored
Nov 22, 2024
by
haiyoucuv
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
init
parent
5e372767
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1120 additions
and
49 deletions
+1120
-49
AISnake.ts
assets/Scripts/Scenes/MainGame/AISnake.ts
+999
-26
AISnakeBack.ts
assets/Scripts/Scenes/MainGame/AISnakeBack.ts
+1
-1
AISnakeBack2.ts
assets/Scripts/Scenes/MainGame/AISnakeBack2.ts
+63
-0
AISnakeBack2.ts.meta
assets/Scripts/Scenes/MainGame/AISnakeBack2.ts.meta
+9
-0
MainGame.ts
assets/Scripts/Scenes/MainGame/MainGame.ts
+3
-1
Food.ts
assets/Scripts/Scenes/MainGame/Props/Food.ts
+1
-1
Snake.ts
assets/Scripts/Scenes/MainGame/Snake.ts
+22
-20
index.html
index.html
+22
-0
No files found.
assets/Scripts/Scenes/MainGame/AISnake.ts
View file @
b2d1c691
import
{
_decorator
}
from
"cc"
;
import
{
_decorator
,
math
,
v3
,
Vec3
,
Node
,
Collider2D
,
Contact2DType
,
PhysicsGroup
}
from
"cc"
;
import
{
Snake
}
from
"./Snake"
;
import
{
DirectionType
}
from
"./Common/Enums"
;
import
{
Global
}
from
"./Global"
;
import
{
MainGame
}
from
"./MainGame"
;
import
{
AIController
}
from
"./AI/AIController"
;
import
{
aiPool
}
from
"./Manager/CommonPool"
;
const
{
ccclass
}
=
_decorator
;
const
{
ccclass
,
property
}
=
_decorator
;
enum
AIState
{
HUNTING
,
// 追逐食物
INTERCEPTING
,
// 拦截玩家
ESCAPING
,
// 逃离危险
WANDERING
,
// 随机游走
ASSISTING
// 协助其他AI攻击玩家
}
@
ccclass
(
"AISnake"
)
export
class
AISnake
extends
Snake
{
@
property
({
range
:
[
1
,
5
],
tooltip
:
"AI难度(1-5)"
})
// private difficulty: number = 5;
private
difficulty
:
number
=
1
+
Math
.
random
()
*
4
;
private
aiController
:
AIController
;
private
updateTimer
:
number
=
0
;
private
readonly
UPDATE_INTERVAL
:
number
=
0.1
;
private
currentState
:
AIState
=
AIState
.
WANDERING
;
private
behaviorTimer
:
number
=
0
;
private
targetFood
:
Vec3
=
null
;
private
targetSnake
:
Snake
=
null
;
private
escapeTarget
:
Snake
=
null
;
async
init
(
config
:
any
)
{
await
super
.
init
(
config
);
// const difficulty = 1 + Math.random() * 4;
this
.
aiController
=
new
AIController
(
this
,
5
);
}
private
readonly
BASE_VIEW_DISTANCE
=
300
;
private
readonly
INTERCEPT_DISTANCE
=
350
;
// 降低拦截距离
private
readonly
PREDICTION_TIME
=
1.2
;
// 增加预测时间
private
readonly
ESCAPE_BOUNDARY
=
150
;
// 增加边界安全距离
private
readonly
SAFE_MARGIN
=
3.0
;
// 增加安全边际
private
readonly
COLLISION_CHECK_DISTANCE
=
500
;
// 增加碰撞检测距离
private
readonly
ASSIST_DISTANCE
=
500
;
// 协助攻击的最大距离
private
readonly
DANGER_ANGLE_THRESHOLD
=
90
;
// 扩大危险角度范围
private
readonly
COUNTER_ATTACK_THRESHOLD
=
0.8
;
// 反击判定阈值
private
readonly
SAFE_DISTANCE_MULTIPLIER
=
1.5
;
// 安全距离倍数
private
assistTarget
:
AISnake
=
null
;
// 正在协助的AI蛇
onEnable
()
{
super
.
onEnable
();
//
const eye = this.head.getChildByName("范围").getComponent(Collider2D);
//
eye.on(Contact2DType.BEGIN_CONTACT, this.onBeginEye, this);
const
eye
=
this
.
head
.
getChildByName
(
"范围"
).
getComponent
(
Collider2D
);
eye
.
on
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginEye
,
this
);
}
//
onBeginEye(selfCollider: Collider2D, otherCollider: Collider2D) {
//
super.onBeginEye(selfCollider, otherCollider);
//
if (otherCollider.group === PhysicsGroup["Body"] && otherCollider.tag != this.tag) {
//
// 碰到其他蛇身
//
// this.setAngle(this.head.angle + 180);
//
// this.isFast = true;
//
this.setState(AIState.ESCAPING, otherCollider.node.parent.getComponent(Snake));
//
}
//
}
onBeginEye
(
selfCollider
:
Collider2D
,
otherCollider
:
Collider2D
)
{
super
.
onBeginEye
(
selfCollider
,
otherCollider
);
if
(
otherCollider
.
group
===
PhysicsGroup
[
"Body"
]
&&
otherCollider
.
tag
!=
this
.
tag
)
{
// 碰到其他蛇身
// this.setAngle(this.head.angle + 180);
// this.isFast = true;
this
.
setState
(
AIState
.
ESCAPING
,
otherCollider
.
node
.
parent
.
getComponent
(
Snake
));
}
}
death
()
{
super
.
death
();
...
...
@@ -45,19 +69,968 @@ export class AISnake extends Snake {
aiPool
.
put
(
this
.
node
);
MainGame
.
ins
.
initAnimal
(
1
);
}
private
get
difficultyParams
()
{
return
{
reactionTime
:
math
.
lerp
(
0.8
,
0.15
,
(
this
.
difficulty
-
1
)
/
4
),
viewDistance
:
this
.
BASE_VIEW_DISTANCE
*
(
1
+
(
this
.
difficulty
-
1
)
*
0.2
),
interceptDistance
:
this
.
INTERCEPT_DISTANCE
*
(
1
+
(
this
.
difficulty
-
1
)
*
0.2
),
aggressiveness
:
math
.
lerp
(
0.2
,
0.7
,
(
this
.
difficulty
-
1
)
/
4
),
// 攻击性
predictionAccuracy
:
math
.
lerp
(
0.6
,
0.9
,
(
this
.
difficulty
-
1
)
/
4
),
turnSpeed
:
math
.
lerp
(
2
,
4.5
,
(
this
.
difficulty
-
1
)
/
4
)
// 略微降低最大转向速度
};
}
onUpdate
(
dt
:
number
)
{
if
(
!
this
.
isLife
)
return
;
this
.
update
Timer
+=
dt
;
if
(
this
.
updateTimer
>=
this
.
UPDATE_INTERVAL
)
{
this
.
update
Timer
=
0
;
this
.
aiController
.
update
(
dt
);
this
.
behavior
Timer
+=
dt
;
if
(
this
.
behaviorTimer
>=
this
.
difficultyParams
.
reactionTime
)
{
this
.
behavior
Timer
=
0
;
this
.
updateAIState
(
);
}
this
.
executeCurrentState
(
dt
);
super
.
onUpdate
(
dt
);
}
private
updateAIState
()
{
const
myPos
=
this
.
head
.
getPosition
();
const
player
=
MainGame
.
ins
.
player
;
// 只有在非常危险时才逃跑
if
(
this
.
isInDangerousPosition
(
myPos
))
{
this
.
setState
(
AIState
.
ESCAPING
,
null
);
return
;
}
// 检查是否有致命威胁需要逃跑
const
threat
=
this
.
findNearestThreat
();
if
(
threat
&&
this
.
isLethalThreat
(
threat
))
{
this
.
setState
(
AIState
.
ESCAPING
,
threat
);
return
;
}
// 优先检查是否可以协助其他AI攻击玩家
const
assistPartner
=
this
.
findAssistPartner
(
player
);
if
(
assistPartner
)
{
this
.
setState
(
AIState
.
ASSISTING
,
assistPartner
);
return
;
}
// 检查是否可以拦截玩家
if
(
this
.
canInterceptPlayer
(
player
))
{
this
.
setState
(
AIState
.
INTERCEPTING
,
player
);
return
;
}
// 寻找食物
const
nearestFood
=
this
.
findNearestFood
();
if
(
nearestFood
)
{
this
.
setState
(
AIState
.
HUNTING
,
nearestFood
);
return
;
}
// 默认追击玩家
if
(
player
&&
player
.
isLife
&&
Vec3
.
distance
(
myPos
,
player
.
head
.
getPosition
())
<
this
.
BASE_VIEW_DISTANCE
// 只在较近距离时考虑追击
&&
this
.
getSnakeLen
()
>
player
.
getSnakeLen
()
*
0.8
// 保持一定实力优势
&&
math
.
random
()
<
this
.
difficultyParams
.
aggressiveness
*
0.6
)
{
// 追击概率
this
.
setState
(
AIState
.
INTERCEPTING
,
player
);
}
else
{
this
.
setState
(
AIState
.
WANDERING
,
null
);
}
}
// 判断是否在极度危险的位置(非常靠近边界)
private
isInDangerousPosition
(
position
:
Vec3
):
boolean
{
const
dangerBuffer
=
this
.
ESCAPE_BOUNDARY
;
const
mapWidth
=
Global
.
MAP_WIDTH
;
const
mapHeight
=
Global
.
MAP_HEIGHT
;
return
(
position
.
x
>
mapWidth
/
2
-
dangerBuffer
||
position
.
x
<
-
mapWidth
/
2
+
dangerBuffer
||
position
.
y
>
mapHeight
/
2
-
dangerBuffer
||
position
.
y
<
-
mapHeight
/
2
+
dangerBuffer
);
}
// 判断是否是致命威胁
private
isLethalThreat
(
threat
:
Snake
):
boolean
{
const
myPos
=
this
.
head
.
getPosition
();
const
threatPos
=
threat
.
head
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
threatPos
);
return
(
distance
<
this
.
BASE_VIEW_DISTANCE
*
0.5
&&
// 减小威胁判定距离
threat
.
getSnakeLen
()
>
this
.
getSnakeLen
()
*
1.5
// 只有当对方明显比自己强时才逃跑
);
}
private
setState
(
state
:
AIState
,
target
:
any
)
{
this
.
currentState
=
state
;
switch
(
state
)
{
case
AIState
.
HUNTING
:
this
.
targetFood
=
target
as
Vec3
;
break
;
case
AIState
.
INTERCEPTING
:
this
.
targetSnake
=
target
as
Snake
;
break
;
case
AIState
.
ESCAPING
:
this
.
escapeTarget
=
target
as
Snake
;
break
;
case
AIState
.
WANDERING
:
this
.
targetFood
=
null
;
this
.
targetSnake
=
null
;
this
.
escapeTarget
=
null
;
break
;
case
AIState
.
ASSISTING
:
this
.
assistTarget
=
target
as
AISnake
;
this
.
targetSnake
=
MainGame
.
ins
.
player
;
break
;
}
}
// 寻找可以协助的AI蛇
private
findAssistPartner
(
player
:
Snake
):
AISnake
|
null
{
if
(
!
player
||
!
player
.
isLife
)
return
null
;
const
myPos
=
this
.
head
.
getPosition
();
const
allAISnakes
=
MainGame
.
ins
.
animalNode
.
children
.
map
(
node
=>
node
.
getComponent
(
AISnake
))
.
filter
(
snake
=>
snake
&&
snake
!==
this
&&
snake
.
isLife
&&
(
snake
.
currentState
===
AIState
.
INTERCEPTING
||
snake
.
currentState
===
AIState
.
ASSISTING
)
);
for
(
const
aiSnake
of
allAISnakes
)
{
const
distance
=
Vec3
.
distance
(
myPos
,
aiSnake
.
head
.
getPosition
());
// 减小协助范围,提高协助条件
if
(
distance
<
this
.
ASSIST_DISTANCE
*
0.7
&&
// 减小协助范围
this
.
getSnakeLen
()
>
player
.
getSnakeLen
()
*
0.7
&&
// 保持一定实力才协助
Vec3
.
distance
(
myPos
,
player
.
head
.
getPosition
())
<
this
.
ASSIST_DISTANCE
*
0.8
)
{
// 确保玩家在较近范围内
return
aiSnake
;
}
}
return
null
;
}
// 执行帮助
private
executeAssisting
()
{
if
(
!
this
.
assistTarget
||
!
this
.
assistTarget
.
isLife
||
!
this
.
targetSnake
||
!
this
.
targetSnake
.
isLife
)
{
return
;
}
const
playerPos
=
this
.
targetSnake
.
head
.
getPosition
();
const
partnerPos
=
this
.
assistTarget
.
head
.
getPosition
();
// 计算包围位置:在玩家和协助目标的另一侧
const
angle
=
this
.
calculateTargetAngle
(
playerPos
);
const
oppositeAngle
=
(
angle
+
180
)
%
360
;
// 计算包围点
const
surroundDistance
=
100
;
const
radian
=
oppositeAngle
*
Math
.
PI
/
180
;
const
surroundX
=
playerPos
.
x
+
Math
.
cos
(
radian
)
*
surroundDistance
;
const
surroundY
=
playerPos
.
y
+
Math
.
sin
(
radian
)
*
surroundDistance
;
const
surroundPos
=
v3
(
surroundX
,
surroundY
,
0
);
// 移动到包围点
const
targetAngle
=
this
.
calculateTargetAngle
(
surroundPos
);
this
.
smoothRotateToAngle
(
targetAngle
,
this
.
difficultyParams
.
turnSpeed
);
this
.
isFast
=
true
;
}
private
executeCurrentState
(
dt
:
number
)
{
const
threat
=
this
.
findNearbyAIToAvoid
();
if
(
threat
&&
threat
.
dangerLevel
>
10
)
{
// 降低触发避让的阈值
// 计算躲避角度
const
avoidAngle
=
this
.
calculateAvoidanceAngle
(
threat
.
snake
);
// 根据危险程度调整躲避行为
if
(
threat
.
dangerLevel
>
30
)
{
// 高危险:立即急转
this
.
smoothRotateToAngle
(
avoidAngle
,
this
.
difficultyParams
.
turnSpeed
*
2
);
this
.
isFast
=
false
;
// 降速以提高控制精度
}
else
{
// 中等危险:渐进式躲避
const
currentAction
=
this
.
getCurrentStateAngle
();
const
blendWeight
=
threat
.
dangerLevel
/
100
;
const
finalAngle
=
this
.
blendAngles
(
currentAction
,
avoidAngle
,
blendWeight
);
this
.
smoothRotateToAngle
(
finalAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.5
);
}
return
;
// 优先处理躲避
}
// 执行原有状态逻辑
switch
(
this
.
currentState
)
{
case
AIState
.
HUNTING
:
this
.
executeHunting
();
break
;
case
AIState
.
INTERCEPTING
:
this
.
executeIntercepting
();
break
;
case
AIState
.
ESCAPING
:
this
.
executeEscaping
();
break
;
case
AIState
.
WANDERING
:
this
.
executeWandering
();
break
;
case
AIState
.
ASSISTING
:
this
.
executeAssisting
();
break
;
}
}
// 获取当前状态下的目标角度
private
getCurrentStateAngle
():
number
{
switch
(
this
.
currentState
)
{
case
AIState
.
HUNTING
:
if
(
this
.
targetFood
)
{
return
this
.
calculateTargetAngle
(
this
.
targetFood
);
}
break
;
case
AIState
.
INTERCEPTING
:
case
AIState
.
ASSISTING
:
if
(
this
.
targetSnake
&&
this
.
targetSnake
.
isLife
)
{
const
predictedPos
=
this
.
predictTargetPosition
(
this
.
targetSnake
);
return
this
.
calculateTargetAngle
(
predictedPos
);
}
break
;
case
AIState
.
ESCAPING
:
if
(
this
.
escapeTarget
&&
this
.
escapeTarget
.
isLife
)
{
// 逃跑方向是远离目标
return
this
.
calculateEscapeAngle
(
this
.
escapeTarget
.
head
.
getPosition
());
}
break
;
case
AIState
.
WANDERING
:
// 游荡状态保持当前角度
return
this
.
head
.
angle
;
}
// 如果没有特定目标,返回当前角度
return
this
.
head
.
angle
;
}
private
canInterceptPlayer
(
player
:
Snake
):
boolean
{
if
(
!
player
||
!
player
.
isLife
)
return
false
;
const
params
=
this
.
difficultyParams
;
const
myPos
=
this
.
head
.
getPosition
();
const
playerPos
=
player
.
head
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
playerPos
);
return
distance
<
params
.
interceptDistance
*
0.7
&&
// 进一步减小拦截距离
this
.
getSnakeLen
()
>
player
.
getSnakeLen
()
*
1.2
&&
// 提高长度要求
math
.
random
()
<
params
.
aggressiveness
*
0.6
&&
// 降低激进程度
!
this
.
isInDangerousPosition
(
playerPos
);
// 不在边界附近才拦截
}
private
executeHunting
()
{
if
(
!
this
.
targetFood
)
return
;
const
myPos
=
this
.
head
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
this
.
targetFood
);
const
targetAngle
=
this
.
calculateTargetAngle
(
this
.
targetFood
);
const
angleDiff
=
Math
.
abs
(
this
.
head
.
angle
-
targetAngle
);
// 检查是否需要避开自己的身体
const
needAvoidBody
=
this
.
willHitOwnBody
(
targetAngle
);
if
(
needAvoidBody
)
{
// 寻找更优的替代路径
const
alternativeAngle
=
this
.
findBetterAngleToFood
(
this
.
targetFood
);
if
(
alternativeAngle
!==
null
)
{
this
.
smoothRotateToAngle
(
alternativeAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.8
);
}
else
{
// 如果没有找到好的替代路径,执行更激进的转向
this
.
executeAggressiveTurn
(
targetAngle
);
}
this
.
isFast
=
false
;
}
else
{
// 根据角度差决定转向策略
if
(
angleDiff
>
90
)
{
// 大角度差时执行快速转向
this
.
executeAggressiveTurn
(
targetAngle
);
this
.
isFast
=
false
;
}
else
{
// 小角度差时正常追逐
this
.
smoothRotateToAngle
(
targetAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.5
);
this
.
isFast
=
distance
<
this
.
BASE_VIEW_DISTANCE
/
2
;
}
}
}
// 更激进的转向方法
private
executeAggressiveTurn
(
targetAngle
:
number
)
{
const
currentAngle
=
this
.
head
.
angle
;
let
angleDiff
=
targetAngle
-
currentAngle
;
// 标准化角度差到 -180 到 180 度范围
while
(
angleDiff
>
180
)
angleDiff
-=
360
;
while
(
angleDiff
<
-
180
)
angleDiff
+=
360
;
// 使用更大的转向速度
const
turnSpeed
=
this
.
difficultyParams
.
turnSpeed
*
2.5
;
this
.
head
.
angle
+=
math
.
clamp
(
angleDiff
,
-
turnSpeed
,
turnSpeed
);
}
// 寻找更好的替代角度
private
findBetterAngleToFood
(
foodPos
:
Vec3
):
number
|
null
{
const
myPos
=
this
.
head
.
getPosition
();
const
directAngle
=
this
.
calculateTargetAngle
(
foodPos
);
const
currentDistance
=
Vec3
.
distance
(
myPos
,
foodPos
);
// 根据当前角度差决定搜索范围
const
angleDiff
=
Math
.
abs
(
this
.
head
.
angle
-
directAngle
);
const
searchRange
=
angleDiff
>
90
?
[
-
120
,
-
90
,
-
60
,
-
45
,
-
30
,
30
,
45
,
60
,
90
,
120
]
:
[
-
60
,
-
45
,
-
30
,
-
15
,
15
,
30
,
45
,
60
];
let
bestAngle
=
null
;
let
bestImprovement
=
0
;
for
(
const
offset
of
searchRange
)
{
const
testAngle
=
directAngle
+
offset
;
if
(
this
.
willHitOwnBody
(
testAngle
))
continue
;
const
futurePos
=
this
.
predictFuturePosition
(
myPos
,
testAngle
,
this
.
radius
*
5
);
const
newDistance
=
Vec3
.
distance
(
futurePos
,
foodPos
);
// 计算路径改善程度
const
improvement
=
currentDistance
-
newDistance
;
// 选择最佳改善路径
if
(
improvement
>
bestImprovement
)
{
bestImprovement
=
improvement
;
bestAngle
=
testAngle
;
}
}
return
bestAngle
;
}
// 检查是否会撞到自己的身体
private
willHitOwnBody
(
angle
:
number
):
boolean
{
const
myPos
=
this
.
head
.
getPosition
();
const
checkDistance
=
this
.
radius
*
4
;
// 检查前方较短的距离
const
futurePos
=
this
.
predictFuturePosition
(
myPos
,
angle
,
checkDistance
);
// 检查与自己身体的碰撞
for
(
const
bodyPart
of
this
.
bodyArr
)
{
const
bodyDistance
=
Vec3
.
distance
(
futurePos
,
bodyPart
.
getPosition
());
if
(
bodyDistance
<
this
.
radius
*
2.5
)
{
// 略大的碰撞检测范围
return
true
;
}
}
return
false
;
}
private
executeIntercepting
()
{
if
(
!
this
.
targetSnake
||
!
this
.
targetSnake
.
isLife
)
return
;
const
myPos
=
this
.
head
.
getPosition
();
const
nearbyAI
=
this
.
findNearbyAIToAvoid
();
if
(
nearbyAI
)
{
const
predictedPos
=
this
.
predictTargetPosition
(
this
.
targetSnake
);
const
targetAngle
=
this
.
calculateTargetAngle
(
predictedPos
);
const
avoidAngle
=
this
.
calculateEscapeAngle
(
nearbyAI
.
snake
.
head
.
getPosition
());
// 增加躲避权重
const
aiDistance
=
Vec3
.
distance
(
myPos
,
nearbyAI
.
snake
.
head
.
getPosition
());
const
avoidWeight
=
math
.
clamp
(
1
-
aiDistance
/
(
this
.
BASE_VIEW_DISTANCE
*
0.6
),
0.4
,
0.95
);
// 增加避让权重
const
finalAngle
=
this
.
blendAngles
(
targetAngle
,
avoidAngle
,
avoidWeight
);
this
.
smoothRotateToAngle
(
finalAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.5
);
this
.
isFast
=
false
;
// 避让时始终降速
}
else
{
const
predictedPos
=
this
.
predictTargetPosition
(
this
.
targetSnake
);
const
targetAngle
=
this
.
calculateTargetAngle
(
predictedPos
);
// 直接追击时使用更快的转向速度
this
.
smoothRotateToAngle
(
targetAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.5
);
}
this
.
isFast
=
true
;
}
// 混合两个角度
private
blendAngles
(
angle1
:
number
,
angle2
:
number
,
weight
:
number
):
number
{
// 确保角度在 0-360 范围内
while
(
angle1
<
0
)
angle1
+=
360
;
while
(
angle2
<
0
)
angle2
+=
360
;
while
(
angle1
>=
360
)
angle1
-=
360
;
while
(
angle2
>=
360
)
angle2
-=
360
;
// 计算角度差
let
diff
=
angle2
-
angle1
;
if
(
diff
>
180
)
diff
-=
360
;
if
(
diff
<
-
180
)
diff
+=
360
;
// 根据权重混合角度
let
result
=
angle1
+
diff
*
weight
;
while
(
result
<
0
)
result
+=
360
;
while
(
result
>=
360
)
result
-=
360
;
return
result
;
}
// 寻找需要躲避的附近AI
private
findNearbyAIToAvoid
():
{
snake
:
Snake
,
dangerLevel
:
number
}
|
null
{
const
myPos
=
this
.
head
.
getPosition
();
const
myFuturePos
=
this
.
predictFuturePosition
(
myPos
,
this
.
head
.
angle
,
this
.
speed
*
2
);
let
maxDanger
=
0
;
let
mostDangerousSnake
=
null
;
const
myLength
=
this
.
getSnakeLen
();
let
canCounterAttack
=
false
;
const
allSnakes
=
[...
MainGame
.
ins
.
animalNode
.
children
,
MainGame
.
ins
.
player
.
node
]
.
map
(
node
=>
node
.
getComponent
(
Snake
))
.
filter
(
snake
=>
snake
&&
snake
!==
this
&&
snake
.
isLife
);
for
(
const
snake
of
allSnakes
)
{
let
snakeDanger
=
0
;
const
headDistance
=
Vec3
.
distance
(
myPos
,
snake
.
head
.
getPosition
());
const
isPlayer
=
snake
===
MainGame
.
ins
.
player
;
// 对玩家增加额外的危险系数
const
playerDangerMultiplier
=
isPlayer
?
1.5
:
1.0
;
// 检查头部威胁
if
(
headDistance
<
this
.
COLLISION_CHECK_DISTANCE
)
{
const
angleToHead
=
this
.
calculateTargetAngle
(
snake
.
head
.
getPosition
());
const
headAngleDiff
=
Math
.
abs
(
this
.
head
.
angle
-
angleToHead
);
// 对玩家扩大危险角度阈值
const
effectiveAngleThreshold
=
isPlayer
?
this
.
DANGER_ANGLE_THRESHOLD
*
1.2
:
this
.
DANGER_ANGLE_THRESHOLD
;
if
(
headAngleDiff
<
effectiveAngleThreshold
)
{
const
baseDanger
=
(
this
.
COLLISION_CHECK_DISTANCE
-
headDistance
)
/
this
.
COLLISION_CHECK_DISTANCE
*
100
;
snakeDanger
=
Math
.
max
(
snakeDanger
,
baseDanger
*
playerDangerMultiplier
);
}
}
// 检查身体威胁
for
(
let
i
=
0
;
i
<
snake
.
bodyArr
.
length
;
i
++
)
{
const
bodyPart
=
snake
.
bodyArr
[
i
];
const
bodyDistance
=
Vec3
.
distance
(
myPos
,
bodyPart
.
getPosition
());
const
futureDist
=
Vec3
.
distance
(
myFuturePos
,
bodyPart
.
getPosition
());
const
bodyAngle
=
this
.
calculateTargetAngle
(
bodyPart
.
getPosition
());
const
angleDiff
=
Math
.
abs
(
this
.
head
.
angle
-
bodyAngle
);
// 对玩家的身体也使用更大的检测范围
const
effectiveCheckDistance
=
isPlayer
?
this
.
COLLISION_CHECK_DISTANCE
*
1.2
:
this
.
COLLISION_CHECK_DISTANCE
;
if
(
bodyDistance
<
effectiveCheckDistance
&&
angleDiff
<
this
.
DANGER_ANGLE_THRESHOLD
)
{
const
distanceDanger
=
(
effectiveCheckDistance
-
bodyDistance
)
/
effectiveCheckDistance
;
const
angleDanger
=
(
this
.
DANGER_ANGLE_THRESHOLD
-
angleDiff
)
/
this
.
DANGER_ANGLE_THRESHOLD
;
const
futureDanger
=
futureDist
<
bodyDistance
?
2.5
:
1
;
// 增加未来碰撞的危险系数
const
partDanger
=
(
distanceDanger
*
angleDanger
*
futureDanger
)
*
120
*
playerDangerMultiplier
;
snakeDanger
=
Math
.
max
(
snakeDanger
,
partDanger
);
}
}
// 如果是玩家且正在加速,进一步提高危险等级
if
(
isPlayer
&&
snake
.
isFast
)
{
snakeDanger
*=
1.3
;
}
// 更新最危险的蛇
if
(
snakeDanger
>
maxDanger
)
{
maxDanger
=
snakeDanger
;
mostDangerousSnake
=
snake
;
}
}
// 降低触发避让的阈值,使AI更容易进入避让状态
return
maxDanger
>
15
?
{
snake
:
mostDangerousSnake
,
dangerLevel
:
maxDanger
,
}
:
null
;
}
private
predictFuturePosition
(
currentPos
:
Vec3
,
angle
:
number
,
speed
:
number
):
Vec3
{
const
radian
=
angle
*
Math
.
PI
/
180
;
const
futureX
=
currentPos
.
x
+
Math
.
cos
(
radian
)
*
speed
;
const
futureY
=
currentPos
.
y
+
Math
.
sin
(
radian
)
*
speed
;
return
v3
(
futureX
,
futureY
,
0
);
}
private
executeEscaping
()
{
if
(
!
this
.
escapeTarget
?.
isLife
)
{
// 如果没有特定的逃离目标,检查并避开所有潜在威胁
this
.
avoidAllThreats
();
return
;
}
const
myPos
=
this
.
head
.
getPosition
();
const
threatPos
=
this
.
escapeTarget
.
head
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
threatPos
);
// 紧急避让判定
const
isEmergency
=
distance
<
this
.
radius
*
3
;
const
escapeAngle
=
this
.
calculateAvoidanceAngle
(
this
.
escapeTarget
);
if
(
isEmergency
)
{
// 紧急情况:直接设置角度
this
.
head
.
angle
=
escapeAngle
;
this
.
isFast
=
false
;
}
else
{
// 非紧急情况:快速但平滑地转向
const
angleDiff
=
escapeAngle
-
this
.
head
.
angle
;
// 标准化角度差到 -180 到 180 度范围
const
normalizedDiff
=
(
angleDiff
+
180
)
%
360
-
180
;
this
.
head
.
angle
+=
math
.
clamp
(
normalizedDiff
,
-
15
,
15
);
this
.
isFast
=
distance
>
this
.
radius
*
5
;
}
}
// 避开所有潜在威胁
private
avoidAllThreats
()
{
const
myPos
=
this
.
head
.
getPosition
();
const
allSnakes
=
[...
MainGame
.
ins
.
animalNode
.
children
,
MainGame
.
ins
.
player
.
node
]
.
map
(
node
=>
node
.
getComponent
(
Snake
))
.
filter
(
snake
=>
snake
&&
snake
!==
this
&&
snake
.
isLife
);
let
nearestThreatDistance
=
Infinity
;
let
bestEscapeAngle
=
this
.
head
.
angle
;
let
hasThreats
=
false
;
// 检查所有潜在威胁
for
(
const
snake
of
allSnakes
)
{
const
distance
=
Vec3
.
distance
(
myPos
,
snake
.
head
.
getPosition
());
if
(
distance
<
this
.
COLLISION_CHECK_DISTANCE
)
{
hasThreats
=
true
;
if
(
distance
<
nearestThreatDistance
)
{
nearestThreatDistance
=
distance
;
bestEscapeAngle
=
this
.
calculateAvoidanceAngle
(
snake
);
}
}
}
if
(
hasThreats
)
{
// 有威胁时执行避让
const
isEmergency
=
nearestThreatDistance
<
this
.
radius
*
3
;
if
(
isEmergency
)
{
this
.
head
.
angle
=
bestEscapeAngle
;
this
.
isFast
=
false
;
}
else
{
const
angleDiff
=
bestEscapeAngle
-
this
.
head
.
angle
;
const
normalizedDiff
=
(
angleDiff
+
180
)
%
360
-
180
;
this
.
head
.
angle
+=
math
.
clamp
(
normalizedDiff
,
-
15
,
15
);
this
.
isFast
=
nearestThreatDistance
>
this
.
radius
*
5
;
}
}
else
{
// 没有威胁时检查边界
this
.
avoidBoundary
();
}
}
// 计算避让角度的方法,增加紧急情况下的处理
private
calculateAvoidanceAngle
(
threat
:
Snake
):
number
{
const
myPos
=
this
.
head
.
getPosition
();
const
threatPos
=
threat
.
head
.
getPosition
();
const
baseEscapeAngle
=
this
.
calculateEscapeAngle
(
threatPos
);
// 检查基础逃生角度是否安全
if
(
!
this
.
willHitBoundary
(
baseEscapeAngle
))
{
return
baseEscapeAngle
;
}
// 如果基础角度不安全,寻找最佳替代角度
const
angles
=
[
baseEscapeAngle
,
baseEscapeAngle
+
45
,
baseEscapeAngle
-
45
,
baseEscapeAngle
+
90
,
baseEscapeAngle
-
90
,
baseEscapeAngle
+
135
,
baseEscapeAngle
-
135
,
baseEscapeAngle
+
180
];
let
bestAngle
=
baseEscapeAngle
;
let
maxSafety
=
-
Infinity
;
for
(
const
angle
of
angles
)
{
if
(
this
.
willHitBoundary
(
angle
))
continue
;
const
safety
=
this
.
evaluateEscapeAngleSafety
(
angle
,
threat
);
if
(
safety
>
maxSafety
)
{
maxSafety
=
safety
;
bestAngle
=
angle
;
}
}
return
bestAngle
;
}
// 评估逃生角度的安全性
private
evaluateEscapeAngleSafety
(
angle
:
number
,
threat
:
Snake
):
number
{
const
myPos
=
this
.
head
.
getPosition
();
const
futurePos
=
this
.
predictFuturePosition
(
myPos
,
angle
,
this
.
radius
*
4
);
let
safety
=
100
;
// 检查与威胁的距离
const
threatDistance
=
Vec3
.
distance
(
futurePos
,
threat
.
head
.
getPosition
());
safety
+=
threatDistance
;
// 检查边界距离
const
boundaryDist
=
this
.
getDistanceToBoundary
(
futurePos
);
safety
+=
boundaryDist
*
2
;
// 检查与其他蛇的距离
const
allSnakes
=
[...
MainGame
.
ins
.
animalNode
.
children
,
MainGame
.
ins
.
player
.
node
]
.
map
(
node
=>
node
.
getComponent
(
Snake
))
.
filter
(
snake
=>
snake
&&
snake
!==
this
&&
snake
!==
threat
&&
snake
.
isLife
);
for
(
const
snake
of
allSnakes
)
{
const
distance
=
Vec3
.
distance
(
futurePos
,
snake
.
head
.
getPosition
());
if
(
distance
<
this
.
COLLISION_CHECK_DISTANCE
)
{
safety
-=
(
this
.
COLLISION_CHECK_DISTANCE
-
distance
);
}
}
return
safety
;
}
private
willHitBoundary
(
angle
:
number
):
boolean
{
const
myPos
=
this
.
head
.
getPosition
();
const
futurePos
=
this
.
predictFuturePosition
(
myPos
,
angle
,
this
.
radius
*
4
);
const
boundaryDist
=
this
.
getDistanceToBoundary
(
futurePos
);
return
boundaryDist
<
this
.
ESCAPE_BOUNDARY
;
}
// 获取到边界的距离
private
getDistanceToBoundary
(
position
:
Vec3
):
number
{
const
mapWidth
=
Global
.
MAP_WIDTH
;
const
mapHeight
=
Global
.
MAP_HEIGHT
;
return
Math
.
min
(
mapWidth
/
2
-
Math
.
abs
(
position
.
x
),
mapHeight
/
2
-
Math
.
abs
(
position
.
y
)
);
}
private
executeWandering
()
{
// 增加方向改变的概率
if
(
math
.
randomRangeInt
(
0
,
30
)
==
0
)
{
const
direction
=
math
.
randomRangeInt
(
0
,
3
);
const
speed
=
math
.
randomRangeInt
(
1
,
3
);
if
(
direction
===
DirectionType
.
LEFT
)
{
this
.
head
.
angle
+=
speed
;
}
else
if
(
direction
===
DirectionType
.
RIGHT
)
{
this
.
head
.
angle
-=
speed
;
}
}
// 减少速度变化的频率
this
.
isFast
=
math
.
random
()
<
this
.
difficultyParams
.
aggressiveness
*
0.05
;
}
private
avoidBoundary
()
{
const
myPos
=
this
.
head
.
getPosition
();
const
mapWidth
=
Global
.
MAP_WIDTH
;
const
mapHeight
=
Global
.
MAP_HEIGHT
;
const
boundaryBuffer
=
this
.
ESCAPE_BOUNDARY
;
let
targetAngle
=
this
.
head
.
angle
;
let
isCorner
=
false
;
// 检查四角区域
if
(
myPos
.
x
<
-
mapWidth
/
2
+
boundaryBuffer
&&
myPos
.
y
<
-
mapHeight
/
2
+
boundaryBuffer
)
{
targetAngle
=
45
;
// 右上
isCorner
=
true
;
}
else
if
(
myPos
.
x
>
mapWidth
/
2
-
boundaryBuffer
&&
myPos
.
y
<
-
mapHeight
/
2
+
boundaryBuffer
)
{
targetAngle
=
135
;
// 左上
isCorner
=
true
;
}
else
if
(
myPos
.
x
<
-
mapWidth
/
2
+
boundaryBuffer
&&
myPos
.
y
>
mapHeight
/
2
-
boundaryBuffer
)
{
targetAngle
=
315
;
// 右下
isCorner
=
true
;
}
else
if
(
myPos
.
x
>
mapWidth
/
2
-
boundaryBuffer
&&
myPos
.
y
>
mapHeight
/
2
-
boundaryBuffer
)
{
targetAngle
=
225
;
// 左下
isCorner
=
true
;
}
else
{
// 检查边界
if
(
myPos
.
x
<
-
mapWidth
/
2
+
boundaryBuffer
)
{
targetAngle
=
0
;
// 向右
}
else
if
(
myPos
.
x
>
mapWidth
/
2
-
boundaryBuffer
)
{
targetAngle
=
180
;
// 向左
}
if
(
myPos
.
y
<
-
mapHeight
/
2
+
boundaryBuffer
)
{
targetAngle
=
90
;
// 向上
}
else
if
(
myPos
.
y
>
mapHeight
/
2
-
boundaryBuffer
)
{
targetAngle
=
270
;
// 向下
}
}
// 如果在角落,锁定方向,避免频繁调整
if
(
isCorner
)
{
this
.
smoothRotateToAngle
(
targetAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.5
);
}
else
{
this
.
smoothRotateToAngle
(
targetAngle
,
this
.
difficultyParams
.
turnSpeed
);
}
}
private
findNearestFood
():
Vec3
|
null
{
const
myPos
=
this
.
head
.
getPosition
();
let
nearestFood
=
null
;
let
minDistance
=
this
.
difficultyParams
.
viewDistance
;
const
foods
=
MainGame
.
ins
.
fondManger
.
node
.
children
;
const
boundaryBuffer
=
this
.
ESCAPE_BOUNDARY
;
const
mapWidth
=
Global
.
MAP_WIDTH
;
const
mapHeight
=
Global
.
MAP_HEIGHT
;
for
(
const
food
of
foods
)
{
if
(
!
food
.
isValid
||
!
food
.
active
)
continue
;
const
foodPos
=
food
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
foodPos
);
// 检查食物是否靠近墙体
if
(
foodPos
.
x
<
-
mapWidth
/
2
+
boundaryBuffer
||
foodPos
.
x
>
mapWidth
/
2
-
boundaryBuffer
||
foodPos
.
y
<
-
mapHeight
/
2
+
boundaryBuffer
||
foodPos
.
y
>
mapHeight
/
2
-
boundaryBuffer
)
{
continue
;
// 跳过靠近墙体的食物
}
if
(
distance
<
minDistance
)
{
const
randomFactor
=
math
.
lerp
(
0.7
,
1
,
this
.
difficultyParams
.
predictionAccuracy
);
if
(
math
.
random
()
<
randomFactor
)
{
minDistance
=
distance
;
nearestFood
=
foodPos
;
}
}
}
if
(
nearestFood
&&
this
.
difficultyParams
.
predictionAccuracy
>
0.7
)
{
const
competitors
=
this
.
findCompetitorsForFood
(
nearestFood
,
minDistance
);
if
(
competitors
.
length
>
0
&&
!
this
.
canReachFoodFirst
(
nearestFood
,
minDistance
,
competitors
))
{
return
this
.
findAlternativeFood
(
foods
,
myPos
,
this
.
difficultyParams
.
viewDistance
,
competitors
);
}
}
if
(
nearestFood
)
{
const
competitors
=
this
.
findCompetitorsForFood
(
nearestFood
,
minDistance
);
if
(
this
.
difficultyParams
.
predictionAccuracy
>
0.7
)
{
if
(
competitors
.
length
>
0
&&
!
this
.
canReachFoodFirst
(
nearestFood
,
minDistance
,
competitors
))
{
return
this
.
findAlternativeFood
(
foods
,
myPos
,
this
.
difficultyParams
.
viewDistance
,
competitors
);
}
}
const
angleToFood
=
this
.
calculateTargetAngle
(
nearestFood
);
const
currentAngle
=
this
.
head
.
angle
;
const
angleDiff
=
Math
.
abs
(
angleToFood
-
currentAngle
);
// 如果需要大幅度转向,考虑寻找其他食物
if
(
angleDiff
>
90
&&
angleDiff
<
270
)
{
const
alternativeFood
=
this
.
findAlternativeFood
(
foods
,
myPos
,
this
.
difficultyParams
.
viewDistance
,
competitors
);
if
(
alternativeFood
)
{
const
altAngleDiff
=
Math
.
abs
(
this
.
calculateTargetAngle
(
alternativeFood
)
-
currentAngle
);
if
(
altAngleDiff
<
angleDiff
)
{
nearestFood
=
alternativeFood
;
}
}
}
}
return
nearestFood
;
}
// 寻找替代食物
private
findAlternativeFood
(
foods
:
Node
[],
myPos
:
Vec3
,
viewDistance
:
number
,
competitors
:
Snake
[]):
Vec3
|
null
{
let
bestAlternative
=
null
;
let
bestScore
=
-
1
;
for
(
const
food
of
foods
)
{
if
(
!
food
.
isValid
||
!
food
.
active
)
continue
;
const
foodPos
=
food
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
foodPos
);
if
(
distance
>
viewDistance
)
continue
;
// 计算这个食物的得分(考虑距离和竞争者)
const
score
=
this
.
calculateFoodScore
(
foodPos
,
distance
,
competitors
);
if
(
score
>
bestScore
)
{
bestScore
=
score
;
bestAlternative
=
foodPos
;
}
}
return
bestAlternative
;
}
// 计算食物的得分
private
calculateFoodScore
(
foodPos
:
Vec3
,
distance
:
number
,
competitors
:
Snake
[]):
number
{
const
params
=
this
.
difficultyParams
;
// 基础分数(距离越近分数越高)
let
score
=
1
-
(
distance
/
params
.
viewDistance
);
// 根据竞争者调整分数
for
(
const
competitor
of
competitors
)
{
const
competitorDistance
=
Vec3
.
distance
(
competitor
.
head
.
getPosition
(),
foodPos
);
if
(
competitorDistance
<
distance
)
{
// 如果竞争者更近,降低分数
score
*=
0.5
;
}
}
// 根据难度添加一些随机性
score
*=
math
.
lerp
(
0.8
,
1.2
,
math
.
random
()
*
params
.
predictionAccuracy
);
return
score
;
}
// 判断是否能在竞争者之前到达食物
private
canReachFoodFirst
(
foodPos
:
Vec3
,
myDistance
:
number
,
competitors
:
Snake
[]):
boolean
{
const
mySpeed
=
this
.
speed
*
this
.
moveScale
;
const
myTimeToReach
=
myDistance
/
mySpeed
;
for
(
const
competitor
of
competitors
)
{
const
competitorPos
=
competitor
.
head
.
getPosition
();
const
competitorDistance
=
Vec3
.
distance
(
competitorPos
,
foodPos
);
const
competitorSpeed
=
competitor
.
speed
*
(
competitor
instanceof
AISnake
?
2
:
1
);
const
competitorTimeToReach
=
competitorDistance
/
competitorSpeed
;
if
(
competitorTimeToReach
<
myTimeToReach
)
{
return
false
;
}
}
return
true
;
}
// 寻找同样在追逐食物的竞争者
private
findCompetitorsForFood
(
foodPos
:
Vec3
,
myDistance
:
number
):
Snake
[]
{
const
competitors
:
Snake
[]
=
[];
const
allSnakes
=
[...
MainGame
.
ins
.
animalNode
.
children
,
MainGame
.
ins
.
player
.
node
];
for
(
const
snakeNode
of
allSnakes
)
{
const
snake
=
snakeNode
.
getComponent
(
Snake
);
if
(
snake
===
this
||
!
snake
.
isLife
)
continue
;
const
distance
=
Vec3
.
distance
(
snake
.
head
.
getPosition
(),
foodPos
);
// 只考虑距离相近或更近的竞争者
if
(
distance
<=
myDistance
*
1.2
)
{
competitors
.
push
(
snake
);
}
}
return
competitors
;
}
private
findNearestThreat
():
Snake
|
null
{
const
myPos
=
this
.
head
.
getPosition
();
const
myLength
=
this
.
getSnakeLen
();
let
nearestThreat
=
null
;
let
minDistance
=
this
.
difficultyParams
.
viewDistance
;
const
allSnakes
=
[...
MainGame
.
ins
.
animalNode
.
children
,
MainGame
.
ins
.
player
.
node
];
for
(
const
snakeNode
of
allSnakes
)
{
const
snake
=
snakeNode
.
getComponent
(
Snake
);
if
(
snake
===
this
)
continue
;
// 检查头部威胁,使用两条蛇的实际半径
const
headDistance
=
Vec3
.
distance
(
myPos
,
snake
.
head
.
getPosition
());
const
combinedRadius
=
(
this
.
radius
+
snake
.
radius
)
*
this
.
SAFE_MARGIN
;
if
(
headDistance
<
combinedRadius
&&
snake
.
getSnakeLen
()
>
myLength
*
1.2
)
{
minDistance
=
headDistance
;
nearestThreat
=
snake
;
}
// 检查身体威胁,使用实际的身体半径
for
(
let
i
=
0
;
i
<
snake
.
bodyArr
.
length
;
i
++
)
{
const
bodyPart
=
snake
.
bodyArr
[
i
];
const
bodyDistance
=
Vec3
.
distance
(
myPos
,
bodyPart
.
getPosition
());
// 根据身体节点的位置计算实际威胁距离
const
effectiveRadius
=
snake
.
radius
*
(
1
+
i
*
0.1
);
// 身体越后面的部分威胁范围略大
const
threatDistance
=
(
this
.
radius
+
effectiveRadius
)
*
this
.
SAFE_MARGIN
;
if
(
bodyDistance
<
threatDistance
)
{
minDistance
=
bodyDistance
;
nearestThreat
=
snake
;
break
;
}
}
}
return
nearestThreat
;
}
private
calculateEscapeAngle
(
threatPos
:
Vec3
):
number
{
const
myPos
=
this
.
head
.
getPosition
();
return
math
.
toDegree
(
Math
.
atan2
(
myPos
.
y
-
threatPos
.
y
,
myPos
.
x
-
threatPos
.
x
));
}
private
calculateTargetAngle
(
targetPos
:
Vec3
):
number
{
const
myPos
=
this
.
head
.
getPosition
();
return
math
.
toDegree
(
Math
.
atan2
(
targetPos
.
y
-
myPos
.
y
,
targetPos
.
x
-
myPos
.
x
));
}
private
smoothRotateToAngle
(
targetAngle
:
number
,
turnSpeed
:
number
)
{
const
currentAngle
=
this
.
head
.
angle
;
let
angleDiff
=
targetAngle
-
currentAngle
;
// 标准化角度差到 -180 到 180 度范围
while
(
angleDiff
>
180
)
angleDiff
-=
360
;
while
(
angleDiff
<
-
180
)
angleDiff
+=
360
;
// 增加转向速度,减少平滑过渡
const
actualTurnSpeed
=
turnSpeed
*
1.5
;
// 增加基础转向速度
// 根据角度差的大小调整转向速度
const
speedMultiplier
=
Math
.
abs
(
angleDiff
)
>
90
?
2
:
1.5
;
this
.
head
.
angle
+=
math
.
clamp
(
angleDiff
,
-
actualTurnSpeed
*
speedMultiplier
,
actualTurnSpeed
*
speedMultiplier
);
}
private
predictTargetPosition
(
target
:
Snake
):
Vec3
{
const
targetPos
=
target
.
head
.
getPosition
();
const
targetAngle
=
target
.
head
.
angle
;
const
targetSpeed
=
target
.
isFast
?
target
.
speed
*
2
:
target
.
speed
;
const
radian
=
targetAngle
*
Math
.
PI
/
180
;
const
predictX
=
targetPos
.
x
+
Math
.
cos
(
radian
)
*
targetSpeed
*
this
.
PREDICTION_TIME
*
this
.
difficultyParams
.
predictionAccuracy
;
const
predictY
=
targetPos
.
y
+
Math
.
sin
(
radian
)
*
targetSpeed
*
this
.
PREDICTION_TIME
*
this
.
difficultyParams
.
predictionAccuracy
;
return
v3
(
predictX
,
predictY
,
0
);
}
setDifficulty
(
level
:
number
)
{
this
.
difficulty
=
math
.
clamp
(
level
,
1
,
5
);
}
}
\ No newline at end of file
assets/Scripts/Scenes/MainGame/AISnakeBack.ts
View file @
b2d1c691
...
...
@@ -57,7 +57,7 @@ export class AISnake extends Snake {
// 碰到其他蛇身
// this.setAngle(this.head.angle + 180);
// this.isFast = true;
this
.
setState
(
AIState
.
ESCAPING
,
otherCollider
.
node
.
parent
.
getComponent
(
Snake
));
this
.
setState
(
AIState
.
ESCAPING
,
otherCollider
.
node
?.
parent
?
.
getComponent
(
Snake
));
}
}
...
...
assets/Scripts/Scenes/MainGame/AISnakeBack2.ts
0 → 100644
View file @
b2d1c691
import
{
_decorator
}
from
"cc"
;
import
{
Snake
}
from
"./Snake"
;
import
{
MainGame
}
from
"./MainGame"
;
import
{
AIController
}
from
"./AI/AIController"
;
import
{
aiPool
}
from
"./Manager/CommonPool"
;
const
{
ccclass
}
=
_decorator
;
@
ccclass
(
"AISnake"
)
export
class
AISnake
extends
Snake
{
private
aiController
:
AIController
;
private
updateTimer
:
number
=
0
;
private
readonly
UPDATE_INTERVAL
:
number
=
0.1
;
async
init
(
config
:
any
)
{
await
super
.
init
(
config
);
// const difficulty = 1 + Math.random() * 4;
this
.
aiController
=
new
AIController
(
this
,
5
);
}
onEnable
()
{
super
.
onEnable
();
// const eye = this.head.getChildByName("范围").getComponent(Collider2D);
// eye.on(Contact2DType.BEGIN_CONTACT, this.onBeginEye, this);
}
// onBeginEye(selfCollider: Collider2D, otherCollider: Collider2D) {
// super.onBeginEye(selfCollider, otherCollider);
// if (otherCollider.group === PhysicsGroup["Body"] && otherCollider.tag != this.tag) {
// // 碰到其他蛇身
// // this.setAngle(this.head.angle + 180);
// // this.isFast = true;
// this.setState(AIState.ESCAPING, otherCollider.node.parent.getComponent(Snake));
// }
// }
death
()
{
super
.
death
();
this
.
node
.
removeFromParent
();
aiPool
.
put
(
this
.
node
);
MainGame
.
ins
.
initAnimal
(
1
);
}
onUpdate
(
dt
:
number
)
{
if
(
!
this
.
isLife
)
return
;
this
.
updateTimer
+=
dt
;
if
(
this
.
updateTimer
>=
this
.
UPDATE_INTERVAL
)
{
this
.
updateTimer
=
0
;
this
.
aiController
.
update
(
dt
);
}
super
.
onUpdate
(
dt
);
}
}
\ No newline at end of file
assets/Scripts/Scenes/MainGame/AISnakeBack2.ts.meta
0 → 100644
View file @
b2d1c691
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "fc484593-2f1d-43f4-8f3a-99561dbc1481",
"files": [],
"subMetas": {},
"userData": {}
}
assets/Scripts/Scenes/MainGame/MainGame.ts
View file @
b2d1c691
...
...
@@ -122,7 +122,9 @@ export class MainGame extends Scene {
wallRight
.
setPosition
(
Global
.
MAP_WIDTH
/
2
,
0
);
wallRight
.
getComponent
(
UITransform
).
height
=
Global
.
MAP_HEIGHT
;
this
.
player
.
init
();
this
.
player
.
init
({
// initEnergy: 100
});
// 初始化食物和NPC
this
.
fondManger
.
init
(
this
.
maxFood
);
...
...
assets/Scripts/Scenes/MainGame/Props/Food.ts
View file @
b2d1c691
...
...
@@ -15,7 +15,7 @@ export class Food extends Component {
set
energy
(
energy
:
number
)
{
this
.
_energy
=
energy
;
const
scale
=
1
+
(
energy
-
1
)
/
10
;
const
scale
=
1
+
(
energy
-
1
)
/
3
;
this
.
node
.
scale
.
set
(
scale
,
scale
);
}
...
...
assets/Scripts/Scenes/MainGame/Snake.ts
View file @
b2d1c691
...
...
@@ -53,7 +53,7 @@ export class Snake extends Component {
// 蛇的状态
isLife
:
boolean
=
false
;
private
scale
:
number
=
0.2
;
speed
:
number
=
6
00
;
speed
:
number
=
3
00
;
private
energy
:
number
=
0
;
protected
tag
:
number
=
0
;
...
...
@@ -62,8 +62,6 @@ export class Snake extends Component {
private
vh
:
number
=
Global
.
visibleSize
.
height
/
2
+
100
;
private
ready
:
boolean
=
false
;
tileNode
:
Node
=
null
;
get
radius
()
{
return
this
.
scale
*
29
;
}
...
...
@@ -74,7 +72,6 @@ export class Snake extends Component {
const
{
x
=
0
,
y
=
0
,
angle
=
0
,
scale
=
0.5
,
skinName
=
"default"
,
bodyCount
=
5
,
initEnergy
=
5
,
}
=
config
;
...
...
@@ -84,7 +81,7 @@ export class Snake extends Component {
this
.
energy
=
0
;
this
.
bodyArr
=
[];
this
.
scale
=
scale
;
this
.
speed
=
this
.
speed
*
scale
;
//
this.speed = this.speed * scale;
this
.
tag
=
Snake
.
tag
++
;
// 设置头部
...
...
@@ -98,7 +95,6 @@ export class Snake extends Component {
// this.head.getComponent(UITransform).anchorX = (bw / 2) / hw;
this
.
head
.
getComponent
(
UITransform
).
anchorX
=
(
bw
/
2
)
/
hw
;
this
.
addEnergy
(
initEnergy
);
// 创建尾巴节点
const
tile
=
bodyPool
.
get
()
||
instantiate
(
this
.
bodyPrefab
);
...
...
@@ -120,6 +116,9 @@ export class Snake extends Component {
this
.
node
.
addChild
(
tile
);
this
.
bodyArr
.
push
(
tile
);
// 创建身体节点
this
.
addEnergy
(
initEnergy
);
this
.
isLife
=
true
;
this
.
ready
=
true
;
}
...
...
@@ -166,33 +165,34 @@ export class Snake extends Component {
scale
:
v3
(
0
,
0
)
})
.
call
(()
=>
{
otherCollider
.
node
.
getComponent
(
Food
).
recycle
();
if
(
!
this
.
isLife
)
return
;
if
(
foodType
==
FoodType
.
FOOD
)
{
this
.
addEnergy
(
1
);
}
const
foodTs
=
otherCollider
.
getComponent
(
Food
);
this
.
addEnergy
(
foodTs
.
energy
);
foodTs
.
recycle
();
})
.
start
();
}
}
// 能量与成长
lastRemaining
=
0
;
private
addEnergy
(
value
:
number
)
{
this
.
energy
+=
value
;
const
growthThreshold
=
Math
.
floor
(
4
*
this
.
scale
);
while
(
this
.
energy
>=
growthThreshold
)
{
value
+=
this
.
lastRemaining
;
while
(
value
>=
growthThreshold
)
{
this
.
grow
();
this
.
energy
-=
growthThreshold
;
value
-=
growthThreshold
;
if
(
this
.
scale
<
1
)
{
this
.
scale
+=
0.005
;
}
}
this
.
speed
=
600
*
this
.
scal
e
;
this
.
lastRemaining
=
valu
e
;
// this.speed = 600 * this.scale;
}
// 蛇身体生长
...
...
@@ -229,7 +229,7 @@ export class Snake extends Component {
isFast
=
false
;
private
positions
:
Vec3
[]
=
[];
// 存储历史位置点
private
readonly
HISTORY_LENGTH
=
100
;
// 增加历史点数量
private
readonly
SEGMENT_SPACING
=
4
;
// 增加节点间距
private
readonly
SEGMENT_SPACING
=
8
;
// 增加节点间距
moveTime
=
1
/
60
;
totalTime
=
0
;
...
...
@@ -262,10 +262,12 @@ export class Snake extends Component {
this
.
head
.
setPosition
(
newHeadPos
);
this
.
head
.
setScale
(
this
.
scale
,
this
.
scale
);
const
space
=
~~
(
this
.
SEGMENT_SPACING
*
this
.
scale
);
// 存储历史位置
this
.
positions
.
unshift
(
newHeadPos
.
clone
());
// 确保历史位置点足够多,以容纳所有身体节点
const
requiredLength
=
this
.
bodyArr
.
length
*
this
.
SEGMENT_SPACING
+
1
;
const
requiredLength
=
this
.
bodyArr
.
length
*
space
+
1
;
if
(
this
.
positions
.
length
>
Math
.
max
(
requiredLength
,
this
.
HISTORY_LENGTH
))
{
this
.
positions
.
pop
();
}
...
...
@@ -275,7 +277,7 @@ export class Snake extends Component {
const
body
=
this
.
bodyArr
[
i
];
// 为每个节点计算一个固定的偏移量
const
offset
=
(
i
+
1
)
*
this
.
SEGMENT_SPACING
;
const
offset
=
(
i
+
1
)
*
space
;
// 确保不会超出历史位置数组范围
if
(
offset
<
this
.
positions
.
length
)
{
...
...
@@ -332,8 +334,8 @@ export class Snake extends Component {
bodyPool
.
put
(
body
);
return
{
x
:
body
.
position
.
x
+
10
-
5
,
y
:
body
.
position
.
y
+
10
-
5
,
x
:
body
.
position
.
x
+
Math
.
random
()
*
10
-
5
,
y
:
body
.
position
.
y
+
Math
.
random
()
*
10
-
5
,
energy
:
~~
(
this
.
energy
/
len
),
};
});
...
...
index.html
0 → 100644
View file @
b2d1c691
<!DOCTYPE html>
<html
lang=
"en"
>
<head>
<meta
charset=
"UTF-8"
>
<title>
Title
</title>
</head>
<body>
<script>
const
canvas
=
document
.
createElement
(
"canvas"
);
canvas
.
width
=
500
;
canvas
.
height
=
500
;
const
ctx
=
canvas
.
getContext
(
"2d"
);
ctx
.
fillStyle
=
"transparent"
;
ctx
.
fillRect
(
0
,
0
,
500
,
500
);
const
b64
=
canvas
.
toDataURL
(
"image/png"
);
console
.
log
(
b64
);
</script>
</body>
</html>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment