I wanted my enemy’s to have organic gravity instead of them just teleporting to the ground so I was thinking I could just turn translate local into teleport but it didn’t work:
// Basic enemy AI - version 1.4
var Enemy = pc.createScript('enemy');
Enemy.attributes.add('viewDistance', { type: 'number', default: 20, title: 'View Distance', description: 'The distance of a target is being visible' });
Enemy.attributes.add('hearDistance', { type: 'number', default: 10, title: 'Hear Distance', description: 'The distance of a target is being audible' });
Enemy.attributes.add('searchDistance', { type: 'number', default: 10, title: 'Search Distance', description: 'The distance of searching based on the origin' });
Enemy.attributes.add('attackDistance', { type: 'number', default: 5, title: 'Attack Distance', description: 'The distance of attacking based on the equipment' });
Enemy.attributes.add('stopDistance', { type: 'number', default: 2, title: 'Stop Distance', description: 'The stop distance from destination' });
Enemy.attributes.add('moveSpeed', { type: 'number', default: 2, title: 'Move Speed', description: 'The default speed for moving' });
Enemy.attributes.add('rotateSpeed', { type: 'number', default: 2, title: 'Rotate Speed', description: 'The default speed for rotating' });
Enemy.attributes.add('sensorLength', { type: 'number', default: 1, title: 'Sensor Length', description: 'The length of sensors to detect obstacles' });
Enemy.attributes.add('sensorHeight', { type: 'number', default: 1, title: 'Sensor Height', description: 'The height of sensors to detect obstacles' });
Enemy.attributes.add('fleeDistance', {
type: 'number',
default: 20,
title: 'Flee Distance',
description: "Tells the entity if it should attack on sight or not"
})
Enemy.attributes.add('health', { type: 'number', default: 100, title: 'Default Health', aggressive: "The health of the entity" })
Enemy.attributes.add('alignment', {
type: 'number',
enum: [
{ 'passive': 1 },
{ 'aggressive': 2 },
{ 'neutral': 3 }
],
description: "Tells the entity if it should attack on sight or not"
})
Enemy.attributes.add('meleeDistance', { type: "number", default: 3, description: "The distance to attack the player", title: "Melee Distance" })
Enemy.attributes.add('meleeCooldown', { type: "number", default: 3, description: "The cooldown to attack the player", title: 'Melee Cooldown' })
Enemy.attributes.add('doMelee', { type: "boolean", description: "Tells the entity if it should do melee", title: 'Do Melee?' })
Enemy.attributes.add('natScared', {
type: 'boolean',
description: "Tells the entity if it's naturally scared of the player. Haveent tested with aggressive yet"
})
Enemy.attributes.add('showSensors', { type: 'boolean', default: false, title: 'Show Sensors', description: 'Ability to show the sensors for debugging' });
// Initialize code called once per entity
Enemy.prototype.initialize = function () {
console.log('Basic enemy AI - version 1.4 beta');
this.enemy = this.entity;
this.enemyId = pc.guid.create();
this.enemyState = 'Patrol';
this.enemyTargets = this.app.root.findByTag('Target');
this.enemyTarget = null;
this.meleeTimer = 0
this.enemyTimer = 0;
this.enemyMoveSpeed = 0;
this.enemyTargetDistance = 0;
this.enemyTargetPosition = new pc.Vec3();
this.enemyDestination = new pc.Vec3();
this.enemyDirection = new pc.Vec3();
this.enemyRotation = new pc.Vec3();
this.enemyOrigin = this.enemy.getPosition().clone();
this.enemyCanSeeTarget = false;
this.enemyCanHearTarget = false;
this.enemyIsAlive = true;
this.enemyIsAttacking = false;
this.enemyIsMoving = false;
};
// Update code called every frame
Enemy.prototype.update = function (dt) {
this.entity.syncHierarchy()
this.UpdateTarget(dt);
this.UpdateMovement(dt);
this.UpdateAnimation(dt);
this.UpdateState(dt);
};
Enemy.prototype.UpdateTarget = function (dt) {
// Current targets
var targets = [];
// Check all targets in game
for (i = 0; i < this.enemyTargets.length; i++) {
var target = this.enemyTargets[i];
// Reset
target.tags.remove(this.enemyId);
this.enemyCanSeeTarget = false;
this.enemyCanHearTarget = false;
// Check distance to target
var distance = this.enemy.getPosition().distance(target.getPosition());
// Check if target is in view range
if (distance < this.viewDistance) {
var position = target.getPosition();
position.sub(this.enemy.getPosition()).normalize();
var dot = position.dot(this.enemy.forward);
// Check if target is in front
if (dot > 0.5) {
var start = new pc.Vec3(this.enemy.getPosition().x, this.enemy.getPosition().y + this.sensorHeight, this.enemy.getPosition().z);
var end = target.getPosition();
var hit = this.app.systems.rigidbody.raycastFirst(start, end, {
filterCallback: (entity) => entity && entity.rigidbody
});
// Check if target is in view
if (hit && hit.entity === target) {
target.tags.add(this.enemyId);
targets.push(target);
}
}
}
// Check if target is in aubible range
if (distance < this.hearDistance) {
// Check if target is audible
if (target.tags.has('Audible')) {
targets.push(target);
}
}
}
// Find closest target
var closestDistance = 1000;
for (var i = 0; i < targets.length; ++i) {
var target = targets[i];
var distance = target.getPosition().distance(this.entity.getPosition());
if (distance < closestDistance) {
closestDistance = distance;
// Set current target
this.enemyTarget = target;
this.enemyTargetDistance = this.enemy.getPosition().distance(this.enemyTarget.getPosition());
// Target is visible
if (target.tags.has(this.enemyId)) {
this.enemyCanSeeTarget = true;
}
// Target is audible
if (target.tags.has('Audible')) {
this.enemyCanHearTarget = true;
}
}
}
};
// This function update the position and rotation
Enemy.prototype.UpdateMovement = function (dt) {
// Create raycasts
var origin = this.enemy.getPosition();
var start = new pc.Vec3(origin.x, origin.y + this.sensorHeight + 0.5, origin.z);
var startLeft = new pc.Vec3().copy(this.enemy.forward).scale(this.sensorLength).add(start);
var endLeft = new pc.Vec3().copy(this.enemy.right).scale(-this.sensorLength * 1.5).add(startLeft);
var startRight = new pc.Vec3().copy(this.enemy.forward).scale(this.sensorLength).add(start);
var endRight = new pc.Vec3().copy(this.enemy.right).scale(this.sensorLength * 1.5).add(startRight);
var startLeftFront = new pc.Vec3().copy(this.enemy.right).scale(-this.sensorLength / 1.5).add(start);
var endLeftFront = new pc.Vec3().copy(this.enemy.forward).scale(this.sensorLength * 2).add(startLeftFront);
var startRightFront = new pc.Vec3().copy(this.enemy.right).scale(this.sensorLength / 1.5).add(start);
var endRightFront = new pc.Vec3().copy(this.enemy.forward).scale(this.sensorLength * 2).add(startRightFront);
var endLeftBottom = new pc.Vec3(endLeft.x, endLeft.y - this.sensorLength * 10, endLeft.z);
var endRightBottom = new pc.Vec3(endRight.x, endRight.y - this.sensorLength * 10, endRight.z);
var endRightFrontBottom = new pc.Vec3(endRightFront.x, endRightFront.y - this.sensorLength * 10, endRightFront.z);
var endLeftFrontBottom = new pc.Vec3(endLeftFront.x, endLeftFront.y - this.sensorLength * 10, endLeftFront.z);
var endCenterBottom = new pc.Vec3(origin.x, origin.y - this.sensorLength * 10, origin.z);
// Raycast results
var hitLeft = this.RayCast(start, endLeft);
var hitRight = this.RayCast(start, endRight);
var hitLeftFront = this.RayCast(start, endLeftFront);
var hitRightFront = this.RayCast(start, endRightFront);
var hitLeftBottom = this.RayCast(endLeft, endLeftBottom);
var hitRightBottom = this.RayCast(endRight, endRightBottom);
var hitLeftFrontBottom = this.RayCast(endLeftFront, endLeftFrontBottom);
var hitRightFrontBottom = this.RayCast(endRightFront, endRightFrontBottom);
var hitCenterBottom = this.RayCast(start, endCenterBottom);
// Show raycasts
if (this.showSensors) {
this.app.drawLine(start, endLeft, pc.Color.WHITE);
this.app.drawLine(start, endRight, pc.Color.WHITE);
this.app.drawLine(start, endLeftFront, pc.Color.WHITE);
this.app.drawLine(start, endRightFront, pc.Color.WHITE);
this.app.drawLine(endLeft, endLeftBottom, pc.Color.WHITE);
this.app.drawLine(endRight, endRightBottom, pc.Color.WHITE);
this.app.drawLine(endRightFront, endRightFrontBottom, pc.Color.WHITE);
this.app.drawLine(endLeftFront, endLeftFrontBottom, pc.Color.WHITE);
this.app.drawLine(start, endCenterBottom, pc.Color.WHITE);
}
// Keep enemy on ground
/*
if (hitCenterBottom) {
this.entity.setPosition(this.enemy.getPosition().x, hitCenterBottom.point.y, this.enemy.getPosition().z);
}
*/
// If this.enemyDirection.y less than 0 target is on the right and if more than 0 is target is on the left
this.enemyDirection.cross(this.entity.forward, this.enemyRotation.copy(this.enemyDestination).sub(this.entity.getPosition()));
// Reset rotation
var rotation = 0;
// Avoid obstacles on the left
if (hitLeftFront || !hitLeftFrontBottom) {
rotation--;
if (hitLeft || !hitLeftBottom) {
rotation--;
}
}
// Rotate to target on the left
else if (!hitLeft && hitLeftBottom) {
if (this.enemyDirection.y > 0.1) {
rotation++;
}
}
// Avoid obstacles on the right
if (hitRightFront || !hitRightFrontBottom) {
rotation++;
if (hitRight || !hitRightBottom) {
rotation++;
}
}
// Rotate to target on the right
else if (!hitRight && hitRightBottom) {
if (this.enemyDirection.y < -0.1) {
rotation--;
}
}
// Avoid obstacles in front
if ((hitLeftFront || !hitLeftFrontBottom) && (hitRightFront || !hitRightFrontBottom)) {
if ((hitLeft || !hitLeftBottom) && (hitRight || !hitRightBottom)) {
this.enemy.rotate(0, 180, 0);
}
}
// Apply correct rotation
this.enemy.rotate(0, rotation * this.rotateSpeed, 0);
// Move enemy forward
this.enemyIsMoving = false;
if (this.enemyMoveSpeed > 0 && this.enemy.getPosition().distance(this.enemyDestination) > this.stopDistance) {
this.enemy.translateLocal(0, 0, -this.enemyMoveSpeed * dt);
this.enemyIsMoving = true;
}
};
// Prepared function to apply your own animations
Enemy.prototype.UpdateAnimation = function (dt) {
if (this.enemyIsAlive) {
if (this.enemyIsMoving) {
if (this.enemyMoveSpeed > this.moveSpeed) {
this.SetAnimation('Run');
}
else {
this.SetAnimation('Walk');
}
}
else if (this.enemyIsAttacking) {
this.SetAnimation('Attack');
}
else {
this.SetAnimation('Idle');
}
}
else {
this.SetAnimation('Dead');
}
};
// Update state
Enemy.prototype.UpdateState = function (dt) {
// Execute current state
this[this.enemyState](dt);
};
// Patrol state
Enemy.prototype.Patrol = function (dt) {
// Patrol time to destination
this.enemyTimer -= dt;
if (this.enemyTimer < 0) {
// Set new patrol speed, time and destination
this.enemyMoveSpeed = this.moveSpeed;
this.enemyTimer = this.searchDistance / this.enemyMoveSpeed;
this.SetRandomDestination(this.enemyOrigin, this.searchDistance);
}
// What to do if enemy can see target
if (this.enemyCanSeeTarget && this.alignment === 2) {
// What if target is close enough to attack
if (this.enemyTargetDistance < this.attackDistance) {
this.SetState('Combat');
}
// What if target is to far to attack
else {
this.SetState('Chase');
}
}
if (this.enemyCanSeeTarget && this.natScared == true) {
// What if target is close enough to attack
if (this.enemyTargetDistance < this.attackDistance) {
this.SetState('Flee');
}
// What if target is to far to attack
else {
this.SetState('Chase');
}
}
// What to do if enemy can hear target
else if (this.enemyCanHearTarget) {
// Stand still and look
this.enemyTimer = 2;
this.enemyMoveSpeed = 0;
this.SetDestination(this.enemyTarget.getPosition());
}
};
// Chase state
Enemy.prototype.Chase = function (dt) {
// Time to move to target position
this.enemyMoveSpeed = this.moveSpeed * 2;
this.enemyTimer += this.enemyMoveSpeed * dt;
this.SetDestination(this.enemyTarget.getPosition());
// If maximum time is reached
if (this.enemyTimer > this.enemyTargetDistance + this.enemyMoveSpeed) {
this.SetRandomDestination(this.enemyOrigin, this.searchDistance);
this.SetState('Patrol');
// Reset target
this.enemyTarget = null;
}
// What to do if enemy can see target
if (this.enemyCanSeeTarget) {
// Reset timer
this.enemyTimer = 0;
// What if target is close enough to attack
if (this.enemyTargetDistance < this.attackDistance) {
this.SetState('Combat');
}
}
};
//flee state
Enemy.prototype.Flee = function (dt) {
// Time to move to target position
this.enemyMoveSpeed = this.moveSpeed * 3;
this.enemyTimer += this.enemyMoveSpeed * dt;
distanceFromTarget = Helper.getDistance(this.entity.getPosition(), this.enemyTarget.getPosition())
// If maximum time is reached
if (distanceFromTarget > this.fleeDistance) {
this.SetRandomDestination(this.enemyOrigin, this.searchDistance);
this.SetState('Patrol');
// Reset target
this.enemyTarget = null;
}
this.enemyTimer = 0;
// Calculate direction vector from enemy to target
let direction = this.enemyTarget.getPosition().clone().sub(this.entity.getPosition());
// Scale the direction vector by -5 units
let fleeDirection = direction.clone().scale(-5);
// Calculate the destination point
let destination = this.entity.getPosition().clone().add(fleeDirection);
// Set the destination
this.SetDestination(destination);
};
// Combat state
Enemy.prototype.Combat = function (dt) {
// Be sure enemy can see target
if (this.enemyCanSeeTarget) {
// Be sure target is close enought to attack
if (this.enemyTargetDistance < this.attackDistance) {
if (this.doMelee === false) {
this.enemyMoveSpeed = 0;
}
this.SetDestination(this.enemyTarget.getPosition());
// Attack only if enemy is not attacking and not moving
if (!this.enemyIsAttacking && !this.enemyIsMoving && this.doMelee === false) {
this.Attack();
} else {
this.meleeTimer += dt
if (this.meleeTimer > this.meleeCooldown) {
this.Attack();
}
}
}
// What if target is to far to attack
else {
this.SetState('Chase');
}
}
// What if enemy can not see the target
else {
this.SetState('Chase');
}
};
// Dead state
Enemy.prototype.Dead = function (dt) {
// Don't move and stay at position
this.enemyMoveSpeed = 0;
this.SetDestination(this.enemy.getPosition());
};
// Attack example function
Enemy.prototype.Attack = function () {
this.meleeTimer = 0;
if (this.doMelee === false) {
this.enemyIsAttacking = true;
setTimeout(function () {
this.enemyIsAttacking = false;
}.bind(this), 1000);
// Instantiate and fire a new bullet from template
var template = this.app.assets.find('Bullet');
if (template) {
var bullet = template.resource.instantiate();
this.app.root.addChild(bullet);
bullet.setPosition(this.enemy.children[0].getPosition());
this.look = this.enemyTarget.getPosition()
bullet.lookAt(this.look.x, this.look.y + 0.5, this.look.z);
bullet.enabled = true;
}
} else {
this.enemyMoveSpeed = this.moveSpeed * 2.5
let distance = Math.abs(Helper.getDistance(this.entity.getPosition(), this.enemyTarget.getPosition()));
if (distance <= this.meleeDistance) {
setTimeout(function () {
this.enemyMoveSpeed = 0;
// After another 500 milliseconds, revert enemyMoveSpeed back to its original value
setTimeout(function () {
this.enemyMoveSpeed = this.moveSpeed * 2.5; // Restore the original enemyMoveSpeed
}.bind(this), 350);
}.bind(this), 350);
var healthManager = this.enemyTarget.script.health;
if (healthManager) {
// Your health management logic goes here
}
}
}
};
// Set new state
Enemy.prototype.SetState = function (state) {
if (this.enemyState != state) {
this.enemyState = state;
this.enemyTimer = 0;
}
}
// Get random destination around the given position and range
Enemy.prototype.SetRandomDestination = function (position, range) {
var positionX = pc.math.random(position.x - range, position.x + range);
var positionZ = pc.math.random(position.z - range, position.z + range);
this.enemyDestination.copy(new pc.Vec3(positionX, this.enemy.getPosition().y, positionZ));
};
// Set enemy destination based on the given position
Enemy.prototype.SetDestination = function (position) {
this.enemyDestination.copy(new pc.Vec3(position.x, this.enemy.getPosition().y, position.z));
};
// Set new animation
Enemy.prototype.SetAnimation = function (animation) {
if (this.entity.anim && this.entity.anim.enabled) {
if (this.entity.anim.baseLayer.activeState != animation) {
this.entity.anim.baseLayer.transition(animation, 0.2);
}
}
}
// Sensor to detect entities with rigidbody only
Enemy.prototype.RayCast = function (start, end) {
const hit = this.app.systems.rigidbody.raycastFirst(start, end, {
filterCallback: (entity) => entity && entity.rigidbody
&& !entity.tags.has('Ignore') && !entity.tags.has('Target')
});
return hit;
};
Enemy.prototype.damage = function (damage, crit, pos) {
if (this.alignment == 1) {
this.SetState('Flee')
}
if (this.alignment == 2 || this.alignment == 3) {
this.SetState('Chase')
}
this.app.fire('DealtDamage:Trigger', damage, pos, crit);
}