Commit aa7315ad authored by haiyoucuv's avatar haiyoucuv

init

parent 29f47b46
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);
this.v = this.getNewPos( super.onUpdate(dt);
this.head.angle, }
dt,
this.head.getPosition(),
this.speed * 20
);
this.handleBoundaryAvoidance(); private updateAIState() {
this.executeCurrentBehavior(dt); const myPos = this.head.getPosition();
const player = MainGame.ins.player;
super.onUpdate(dt); // 只有在非常危险时才逃跑
if (this.isInDangerousPosition(myPos)) {
this.setState(AIState.ESCAPING, null);
return;
} }
private updateAIBehavior() { // 检查是否有致命威胁需要逃跑
const params = this.difficultyParams; const threat = this.findNearestThreat();
if (threat && this.isLethalThreat(threat)) {
this.setState(AIState.ESCAPING, threat);
return;
}
// 检测危险 // 优先检查是否可以协助其他AI攻击玩家
this.detectThreats(); const assistPartner = this.findAssistPartner(player);
if (assistPartner) {
this.setState(AIState.ASSISTING, assistPartner);
return;
}
// 如果检测到危险,优先躲避 // 检查是否可以拦截玩家
if (this.dangerSnake && math.random() < params.decisionAccuracy) { if (this.canInterceptPlayer(player)) {
this.avoidThreat(); this.setState(AIState.INTERCEPTING, player);
return; return;
} }
// 寻找最近的食物 // 寻找食物
this.findNearestFood(); const nearestFood = this.findNearestFood();
if (nearestFood) {
this.setState(AIState.HUNTING, nearestFood);
return;
}
// 根据难度决定是否追逐食物 // 默认追击玩家
if (this.targetFood && math.random() < params.decisionAccuracy) { if (player && player.isLife &&
this.chaseFood(); 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 { } else {
this.randomMove(); this.setState(AIState.WANDERING, null);
}
}
// 判断是否在极度危险的位置(非常靠近边界)
private isInDangerousPosition(position: Vec3): boolean {
const dangerBuffer = this.ESCAPE_BOUNDARY * 0.5; // 减小危险区域范围
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);
// 根据难度决定是否加速
if (this.shouldSpeedUp()) {
this.isFast = true; 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 { } else {
this.isFast = false; // 中等危险:渐进式躲避
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; // 优先处理躲避
} }
private detectThreats() { // 执行原有状态逻辑
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 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 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 distance = Vec3.distance(myPos, snake.head.getPosition()); const myPos = this.head.getPosition();
if (distance < minDistance && snake.getSnakeLen() >= this.getSnakeLen()) { const distance = Vec3.distance(myPos, this.targetFood);
minDistance = distance;
nearestDanger = snake; // 计算到食物的直接角度
const targetAngle = this.calculateTargetAngle(this.targetFood);
// 检查是否需要避开自己的身体
const needAvoidBody = this.willHitOwnBody(targetAngle);
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;
} }
this.dangerSnake = nearestDanger; // 检查是否会撞到自己的身体
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;
}
} }
private findNearestFood() { return false;
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;
// 获取视野范围内的食物 // 尝试不同的角度偏移
MainGame.ins.fondManger.node.children.forEach(food => { const offsets = [30, -30, 45, -45, 60, -60];
const distance = Vec3.distance(myPos, food.getPosition());
if (distance < minDistance) { for (const offset of offsets) {
minDistance = distance; const testAngle = directAngle + offset;
nearestFood = food.getPosition(); 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;
}
}
} }
});
this.targetFood = nearestFood; // 如果没找到更好的角度,返回直接角度
return directAngle;
} }
private avoidThreat() { private executeIntercepting() {
if (!this.targetSnake || !this.targetSnake.isLife) return;
const myPos = this.head.getPosition(); const myPos = this.head.getPosition();
const dangerPos = this.dangerSnake.head.getPosition(); const nearbyAI = this.findNearbyAIToAvoid();
// 计算逃离角度 if (nearbyAI) {
const angle = math.toDegree(Math.atan2( const predictedPos = this.predictTargetPosition(this.targetSnake);
myPos.y - dangerPos.y, const targetAngle = this.calculateTargetAngle(predictedPos);
myPos.x - dangerPos.x 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.head.angle = angle;
this.isFast = true; this.isFast = true;
} }
private chaseFood() { // 混合两个角度
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 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);
}
}
// 计算追逐角度 // 检查身体威胁
const angle = math.toDegree(Math.atan2( for (let i = 0; i < snake.bodyArr.length; i++) {
this.targetFood.y - myPos.y, const bodyPart = snake.bodyArr[i];
this.targetFood.x - myPos.x const bodyDistance = Vec3.distance(myPos, bodyPart.getPosition());
)); const futureDist = Vec3.distance(myFuturePos, bodyPart.getPosition());
// 平滑转向 // 计算与身体部分的相对运动
const angleDiff = this.head.angle - angle; const bodyAngle = this.calculateTargetAngle(bodyPart.getPosition());
this.head.angle += math.clamp(angleDiff, -3, 3); 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);
}
} }
private handleBoundaryAvoidance() { // 更新最危险的蛇
const halfWidth = Global.MAP_WIDTH / 2; if (snakeDanger > maxDanger) {
const halfHeight = Global.MAP_HIGHT / 2; maxDanger = snakeDanger;
mostDangerousSnake = snake;
}
}
if (this.v.x <= -halfWidth || this.v.x >= halfWidth || return maxDanger > 0 ? { snake: mostDangerousSnake, dangerLevel: maxDanger } : null;
this.v.y <= -halfHeight || this.v.y >= halfHeight) { }
const angleAbs = Math.abs(this.head.angle % 180); private predictFuturePosition(currentPos: Vec3, angle: number, speed: number): Vec3 {
this.direction = DirectionType.DEFAULT; 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() {
const turnAngle = math.lerp( const myPos = this.head.getPosition();
math.randomRangeInt(90, 180),
135, if (this.escapeTarget) {
this.difficultyParams.decisionAccuracy let escapeAngle = this.calculateEscapeAngle(this.escapeTarget.head.getPosition());
);
// 考虑身体部分的位置来调整逃跑角度
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;
}
}
// 检查逃跑方向是否会导致撞墙
if (this.willHitBoundary(escapeAngle)) {
escapeAngle = this.adjustEscapeAngle(escapeAngle);
}
if (angleAbs > 90) { this.smoothRotateToAngle(escapeAngle, this.difficultyParams.turnSpeed * 1.8);
this.head.angle += turnAngle;
} else { } else {
this.head.angle -= turnAngle; this.avoidBoundary();
}
this.isFast = true;
}
private willHitBoundary(angle: number): boolean {
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 futureY = myPos.y + Math.sin(radian) * checkDistance;
return this.isInDangerousPosition(v3(futureX, futureY, 0));
}
private adjustEscapeAngle(originalAngle: number): number {
const adjustAngles = [-45, 45, -90, 90, -135, 135, 180];
const myPos = this.head.getPosition();
for (const adjustment of adjustAngles) {
const newAngle = (originalAngle + adjustment) % 360;
if (!this.willHitBoundary(newAngle)) {
return newAngle;
}
}
return originalAngle; // 如果没有找到更好的角度,返回原角度
}
private executeWandering() {
// 增加方向改变的概率
if (math.randomRangeInt(0, 20) == 0) { // 减少方向改变的频率
const direction = math.randomRangeInt(0, 3);
const speed = math.randomRangeInt(1, 4);
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.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 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;
}
} }
} }
} }
private randomMove() { return nearestFood;
if (math.randomRangeInt(0, 11) == 0) {
this.direction = math.randomRangeInt(0, 3);
this.directionSpeed = math.randomRangeInt(0, 6);
} }
if (this.direction == DirectionType.LEFT) { // 寻找替代食物
this.head.angle += this.directionSpeed; private findAlternativeFood(foods: Node[], myPos: Vec3, viewDistance: number, competitors: Snake[]): Vec3 | null {
} else if (this.direction == DirectionType.RIGHT) { let bestAlternative = null;
this.head.angle -= this.directionSpeed; 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 shouldSpeedUp(): boolean { // 计算食物的得分
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. 随机因素(基于激进度)
if (this.dangerSnake) return true; // 根据竞争者调整分数
for (const competitor of competitors) {
const competitorDistance = Vec3.distance(competitor.head.getPosition(), foodPos);
if (competitorDistance < distance) {
// 如果竞争者更近,降低分数
score *= 0.5;
}
}
if (this.targetFood) { // 根据难度添加一些随机性
const distance = Vec3.distance(this.head.getPosition(), this.targetFood); score *= math.lerp(0.8, 1.2, math.random() * params.predictionAccuracy);
if (distance < this.VIEW_DISTANCE / 2) return true;
return score;
} }
return math.random() < params.aggressiveness;
// 判断是否能在竞争者之前到达食物
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 (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;
private executeCurrentBehavior(dt: number) { 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);
} }
......
...@@ -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_HEIGHT / 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_HEIGHT / 2 - 50),
Global.MAP_HIGHT / 2 - 50 Global.MAP_HEIGHT / 2 - 50
); );
this.reInit(x, y); this.reInit(x, y);
}); });
......
...@@ -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_HIGHT / 2 - 50), Global.MAP_HIGHT / 2 - 50); y = math.randomRangeInt(-(Global.MAP_HEIGHT / 2 - 50), Global.MAP_HEIGHT / 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_HIGHT / 2 - 50), Global.MAP_HIGHT / 2 - 50); const y = math.randomRangeInt(-(Global.MAP_HEIGHT / 2 - 50), Global.MAP_HEIGHT / 2 - 50);
this.addFood(x, y); this.addFood(x, y);
}; };
......
...@@ -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_HEIGHT: number = 4000;
/** 当前皮肤ID */ /** 当前皮肤ID */
public static skinId: number = 0; public static skinId: number = 0;
......
...@@ -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_HEIGHT = 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_HEIGHT / 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_HEIGHT / 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_HEIGHT;
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_HEIGHT;
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_HIGHT / 2 - 50), Global.MAP_HIGHT / 2 - 50); const y = math.randomRangeInt(-(Global.MAP_HEIGHT / 2 - 50), Global.MAP_HEIGHT / 2 - 50);
node.getComponent(AISnake)?.init({ node.getComponent(AISnake)?.init({
x, y, x, y,
......
...@@ -46,15 +46,15 @@ export class Snake extends Component { ...@@ -46,15 +46,15 @@ export class Snake extends Component {
@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;
...@@ -63,6 +63,10 @@ export class Snake extends Component { ...@@ -63,6 +63,10 @@ export class Snake extends Component {
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() {
return this.scale * 50;
}
// 初始化方法 // 初始化方法
public async init(config: IInitConfig = {}) { public async init(config: IInitConfig = {}) {
...@@ -280,7 +284,7 @@ export class Snake extends Component { ...@@ -280,7 +284,7 @@ export class Snake extends Component {
// 边界检查 // 边界检查
const mapHalfWidth = Global.MAP_WIDTH / 2; const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HIGHT / 2; const mapHalfHeight = Global.MAP_HEIGHT / 2;
if ( if (
newHeadPos.x <= -mapHalfWidth || newHeadPos.x >= mapHalfWidth || newHeadPos.x <= -mapHalfWidth || newHeadPos.x >= mapHalfWidth ||
newHeadPos.y <= -mapHalfHeight || newHeadPos.y >= mapHalfHeight newHeadPos.y <= -mapHalfHeight || newHeadPos.y >= mapHalfHeight
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment