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
aa7315ad
Commit
aa7315ad
authored
Nov 20, 2024
by
haiyoucuv
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
init
parent
29f47b46
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1112 additions
and
441 deletions
+1112
-441
AISnake.ts
assets/Scripts/Scenes/MainGame/AISnake.ts
+794
-127
Animal.ts
assets/Scripts/Scenes/MainGame/Animal.ts
+3
-3
FondManger.ts
assets/Scripts/Scenes/MainGame/FondManger.ts
+2
-2
Global.ts
assets/Scripts/Scenes/MainGame/Global.ts
+1
-1
MainGame.ts
assets/Scripts/Scenes/MainGame/MainGame.ts
+6
-6
Snake.ts
assets/Scripts/Scenes/MainGame/Snake.ts
+306
-302
No files found.
assets/Scripts/Scenes/MainGame/AISnake.ts
View file @
aa7315ad
import
{
_decorator
,
math
,
v3
,
Vec3
}
from
"cc"
;
import
{
_decorator
,
math
,
v3
,
Vec3
,
Node
}
from
"cc"
;
import
{
Snake
}
from
"./Snake"
;
import
{
Snake
}
from
"./Snake"
;
import
{
DirectionType
}
from
"./Enums"
;
import
{
DirectionType
}
from
"./Enums"
;
import
{
Global
}
from
"./Global"
;
import
{
Global
}
from
"./Global"
;
...
@@ -6,34 +6,49 @@ import { MainGame } from "./MainGame";
...
@@ -6,34 +6,49 @@ import { MainGame } from "./MainGame";
const
{
ccclass
,
property
}
=
_decorator
;
const
{
ccclass
,
property
}
=
_decorator
;
enum
AIState
{
HUNTING
,
// 追逐食物
INTERCEPTING
,
// 拦截玩家
ESCAPING
,
// 逃离危险
WANDERING
,
// 随机游走
ASSISTING
// 协助其他AI攻击玩家
}
@
ccclass
(
"AISnake"
)
@
ccclass
(
"AISnake"
)
export
class
AISnake
extends
Snake
{
export
class
AISnake
extends
Snake
{
// AI难度等级
@
property
({
@
property
({
range
:
[
1
,
5
],
range
:
[
1
,
5
],
tooltip
:
"AI难度(1-5)"
tooltip
:
"AI难度(1-5)"
})
})
private
difficulty
:
number
=
5
;
private
difficulty
:
number
=
5
;
private
v
=
v3
();
private
currentState
:
AIState
=
AIState
.
WANDERING
;
private
direction
:
DirectionType
=
DirectionType
.
DEFAULT
;
private
directionSpeed
:
number
=
0
;
// AI行为相关参数
private
readonly
VIEW_DISTANCE
:
number
=
300
;
// 视野范围
private
readonly
AVOID_DISTANCE
:
number
=
100
;
// 躲避距离
private
targetFood
:
Vec3
=
null
;
private
dangerSnake
:
Snake
=
null
;
private
behaviorTimer
:
number
=
0
;
private
behaviorTimer
:
number
=
0
;
private
readonly
BEHAVIOR_UPDATE_TIME
=
0.5
;
// 行为更新间隔
private
targetFood
:
Vec3
=
null
;
private
targetSnake
:
Snake
=
null
;
private
escapeTarget
:
Snake
=
null
;
private
readonly
BASE_VIEW_DISTANCE
=
300
;
private
readonly
INTERCEPT_DISTANCE
=
400
;
private
readonly
PREDICTION_TIME
=
1.0
;
private
readonly
ESCAPE_BOUNDARY
=
200
;
private
readonly
ASSIST_DISTANCE
=
500
;
// 协助攻击的最大距离
private
readonly
SAFE_MARGIN
=
2
;
// 安全边际系数
private
readonly
COLLISION_CHECK_DISTANCE
=
400
;
// 增加碰撞检测距离
private
readonly
BODY_AVOID_MARGIN
=
2.5
;
// 增加身体避让边际
private
readonly
DANGER_ANGLE_THRESHOLD
=
75
;
// 扩大危险角度范围
private
assistTarget
:
AISnake
=
null
;
// 正在协助的AI蛇
// 难度相关参数
private
get
difficultyParams
()
{
private
get
difficultyParams
()
{
return
{
return
{
reactionTime
:
math
.
lerp
(
0.8
,
0.2
,
(
this
.
difficulty
-
1
)
/
4
),
reactionTime
:
math
.
lerp
(
0.8
,
0.2
,
(
this
.
difficulty
-
1
)
/
4
),
viewDistance
:
math
.
lerp
(
200
,
400
,
(
this
.
difficulty
-
1
)
/
4
),
viewDistance
:
this
.
BASE_VIEW_DISTANCE
*
(
1
+
(
this
.
difficulty
-
1
)
*
0.2
),
decisionAccuracy
:
math
.
lerp
(
0.4
,
0.9
,
(
this
.
difficulty
-
1
)
/
4
),
interceptDistance
:
this
.
INTERCEPT_DISTANCE
*
(
1
+
(
this
.
difficulty
-
1
)
*
0.2
),
aggressiveness
:
math
.
lerp
(
0.2
,
0.8
,
(
this
.
difficulty
-
1
)
/
4
)
aggressiveness
:
math
.
lerp
(
0.3
,
0.9
,
(
this
.
difficulty
-
1
)
/
4
),
predictionAccuracy
:
math
.
lerp
(
0.5
,
0.9
,
(
this
.
difficulty
-
1
)
/
4
),
turnSpeed
:
math
.
lerp
(
2
,
5
,
(
this
.
difficulty
-
1
)
/
4
)
};
};
}
}
...
@@ -43,185 +58,837 @@ export class AISnake extends Snake {
...
@@ -43,185 +58,837 @@ export class AISnake extends Snake {
this
.
behaviorTimer
+=
dt
;
this
.
behaviorTimer
+=
dt
;
if
(
this
.
behaviorTimer
>=
this
.
difficultyParams
.
reactionTime
)
{
if
(
this
.
behaviorTimer
>=
this
.
difficultyParams
.
reactionTime
)
{
this
.
behaviorTimer
=
0
;
this
.
behaviorTimer
=
0
;
this
.
updateAIBehavior
();
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
);
}
}
}
// 预判断位置
// 判断是否在极度危险的位置(非常靠近边界)
this
.
v
=
this
.
getNewPos
(
private
isInDangerousPosition
(
position
:
Vec3
):
boolean
{
this
.
head
.
angle
,
const
dangerBuffer
=
this
.
ESCAPE_BOUNDARY
*
0.5
;
// 减小危险区域范围
dt
,
const
mapWidth
=
Global
.
MAP_WIDTH
;
this
.
head
.
getPosition
(),
const
mapHeight
=
Global
.
MAP_HEIGHT
;
this
.
speed
*
20
return
(
position
.
x
>
mapWidth
/
2
-
dangerBuffer
||
position
.
x
<
-
mapWidth
/
2
+
dangerBuffer
||
position
.
y
>
mapHeight
/
2
-
dangerBuffer
||
position
.
y
<
-
mapHeight
/
2
+
dangerBuffer
);
);
}
this
.
handleBoundaryAvoidance
();
// 判断是否是致命威胁
this
.
executeCurrentBehavior
(
dt
);
private
isLethalThreat
(
threat
:
Snake
):
boolean
{
const
myPos
=
this
.
head
.
getPosition
();
const
threatPos
=
threat
.
head
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
threatPos
);
super
.
onUpdate
(
dt
);
return
(
distance
<
this
.
BASE_VIEW_DISTANCE
*
0.5
&&
// 减小威胁判定距离
threat
.
getSnakeLen
()
>
this
.
getSnakeLen
()
*
1.5
// 只有当对方明显比自己强时才逃跑
);
}
}
private
updateAIBehavior
()
{
private
setState
(
state
:
AIState
,
target
:
any
)
{
const
params
=
this
.
difficultyParams
;
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
)
{
this
.
detectThreats
();
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
;
}
// 如果检测到危险,优先躲避
// 添加新的执行方法
if
(
this
.
dangerSnake
&&
math
.
random
()
<
params
.
decisionAccuracy
)
{
private
executeAssisting
(
)
{
this
.
avoidThreat
();
if
(
!
this
.
assistTarget
||
!
this
.
assistTarget
.
isLife
||
!
this
.
targetSnake
||
!
this
.
targetSnake
.
isLife
)
{
return
;
return
;
}
}
// 寻找最近的食物
const
playerPos
=
this
.
targetSnake
.
head
.
getPosition
();
this
.
findNearestFood
();
const
partnerPos
=
this
.
assistTarget
.
head
.
getPosition
();
// 根据难度决定是否追逐食物
// 计算包围位置:在玩家和协助目标的另一侧
if
(
this
.
targetFood
&&
math
.
random
()
<
params
.
decisionAccuracy
)
{
const
angle
=
this
.
calculateTargetAngle
(
playerPos
);
this
.
chaseFood
();
const
oppositeAngle
=
(
angle
+
180
)
%
360
;
}
else
{
this
.
randomMove
();
// 计算包围点
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
isNearBoundary
(
position
:
Vec3
):
boolean
{
const
boundaryBuffer
=
this
.
ESCAPE_BOUNDARY
;
const
mapWidth
=
Global
.
MAP_WIDTH
;
const
mapHeight
=
Global
.
MAP_HEIGHT
;
return
(
position
.
x
>
mapWidth
/
2
-
boundaryBuffer
||
position
.
x
<
-
mapWidth
/
2
+
boundaryBuffer
||
position
.
y
>
mapHeight
/
2
-
boundaryBuffer
||
position
.
y
<
-
mapHeight
/
2
+
boundaryBuffer
);
}
private
executeCurrentState
(
dt
:
number
)
{
const
threat
=
this
.
findNearbyAIToAvoid
();
if
(
threat
&&
threat
.
dangerLevel
>
30
)
{
// 降低触发避让的阈值
// 计算躲避角度
const
avoidAngle
=
this
.
calculateAvoidanceAngle
(
threat
.
snake
);
// 根据危险程度调整躲避行为
if
(
threat
.
dangerLevel
>
70
)
{
// 高危险:立即急转
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
;
// 优先处理躲避
}
}
// 根据难度决定是否加速
// 执行原有状态逻辑
if
(
this
.
shouldSpeedUp
())
{
switch
(
this
.
currentState
)
{
this
.
isFast
=
true
;
case
AIState
.
HUNTING
:
this
.
executeHunting
();
break
;
}
else
{
case
AIState
.
INTERCEPTING
:
this
.
executeIntercepting
();
break
;
this
.
isFast
=
false
;
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
calculateAvoidanceAngle
(
threat
:
Snake
):
number
{
const
myPos
=
this
.
head
.
getPosition
();
const
threatPos
=
threat
.
head
.
getPosition
();
const
baseEscapeAngle
=
this
.
calculateEscapeAngle
(
threatPos
);
// 尝试多个躲避角度
const
angles
=
[
baseEscapeAngle
,
baseEscapeAngle
+
45
,
baseEscapeAngle
-
45
,
baseEscapeAngle
+
90
,
baseEscapeAngle
-
90
];
// 选择最安全的角度
let
bestAngle
=
baseEscapeAngle
;
let
maxSafety
=
-
1
;
for
(
const
angle
of
angles
)
{
const
futurePos
=
this
.
predictFuturePosition
(
myPos
,
angle
,
this
.
speed
*
3
);
let
safety
=
Vec3
.
distance
(
futurePos
,
threatPos
);
// 检查这个角度是否会导致撞墙
if
(
this
.
willHitBoundary
(
angle
))
{
continue
;
}
// 检查与威胁物的身体的距离
for
(
const
bodyPart
of
threat
.
bodyArr
)
{
const
bodyDist
=
Vec3
.
distance
(
futurePos
,
bodyPart
.
getPosition
());
safety
=
Math
.
min
(
safety
,
bodyDist
);
}
if
(
safety
>
maxSafety
)
{
maxSafety
=
safety
;
bestAngle
=
angle
;
}
}
}
return
bestAngle
;
}
}
private
detectThreats
()
{
private
canInterceptPlayer
(
player
:
Snake
):
boolean
{
if
(
!
player
||
!
player
.
isLife
)
return
false
;
const
params
=
this
.
difficultyParams
;
const
params
=
this
.
difficultyParams
;
const
myPos
=
this
.
head
.
getPosition
();
const
myPos
=
this
.
head
.
getPosition
();
let
nearestDanger
=
null
;
const
playerPos
=
player
.
head
.
getPosition
()
;
let
minDistance
=
params
.
viewDistance
;
const
distance
=
Vec3
.
distance
(
myPos
,
playerPos
)
;
// 检测其他蛇
// 降低拦截距离和提高长度要求
const
allSnakes
=
[...
MainGame
.
ins
.
animalNode
.
children
];
return
distance
<
params
.
interceptDistance
*
0.8
&&
// 减小拦截距离
allSnakes
.
push
(
MainGame
.
ins
.
player
.
node
);
this
.
getSnakeLen
()
>
player
.
getSnakeLen
()
*
0.8
&&
// 提高长度要求
math
.
random
()
<
params
.
aggressiveness
*
0.8
;
// 降低激进程度
}
for
(
const
snakeNode
of
allSnakes
)
{
private
executeHunting
()
{
const
snake
=
snakeNode
.
getComponent
(
Snake
);
if
(
!
this
.
targetFood
)
return
;
if
(
snake
===
this
)
continue
;
const
myPos
=
this
.
head
.
getPosition
();
const
distance
=
Vec3
.
distance
(
myPos
,
this
.
targetFood
);
// 计算到食物的直接角度
const
targetAngle
=
this
.
calculateTargetAngle
(
this
.
targetFood
);
const
distance
=
Vec3
.
distance
(
myPos
,
snake
.
head
.
getPosition
());
// 检查是否需要避开自己的身体
if
(
distance
<
minDistance
&&
snake
.
getSnakeLen
()
>=
this
.
getSnakeLen
())
{
const
needAvoidBody
=
this
.
willHitOwnBody
(
targetAngle
);
minDistance
=
distance
;
nearestDanger
=
snake
;
if
(
needAvoidBody
)
{
// 寻找替代路径
const
alternativeAngle
=
this
.
findAlternativeAngleToFood
(
this
.
targetFood
);
this
.
smoothRotateToAngle
(
alternativeAngle
,
this
.
difficultyParams
.
turnSpeed
);
}
else
{
// 直接前进
this
.
smoothRotateToAngle
(
targetAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.5
);
}
// 根据距离和路径调整速度
this
.
isFast
=
distance
<
this
.
BASE_VIEW_DISTANCE
/
2
&&
!
needAvoidBody
;
}
// 检查是否会撞到自己的身体
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
;
}
}
}
}
this
.
dangerSnake
=
nearestDanger
;
return
false
;
}
}
private
findNearestFood
()
{
// 寻找到食物的替代角度
const
params
=
this
.
difficultyParams
;
private
findAlternativeAngleToFood
(
foodPos
:
Vec3
):
number
{
const
myPos
=
this
.
head
.
getPosition
();
const
myPos
=
this
.
head
.
getPosition
();
let
nearestFood
=
null
;
const
directAngle
=
this
.
calculateTargetAngle
(
foodPos
);
let
minDistance
=
params
.
viewDistance
;
// 尝试不同的角度偏移
const
offsets
=
[
30
,
-
30
,
45
,
-
45
,
60
,
-
60
];
for
(
const
offset
of
offsets
)
{
const
testAngle
=
directAngle
+
offset
;
if
(
!
this
.
willHitOwnBody
(
testAngle
))
{
// 检查这个角度是否会让我们更接近食物
const
futurePos
=
this
.
predictFuturePosition
(
myPos
,
testAngle
,
this
.
radius
*
4
);
const
currentDistance
=
Vec3
.
distance
(
myPos
,
foodPos
);
const
futureDistance
=
Vec3
.
distance
(
futurePos
,
foodPos
);
if
(
futureDistance
<
currentDistance
)
{
return
testAngle
;
}
}
}
// 获取视野范围内的食物
// 如果没找到更好的角度,返回直接角度
MainGame
.
ins
.
fondManger
.
node
.
children
.
forEach
(
food
=>
{
return
directAngle
;
const
distance
=
Vec3
.
distance
(
myPos
,
food
.
getPosition
());
}
if
(
distance
<
minDistance
)
{
minDistance
=
distance
;
private
executeIntercepting
()
{
nearestFood
=
food
.
getPosition
();
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.5
),
0.3
,
0.9
);
// 提高最小权重
const
finalAngle
=
this
.
blendAngles
(
targetAngle
,
avoidAngle
,
avoidWeight
);
// 使用更快的转向速度进行躲避
this
.
smoothRotateToAngle
(
finalAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.8
);
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
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
());
// 检查头部威胁
if
(
headDistance
<
this
.
COLLISION_CHECK_DISTANCE
)
{
const
angleToHead
=
this
.
calculateTargetAngle
(
snake
.
head
.
getPosition
());
const
headAngleDiff
=
Math
.
abs
(
this
.
head
.
angle
-
angleToHead
);
if
(
headAngleDiff
<
this
.
DANGER_ANGLE_THRESHOLD
)
{
snakeDanger
=
Math
.
max
(
snakeDanger
,
(
this
.
COLLISION_CHECK_DISTANCE
-
headDistance
)
/
this
.
COLLISION_CHECK_DISTANCE
*
100
);
}
}
// 检查身体威胁
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
);
// 根据距离和角度计算危险程度
if
(
bodyDistance
<
this
.
COLLISION_CHECK_DISTANCE
&&
angleDiff
<
this
.
DANGER_ANGLE_THRESHOLD
)
{
const
distanceDanger
=
(
this
.
COLLISION_CHECK_DISTANCE
-
bodyDistance
)
/
this
.
COLLISION_CHECK_DISTANCE
;
const
angleDanger
=
(
this
.
DANGER_ANGLE_THRESHOLD
-
angleDiff
)
/
this
.
DANGER_ANGLE_THRESHOLD
;
const
futureDanger
=
futureDist
<
bodyDistance
?
1.5
:
1
;
// 如果预测位置更近,增加危险系数
const
partDanger
=
(
distanceDanger
*
angleDanger
*
futureDanger
)
*
100
;
snakeDanger
=
Math
.
max
(
snakeDanger
,
partDanger
);
}
}
}
});
this
.
targetFood
=
nearestFood
;
// 更新最危险的蛇
if
(
snakeDanger
>
maxDanger
)
{
maxDanger
=
snakeDanger
;
mostDangerousSnake
=
snake
;
}
}
return
maxDanger
>
0
?
{
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
avoidThreat
()
{
private
executeEscaping
()
{
const
myPos
=
this
.
head
.
getPosition
();
const
myPos
=
this
.
head
.
getPosition
();
const
dangerPos
=
this
.
dangerSnake
.
head
.
getPosition
();
// 计算逃离角度
if
(
this
.
escapeTarget
)
{
const
angle
=
math
.
toDegree
(
Math
.
atan2
(
let
escapeAngle
=
this
.
calculateEscapeAngle
(
this
.
escapeTarget
.
head
.
getPosition
());
myPos
.
y
-
dangerPos
.
y
,
myPos
.
x
-
dangerPos
.
x
// 考虑身体部分的位置来调整逃跑角度
));
for
(
const
bodyPart
of
this
.
escapeTarget
.
bodyArr
)
{
const
bodyDistance
=
Vec3
.
distance
(
myPos
,
bodyPart
.
getPosition
());
if
(
bodyDistance
<
this
.
BASE_VIEW_DISTANCE
*
0.7
)
{
const
bodyEscapeAngle
=
this
.
calculateEscapeAngle
(
bodyPart
.
getPosition
());
// 综合考虑头部和身体的逃跑角度
escapeAngle
=
(
escapeAngle
+
bodyEscapeAngle
)
/
2
;
}
}
this
.
head
.
angle
=
angle
;
// 检查逃跑方向是否会导致撞墙
if
(
this
.
willHitBoundary
(
escapeAngle
))
{
escapeAngle
=
this
.
adjustEscapeAngle
(
escapeAngle
);
}
this
.
smoothRotateToAngle
(
escapeAngle
,
this
.
difficultyParams
.
turnSpeed
*
1.8
);
}
else
{
this
.
avoidBoundary
();
}
this
.
isFast
=
true
;
this
.
isFast
=
true
;
}
}
private
chaseFood
()
{
private
willHitBoundary
(
angle
:
number
):
boolean
{
const
myPos
=
this
.
head
.
getPosition
();
const
myPos
=
this
.
head
.
getPosition
();
const
radian
=
angle
*
Math
.
PI
/
180
;
const
checkDistance
=
this
.
ESCAPE_BOUNDARY
;
// 计算追逐角度
const
futureX
=
myPos
.
x
+
Math
.
cos
(
radian
)
*
checkDistance
;
const
angle
=
math
.
toDegree
(
Math
.
atan2
(
const
futureY
=
myPos
.
y
+
Math
.
sin
(
radian
)
*
checkDistance
;
this
.
targetFood
.
y
-
myPos
.
y
,
this
.
targetFood
.
x
-
myPos
.
x
));
// 平滑转向
return
this
.
isInDangerousPosition
(
v3
(
futureX
,
futureY
,
0
));
const
angleDiff
=
this
.
head
.
angle
-
angle
;
this
.
head
.
angle
+=
math
.
clamp
(
angleDiff
,
-
3
,
3
);
}
}
private
handleBoundaryAvoidance
()
{
private
adjustEscapeAngle
(
originalAngle
:
number
):
number
{
const
halfWidth
=
Global
.
MAP_WIDTH
/
2
;
const
adjustAngles
=
[
-
45
,
45
,
-
90
,
90
,
-
135
,
135
,
180
]
;
const
halfHeight
=
Global
.
MAP_HIGHT
/
2
;
const
myPos
=
this
.
head
.
getPosition
()
;
if
(
this
.
v
.
x
<=
-
halfWidth
||
this
.
v
.
x
>=
halfWidth
||
for
(
const
adjustment
of
adjustAngles
)
{
this
.
v
.
y
<=
-
halfHeight
||
this
.
v
.
y
>=
halfHeight
)
{
const
newAngle
=
(
originalAngle
+
adjustment
)
%
360
;
if
(
!
this
.
willHitBoundary
(
newAngle
))
{
return
newAngle
;
}
}
const
angleAbs
=
Math
.
abs
(
this
.
head
.
angle
%
180
);
return
originalAngle
;
// 如果没有找到更好的角度,返回原角度
this
.
direction
=
DirectionType
.
DEFAULT
;
}
// 根据难度调整转向的精确度
private
executeWandering
()
{
const
turnAngle
=
math
.
lerp
(
// 增加方向改变的概率
math
.
randomRangeInt
(
90
,
180
),
if
(
math
.
randomRangeInt
(
0
,
20
)
==
0
)
{
// 减少方向改变的频率
135
,
const
direction
=
math
.
randomRangeInt
(
0
,
3
);
this
.
difficultyParams
.
decisionAccuracy
const
speed
=
math
.
randomRangeInt
(
1
,
4
);
);
if
(
angleAbs
>
90
)
{
if
(
direction
===
DirectionType
.
LEFT
)
{
this
.
head
.
angle
+=
turnAngle
;
this
.
head
.
angle
+=
speed
;
}
else
{
}
else
if
(
direction
===
DirectionType
.
RIGHT
)
{
this
.
head
.
angle
-=
turnAngle
;
this
.
head
.
angle
-=
speed
;
}
}
// 减少速度变化的频率
this
.
isFast
=
math
.
random
()
<
this
.
difficultyParams
.
aggressiveness
*
0.1
;
}
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
randomMove
()
{
private
findNearestFood
():
Vec3
|
null
{
if
(
math
.
randomRangeInt
(
0
,
11
)
==
0
)
{
const
myPos
=
this
.
head
.
getPosition
();
this
.
direction
=
math
.
randomRangeInt
(
0
,
3
);
let
nearestFood
=
null
;
this
.
directionSpeed
=
math
.
randomRangeInt
(
0
,
6
);
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
(
this
.
direction
==
DirectionType
.
LEFT
)
{
if
(
nearestFood
)
{
this
.
head
.
angle
+=
this
.
directionSpeed
;
const
competitors
=
this
.
findCompetitorsForFood
(
nearestFood
,
minDistance
);
}
else
if
(
this
.
direction
==
DirectionType
.
RIGHT
)
{
this
.
head
.
angle
-=
this
.
directionSpeed
;
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
shouldSpeedUp
():
boolean
{
// 寻找替代食物
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
;
const
params
=
this
.
difficultyParams
;
// 在以下情况下加速:
// 基础分数(距离越近分数越高)
// 1. 追逐食物且距离适中
let
score
=
1
-
(
distance
/
params
.
viewDistance
);
// 2. 逃离危险
// 3. 随机因素(基于激进度)
// 根据竞争者调整分数
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
.
isFast
?
2
:
1
);
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
(
this
.
dangerSnake
)
return
true
;
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
;
if
(
this
.
targetFood
)
{
const
distance
=
Vec3
.
distance
(
snake
.
head
.
getPosition
(),
foodPos
);
const
distance
=
Vec3
.
distance
(
this
.
head
.
getPosition
(),
this
.
targetFood
);
// 只考虑距离相近或更近的竞争者
if
(
distance
<
this
.
VIEW_DISTANCE
/
2
)
return
true
;
if
(
distance
<=
myDistance
*
1.2
)
{
competitors
.
push
(
snake
);
}
}
}
return
math
.
random
()
<
params
.
aggressivenes
s
;
return
competitor
s
;
}
}
private
executeCurrentBehavior
(
dt
:
number
)
{
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
);
}
}
// 设置AI难度
setDifficulty
(
level
:
number
)
{
setDifficulty
(
level
:
number
)
{
this
.
difficulty
=
math
.
clamp
(
level
,
1
,
5
);
this
.
difficulty
=
math
.
clamp
(
level
,
1
,
5
);
}
}
...
...
assets/Scripts/Scenes/MainGame/Animal.ts
View file @
aa7315ad
...
@@ -365,7 +365,7 @@ export class Animal extends Component {
...
@@ -365,7 +365,7 @@ export class Animal extends Component {
);
);
const
halfWidth
=
Global
.
MAP_WIDTH
/
2
;
const
halfWidth
=
Global
.
MAP_WIDTH
/
2
;
const
halfHeight
=
Global
.
MAP_HIGHT
/
2
;
const
halfHeight
=
Global
.
MAP_H
E
IGHT
/
2
;
// 如果要超出地图边界,则转向
// 如果要超出地图边界,则转向
if
(
this
.
v
.
x
<=
-
halfWidth
||
this
.
v
.
x
>=
halfWidth
||
if
(
this
.
v
.
x
<=
-
halfWidth
||
this
.
v
.
x
>=
halfWidth
||
...
@@ -418,8 +418,8 @@ export class Animal extends Component {
...
@@ -418,8 +418,8 @@ export class Animal extends Component {
Global
.
MAP_WIDTH
/
2
-
50
Global
.
MAP_WIDTH
/
2
-
50
);
);
const
y
=
math
.
randomRangeInt
(
const
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_HIGHT
/
2
-
50
),
-
(
Global
.
MAP_H
E
IGHT
/
2
-
50
),
Global
.
MAP_HIGHT
/
2
-
50
Global
.
MAP_H
E
IGHT
/
2
-
50
);
);
this
.
reInit
(
x
,
y
);
this
.
reInit
(
x
,
y
);
});
});
...
...
assets/Scripts/Scenes/MainGame/FondManger.ts
View file @
aa7315ad
...
@@ -70,7 +70,7 @@ export class FondManger extends Component {
...
@@ -70,7 +70,7 @@ export class FondManger extends Component {
}
}
if
(
!
y
)
{
if
(
!
y
)
{
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_H
IGHT
/
2
-
50
),
Global
.
MAP_H
IGHT
/
2
-
50
);
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_H
EIGHT
/
2
-
50
),
Global
.
MAP_HE
IGHT
/
2
-
50
);
}
}
// 从对象池获取食物节点
// 从对象池获取食物节点
...
@@ -101,7 +101,7 @@ export class FondManger extends Component {
...
@@ -101,7 +101,7 @@ export class FondManger extends Component {
initItem
=
(
_
:
number
)
=>
{
initItem
=
(
_
:
number
)
=>
{
// 随机生成位置
// 随机生成位置
const
x
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_WIDTH
/
2
-
50
),
Global
.
MAP_WIDTH
/
2
-
50
);
const
x
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_WIDTH
/
2
-
50
),
Global
.
MAP_WIDTH
/
2
-
50
);
const
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_H
IGHT
/
2
-
50
),
Global
.
MAP_H
IGHT
/
2
-
50
);
const
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_H
EIGHT
/
2
-
50
),
Global
.
MAP_HE
IGHT
/
2
-
50
);
this
.
addFood
(
x
,
y
);
this
.
addFood
(
x
,
y
);
};
};
...
...
assets/Scripts/Scenes/MainGame/Global.ts
View file @
aa7315ad
...
@@ -22,7 +22,7 @@ export class Global {
...
@@ -22,7 +22,7 @@ export class Global {
public
static
MAP_WIDTH
:
number
=
5000
;
public
static
MAP_WIDTH
:
number
=
5000
;
/** 地图高度 */
/** 地图高度 */
public
static
MAP_HIGHT
:
number
=
4000
;
public
static
MAP_H
E
IGHT
:
number
=
4000
;
/** 当前皮肤ID */
/** 当前皮肤ID */
public
static
skinId
:
number
=
0
;
public
static
skinId
:
number
=
0
;
...
...
assets/Scripts/Scenes/MainGame/MainGame.ts
View file @
aa7315ad
...
@@ -95,24 +95,24 @@ export class MainGame extends Scene {
...
@@ -95,24 +95,24 @@ export class MainGame extends Scene {
// EPhysics2DDrawFlags.Shape;
// EPhysics2DDrawFlags.Shape;
Global
.
MAP_WIDTH
=
this
.
uiBg
.
contentSize
.
x
;
Global
.
MAP_WIDTH
=
this
.
uiBg
.
contentSize
.
x
;
Global
.
MAP_HIGHT
=
this
.
uiBg
.
contentSize
.
y
;
Global
.
MAP_H
E
IGHT
=
this
.
uiBg
.
contentSize
.
y
;
// 初始化墙壁
// 初始化墙壁
const
wallTop
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_TOP"
);
const
wallTop
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_TOP"
);
wallTop
.
setPosition
(
0
,
Global
.
MAP_HIGHT
/
2
);
wallTop
.
setPosition
(
0
,
Global
.
MAP_H
E
IGHT
/
2
);
wallTop
.
getComponent
(
UITransform
).
width
=
Global
.
MAP_WIDTH
;
wallTop
.
getComponent
(
UITransform
).
width
=
Global
.
MAP_WIDTH
;
const
wallBottom
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_BOTTOM"
);
const
wallBottom
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_BOTTOM"
);
wallBottom
.
setPosition
(
0
,
-
Global
.
MAP_HIGHT
/
2
);
wallBottom
.
setPosition
(
0
,
-
Global
.
MAP_H
E
IGHT
/
2
);
wallBottom
.
getComponent
(
UITransform
).
width
=
Global
.
MAP_WIDTH
;
wallBottom
.
getComponent
(
UITransform
).
width
=
Global
.
MAP_WIDTH
;
const
wallLeft
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_LEFT"
);
const
wallLeft
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_LEFT"
);
wallLeft
.
setPosition
(
-
Global
.
MAP_WIDTH
/
2
,
0
);
wallLeft
.
setPosition
(
-
Global
.
MAP_WIDTH
/
2
,
0
);
wallLeft
.
getComponent
(
UITransform
).
height
=
Global
.
MAP_HIGHT
;
wallLeft
.
getComponent
(
UITransform
).
height
=
Global
.
MAP_H
E
IGHT
;
const
wallRight
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_RIGHT"
);
const
wallRight
=
this
.
uiBg
.
node
.
getChildByName
(
"WALL_RIGHT"
);
wallRight
.
setPosition
(
Global
.
MAP_WIDTH
/
2
,
0
);
wallRight
.
setPosition
(
Global
.
MAP_WIDTH
/
2
,
0
);
wallRight
.
getComponent
(
UITransform
).
height
=
Global
.
MAP_HIGHT
;
wallRight
.
getComponent
(
UITransform
).
height
=
Global
.
MAP_H
E
IGHT
;
this
.
player
.
init
();
this
.
player
.
init
();
...
@@ -190,7 +190,7 @@ export class MainGame extends Scene {
...
@@ -190,7 +190,7 @@ export class MainGame extends Scene {
initItem
=
(
index
:
number
)
=>
{
initItem
=
(
index
:
number
)
=>
{
const
node
=
PoolManager
.
instance
.
getNode
(
this
.
animalPrefab
);
const
node
=
PoolManager
.
instance
.
getNode
(
this
.
animalPrefab
);
const
x
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_WIDTH
/
2
-
50
),
Global
.
MAP_WIDTH
/
2
-
50
);
const
x
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_WIDTH
/
2
-
50
),
Global
.
MAP_WIDTH
/
2
-
50
);
const
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_H
IGHT
/
2
-
50
),
Global
.
MAP_H
IGHT
/
2
-
50
);
const
y
=
math
.
randomRangeInt
(
-
(
Global
.
MAP_H
EIGHT
/
2
-
50
),
Global
.
MAP_HE
IGHT
/
2
-
50
);
node
.
getComponent
(
AISnake
)?.
init
({
node
.
getComponent
(
AISnake
)?.
init
({
x
,
y
,
x
,
y
,
...
...
assets/Scripts/Scenes/MainGame/Snake.ts
View file @
aa7315ad
import
{
import
{
_decorator
,
_decorator
,
Collider2D
,
Collider2D
,
Component
,
Component
,
Contact2DType
,
Contact2DType
,
math
,
math
,
Node
,
Node
,
PhysicsGroup
,
PhysicsGroup
,
Prefab
,
Prefab
,
Sprite
,
Sprite
,
SpriteFrame
,
SpriteFrame
,
tween
,
tween
,
v2
,
v2
,
v3
,
v3
,
Vec3
,
Vec3
,
}
from
"cc"
;
}
from
"cc"
;
import
{
FoodType
}
from
"./Enums"
;
import
{
FoodType
}
from
"./Enums"
;
import
{
Global
}
from
"./Global"
;
import
{
Global
}
from
"./Global"
;
...
@@ -26,337 +26,341 @@ import { BuffType } from "./Buff/BuffType";
...
@@ -26,337 +26,341 @@ import { BuffType } from "./Buff/BuffType";
const
{
ccclass
,
property
}
=
_decorator
;
const
{
ccclass
,
property
}
=
_decorator
;
export
interface
IInitConfig
{
export
interface
IInitConfig
{
x
?:
number
;
x
?:
number
;
y
?:
number
;
y
?:
number
;
angle
?:
number
;
angle
?:
number
;
skinName
?:
string
;
skinName
?:
string
;
scale
?:
number
;
scale
?:
number
;
bodyCount
?:
number
;
bodyCount
?:
number
;
}
}
@
ccclass
(
"Snake"
)
@
ccclass
(
"Snake"
)
export
class
Snake
extends
Component
{
export
class
Snake
extends
Component
{
static
tag
:
number
=
0
;
static
tag
:
number
=
0
;
// 属性装饰器
// 属性装饰器
@
property
({
type
:
Node
,
displayName
:
"头部"
})
head
:
Node
=
null
;
@
property
({
type
:
Node
,
displayName
:
"头部"
})
head
:
Node
=
null
;
@
property
(
Prefab
)
bodyPrefab
:
Prefab
=
null
;
@
property
(
Prefab
)
bodyPrefab
:
Prefab
=
null
;
// 私有成员变量
// 私有成员变量
private
bodyArr
:
Node
[]
=
[];
bodyArr
:
Node
[]
=
[];
private
imgHead
:
SpriteFrame
=
null
;
private
imgHead
:
SpriteFrame
=
null
;
private
imgBody1
:
SpriteFrame
=
null
;
private
imgBody1
:
SpriteFrame
=
null
;
private
imgBody2
:
SpriteFrame
=
null
;
private
imgBody2
:
SpriteFrame
=
null
;
// 蛇的状态
// 蛇的状态
protected
isLife
:
boolean
=
false
;
isLife
:
boolean
=
false
;
private
scale
:
number
=
0.2
;
private
scale
:
number
=
0.2
;
protected
speed
:
number
=
600
;
speed
:
number
=
600
;
private
energy
:
number
=
0
;
private
energy
:
number
=
0
;
private
tag
:
number
=
0
;
private
tag
:
number
=
0
;
// 位置相关
// 位置相关
private
vw
:
number
=
Global
.
visibleSize
.
width
/
2
+
100
;
private
vw
:
number
=
Global
.
visibleSize
.
width
/
2
+
100
;
private
vh
:
number
=
Global
.
visibleSize
.
height
/
2
+
100
;
private
vh
:
number
=
Global
.
visibleSize
.
height
/
2
+
100
;
private
ready
:
boolean
=
false
;
private
ready
:
boolean
=
false
;
// 初始化方法
get
radius
()
{
public
async
init
(
config
:
IInitConfig
=
{})
{
return
this
.
scale
*
50
;
}
const
{
x
=
0
,
y
=
0
,
angle
=
0
,
scale
=
0.2
,
// 初始化方法
skinName
=
"default"
,
bodyCount
=
5
,
public
async
init
(
config
:
IInitConfig
=
{})
{
}
=
config
;
const
{
await
this
.
setSkin
(
skinName
);
x
=
0
,
y
=
0
,
angle
=
0
,
scale
=
0.2
,
skinName
=
"default"
,
bodyCount
=
5
,
this
.
ready
=
false
;
}
=
config
;
this
.
energy
=
0
;
this
.
bodyArr
=
[]
;
await
this
.
setSkin
(
skinName
)
;
this
.
scale
=
scale
;
this
.
speed
=
this
.
speed
*
scal
e
;
this
.
ready
=
fals
e
;
this
.
tag
=
Snake
.
tag
++
;
this
.
energy
=
0
;
this
.
bodyArr
=
[];
// 设置头部
this
.
scale
=
scale
;
this
.
head
.
angle
=
ang
le
;
this
.
speed
=
this
.
speed
*
sca
le
;
this
.
head
.
setPosition
(
x
,
y
)
;
this
.
tag
=
Snake
.
tag
++
;
this
.
head
.
setScale
(
scale
,
scale
);
this
.
head
.
getComponent
(
Sprite
).
spriteFrame
=
this
.
imgHead
;
// 设置头部
this
.
head
.
angle
=
angle
;
// 创建身体节点
this
.
head
.
setPosition
(
x
,
y
);
for
(
let
i
=
0
;
i
<
bodyCount
;
i
++
)
{
this
.
head
.
setScale
(
scale
,
scale
);
const
body
=
PoolManager
.
instance
.
getNode
(
this
.
bodyPrefab
,
this
.
node
)
;
this
.
head
.
getComponent
(
Sprite
).
spriteFrame
=
this
.
imgHead
;
const
collider
=
body
.
getComponent
(
Collider2D
);
collider
.
tag
=
this
.
tag
;
// 创建身体节点
for
(
let
i
=
0
;
i
<
bodyCount
;
i
++
)
{
body
.
angle
=
angle
;
const
body
=
PoolManager
.
instance
.
getNode
(
this
.
bodyPrefab
,
this
.
node
)
;
body
.
setPosition
(
-
99999
,
-
99999
);
const
collider
=
body
.
getComponent
(
Collider2D
);
body
.
setScale
(
scale
,
scale
)
;
collider
.
tag
=
this
.
tag
;
// 设置身体部分的贴图
body
.
angle
=
angle
;
body
.
getComponent
(
Sprite
).
spriteFrame
=
i
%
2
==
0
?
this
.
imgBody1
:
this
.
imgBody2
;
body
.
setPosition
(
-
99999
,
-
99999
)
;
body
.
setScale
(
scale
,
scale
);
body
.
active
=
false
;
this
.
bodyArr
.
push
(
body
);
// 设置身体部分的贴图
}
body
.
getComponent
(
Sprite
).
spriteFrame
=
i
%
2
==
0
?
this
.
imgBody1
:
this
.
imgBody2
;
this
.
isLife
=
tru
e
;
body
.
active
=
fals
e
;
this
.
ready
=
true
;
this
.
bodyArr
.
push
(
body
)
;
}
}
async
setSkin
(
skinName
:
string
)
{
this
.
isLife
=
true
;
const
skin
=
await
loadSkin
(
skinName
);
this
.
ready
=
true
;
this
.
imgHead
=
skin
.
getSpriteFrame
(
"head"
);
}
this
.
imgBody1
=
skin
.
getSpriteFrame
(
"body1"
);
this
.
imgBody2
=
skin
.
getSpriteFrame
(
"body2"
);
async
setSkin
(
skinName
:
string
)
{
const
skin
=
await
loadSkin
(
skinName
);
this
.
imgHead
=
skin
.
getSpriteFrame
(
"head"
);
this
.
imgBody1
=
skin
.
getSpriteFrame
(
"body1"
);
this
.
imgBody2
=
skin
.
getSpriteFrame
(
"body2"
);
}
onEnable
()
{
const
collider
=
this
.
head
.
getComponent
(
Collider2D
);
collider
.
on
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginHead
,
this
);
const
eye
=
this
.
head
.
getChildByName
(
"范围"
).
getComponent
(
Collider2D
);
eye
.
on
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginEye
,
this
);
}
onDisable
()
{
const
collider
=
this
.
head
.
getComponent
(
Collider2D
);
collider
.
off
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginHead
,
this
);
const
eye
=
this
.
head
.
getChildByName
(
"范围"
).
getComponent
(
Collider2D
);
eye
.
off
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginEye
,
this
);
}
// 碰撞检测
private
onBeginHead
(
selfCollider
:
Collider2D
,
otherCollider
:
Collider2D
)
{
if
(
otherCollider
.
group
===
PhysicsGroup
[
"Body"
]
&&
otherCollider
.
tag
!=
this
.
tag
)
{
this
.
death
();
}
}
}
onEnable
()
{
const
collider
=
this
.
head
.
getComponent
(
Collider2D
);
private
onBeginEye
(
selfCollider
:
Collider2D
,
otherCollider
:
Collider2D
)
{
collider
.
on
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginHead
,
this
);
if
(
otherCollider
.
group
===
PhysicsGroup
[
"Prop"
])
{
const
foodType
=
otherCollider
.
tag
;
const
eye
=
this
.
head
.
getChildByName
(
"范围"
).
getComponent
(
Collider2D
);
eye
.
on
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginEye
,
this
);
// 食物吃掉的动画
tween
(
otherCollider
.
node
)
.
to
(
0.3
,
{
position
:
this
.
head
.
getPosition
(),
scale
:
v3
(
0
,
0
)
})
.
call
(()
=>
{
PoolManager
.
instance
.
putNode
(
otherCollider
.
node
);
if
(
foodType
==
FoodType
.
FOOD
)
{
this
.
addEnergy
(
1
);
}
})
.
start
();
}
}
}
onDisable
()
{
// 能量与成长
const
collider
=
this
.
head
.
getComponent
(
Collider2D
);
private
addEnergy
(
value
:
number
)
{
collider
.
off
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginHead
,
this
);
this
.
energy
+=
value
;
const
growthThreshold
=
Math
.
floor
(
10
*
this
.
scale
);
const
eye
=
this
.
head
.
getChildByName
(
"范围"
).
getComponent
(
Collider2D
);
if
(
this
.
energy
>=
growthThreshold
)
{
eye
.
off
(
Contact2DType
.
BEGIN_CONTACT
,
this
.
onBeginEye
,
this
);
this
.
grow
(
);
}
this
.
energy
-=
growthThreshold
;
// 碰撞检测
if
(
this
.
scale
<
0.8
)
{
private
onBeginHead
(
selfCollider
:
Collider2D
,
otherCollider
:
Collider2D
)
{
this
.
scale
+=
0.005
;
if
(
otherCollider
.
group
===
PhysicsGroup
[
"Body"
]
&&
otherCollider
.
tag
!=
this
.
tag
)
{
}
this
.
death
();
this
.
speed
=
600
*
this
.
scale
;
}
}
private
onBeginEye
(
selfCollider
:
Collider2D
,
otherCollider
:
Collider2D
)
{
if
(
otherCollider
.
group
===
PhysicsGroup
[
"Prop"
])
{
const
foodType
=
otherCollider
.
tag
;
// 食物吃掉的动画
tween
(
otherCollider
.
node
)
.
to
(
0.3
,
{
position
:
this
.
head
.
getPosition
(),
scale
:
v3
(
0
,
0
)
})
.
call
(()
=>
{
PoolManager
.
instance
.
putNode
(
otherCollider
.
node
);
if
(
foodType
==
FoodType
.
FOOD
)
{
this
.
addEnergy
(
1
);
}
})
.
start
();
}
}
}
}
// 能量与成长
// 蛇身体生长
private
addEnergy
(
value
:
number
)
{
private
grow
()
{
this
.
energy
+=
value
;
const
len
=
this
.
bodyArr
.
length
;
const
growthThreshold
=
Math
.
floor
(
10
*
this
.
scale
);
const
newBody
=
PoolManager
.
instance
.
getNode
(
this
.
bodyPrefab
,
this
.
node
);
newBody
.
angle
=
this
.
bodyArr
[
len
-
1
].
angle
;
newBody
.
setPosition
(
this
.
bodyArr
[
len
-
1
].
getPosition
());
newBody
.
setScale
(
this
.
scale
,
this
.
scale
);
if
(
this
.
energy
>=
growthThreshold
)
{
newBody
.
getComponent
(
Sprite
).
spriteFrame
=
len
%
2
==
0
?
this
.
imgBody1
:
this
.
imgBody2
;
this
.
grow
();
newBody
.
getComponent
(
Collider2D
).
tag
=
this
.
tag
;
this
.
energy
-=
growthThreshold
;
if
(
this
.
scale
<
0.8
)
{
newBody
.
active
=
isIntersect
(
this
.
scale
+=
0.005
;
newBody
.
getPosition
(),
}
this
.
head
.
getPosition
(),
this
.
speed
=
600
*
this
.
scale
;
this
.
vw
,
}
this
.
vh
}
);
// 蛇身体生长
this
.
bodyArr
.
splice
(
len
,
0
,
newBody
);
private
grow
()
{
}
const
len
=
this
.
bodyArr
.
length
;
const
newBody
=
PoolManager
.
instance
.
getNode
(
this
.
bodyPrefab
,
this
.
node
);
newBody
.
angle
=
this
.
bodyArr
[
len
-
1
].
angle
;
newBody
.
setPosition
(
this
.
bodyArr
[
len
-
1
].
getPosition
());
newBody
.
setScale
(
this
.
scale
,
this
.
scale
);
newBody
.
getComponent
(
Sprite
).
spriteFrame
=
len
%
2
==
0
?
this
.
imgBody1
:
this
.
imgBody2
;
newBody
.
getComponent
(
Collider2D
).
tag
=
this
.
tag
;
newBody
.
active
=
isIntersect
(
newBody
.
getPosition
(),
this
.
head
.
getPosition
(),
this
.
vw
,
this
.
vh
);
this
.
bodyArr
.
splice
(
len
,
0
,
newBody
);
setAngle
(
angle
:
number
)
{
}
this
.
isLife
&&
(
this
.
head
.
angle
=
angle
);
}
setAngle
(
angle
:
number
)
{
isFast
=
false
;
this
.
isLife
&&
(
this
.
head
.
angle
=
angle
);
private
positions
:
Vec3
[]
=
[];
// 存储历史位置点
}
private
readonly
HISTORY_LENGTH
=
100
;
// 增加历史点数量
private
readonly
SEGMENT_SPACING
=
5
;
// 增加节点间距
isFast
=
false
;
moveTime
=
1
/
60
;
private
positions
:
Vec3
[]
=
[];
// 存储历史位置点
totalTime
=
0
;
private
readonly
HISTORY_LENGTH
=
100
;
// 增加历史点数量
private
readonly
SEGMENT_SPACING
=
5
;
// 增加节点间距
moveTime
=
1
/
60
;
onUpdate
(
dt
:
number
)
{
totalTime
=
0
;
onUpdate
(
dt
:
number
)
{
this
.
buffManager
.
update
(
dt
);
this
.
buffManager
.
update
(
dt
);
let
speedScale
=
1
;
if
(
this
.
isFast
)
{
let
speedScale
=
1
;
speedScale
+=
1
;
if
(
this
.
isFast
)
{
speedScale
+=
1
;
}
this
.
totalTime
+=
dt
*
speedScale
;
while
(
this
.
totalTime
>=
this
.
moveTime
)
{
this
.
totalTime
-=
this
.
moveTime
;
this
.
move
(
this
.
moveTime
);
}
}
}
protected
move
(
dt
:
number
)
{
this
.
totalTime
+=
dt
*
speedScale
;
if
(
!
this
.
ready
||
!
this
.
isLife
)
{
while
(
this
.
totalTime
>=
this
.
moveTime
)
{
return
;
this
.
totalTime
-=
this
.
moveTime
;
}
this
.
move
(
this
.
moveTime
);
}
// 更新头部位置
}
const
newHeadPos
=
this
.
getNewPos
(
this
.
head
.
angle
,
dt
,
this
.
head
.
getPosition
()
);
this
.
head
.
setPosition
(
newHeadPos
);
this
.
head
.
setScale
(
this
.
scale
,
this
.
scale
);
// 存储历史位置
this
.
positions
.
unshift
(
newHeadPos
.
clone
());
// 确保历史位置点足够多,以容纳所有身体节点
const
requiredLength
=
this
.
bodyArr
.
length
*
this
.
SEGMENT_SPACING
+
1
;
if
(
this
.
positions
.
length
>
Math
.
max
(
requiredLength
,
this
.
HISTORY_LENGTH
))
{
this
.
positions
.
pop
();
}
// 更新身体节点位置
for
(
let
i
=
0
;
i
<
this
.
bodyArr
.
length
;
i
++
)
{
const
body
=
this
.
bodyArr
[
i
];
// 为每个节点计算一个固定的偏移量
const
offset
=
(
i
+
1
)
*
this
.
SEGMENT_SPACING
;
// 确保不会超出历史位置数组范围
if
(
offset
<
this
.
positions
.
length
)
{
const
targetPos
=
this
.
positions
[
offset
];
// 计算角度
if
(
offset
>
0
)
{
const
prevPos
=
this
.
positions
[
offset
-
1
];
body
.
angle
=
Math
.
atan2
(
targetPos
.
y
-
prevPos
.
y
,
targetPos
.
x
-
prevPos
.
x
)
*
180
/
Math
.
PI
;
}
body
.
setPosition
(
targetPos
);
body
.
setScale
(
this
.
scale
,
this
.
scale
);
body
.
setSiblingIndex
(
this
.
bodyArr
.
length
-
i
);
body
.
active
=
isIntersect
(
targetPos
,
this
.
head
.
getPosition
(),
this
.
vw
,
this
.
vh
);
}
}
// 边界检查
const
mapHalfWidth
=
Global
.
MAP_WIDTH
/
2
;
const
mapHalfHeight
=
Global
.
MAP_HIGHT
/
2
;
if
(
newHeadPos
.
x
<=
-
mapHalfWidth
||
newHeadPos
.
x
>=
mapHalfWidth
||
newHeadPos
.
y
<=
-
mapHalfHeight
||
newHeadPos
.
y
>=
mapHalfHeight
)
{
this
.
death
();
}
protected
move
(
dt
:
number
)
{
if
(
!
this
.
ready
||
!
this
.
isLife
)
{
return
;
}
}
getSnakeLen
()
{
// 更新头部位置
return
this
.
bodyArr
.
length
;
const
newHeadPos
=
this
.
getNewPos
(
this
.
head
.
angle
,
dt
,
this
.
head
.
getPosition
()
);
this
.
head
.
setPosition
(
newHeadPos
);
this
.
head
.
setScale
(
this
.
scale
,
this
.
scale
);
// 存储历史位置
this
.
positions
.
unshift
(
newHeadPos
.
clone
());
// 确保历史位置点足够多,以容纳所有身体节点
const
requiredLength
=
this
.
bodyArr
.
length
*
this
.
SEGMENT_SPACING
+
1
;
if
(
this
.
positions
.
length
>
Math
.
max
(
requiredLength
,
this
.
HISTORY_LENGTH
))
{
this
.
positions
.
pop
();
}
}
//
死亡处理
//
更新身体节点位置
public
death
(
)
{
for
(
let
i
=
0
;
i
<
this
.
bodyArr
.
length
;
i
++
)
{
if
(
!
this
.
isLife
)
return
;
const
body
=
this
.
bodyArr
[
i
]
;
this
.
isLife
=
false
;
// 为每个节点计算一个固定的偏移量
this
.
node
.
active
=
false
;
const
offset
=
(
i
+
1
)
*
this
.
SEGMENT_SPACING
;
this
.
initFond
(
this
.
bodyArr
.
length
);
// 确保不会超出历史位置数组范围
}
if
(
offset
<
this
.
positions
.
length
)
{
const
targetPos
=
this
.
positions
[
offset
];
protected
getNewPos
(
angle
:
number
,
dt
:
number
,
currentPos
:
Vec3
,
speed
:
number
=
this
.
speed
):
Vec3
{
// 计算角度
const
radian
=
angle
/
180
*
Math
.
PI
;
if
(
offset
>
0
)
{
const
direction
=
v2
(
Math
.
cos
(
radian
),
Math
.
sin
(
radian
));
const
prevPos
=
this
.
positions
[
offset
-
1
];
body
.
angle
=
Math
.
atan2
(
targetPos
.
y
-
prevPos
.
y
,
targetPos
.
x
-
prevPos
.
x
)
*
180
/
Math
.
PI
;
}
return
v3
(
currentPos
.
x
+
dt
*
direction
.
x
*
speed
,
currentPos
.
y
+
dt
*
direction
.
y
*
speed
,
0
);
body
.
setPosition
(
targetPos
);
}
body
.
setScale
(
this
.
scale
,
this
.
scale
);
body
.
setSiblingIndex
(
this
.
bodyArr
.
length
-
i
);
/**
body
.
active
=
isIntersect
(
* 初始化食物
targetPos
,
*/
this
.
head
.
getPosition
(),
initItem
=
(
index
:
number
)
=>
{
this
.
vw
,
const
bp
=
this
.
bodyArr
[
index
].
getPosition
();
this
.
vh
MainGame
.
ins
.
fondManger
.
addFood
(
math
.
randomRangeInt
(
bp
.
x
-
10
,
bp
.
x
+
11
),
math
.
randomRangeInt
(
bp
.
y
-
20
,
bp
.
y
+
21
)
);
);
this
.
bodyArr
[
index
].
setPosition
(
9999
,
9999
);
}
this
.
bodyArr
[
index
].
active
=
false
;
};
/**
* 初始化食物
*/
async
initFond
(
count
:
number
)
{
await
executePreFrame
(
getItemGenerator
(
count
,
this
.
initItem
),
1
,
this
);
this
.
ready
=
true
;
}
}
// 边界检查
private
buffManager
:
BuffManager
=
new
BuffManager
(
this
)
;
const
mapHalfWidth
=
Global
.
MAP_WIDTH
/
2
;
private
isInvincible
:
boolean
=
false
;
const
mapHalfHeight
=
Global
.
MAP_HEIGHT
/
2
;
private
speedMultiplier
:
number
=
1
;
if
(
newHeadPos
.
x
<=
-
mapHalfWidth
||
newHeadPos
.
x
>=
mapHalfWidth
||
onLoad
()
{
newHeadPos
.
y
<=
-
mapHalfHeight
||
newHeadPos
.
y
>=
mapHalfHeight
// ... 其他初始化代码 ...
)
{
this
.
buffManager
=
new
BuffManager
(
this
);
this
.
death
(
);
}
}
// 添加Buff的便捷方法
}
addBuff
(
type
:
BuffType
,
duration
?:
number
,
value
?:
number
)
{
const
buff
=
this
.
buffManager
.
createBuff
(
type
,
duration
,
value
);
getSnakeLen
()
{
if
(
buff
)
{
return
this
.
bodyArr
.
length
;
this
.
buffManager
.
addBuff
(
buff
);
}
}
// 死亡处理
public
death
()
{
if
(
!
this
.
isLife
)
return
;
this
.
isLife
=
false
;
this
.
node
.
active
=
false
;
this
.
initFond
(
this
.
bodyArr
.
length
);
}
protected
getNewPos
(
angle
:
number
,
dt
:
number
,
currentPos
:
Vec3
,
speed
:
number
=
this
.
speed
):
Vec3
{
const
radian
=
angle
/
180
*
Math
.
PI
;
const
direction
=
v2
(
Math
.
cos
(
radian
),
Math
.
sin
(
radian
));
return
v3
(
currentPos
.
x
+
dt
*
direction
.
x
*
speed
,
currentPos
.
y
+
dt
*
direction
.
y
*
speed
,
0
);
}
/**
* 初始化食物
*/
initItem
=
(
index
:
number
)
=>
{
const
bp
=
this
.
bodyArr
[
index
].
getPosition
();
MainGame
.
ins
.
fondManger
.
addFood
(
math
.
randomRangeInt
(
bp
.
x
-
10
,
bp
.
x
+
11
),
math
.
randomRangeInt
(
bp
.
y
-
20
,
bp
.
y
+
21
)
);
this
.
bodyArr
[
index
].
setPosition
(
9999
,
9999
);
this
.
bodyArr
[
index
].
active
=
false
;
};
/**
* 初始化食物
*/
async
initFond
(
count
:
number
)
{
await
executePreFrame
(
getItemGenerator
(
count
,
this
.
initItem
),
1
,
this
);
this
.
ready
=
true
;
}
private
buffManager
:
BuffManager
=
new
BuffManager
(
this
);
private
isInvincible
:
boolean
=
false
;
private
speedMultiplier
:
number
=
1
;
onLoad
()
{
// ... 其他初始化代码 ...
this
.
buffManager
=
new
BuffManager
(
this
);
}
// 添加Buff的便捷方法
addBuff
(
type
:
BuffType
,
duration
?:
number
,
value
?:
number
)
{
const
buff
=
this
.
buffManager
.
createBuff
(
type
,
duration
,
value
);
if
(
buff
)
{
this
.
buffManager
.
addBuff
(
buff
);
}
}
}
// 提供给Buff使用的方法
// 提供给Buff使用的方法
setInvincible
(
value
:
boolean
)
{
setInvincible
(
value
:
boolean
)
{
this
.
isInvincible
=
value
;
this
.
isInvincible
=
value
;
}
}
setSpeedMultiplier
(
value
:
number
)
{
setSpeedMultiplier
(
value
:
number
)
{
this
.
speedMultiplier
=
value
;
this
.
speedMultiplier
=
value
;
}
}
}
}
\ 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