How would I make the enemy ai support dynamic bodies

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);

}

Hi @Literally_Kirby!

Unfortunately, I don’t think it’s possible to keep a stable AI if you use a dynamic rigidbody. It also will require some more effort to adjust the script.

What is the reason you want to use a dynamic rigidbody for the AI?

Thinking about it again, I don’t see why the AI can’t be stable with a dynamic rigidbody. There will be less risk the AI is moving into obstacles.

At least you need to change the UpdateMovement function. For example you need use rigidbody.applyForce instead of translateLocal and you need to use applyTorque instead of rotate.

ill see if that works, and i want it to be dynamic so the enemy doesnt just teleport to the ground.

Alright, that’s a feature and you can also disable or modify this feature.

sorry about that, ok so i’ve semi support for dynamic bodies now but the enemy can still walk off the map if it can’t turn in time, but I couldnt find where in ur code where you handled that, you mind showing me?

Did you already adjust the value of the rotation speed attribute? This value probably need to be higher to get enough force.

i eventually figured it out heres my script and settings:

// 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('forcemulti', { type: 'number', default: 1000, title: 'Force Multiplier', 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.UpdateTarget(dt);
    this.UpdateMovement(dt);
    this.UpdateAnimation(dt);
    this.UpdateState(dt);
};

Enemy.prototype.UpdateTarget = function (dt) {
    //this.entity.syncHierarchy()
    // 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)) {
            if (this.entity.rigidbody.type == "dynamic") {
                this.enemyMoveSpeed = 0
                this.enemy.rigidbody.applyTorque(0, 180, 0);
            }

            if (this.entity.rigidbody.type == "kinematic") {

                this.enemy.rotate(0, 180, 0);
            }

        }
    }

    // Apply correct rotation
    if (this.entity.rigidbody.type == "dynamic") {
        this.enemy.rigidbody.applyTorque(0, rotation * this.rotateSpeed, 0);
    }

    if (this.entity.rigidbody.type == "kinematic") {

        this.enemy.rotate(0, rotation * this.rotateSpeed, 0);
    }


    // Move enemy forward
    this.enemyIsMoving = false;
    move = this.entity.forward
    move.normalize()
    move.scale(this.enemyMoveSpeed * this.forcemulti)
    if (this.enemyMoveSpeed > 0 && (this.enemy.getPosition().distance(this.enemyDestination) + 3) > this.stopDistance) {
        if (this.entity.rigidbody.type == "dynamic") {
            this.enemy.rigidbody.applyForce(move);
        }

        if (this.entity.rigidbody.type == "kinematic") {
            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.5;
                // 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);
    this.health -= damage

    if (this.health <= 0) {
        this.death()
    }

}

Enemy.prototype.death = function () {
    this.SetState('Dead')
    setTimeout(function () {
        this.entity.destroy()

    }.bind(this), 1000);
}

Screenshot 2024-03-21 2.23.59 PM

1 Like

Thanks for sharing, I will check this as well when I have some more time.

What are your values for the speed and rotate attributes @Literally_Kirby?

i had rotate at around 75 and speed at 2 but the force multiplier at 1000

1 Like