Commit b2d1c691 authored by haiyoucuv's avatar haiyoucuv

init

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