AI scripting for NPCs or BOTs

Hello everyone!

I started to play with PlayCanvas and so far I love it. Came from Unity3D (like running away from hell, it doesn’t support UnityScript anymore) and glad to be part of the web developers group again.

I’m in the process of developing a VR game and got stuck with the AI of the enemies. I think what I’m not understanding here is the way JavaScript can (or cannot) wait X seconds in another thread while continuing the main one.

I’m also having big problems understanding how to move/rotate an entity:

  • Translate/Rotate are for non rigidbodies, right?
  • ApplyForce/ApplyTorque are for rigidbodies, right?
  • For the life of me I cannot deduce how to create a vector representing a random direction to walk on. Do I need to use ApplyForce with pc.Vec3.FORWARD to walk on the X axis, multiplied with a const speed and the deltatime? How can I align my model to that direction? Cause it seems to be walking backwards instead of forwards
  • If I ApplyTorque to my entity it spins and accelerates nonstop in matter of seconds, even when multiplying with deltatime. But again, Rotate is not an option here, right?
  • Is wrong to have an empty entity with a colission capsule, rigidbody and the AI script, and a child entity with the model, the textures and the animations? This way I can align better the model to the collision capsule, and separate the concepts for better understading.
  • How can I know if the enemy is seeing me? Raycasting and checking if the ray collides with me but not with the terrain, I guess?
  • How can I know the vector which represents the direction from where my enemy should move to reach me? I’m a bit a n00b in mathematics and vectors here, sorry.
  • How can I wait X seconds for something to happen? I tried the setTimeout concept but I kind of miss an async/await implementation here. Let’s say I want to rotate during X seconds, and after that, continue my coroutine. Can I do that with a single thread? In Unity3D we had some sort of async coroutine which could idle for a few seconds before continuing.

Maybe I’m explaining myself a bit bad, and maybe (surely) I’m mistaken and it simply cannot be done, but so far the concept of what I’m achieving (based on really simple Unity3D tutorials like https://www.youtube.com/watch?v=aEPSuGlcTUQ) to do is the following (in pseudocode):

// initialize function
initialize () {
  this.isTurningLeft = false
  this.isTurningRight = false
  this.isWandering = false
  this.isMoving = false
  this.isWaiting = false
  this.moveSpeed = 3
  this.rotationSpeed = 100
}
// update function
update (dt) {
  if (!this.isWandering) this.wander()
  if (this.isRotatingRight) this.entity.rigidbody.applyTorque(pc.Vec3.RIGHT * this.rotationSpeed * dt)
  if (this.isRotatingLeft) this.entity.rigidbody.applyTorque(pc.Vec3.LEFT* this.rotationSpeed * dt)
  if (this.isWalking) this.entity.rigidbody.applyForce(pc.Vec3.FORWARD * this.moveSpeed * dt)
}
// wander function, ideally async
wander () {
  rotTime = getRandomInt(1, 5)
  rotWait = getRandomInt(1, 5)
  rotLR = getRandomInt(1, 2) // rotating Left or Right
  walkWait = getRandomInt(1, 5)
  walkTime = getRandomInt(1, 5)  
  this.isWandering = true
  await walkWait * 1000 // <- wait some seconds, how can achieve that? setTimeout?
  this.isWalking = true
  await walkTime * 1000 // <- wait some seconds, how can achieve that? setTimeout?
  if (rotLR === 1) {
    this.isRotatingRight = true
    await rotTime * 1000 // <- wait some seconds, how can achieve that? setTimeout?
    this.isRotatingRight = false
  }
  if (rotLR === 2) {
    this.isRotatingLeft = true
    await rotTime * 1000 // <- wait some seconds, how can achieve that? setTimeout?
    this.isRotatingLeft = false
  }
  this.isWandering = false
}

Can you give me some insight on this subject?

Thanks in advance and congratulations on such a great (and free!) piece of software.

Answers next to the Qs

Things to note, double check that the models are orientated correct to the parent entity so that they are facing down the negative Z axis (forward in PlayCanva’s orientation system).

There’s teleport function that allows you to set the rotation and position of a rigidbody instantly.

Steering NPCs using forces is painful. You may want to use set linearVelocity and angularVelocity instead for finer control.

Okey @yaustar, I think I got things a bit more clearer, thanks!

I recreated my project to test this new information and this is what I’ve done so far:

https://playcanvas.com/editor/scene/648086

I followed your advices of using linearVelocity/angularVelocity instead of applying forces, the local forward direction instead of the global FORWARD one, and implemented a new type of subroutine state machine that might or might not work as expected (probably not).

Just for the record, if you have the time, can you check it out and point me all the bad moves I surely’ve made?

It’s just a mixamo character with 4 animations looped with a simple IA scripting. It turns in the wrong axis, loops the animations with no movement at all, dissapears from the screen ignoring the plane and body colliders, and mocks me to the point of making me sad.

What a heck of an AI, right? :slight_smile:

Thanks in advance!

Looks like its falling over.

Why is the Angular Factor 0 for the Y axis?

image

At the moment, this is set so that it can only rotate on the X axis. Was that deliberate?

Not at all. It was at 0 1 0 initially, I’m just testing myself at the moment, trying to understand what is happening.

I’m starting to think that I’m kind of mixing the local and global coordinates. What I’m trying to accomplish is to walk forward the direction Z of my NPC character, but in the global Z axis, not in my character’s Z axis.

Does that make sense?

1 Like

If you’re trying to move an NPC, in relation to the player, you should use world-coordinate space, right? Because if you use local space on a model, you will end up having a lot of weird directions and things working around.

My advice would be to use simple teleportation, angular velocity, like said above, and of course, use world coordinates as much as possible. I don’t exactly understand the problems here, but I’m here to help if you have any questions (that I can answer :rofl:) I’m not nearly as profficient at this as Yaustar, unfortunately.

Here’s a crazy idea: what if you take the rotations of the collider box, as it takes the model with it, for your normal.forward stuff. There’s no reason to be using the model’s z axis, no?

Once again, if I’m screwing up here, lemme know :wink:

Okay, cool. Let’s summarize a bit, shall we?

First of all, the setup. As I want my NPC (collider and children model) to move only forward (referenced to the collider itself), and turn right/left (again referenced to the collider itself), this is the setup I came up with:

image

Now, the coordinates. I don’t think I should use world coordinates, but instead the local this.entity.forward vector, because I want to move the NPC forward (Z) to what he is facing in that moment, not the global Z (which is allways the same), right? The Y axis works the same I guess, I want my NPC to turn his right, not the world’s right… Right? :smile:

I really think I’m overkilling this simple task right now. If I try to torque my entity, they either do not torque at all, or spins furiously uncontrolled, and can’t even be syncronized with my turn animation at all. The same for moving. If I apply a force with entity.forward, it either doesn’t move at all, or as I increase the digits my NPC dissapears from the map at lightspeed.

I’m sure this has to deal with the Kilograms my NPC weights (50kg), the damping, the factors, the frictions and whatever other mathematical magnitude I’m surely forgetting…

So, to summarize again:

  • To rotate an entity (and its children with it), I should use:
this.entity.rigidbody.applyTorque(this.entity.up * this.rotateSpeed * dt);
  • To move an entity (and its children with it), I should use:
this.entity.rigidbody.applyForce(this.entity.forward * this.moveSpeed * dt);

Or instead:

this.entity.rigidbody.applyTorque(new pc.Vec3(0, this.rotateSpeed * dt, 0);
this.entity.rigidbody.applyForce(new pc.Vec3(0, 0, this.moveSpeed * dt);

Or neither?

You can use world coordinates for rotation and local coordinates for moving forward. Just make sure you use the collider’s normal.forward and not the model’s. That’s all I mean, I think :smiley:

@fergardi I just looked at your project again. It appears you’re using moving animations. That can mess up the fact that your using rigidbody on the collider. Basically, since you don’t want to get into root motion (trust me). When downloading Mixamo animations, make sure to uncheck the little movement checkbox.

I mean, that’s just how I do my animations. You may do things a little differently, I would have no idea.

Do you mean to check the “In Place” checkbox?

Okay, I got it working for the forward anymation. @TheCodeCrafter indeed helped me pointing out about using world torque but local forward, and the static moving animation checkbox mixamo problem.

I got it working with magic numbers, which I hate:

this.entity.rigidbody.applyTorque(pc.Vec3.UP.clone().scale(-130));
this.entity.rigidbody.applyForce(this.entity.forward.clone().scale(-400));

That walks forward at excellent speed, but turns right quite a bit asynchronous from the turn movement. Besides this merely cosmetic enhancement, why are those numbers like that? I want to understand the math behind. It surely has to do with my NPC weight, the friction of the floor, and the torque acceleration.

And going back to the failing animation, what is the correct approach here? If my animation lasts to complete, say, 1 second, and I’m torquing 1000ms, how should I calculate the correct ammount of force provided to the torque to be in sync with the animation ending? And even more, I’m actual torquing while animating to the right, that makes like a double sensation of movement. How should I correct that? Animate without torquing, and then this.entity.rotate (not torquing) the entity to match the animation ending instantly?

I know what you mean :smiley:

With all these physics systems in the world, sometimes it’s pretty difficult to get 'em to work without magic numbers!

As far as I know the force that you’re applying has simply to do with friction of the floor and mass of the character.

I could imagine the equation being something like:

movement = g(force / friction) * mass
movement = g(-400 / friction) * 99

With g being some constant used in the ammo.js engine to keep the unit system working.

I’m not completely suited to answer your questions about these, but I’ll go digging in ammo.js to see if I can find some physics equations (which is obviously, what this is boiling down to :rofl:)

If you have any further questions, I’d be more than happy to help you out with your project!

Well, thank you for that.

I want to know how to make proper rotation animation.

If I torque right/left, and meanwhile animate it (just like I do when walking), then it looks like a double animation, because I’m actually animating while torquing.

How do you deal with that? Do you ignore the turn animation and just torque the character? Do you play de animation without torquing, and when finished, you instantly rotate the character to match the new forward?

Well, so basically you want to make it so that the character rotates and still looks smooth with the animation, no?

I generally implement that by taking a “Strafe Forward Left” and “Strafe Forward Right” animations (In Place, of course) Those animations generally seem to blend well enough to look like rotation footings. Try that! Also: Using torque has never been a problem for me…

Describe it if I got it wrong!

Okey, got it working now! The problem for me was thinking about “root” animation indeed. It’s much easier to find an “static” animation that “looks” good while turning/forwarding, and not making the code match the animation you like.

I got my NPC bot to wander around now. It idles, turns left/right, and walks forward, and repeats. Once ones knows how to, is not as hard as I expected.

Now several more questions, if I may, as I want to know more.

  • How can I make the NPCs “check” for enemies (me) between a radius, “target” me if found, calculate the direction between two points (him and me), and come after me?
  • I’ve never done a raycasting in PlayCanvas before. Do I need vectors, quaternions, euler angles…?
  • The raycasting can detect colliders between two points? Trees, houses, terrain, anything?

Thanks for your patience.

Nice, I got the raycasting working!

But, when an NPC finds someone, and I make it lookAt the target, it kind of loses his rotation angles and starts running backwards or strafing (or that is what I see). Maybe I dont need the entity lookAt the target, but the model, I mean, the first children of the entity?

this.entity.lookAt(this.target.getPosition());
this.entity.children[0].lookAt(this.target.getPosition());

How do you rotate an entity (with dynamic rigidbody) towards a selected point? You cannot torque instantly, because it spins out of control, and you cannot rotate directly, cause you have a rigidbody. I supposed it would work with lookAt, but then it misleads the direction when I applyForce to it.

Unfortunately, that means you’ll have to make use of the rigidbody to acheive needed results. I suggest this sort of formula:

var hitPos = hit.entity.getPosition();
var currentPos = this.entity.getPosition();

var angle = pc.Vec3.normalize(pos1 - pos2);

this.entity.rigidbody.teleport(currentPos.x, currentPos.y, currentPos.z, angle.x, angle.y, angle.z);

Basically, this code will teleport your AI to rotate towards the location of the hit. Obviously you don’t want this to teleport instantly, and I’m sure you’ll figure out a way to rotate it in a way you desire.

Let me know if you have any more issues!

I’m kind of missing something here.

Doesn’t matter how I try to accomplish the entity rotation, it never rotates as expected (or at all).

Given my current setup:

Which consist on:

  • A parent entity with a capsule collider, a dynamic rigidbody and the IA script
  • A child entity with a model, textures and animations
  • Both of them aligned in the same direction (-Z axis, if I read the docs correctly)

If I try to rotate, torque, lookAt, or teleport my parent entity, the model never ends in the direction I need it to be. Does my child entity not rotate in the same direction the parent does? Do I need to rotate/torque/teleport/lookAt both my parent entity and my child entity? I am mising something here?

image

As seen in the image, both my NPCs are running forward each other (I can notice how they reach a common point from far away), but not rotated at all.