Commit c8b06f72 authored by haiyoucuv's avatar haiyoucuv

init

parent 76cc6857
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "93928d4e-7784-4d15-bf81-9d1285ecedbc",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, Vec3, Node, math } from "cc";
const { ccclass, property } = _decorator;
@ccclass("AIBehaviorParams")
export class AIBehaviorParams {
readonly BASE_VIEW_DISTANCE = 300;
readonly INTERCEPT_DISTANCE = 350;
readonly PREDICTION_TIME = 1.2;
readonly ESCAPE_BOUNDARY = 150;
constructor(private difficulty: number) {}
get params() {
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)
};
}
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "073c8734-0f6d-4d8f-b56f-62fe10a8a8a7",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, Vec3, Node } from "cc";
import { Snake } from "../Snake";
import { AIState } from "./AIState";
import { AIBehaviorParams } from "./AIBehaviorParams";
import { AIDecision } from "./AIDecision";
import { AIMovement } from "./AIMovement";
import { AIPerception } from "./AIPerception";
const { ccclass, property } = _decorator;
@ccclass("AIController")
export class AIController {
private snake: Snake;
private state: AIState = AIState.WANDERING;
private params: AIBehaviorParams;
private perception: AIPerception;
private decision: AIDecision;
private movement: AIMovement;
constructor(snake: Snake, difficulty: number) {
this.snake = snake;
this.params = new AIBehaviorParams(difficulty);
this.perception = new AIPerception(snake, this.params);
this.decision = new AIDecision(this.params);
this.movement = new AIMovement(snake, this.params);
}
update(dt: number) {
if (!this.snake.isLife) return;
// 感知环境
const environment = this.perception.analyze();
// 决策
const decision = this.decision.makeDecision(environment);
// 执行行为
this.movement.execute(decision);
}
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "6afcea20-f66c-4638-b432-f28456ee6840",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, Vec3, math } from "cc";
import { AIBehaviorParams } from "./AIBehaviorParams";
import { PerceptionResult } from "./AIPerception";
import { Snake } from "../Snake";
import { AIState } from "./AIState";
import { Global } from "../Global";
const { ccclass, property } = _decorator;
export interface DecisionResult {
targetState: AIState;
targetPosition: Vec3 | null;
moveSpeed: number;
shouldBoost: boolean;
targetSnake: Snake | null;
assistPartner: Snake | null;
}
@ccclass("AIDecision")
export class AIDecision {
private readonly DANGER_THRESHOLD = 0.5;
private readonly LENGTH_ADVANTAGE_THRESHOLD = 1.8;
private readonly SAFE_DISTANCE = 200;
private readonly HUNTING_RANGE = 400;
private readonly MIN_HUNTING_SCORE = 0.2;
constructor(private params: AIBehaviorParams) { }
makeDecision(perception: PerceptionResult): DecisionResult {
// 计算综合评分
const scores = this.calculateActionScores(perception);
// 选择得分最高的行为
const bestAction = this.selectBestAction(scores);
// 执行对应的决策
switch (bestAction) {
case 'escape': return this.makeEscapeDecision(perception);
case 'hunt': return this.makeHuntingDecision(perception);
case 'attack': return this.makeAttackDecision(perception);
case 'assist': return this.makeAssistDecision(perception);
default: return this.makeWanderingDecision(perception);
}
}
private calculateActionScores(perception: PerceptionResult): Record<string, number> {
const scores: Record<string, number> = {
escape: this.calculateEscapeScore(perception),
hunt: this.calculateHuntingScore(perception),
attack: this.calculateAttackScore(perception),
assist: this.calculateAssistScore(perception),
wander: 0.2
};
this.adjustScoresBasedOnSituation(scores, perception);
return scores;
}
private calculateEscapeScore(perception: PerceptionResult): number {
let score = 0;
const myPos = perception.snake.head.getPosition();
// 更自然的危险评估
if (perception.dangerLevel > this.DANGER_THRESHOLD) {
// 不会过度恐慌,而是根据实力比例来判断
const panicFactor = perception.powerRatio < 0.7 ? 1.2 : 0.8;
score = 0.7 + perception.dangerLevel * 0.3 * panicFactor;
}
// 更智能的边界处理
if (perception.boundaryDistance < this.params.ESCAPE_BOUNDARY) {
const boundaryFactor = perception.boundaryDistance / this.params.ESCAPE_BOUNDARY;
// 靠近边界时会提前转向,而不是等到最后一刻
score += (1 - Math.pow(boundaryFactor, 2)) * 0.6;
}
// 更自然的威胁反应
if (perception.nearestThreat) {
const distanceToThreat = Vec3.distance(myPos, perception.nearestThreat.head.getPosition());
const threatLength = perception.nearestThreat.getSnakeLen();
const myLength = perception.snake.getSnakeLen();
// 根据体型差异调整反应
const lengthRatio = myLength / threatLength;
if (lengthRatio < 0.8) {
// 弱势时更容易逃跑
score += (1 - distanceToThreat / (this.SAFE_DISTANCE * 2)) * 0.7;
} else if (lengthRatio < 1.2) {
// 势均力敌时保持警惕但不会过度逃避
score += (1 - distanceToThreat / (this.SAFE_DISTANCE * 1.5)) * 0.4;
}
}
return Math.min(score, 1);
}
private calculateHuntingScore(perception: PerceptionResult): number {
if (!perception.nearestFood) return this.MIN_HUNTING_SCORE;
const distanceToFood = Vec3.distance(
perception.snake.head.getPosition(),
perception.nearestFood
);
if (distanceToFood > this.HUNTING_RANGE) return this.MIN_HUNTING_SCORE;
let score = (1 - distanceToFood / this.HUNTING_RANGE) *
(1 - perception.dangerLevel * 0.5) * 1.2;
// 近距离食物加分
if (distanceToFood < this.HUNTING_RANGE * 0.3) {
score += 0.3;
}
// 根据局势调整
score *= (1 + perception.territoryControl * 0.3);
score *= (1 + perception.powerRatio * 0.2);
return Math.min(score, 1);
}
private calculateAttackScore(perception: PerceptionResult): number {
if (!perception.player?.isLife || perception.dangerLevel > 0.3) return 0;
const myLength = perception.snake.getSnakeLen();
const playerLength = perception.player.getSnakeLen();
if (myLength <= playerLength * this.LENGTH_ADVANTAGE_THRESHOLD) return 0;
const distanceToPlayer = Vec3.distance(
perception.snake.head.getPosition(),
perception.player.head.getPosition()
);
let score = (1 - perception.dangerLevel) *
(myLength / playerLength - this.LENGTH_ADVANTAGE_THRESHOLD) *
(1 - distanceToPlayer / this.params.params.viewDistance) * 0.4;
// 根据局势调整
score *= perception.territoryControl;
score *= perception.powerRatio * 0.5;
return Math.min(score, 1);
}
private calculateAssistScore(perception: PerceptionResult): number {
if (!perception.assistPartner || !perception.player?.isLife ||
perception.dangerLevel > 0.3) return 0;
const combinedLength = perception.snake.getSnakeLen() +
perception.assistPartner.getSnakeLen();
const playerLength = perception.player.getSnakeLen();
if (combinedLength <= playerLength * 1.5) return 0;
const distance = Vec3.distance(
perception.snake.head.getPosition(),
perception.assistPartner.head.getPosition()
);
let score = (1 - perception.dangerLevel) *
(combinedLength / playerLength - 1.5) *
(1 - distance / this.params.params.viewDistance) * 0.4;
score *= perception.territoryControl;
return Math.min(score, 1);
}
private adjustScoresBasedOnSituation(
scores: Record<string, number>,
perception: PerceptionResult
): void {
// 根据局势调整行为权重
if (perception.powerRatio > 1.5 && perception.territoryControl > 0.6) {
scores.hunt *= 1.3;
scores.attack *= 1.2;
} else if (perception.powerRatio < 0.7 || perception.territoryControl < 0.3) {
scores.escape *= 1.4;
scores.wander *= 1.2;
}
// 标准化分数
const total = Object.values(scores).reduce((a, b) => a + b, 0);
if (total > 0) {
for (const key in scores) {
scores[key] /= total;
}
}
}
private selectBestAction(scores: Record<string, number>): string {
let bestAction = 'wander';
let bestScore = scores.wander;
for (const [action, score] of Object.entries(scores)) {
if (score > bestScore) {
bestScore = score;
bestAction = action;
}
}
return bestAction;
}
private makeEscapeDecision(perception: PerceptionResult): DecisionResult {
const escapePos = this.calculateEscapePosition(perception);
return {
targetState: AIState.ESCAPING,
targetPosition: escapePos,
moveSpeed: 1,
shouldBoost: true,
targetSnake: perception.nearestThreat,
assistPartner: null
};
}
private makeAttackDecision(perception: PerceptionResult): DecisionResult {
return {
targetState: AIState.INTERCEPTING,
targetPosition: this.predictTargetPosition(perception.player),
moveSpeed: 1,
shouldBoost: this.shouldBoostForAttack(perception),
targetSnake: perception.player,
assistPartner: null
};
}
private makeAssistDecision(perception: PerceptionResult): DecisionResult {
return {
targetState: AIState.ASSISTING,
targetPosition: this.calculateAssistPosition(perception),
moveSpeed: 1,
shouldBoost: this.shouldBoostForAssist(perception),
targetSnake: perception.player,
assistPartner: perception.assistPartner
};
}
private makeHuntingDecision(perception: PerceptionResult): DecisionResult {
return {
targetState: AIState.HUNTING,
targetPosition: perception.nearestFood,
moveSpeed: this.calculateHuntingSpeed(perception),
shouldBoost: this.shouldBoostForHunting(perception), // 新增加速判断
targetSnake: null,
assistPartner: null
};
}
// 新增觅食加速判断
private shouldBoostForHunting(perception: PerceptionResult): boolean {
if (!perception.nearestFood) return false;
const distanceToFood = Vec3.distance(
perception.snake.head.getPosition(),
perception.nearestFood
);
// 当食物较近且危险程度较低时加速
return distanceToFood < this.HUNTING_RANGE * 0.4 &&
perception.dangerLevel < 0.3;
}
private makeWanderingDecision(perception: PerceptionResult): DecisionResult {
// 游走时远离玩家
const wanderPos = this.calculateWanderPosition(perception);
if (perception.player?.isLife) {
const playerPos = perception.player.head.getPosition();
const myPos = perception.snake.head.getPosition();
if (Vec3.distance(myPos, playerPos) < this.SAFE_DISTANCE * 2) {
// 远离玩家的方向游走
const awayFromPlayer = new Vec3(
myPos.x - playerPos.x,
myPos.y - playerPos.y,
0
).normalize();
wanderPos.x = myPos.x + awayFromPlayer.x * this.params.params.viewDistance * 0.7;
wanderPos.y = myPos.y + awayFromPlayer.y * this.params.params.viewDistance * 0.7;
}
}
return {
targetState: AIState.WANDERING,
targetPosition: wanderPos,
moveSpeed: 0.8,
shouldBoost: false,
targetSnake: null,
assistPartner: null
};
}
private calculateEscapePosition(perception: PerceptionResult): Vec3 {
const myPos = perception.snake.head.getPosition();
let escapeVector = new Vec3();
// 考虑边界威胁
if (perception.boundaryDistance < this.params.ESCAPE_BOUNDARY) {
escapeVector.add(this.calculateBoundaryEscapeVector(perception));
}
// 考虑蛇的威胁
if (perception.nearestThreat) {
const threatPos = perception.nearestThreat.head.getPosition();
const awayFromThreat = new Vec3(
myPos.x - threatPos.x,
myPos.y - threatPos.y,
0
);
awayFromThreat.normalize().multiplyScalar(this.params.params.viewDistance);
escapeVector.add(awayFromThreat);
}
// 如果没有明确的逃跑方向,生成随机方向
if (escapeVector.length() < 0.1) {
const randomAngle = math.random() * Math.PI * 2;
escapeVector.set(
Math.cos(randomAngle) * this.params.params.viewDistance,
Math.sin(randomAngle) * this.params.params.viewDistance,
0
);
}
return new Vec3(
myPos.x + escapeVector.x,
myPos.y + escapeVector.y,
0
);
}
private calculateAssistPosition(perception: PerceptionResult): Vec3 {
const playerPos = perception.player.head.getPosition();
const partnerPos = perception.assistPartner.head.getPosition();
// 计算包夹位置
const angle = math.random() * Math.PI * 2;
const radius = this.params.params.interceptDistance * 0.7;
return new Vec3(
playerPos.x + Math.cos(angle) * radius,
playerPos.y + Math.sin(angle) * radius,
0
);
}
private calculateWanderPosition(perception: PerceptionResult): Vec3 {
const myPos = perception.snake.head.getPosition();
const targetPos = new Vec3();
// 计算逃离向量
const escapeVector = new Vec3();
// 远离所有威胁
for (const competitor of perception.nearbyCompetitors) {
const competitorPos = competitor.head.getPosition();
const distance = Vec3.distance(myPos, competitorPos);
if (distance < this.SAFE_DISTANCE * 4) {
const away = new Vec3(
myPos.x - competitorPos.x,
myPos.y - competitorPos.y,
0
).normalize().multiplyScalar(1 / Math.max(distance, 1));
escapeVector.add(away);
}
}
// 特别远离玩家
if (perception.player?.isLife) {
const playerPos = perception.player.head.getPosition();
const distanceToPlayer = Vec3.distance(myPos, playerPos);
if (distanceToPlayer < this.SAFE_DISTANCE * 5) {
const awayFromPlayer = new Vec3(
myPos.x - playerPos.x,
myPos.y - playerPos.y,
0
).normalize().multiplyScalar(2 / Math.max(distanceToPlayer, 1));
escapeVector.add(awayFromPlayer);
}
}
// 如果有逃离方向
if (escapeVector.length() > 0.1) {
escapeVector.normalize().multiplyScalar(this.SAFE_DISTANCE * 3);
targetPos.set(
myPos.x + escapeVector.x,
myPos.y + escapeVector.y,
0
);
} else {
// 随机游走
const randomAngle = Math.random() * Math.PI * 2;
const randomDistance = this.SAFE_DISTANCE * (2 + Math.random() * 2);
targetPos.set(
myPos.x + Math.cos(randomAngle) * randomDistance,
myPos.y + Math.sin(randomAngle) * randomDistance,
0
);
}
// 确保在地图范围内
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
const margin = this.SAFE_DISTANCE;
targetPos.x = math.clamp(targetPos.x, -mapHalfWidth + margin, mapHalfWidth - margin);
targetPos.y = math.clamp(targetPos.y, -mapHalfHeight + margin, mapHalfHeight - margin);
return targetPos;
}
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.params.PREDICTION_TIME;
const predictY = targetPos.y + Math.sin(radian) * targetSpeed * this.params.PREDICTION_TIME;
return new Vec3(predictX, predictY, 0);
}
private calculateBoundaryEscapeVector(perception: PerceptionResult): Vec3 {
const myPos = perception.snake.head.getPosition();
const escapeVector = new Vec3();
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
// 根据到边界的距离计算逃离向量
if (myPos.x > mapHalfWidth * 0.8) escapeVector.x = -1;
if (myPos.x < -mapHalfWidth * 0.8) escapeVector.x = 1;
if (myPos.y > mapHalfHeight * 0.8) escapeVector.y = -1;
if (myPos.y < -mapHalfHeight * 0.8) escapeVector.y = 1;
return escapeVector.normalize();
}
private shouldBoostForAttack(perception: PerceptionResult): boolean {
if (!perception.player) return false;
const distanceToPlayer = Vec3.distance(
perception.snake.head.getPosition(),
perception.player.head.getPosition()
);
return distanceToPlayer < this.params.params.interceptDistance * 0.7;
}
private shouldBoostForAssist(perception: PerceptionResult): boolean {
if (!perception.player || !perception.assistPartner) return false;
const distanceToPlayer = Vec3.distance(
perception.snake.head.getPosition(),
perception.player.head.getPosition()
);
return distanceToPlayer < this.params.params.interceptDistance * 0.5;
}
private calculateHuntingSpeed(perception: PerceptionResult): number {
if (!perception.nearestFood) return 1;
const distanceToFood = Vec3.distance(
perception.snake.head.getPosition(),
perception.nearestFood
);
// 调整速度曲线,使其更积极
return math.lerp(0.9, 1.3, 1 - (distanceToFood / this.HUNTING_RANGE));
}
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7e02fc41-ada7-4810-9440-2c40f5f66f4b",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, Vec3, math } from "cc";
import { Snake } from "../Snake";
import { AIBehaviorParams } from "./AIBehaviorParams";
import { AIState } from "./AIState";
import { DecisionResult } from "./AIDecision";
import { Global } from "../Global";
import { MainGame } from "../MainGame";
const { ccclass, property } = _decorator;
@ccclass("AIMovement")
export class AIMovement {
private readonly SAFE_MARGIN = 3.0; // 增加安全边际
private readonly OBSTACLE_CHECK_DISTANCE = 250; // 增加检测距离
private readonly AVOIDANCE_TURN_SPEED = 8; // 提高基础转向速度
private readonly EMERGENCY_TURN_SPEED = 12; // 提高紧急转向速度
private readonly PREDICTION_STEPS = 5; // 减少预测步数以加快反应
private readonly ESCAPE_BOUNDARY = 180; // 增加边界安全距离
constructor(
private snake: Snake,
private params: AIBehaviorParams
) { }
execute(decision: DecisionResult) {
// 更新速度
this.snake.moveScale = decision.moveSpeed;
this.snake.isFast = decision.shouldBoost;
// 执行对应状态的移动
switch (decision.targetState) {
case AIState.HUNTING:
this.executeHunting(decision);
break;
case AIState.INTERCEPTING:
this.executeIntercepting(decision);
break;
case AIState.ESCAPING:
this.executeEscaping(decision);
break;
case AIState.WANDERING:
this.executeWandering(decision);
break;
case AIState.ASSISTING:
this.executeAssisting(decision);
break;
}
}
private executeHunting(decision: DecisionResult) {
if (!decision.targetPosition) return;
this.smoothRotateToTarget(decision.targetPosition);
}
private executeIntercepting(decision: DecisionResult) {
if (!decision.targetSnake?.isLife) return;
const predictedPos = this.predictTargetPosition(decision.targetSnake);
this.smoothRotateToTarget(predictedPos);
}
private executeEscaping(decision: DecisionResult) {
if (!decision.targetPosition) return;
this.smoothRotateToTarget(decision.targetPosition);
}
private executeWandering(decision: DecisionResult) {
if (!decision.targetPosition) return;
this.smoothRotateToTarget(decision.targetPosition);
}
private executeAssisting(decision: DecisionResult) {
if (!decision.targetPosition) return;
this.smoothRotateToTarget(decision.targetPosition);
}
private smoothRotateToTarget(targetPos: Vec3) {
const targetAngle = this.calculateTargetAngle(targetPos);
const obstacleCheck = this.predictObstacles(targetAngle);
const currentAngle = this.snake.head.angle;
let angleDiff = targetAngle - currentAngle;
while (angleDiff > 180) angleDiff -= 360;
while (angleDiff < -180) angleDiff += 360;
if (obstacleCheck.hasObstacle) {
// 立即转向,不再添加随机性
const turnSpeed = obstacleCheck.dangerLevel >= 0.6 ? // 降低危险阈值
this.EMERGENCY_TURN_SPEED :
this.AVOIDANCE_TURN_SPEED;
// 直接使用最佳转向角度
this.snake.head.angle = obstacleCheck.bestAngle;
// 危险时立即减速
this.snake.moveScale *= 0.5;
} else {
const turnSpeed = this.params.params.turnSpeed;
const maxTurn = Math.min(Math.abs(angleDiff), turnSpeed * 1.5);
this.snake.head.angle += Math.sign(angleDiff) * maxTurn;
}
}
private predictObstacles(targetAngle: number): {
hasObstacle: boolean;
bestAngle: number;
dangerLevel: number;
turnDirection: number;
} {
const myPos = this.snake.head.getPosition();
const myAngle = this.snake.head.angle;
let minDanger = Infinity;
let bestAngle = myAngle;
let bestTurnDir = 0;
// 检查更多角度
const checkAngles = [-90, -60, -45, -30, -15, 0, 15, 30, 45, 60, 90];
for (const angleOffset of checkAngles) {
let currentPos = myPos.clone();
let currentAngle = myAngle + angleOffset;
let maxDanger = 0;
let totalDanger = 0;
// 多步预测
for (let step = 1; step <= this.PREDICTION_STEPS; step++) {
const radian = currentAngle * Math.PI / 180;
const stepDistance = this.OBSTACLE_CHECK_DISTANCE / this.PREDICTION_STEPS;
const stepVector = new Vec3(
Math.cos(radian) * stepDistance,
Math.sin(radian) * stepDistance,
0
);
currentPos.add(stepVector);
// 计算边界危险度
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
const boundaryDanger = Math.max(
Math.max(0, (Math.abs(currentPos.x) - (mapHalfWidth - this.ESCAPE_BOUNDARY)) / this.ESCAPE_BOUNDARY),
Math.max(0, (Math.abs(currentPos.y) - (mapHalfHeight - this.ESCAPE_BOUNDARY)) / this.ESCAPE_BOUNDARY)
);
// 计算其他蛇的危险度
const obstacleDistance = this.getObstacleDistance(myPos, currentPos);
const obstacleDanger = Math.max(0, 1 - obstacleDistance / (this.OBSTACLE_CHECK_DISTANCE * this.SAFE_MARGIN));
const stepDanger = Math.max(boundaryDanger * 1.5, obstacleDanger);
maxDanger = Math.max(maxDanger, stepDanger);
totalDanger += stepDanger * (1 - step / this.PREDICTION_STEPS);
}
// 考虑与目标角度的差异
const angleDiff = Math.abs(currentAngle - targetAngle);
const angleWeight = 1 - (angleDiff / 180) * 0.5;
const finalDanger = maxDanger * 0.7 + (totalDanger / this.PREDICTION_STEPS) * 0.3;
const weightedDanger = finalDanger / angleWeight;
if (weightedDanger < minDanger) {
minDanger = weightedDanger;
bestAngle = currentAngle;
bestTurnDir = angleOffset;
}
}
return {
hasObstacle: minDanger > 0.2, // 降低危险阈值,更早触发避障
bestAngle: bestAngle,
dangerLevel: minDanger,
turnDirection: bestTurnDir
};
}
private calculateTargetAngle(targetPos: Vec3): number {
const myPos = this.snake.head.getPosition();
return math.toDegree(Math.atan2(
targetPos.y - myPos.y,
targetPos.x - myPos.x
));
}
private getObstacleDistance(from: Vec3, to: Vec3): number {
// 检查地图边界
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
const margin = 50;
if (Math.abs(to.x) > mapHalfWidth - margin ||
Math.abs(to.y) > mapHalfHeight - margin) {
return 0;
}
// 检查蛇身碰撞
let minDistance = this.OBSTACLE_CHECK_DISTANCE;
const competitors = MainGame.ins.animalNode.children;
for (const competitor of competitors) {
const snake = competitor.getComponent(Snake);
if (!snake?.isLife || snake === this.snake) continue;
// 检查头部碰撞
const headDistance = Vec3.distance(to, snake.head.getPosition());
minDistance = Math.min(minDistance, headDistance);
// 检查身体碰撞
for (let i = 1; i < snake.bodyArr.length; i += 2) {
const bodyPart = snake.bodyArr[i];
const bodyDistance = Vec3.distance(to, bodyPart.getPosition());
minDistance = Math.min(minDistance, bodyDistance);
}
}
return minDistance;
}
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.params.PREDICTION_TIME * this.params.params.predictionAccuracy;
const predictY = targetPos.y + Math.sin(radian) * targetSpeed * this.params.PREDICTION_TIME * this.params.params.predictionAccuracy;
return new Vec3(predictX, predictY, 0);
}
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "bc428e02-e520-444c-b313-e2204525832d",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, Vec3, Node, math } from "cc";
import { Snake } from "../Snake";
import { AIBehaviorParams } from "./AIBehaviorParams";
import { AISnake } from "../AISnake";
import { MainGame } from "../MainGame";
import { Global } from "../Global";
const { ccclass, property } = _decorator;
export interface PerceptionResult {
snake: Snake;
nearestFood: Vec3 | null;
nearestThreat: Snake | null;
nearbyCompetitors: Snake[];
boundaryDistance: number;
player: Snake | null;
assistPartner: AISnake | null;
dangerLevel: number;
territoryControl: number;
powerRatio: number;
}
@ccclass("AIPerception")
export class AIPerception {
private readonly SAFE_MARGIN = 4.0;
private readonly THREAT_DETECTION_RANGE = 500;
private readonly FOOD_EVALUATION_RANGE = 600;
private readonly TERRITORY_INFLUENCE_RANGE = 400;
constructor(
private snake: Snake,
private params: AIBehaviorParams
) { }
analyze(): PerceptionResult {
const myPos = this.snake.head.getPosition();
const player = MainGame.ins.player;
const situationAnalysis = this.analyzeSituation(myPos);
return {
snake: this.snake,
nearestFood: this.findOptimalFood(situationAnalysis),
nearestThreat: this.findPrimaryThreat(situationAnalysis),
nearbyCompetitors: this.findRelevantCompetitors(situationAnalysis),
boundaryDistance: this.calculateBoundaryRisk(myPos),
player: player,
assistPartner: this.findBestAssistPartner(player, situationAnalysis),
dangerLevel: situationAnalysis.threatLevel,
territoryControl: situationAnalysis.territoryControl,
powerRatio: situationAnalysis.powerRatio
};
}
private analyzeSituation(myPos: Vec3) {
const myLength = this.snake.getSnakeLen();
let territoryControl = 0;
let threatLevel = 0;
let powerRatio = 1;
const competitors = this.findNearbyCompetitors();
const threats: Snake[] = [];
for (const competitor of competitors) {
const distance = Vec3.distance(myPos, competitor.head.getPosition());
const competitorLength = competitor.getSnakeLen();
const strengthRatio = myLength / competitorLength;
// 计算影响力衰减
const influence = 1 / (1 + Math.pow(distance / this.TERRITORY_INFLUENCE_RANGE, 2));
// 评估威胁和领域控制
if (strengthRatio < 1.2) { // 略微提高威胁判定阈值
const threatScore = influence * (1.2 - strengthRatio);
threatLevel = Math.max(threatLevel, threatScore);
if (distance < this.THREAT_DETECTION_RANGE) {
threats.push(competitor);
}
} else {
territoryControl += influence * (strengthRatio - 1);
}
}
// 计算整体实力比
const totalLength = competitors.reduce((sum, snake) => sum + snake.getSnakeLen(), 0);
powerRatio = totalLength > 0 ? myLength / (totalLength / competitors.length) : 2;
// 考虑边界威胁
const boundaryRisk = this.calculateBoundaryRisk(myPos);
threatLevel = Math.max(threatLevel, boundaryRisk);
return {
threatLevel: Math.min(1, threatLevel),
territoryControl: Math.min(1, territoryControl),
powerRatio,
competitors,
threats
};
}
private findOptimalFood(situation: any): Vec3 | null {
const foods = MainGame.ins.fondManger.node.children;
let bestFood = null;
let bestScore = -Infinity;
const myPos = this.snake.head.getPosition();
for (const food of foods) {
if (!food.isValid || !food.active) continue;
const foodPos = food.getPosition();
const distance = Vec3.distance(myPos, foodPos);
if (distance > this.FOOD_EVALUATION_RANGE) continue;
const score = this.evaluateFoodValue(foodPos, distance, situation);
if (score > bestScore) {
bestScore = score;
bestFood = foodPos;
}
}
return bestFood;
}
private evaluateFoodValue(foodPos: Vec3, distance: number, situation: any): number {
let score = 1000 - distance; // 基础分数
// 竞争者距离惩罚
for (const competitor of situation.competitors) {
const competitorDist = Vec3.distance(competitor.head.getPosition(), foodPos);
if (competitorDist < distance) {
score *= 0.7;
}
}
// 威胁距离惩罚
for (const threat of situation.threats) {
const threatDist = Vec3.distance(threat.head.getPosition(), foodPos);
if (threatDist < this.SAFE_MARGIN * 2) {
score *= 0.5;
}
}
// 边界安全性评估
score *= this.evaluatePositionSafety(foodPos);
// 根据整体局势调整
score *= (1 - situation.threatLevel * 0.7);
score *= (1 + situation.territoryControl * 0.3);
return score;
}
private findPrimaryThreat(situation: any): Snake | null {
if (situation.threats.length === 0) return null;
const myPos = this.snake.head.getPosition();
let primaryThreat = null;
let highestThreatScore = 0;
for (const threat of situation.threats) {
const distance = Vec3.distance(myPos, threat.head.getPosition());
const lengthRatio = threat.getSnakeLen() / this.snake.getSnakeLen();
const threatScore = lengthRatio * (1 - distance / this.THREAT_DETECTION_RANGE);
if (threatScore > highestThreatScore) {
highestThreatScore = threatScore;
primaryThreat = threat;
}
}
return primaryThreat;
}
private findRelevantCompetitors(situation: any): Snake[] {
return situation.competitors.filter(competitor => {
const distance = Vec3.distance(
this.snake.head.getPosition(),
competitor.head.getPosition()
);
return distance <= this.params.params.viewDistance;
});
}
private calculateBoundaryRisk(position: Vec3): number {
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
const distanceToEdge = Math.min(
mapHalfWidth - Math.abs(position.x),
mapHalfHeight - Math.abs(position.y)
);
return Math.max(0, 1 - distanceToEdge / (this.SAFE_MARGIN * 2));
}
private evaluatePositionSafety(pos: Vec3): number {
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
const distToBoundaryX = Math.min(mapHalfWidth - Math.abs(pos.x), mapHalfWidth);
const distToBoundaryY = Math.min(mapHalfHeight - Math.abs(pos.y), mapHalfHeight);
return Math.min(
1,
(distToBoundaryX / (mapHalfWidth * 0.3)) *
(distToBoundaryY / (mapHalfHeight * 0.3))
);
}
private findBestAssistPartner(player: Snake, situation: any): AISnake | null {
if (!player?.isLife || situation.threatLevel > 0.6) return null;
const myPos = this.snake.head.getPosition();
const myLength = this.snake.getSnakeLen();
let bestPartner: AISnake = null;
let bestScore = 0;
for (const competitor of situation.competitors) {
if (!(competitor instanceof AISnake)) continue;
const distance = Vec3.distance(myPos, competitor.head.getPosition());
if (distance > this.params.params.viewDistance * 0.7) continue;
const combinedLength = myLength + competitor.getSnakeLen();
const playerLength = player.getSnakeLen();
if (combinedLength > playerLength * 1.3) {
const score = this.evaluateAssistPartner(competitor, player, distance);
if (score > bestScore) {
bestScore = score;
bestPartner = competitor;
}
}
}
return bestPartner;
}
private evaluateAssistPartner(partner: AISnake, target: Snake, distance: number): number {
const partnerLength = partner.getSnakeLen();
const targetLength = target.getSnakeLen();
const myLength = this.snake.getSnakeLen();
let score = (partnerLength + myLength) / targetLength;
score *= (1 - distance / this.params.params.viewDistance);
score *= partner.isLife ? 1 : 0;
return score;
}
private findNearestThreat(): Snake | null {
const myPos = this.snake.head.getPosition();
const myLength = this.snake.getSnakeLen();
let nearestThreat = null;
let minDistance = this.params.params.viewDistance;
const allSnakes = [...MainGame.ins.animalNode.children, MainGame.ins.player.node];
for (const snakeNode of allSnakes) {
const snake = snakeNode.getComponent(Snake);
if (snake === this.snake || !snake?.isLife) continue;
// 增加长度检查的阈值,更容易将其他蛇视为威胁
if (snake.getSnakeLen() > myLength * 0.8) {
const headDistance = Vec3.distance(myPos, snake.head.getPosition());
if (headDistance < minDistance) {
minDistance = headDistance;
nearestThreat = snake;
}
}
// 检查身体碰撞的威胁
for (const bodyPart of snake.bodyArr) {
const bodyDistance = Vec3.distance(myPos, bodyPart.getPosition());
if (bodyDistance < this.SAFE_MARGIN * 2) {
minDistance = bodyDistance;
nearestThreat = snake;
break;
}
}
}
return nearestThreat;
}
private findNearbyCompetitors(): Snake[] {
const myPos = this.snake.head.getPosition();
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 || !snake?.isLife) continue;
const distance = Vec3.distance(myPos, snake.head.getPosition());
if (distance <= this.params.params.viewDistance) {
competitors.push(snake);
}
}
return competitors;
}
private getBoundaryDistance(position: Vec3): number {
const mapHalfWidth = Global.MAP_WIDTH / 2;
const mapHalfHeight = Global.MAP_HEIGHT / 2;
const distanceToRight = mapHalfWidth - position.x;
const distanceToLeft = position.x + mapHalfWidth;
const distanceToTop = mapHalfHeight - position.y;
const distanceToBottom = position.y + mapHalfHeight;
return Math.min(distanceToRight, distanceToLeft, distanceToTop, distanceToBottom);
}
// private findAssistPartner(player: Snake): AISnake | null {
// if (!player?.isLife) return null;
// const myPos = this.snake.head.getPosition();
// const playerPos = player.head.getPosition();
// let bestPartner: AISnake = null;
// let bestScore = -1;
// for (const node of MainGame.ins.animalNode.children) {
// const otherAI = node.getComponent(AISnake);
// if (!otherAI?.isLife || otherAI === this.snake) continue;
// const distanceToAI = Vec3.distance(myPos, otherAI.head.getPosition());
// const distanceToPlayer = Vec3.distance(otherAI.head.getPosition(), playerPos);
// if (distanceToAI < this.params.ASSIST_DISTANCE &&
// distanceToPlayer < this.params.ASSIST_DISTANCE) {
// const score = this.calculateAssistScore(otherAI, player);
// if (score > bestScore) {
// bestScore = score;
// bestPartner = otherAI;
// }
// }
// }
// return bestPartner;
// }
private findAssistPartner(player: Snake): AISnake | null {
if (!player?.isLife) return null;
const myPos = this.snake.head.getPosition();
const myLength = this.snake.getSnakeLen();
let bestPartner: AISnake = null;
let bestScore = 0;
for (const snakeNode of MainGame.ins.animalNode.children) {
const partner = snakeNode.getComponent(AISnake);
if (!partner?.isLife || partner === this.snake) continue;
const distance = Vec3.distance(myPos, partner.head.getPosition());
if (distance > this.params.params.viewDistance) continue;
const score = this.calculateAssistScore(partner, player);
if (score > bestScore) {
bestScore = score;
bestPartner = partner;
}
}
return bestPartner;
}
private calculateDangerLevel(position: Vec3): number {
let dangerLevel = 0;
// 边界危险度
const boundaryDistance = this.getBoundaryDistance(position);
if (boundaryDistance < this.params.ESCAPE_BOUNDARY) {
dangerLevel += 1 - (boundaryDistance / this.params.ESCAPE_BOUNDARY);
}
// 威胁危险度
const threat = this.findNearestThreat();
if (threat) {
const distance = Vec3.distance(position, threat.head.getPosition());
const threatFactor = 1 - (distance / this.params.params.viewDistance);
dangerLevel += threatFactor * (threat.getSnakeLen() / this.snake.getSnakeLen());
}
return math.clamp(dangerLevel, 0, 1);
}
// 辅助方法
private calculateAssistScore(partner: AISnake, target: Snake): number {
const partnerLength = partner.getSnakeLen();
const targetLength = target.getSnakeLen();
const myLength = this.snake.getSnakeLen();
return (partnerLength + myLength) / targetLength;
}
private findAlternativeFood(foods: Node[], myPos: Vec3): 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 > this.params.params.viewDistance) continue;
const competitors = this.findCompetitorsForFood(foodPos, distance);
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 {
let score = 1 - (distance / this.params.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() * this.params.params.predictionAccuracy);
return score;
}
private findCompetitorsForFood(foodPos: Vec3, myDistance: number): Snake[] {
return this.findNearbyCompetitors().filter(competitor => {
const distance = Vec3.distance(competitor.head.getPosition(), foodPos);
return distance <= myDistance * 1.2;
});
}
private canReachFoodFirst(foodPos: Vec3, myDistance: number, competitors: Snake[]): boolean {
const mySpeed = this.snake.speed * this.snake.moveScale;
const myTimeToReach = myDistance / mySpeed;
return !competitors.some(competitor => {
const competitorDistance = Vec3.distance(competitor.head.getPosition(), foodPos);
const competitorSpeed = competitor.speed * (competitor instanceof AISnake ? 2 : 1);
return (competitorDistance / competitorSpeed) < myTimeToReach;
});
}
private calculateTargetAngle(targetPos: Vec3): number {
const myPos = this.snake.head.getPosition();
return math.toDegree(Math.atan2(
targetPos.y - myPos.y,
targetPos.x - myPos.x
));
}
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "65f41ffa-1a6b-4ac1-995c-f9cd4151d765",
"files": [],
"subMetas": {},
"userData": {}
}
// AI 状态枚举
export enum AIState {
HUNTING, // 追逐食物
INTERCEPTING, // 拦截玩家
ESCAPING, // 逃离危险
WANDERING, // 随机游走
ASSISTING // 协助其他AI
}
\ No newline at end of file
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "f8e4b5c8-de38-40df-984e-b8c6bd1a746b",
"files": [],
"subMetas": {},
"userData": {}
}
import { _decorator, math, v3, Vec3, Node, Collider2D, Contact2DType, PhysicsGroup } from "cc";
import { _decorator } from "cc";
import { Snake } from "./Snake";
import { DirectionType } from "./Common/Enums";
import { Global } from "./Global";
import { MainGame } from "./MainGame";
import { AIController } from "./AI/AIController";
const { ccclass, property } = _decorator;
enum AIState {
HUNTING, // 追逐食物
INTERCEPTING, // 拦截玩家
ESCAPING, // 逃离危险
WANDERING, // 随机游走
ASSISTING // 协助其他AI攻击玩家
}
const { ccclass } = _decorator;
@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 currentState: AIState = AIState.WANDERING;
private behaviorTimer: number = 0;
private targetFood: Vec3 = null;
private targetSnake: Snake = null;
private escapeTarget: Snake = null;
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 aiController: AIController;
private updateTimer: number = 0;
private readonly UPDATE_INTERVAL: number = 0.1;
private assistTarget: AISnake = null; // 正在协助的AI蛇
async init(config: any) {
await super.init(config);
const difficulty = 1 + Math.random() * 4;
this.aiController = new AIController(this, difficulty);
}
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();
this.node.removeFromParent();
this.destroy();
MainGame.ins.initAnimal(1);
}
console.log(MainGame.ins.animalNode.children.length)
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.behaviorTimer += dt;
if (this.behaviorTimer >= this.difficultyParams.reactionTime) {
this.behaviorTimer = 0;
this.updateAIState();
this.updateTimer += dt;
if (this.updateTimer >= this.UPDATE_INTERVAL) {
this.updateTimer = 0;
this.aiController.update(dt);
}
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 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; // 优先处理躲避
}
// 执行原有状态逻辑
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.isNearBoundary(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
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";
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 currentState: AIState = AIState.WANDERING;
private behaviorTimer: number = 0;
private targetFood: Vec3 = null;
private targetSnake: Snake = null;
private escapeTarget: Snake = null;
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);
}
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();
this.destroy();
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.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
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "e1351be2-c5a8-46eb-b1eb-999920fa114e",
"files": [],
"subMetas": {},
"userData": {}
}
......@@ -101,7 +101,7 @@ export class Snake extends Component {
// 设置身体部分的贴图
body.getComponent(Sprite).spriteFrame = i % 2 == 0 ? this.imgBody1 : this.imgBody2;
// body.active = false;
body.active = false;
this.bodyArr.push(body);
}
......@@ -189,12 +189,12 @@ export class Snake extends Component {
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
// );
newBody.active = isIntersect(
newBody.getPosition(),
this.head.getPosition(),
this.vw,
this.vh
);
this.bodyArr.splice(len, 0, newBody);
}
......@@ -271,12 +271,12 @@ export class Snake extends Component {
body.setScale(this.scale, this.scale);
body.setSiblingIndex(this.bodyArr.length - i);
// body.active = isIntersect(
// targetPos,
// this.head.getPosition(),
// this.vw,
// this.vh
// );
body.active = isIntersect(
targetPos,
this.head.getPosition(),
this.vw,
this.vh
);
}
}
......@@ -302,19 +302,21 @@ export class Snake extends Component {
this.isLife = false;
this.node.active = false;
console.log(MainGame.ins.animalNode.children.length)
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));
const direction = this.getVelocity(angle);
return v3(currentPos.x + dt * direction.x * speed, currentPos.y + dt * direction.y * speed, 0);
}
getVelocity(angle = this.head.angle) {
const radian = angle / 180 * Math.PI;
return v2(Math.cos(radian), Math.sin(radian));
}
/**
* 初始化食物
*/
......
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "0b815457-c723-4762-89c7-4f67b7889f60",
"files": [],
"subMetas": {},
"userData": {}
}
[
{
"__type__": "cc.SceneAsset",
"_name": "Test",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"scene": {
"__id__": 1
}
},
{
"__type__": "cc.Scene",
"_name": "Test",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"autoReleaseAssets": false,
"_globals": {
"__id__": 19
},
"_id": "fa60c8c8-f313-4702-b075-c3a33a34c831"
},
{
"__type__": "cc.Node",
"_name": "Canvas",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 3
},
{
"__id__": 5
},
{
"__id__": 10
}
],
"_active": true,
"_components": [
{
"__id__": 15
},
{
"__id__": 16
},
{
"__id__": 17
},
{
"__id__": 18
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 812,
"y": 375.00000000000006,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "beI88Z2HpFELqR4T5EMHpg"
},
{
"__type__": "cc.Node",
"_name": "Camera",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 4
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 1000
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "ebFwiq8gBFaYpqYbdoDODe"
},
{
"__type__": "cc.Camera",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": null,
"_projection": 0,
"_priority": 0,
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": 406,
"_near": 0,
"_far": 2000,
"_color": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_depth": 1,
"_stencil": 0,
"_clearFlags": 7,
"_rect": {
"__type__": "cc.Rect",
"x": 0,
"y": 0,
"width": 1,
"height": 1
},
"_aperture": 19,
"_shutter": 7,
"_iso": 0,
"_screenScale": 1,
"_visibility": 1108344832,
"_targetTexture": null,
"_postProcess": null,
"_usePostProcess": false,
"_cameraType": -1,
"_trackingType": 0,
"_id": "63WIch3o5BEYRlXzTT0oWc"
},
{
"__type__": "cc.Node",
"_name": "body1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 6
},
{
"__id__": 7
},
{
"__id__": 8
},
{
"__id__": 9
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "3cd6hNUbNKL7VuMBLAd7oM"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 100,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "9ayLeavm9EUrks0W7/dYzB"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "0d8d3b9f-f695-4e4b-b6cb-969a79079832@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": "812JXT1CBFUpKjXNleeZnw"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"enabledContactListener": false,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 2,
"_allowSleep": true,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": "16h/+FNi9Fw4E/Ery6vAxC"
},
{
"__type__": "cc.CircleCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": false,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_radius": 50,
"_id": "11n1lhxMRANaU8hWbVfbd6"
},
{
"__type__": "cc.Node",
"_name": "body2",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 11
},
{
"__id__": 12
},
{
"__id__": 13
},
{
"__id__": 14
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 286.62799999999993,
"y": -5.684341886080802e-14,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "bdYetCJ45Ml73PPKN8dVbE"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 100,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "26YwRDDrNJhI+YfteUY1ho"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": null,
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "74a5a14c-636e-4b82-a474-43ef9ca6a905@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": "e4VUewtHxCqatUAJDd8j6f"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": null,
"enabledContactListener": false,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 2,
"_allowSleep": true,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": "4adApP7lxIF6RXcMZsFZAP"
},
{
"__type__": "cc.CircleCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": null,
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": false,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_radius": 50,
"_id": "5cOeHeLJ1D456yZYCck5Qb"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 1624,
"height": 750
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "d6rUX5yfhMlKoWX2bSbawx"
},
{
"__type__": "cc.Canvas",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_cameraComponent": {
"__id__": 4
},
"_alignCanvasWithScreen": true,
"_id": "12O/ljcVlEqLmVm3U2gEOQ"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 5.684341886080802e-14,
"_bottom": 5.684341886080802e-14,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": "c5V1EV8IpMtrIvY1OE9t2u"
},
{
"__type__": "dc0b5FMI7tFo4vn+ASGmDWi",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"body1": {
"__id__": 5
},
"body2": {
"__id__": 10
},
"_id": "5dhZFzZF5L2JLarkfFHoyB"
},
{
"__type__": "cc.SceneGlobals",
"ambient": {
"__id__": 20
},
"shadows": {
"__id__": 21
},
"_skybox": {
"__id__": 22
},
"fog": {
"__id__": 23
},
"octree": {
"__id__": 24
},
"skin": {
"__id__": 25
},
"lightProbeInfo": {
"__id__": 26
},
"postSettings": {
"__id__": 27
},
"bakedWithStationaryMainLight": false,
"bakedWithHighpLightmap": false
},
{
"__type__": "cc.AmbientInfo",
"_skyColorHDR": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0.520833125
},
"_skyColor": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0.520833125
},
"_skyIllumHDR": 20000,
"_skyIllum": 20000,
"_groundAlbedoHDR": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"_groundAlbedo": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"_skyColorLDR": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.5,
"z": 0.8,
"w": 1
},
"_skyIllumLDR": 20000,
"_groundAlbedoLDR": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.2,
"z": 0.2,
"w": 1
}
},
{
"__type__": "cc.ShadowsInfo",
"_enabled": false,
"_type": 0,
"_normal": {
"__type__": "cc.Vec3",
"x": 0,
"y": 1,
"z": 0
},
"_distance": 0,
"_planeBias": 1,
"_shadowColor": {
"__type__": "cc.Color",
"r": 76,
"g": 76,
"b": 76,
"a": 255
},
"_maxReceived": 4,
"_size": {
"__type__": "cc.Vec2",
"x": 512,
"y": 512
}
},
{
"__type__": "cc.SkyboxInfo",
"_envLightingType": 0,
"_envmapHDR": null,
"_envmap": null,
"_envmapLDR": null,
"_diffuseMapHDR": null,
"_diffuseMapLDR": null,
"_enabled": false,
"_useHDR": true,
"_editableMaterial": null,
"_reflectionHDR": null,
"_reflectionLDR": null,
"_rotationAngle": 0
},
{
"__type__": "cc.FogInfo",
"_type": 0,
"_fogColor": {
"__type__": "cc.Color",
"r": 200,
"g": 200,
"b": 200,
"a": 255
},
"_enabled": false,
"_fogDensity": 0.3,
"_fogStart": 0.5,
"_fogEnd": 300,
"_fogAtten": 5,
"_fogTop": 1.5,
"_fogRange": 1.2,
"_accurate": false
},
{
"__type__": "cc.OctreeInfo",
"_enabled": false,
"_minPos": {
"__type__": "cc.Vec3",
"x": -1024,
"y": -1024,
"z": -1024
},
"_maxPos": {
"__type__": "cc.Vec3",
"x": 1024,
"y": 1024,
"z": 1024
},
"_depth": 8
},
{
"__type__": "cc.SkinInfo",
"_enabled": false,
"_blurRadius": 0.01,
"_sssIntensity": 3
},
{
"__type__": "cc.LightProbeInfo",
"_giScale": 1,
"_giSamples": 1024,
"_bounces": 2,
"_reduceRinging": 0,
"_showProbe": true,
"_showWireframe": true,
"_showConvex": false,
"_data": null,
"_lightProbeSphereVolume": 1
},
{
"__type__": "cc.PostSettingsInfo",
"_toneMappingType": 0
}
]
\ No newline at end of file
{
"ver": "1.1.50",
"importer": "scene",
"imported": true,
"uuid": "fa60c8c8-f313-4702-b075-c3a33a34c831",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}
import { _decorator, Component, ERaycast2DType, Node, PhysicsGroup, PhysicsSystem2D, Vec2 } from "cc";
const { ccclass, property } = _decorator;
@ccclass('Test')
export class Test extends Component {
@property(Node)
body1: Node = null;
@property(Node)
body2: Node = null;
start() {
PhysicsSystem2D.instance.enable = true;
}
update(deltaTime: number) {
const results = PhysicsSystem2D.instance.raycast(
new Vec2(0, 0),
new Vec2(400, 0),
);
console.log(results);
}
}
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "dc0b514c-23bb-45a3-8be7-f804869835a2",
"files": [],
"subMetas": {},
"userData": {}
}
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