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": {}
}
This diff is collapsed.
{
"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": {}
}
This diff is collapsed.
{
"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": {}
}
This diff is collapsed.
This diff is collapsed.
{
"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 { ...@@ -101,7 +101,7 @@ export class Snake extends Component {
// 设置身体部分的贴图 // 设置身体部分的贴图
body.getComponent(Sprite).spriteFrame = i % 2 == 0 ? this.imgBody1 : this.imgBody2; body.getComponent(Sprite).spriteFrame = i % 2 == 0 ? this.imgBody1 : this.imgBody2;
// body.active = false; body.active = false;
this.bodyArr.push(body); this.bodyArr.push(body);
} }
...@@ -189,12 +189,12 @@ export class Snake extends Component { ...@@ -189,12 +189,12 @@ export class Snake extends Component {
newBody.getComponent(Sprite).spriteFrame = len % 2 == 0 ? this.imgBody1 : this.imgBody2; newBody.getComponent(Sprite).spriteFrame = len % 2 == 0 ? this.imgBody1 : this.imgBody2;
newBody.getComponent(Collider2D).tag = this.tag; newBody.getComponent(Collider2D).tag = this.tag;
// newBody.active = isIntersect( newBody.active = isIntersect(
// newBody.getPosition(), newBody.getPosition(),
// this.head.getPosition(), this.head.getPosition(),
// this.vw, this.vw,
// this.vh this.vh
// ); );
this.bodyArr.splice(len, 0, newBody); this.bodyArr.splice(len, 0, newBody);
} }
...@@ -271,12 +271,12 @@ export class Snake extends Component { ...@@ -271,12 +271,12 @@ export class Snake extends Component {
body.setScale(this.scale, this.scale); body.setScale(this.scale, this.scale);
body.setSiblingIndex(this.bodyArr.length - i); body.setSiblingIndex(this.bodyArr.length - i);
// body.active = isIntersect( body.active = isIntersect(
// targetPos, targetPos,
// this.head.getPosition(), this.head.getPosition(),
// this.vw, this.vw,
// this.vh this.vh
// ); );
} }
} }
...@@ -302,19 +302,21 @@ export class Snake extends Component { ...@@ -302,19 +302,21 @@ export class Snake extends Component {
this.isLife = false; this.isLife = false;
this.node.active = false; this.node.active = false;
console.log(MainGame.ins.animalNode.children.length)
this.initFond(this.bodyArr.length); this.initFond(this.bodyArr.length);
} }
protected getNewPos(angle: number, dt: number, currentPos: Vec3, speed: number = this.speed): Vec3 { protected getNewPos(angle: number, dt: number, currentPos: Vec3, speed: number = this.speed): Vec3 {
const radian = angle / 180 * Math.PI; const direction = this.getVelocity(angle);
const direction = v2(Math.cos(radian), Math.sin(radian));
return v3(currentPos.x + dt * direction.x * speed, currentPos.y + dt * direction.y * speed, 0); 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": {}
}
This diff is collapsed.
{
"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