From a9c81140964b9d0228bdf895cb63bd37d5d98e44 Mon Sep 17 00:00:00 2001 From: kts of kettek Date: Sun, 30 Jan 2022 00:24:56 -0800 Subject: [PATCH] Add basic animal AI --- Engine/src/live/AnimalEntity.ts | 86 ++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/Engine/src/live/AnimalEntity.ts b/Engine/src/live/AnimalEntity.ts index 2d45683..45245ab 100644 --- a/Engine/src/live/AnimalEntity.ts +++ b/Engine/src/live/AnimalEntity.ts @@ -1,6 +1,6 @@ import * as planck from 'planck' import { AnimalDefinition, CreatureDefinition } from '../data/animals' -import { Action } from "./Action" +import { Action, adjustAction } from "./Action" import { Entity, Sensor } from "./Entity" import { PuddleEntity } from './PuddleEntity' import { WorldContext } from './World' @@ -11,9 +11,13 @@ export class AnimalEntity extends Entity { isPlayer: boolean = false isMonster: boolean = false thinkTimer: number = 0 // Time since last thought, used by AI. - target?: Entity + wanderTimer: number = 0 // Time since last wander. + nextWander: number = 100 // Time to next wander. + smelledTarget?: Entity + seenTarget?: Entity def: AnimalDefinition mode: CreatureDefinition + desiredActions: Action[] = [] constructor(def: AnimalDefinition) { super(`${def.name}.animal.stand.west.0`) @@ -110,7 +114,7 @@ export class AnimalEntity extends Entity { if (Math.abs(this.velocity[1]) < this.mode.maxSpeed) { this.velocity[1] -= Math.sin(r) * this.mode.acceleration } - let cardinal = this.getCardinal() + let cardinal = this.getCardinal(this.direction) this.sprite.setKey = 'run' if (this.sprite.subsetKey !== cardinal || this.sprite.subsetKey !== 'run') { this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${cardinal}.${this.sprite.frameIndex}`) @@ -119,7 +123,7 @@ export class AnimalEntity extends Entity { } else { this.sprite.animate = false this.sprite.setKey = 'stand' - this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${this.getCardinal()}.0`) + this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${this.getCardinal(this.direction)}.0`) } // @@ -129,9 +133,9 @@ export class AnimalEntity extends Entity { // Eh... let's manually handle velocity this.body?.setLinearVelocity(planck.Vec2(this.velocity[0], this.velocity[1])) } - getCardinal(): string { + getCardinal(direction: number): string { const degreesPerDirection = 360 / 4 - const angle = this.direction + degreesPerDirection / 2 + const angle = direction + degreesPerDirection / 2 if (angle >= 0 * degreesPerDirection && angle < 1 * degreesPerDirection) { return 'west' @@ -144,19 +148,51 @@ export class AnimalEntity extends Entity { } think(delta: number) { this.thinkTimer += delta - while (this.thinkTimer >= 500) { + while (this.thinkTimer >= 100) { if (!this.isMonster) { - if (this.target) { - // Flee from target + if (this.seenTarget) { + let r = Math.atan2(this.y-this.seenTarget.y, this.x-this.seenTarget.x) + let a = r * (180 / Math.PI) + if (a < 0) { + a += 360 + } + a -= 180 + if (a < 0) { + a = 360 - a + } + this.desiredActions = adjustAction([], this.getCardinal(a), 0.85) + this.desiredActions = adjustAction(this.desiredActions, this.getCardinal(Math.random()*360), Math.random()) + } else if (this.smelledTarget) { + let r = Math.atan2(this.y-this.smelledTarget.y, this.x-this.smelledTarget.x) + let a = r * (180 / Math.PI) + if (a < 0) { + a += 360 + } + a -= 180 + if (a < 0) { + a = 360 - a + } + // 5% to walk away, otherwise just chill. + if (Math.random() > 0.95) { + this.desiredActions = adjustAction([], this.getCardinal(a), 0.85) + } } else { - // Wander. + this.wanderTimer += delta + if (this.wanderTimer >= this.nextWander) { + this.nextWander = Math.max(10, Math.random() * 100) + if (Math.random() > 0.15) { + this.desiredActions = [] + } else { + this.desiredActions = adjustAction([], this.getCardinal(Math.random()*360), 1) + } + this.wanderTimer = 0 + } } } - this.thinkTimer -= 500 + this.thinkTimer -= 100 + this.act(this.desiredActions) } } - setTarget(entity: Entity) { - } sense(sensor: Sensor, entity: AnimalEntity) { if (this.isPlayer) return if (sensor.type === 'long') { @@ -175,38 +211,22 @@ export class AnimalEntity extends Entity { } smell(entity: AnimalEntity) { if (entity.isPlayer) { - if (!this.isMonster) { - console.log('alert, we can smell player') - } else { - console.log('we smell the player, wander towards them') - } + this.smelledTarget = entity } } unsmell(entity: AnimalEntity) { if (entity.isPlayer) { - if (!this.isMonster) { - console.log('no longer smell player, wander around') - } else { - console.log('no longer smell player, wander around') - } + this.smelledTarget = undefined } } see(entity: AnimalEntity) { if (entity.isPlayer) { - if (!this.isMonster) { - console.log('oh no, run from player') - } else { - console.log('oh ho, chase player') - } + this.seenTarget = entity } } unsee(entity: AnimalEntity) { if (entity.isPlayer) { - if (!this.isMonster) { - console.log('oh ye, we safe') - } else { - console.log('hunt last seen location') - } + this.seenTarget = undefined } } }