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);
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.aggressiveness; return competitors;
} }
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);
} }
......
...@@ -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,
......
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 * scale; this.ready = false;
this.tag = Snake.tag++; this.energy = 0;
this.bodyArr = [];
// 设置头部 this.scale = scale;
this.head.angle = angle; this.speed = this.speed * scale;
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 = true; body.active = false;
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
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