Help with making my enemy AI system performant and synced

First of all, what type of game are you working on? in some types of games you can make some tricks to remove stress from some systems, also why you choose to use OOP, OOP is more redable than performant code, but for AI it’s great. And last thing is you cannot achieve perfect sync of client/server.

There are few solutions:

  • To sync client and server there is the least invasive methood, which is calculating how much time elapsed from tick() sended on server to current tick() on client, using this time you can teleport enemy to the correct position based on time that have passed

  • Don’t use humanoids, they are OK for few enemies but not for hundreds, even disabling states doesn’t help, you can use RunService and Lerps to simulate movement

1 Like

you dont. you just do newLocalPosition = cframe.new(localPosition, targetPosition).Unit * studs * dt * 60 and keep repeating this. this guesstimate of a line of code basically just moves some object towards a target at a constant rate if you couldnt obviously tell.

lerping in this situation is possible as well. each time the target moves, you recalculate the alpha by resetting the time elapsed since the lerp began to zero, and also recalcualting how long it will take to move to the target by using the local fake humanodis walkspeed (studs/sec) and dividing it by the distance (studs/1sec)

create your own fake physics. we know that gravity in roblox is 196.2 studs/sec^2. to get the distance we need to fall, raycast downwards. to get the time, you can rearrange the formula 0.5 * g * t^2 = d in terms of t (sec) where d is distnace (studs) and g is gravity (studs/sec^2)
you can do this entirely on client. and when the server NEEDS to know the exact position of the npc considering terrain, the server can raycast themselves.
this however doesnt pair too well with the lerping method that i described earlier, because doing this would require you to recalculate the lerp alpha when the Y axis of the local object changes

notes:

  • the Y axis from the .Unit thing must be removed

  • unit code doesnt work. do me.CFrame *= CFrame.new((them.Position - me.Position).Unit * .1 * dt * 60)

I am making an action RPG game where the play fights against lots of types of enemies, so the system to handle such enemies needs to be performant. Are you saying I shouldn’t of used OOP? Also with your syncing time point with tick() is that not using the same logic I mentioned in the post – using equations of motion? And yes I know I should not use many humanoids, but I need a way to move then without using moveto

1 Like

I gave you that in my post.
Anchored hrp + Move at a constant rate towards goal + calculate physics (using os.clock and roblox gravity) per local Y axis change or target positional change + keep on relative part when standing by using ToObjectSpace

Only thing i havent considered is collisions which an probably be done by getting the direction each object tbe humanoids listening to, and checking if any of its 4 corners now exceeds the hrps 4 courners, and the pushing the humanoid in some direction
However this behaviour isnt obvious when the humanoid is smushed through blocks for example

ok, there is an option to use trick, player wouldn’t fight 500 of them at once, you should consider making enemies bigger and stronger, then player will feel they overwhelming him, this way from 500 weak enemies you can make 70-100 strong ones, and result would be the same

Sorry I haven’t had a chance to look at your response yet. I will read what you said and respond to you as soon as I can.

1 Like

you’re good

i put my response into code form. pink is a fake humanoid.

its actually the other way around. on server, the custom humanoid should raycast for its physics. on client, the physics should be an actual humanoid, mimicking the movement of the fake.

Okay I see how you can use the cframe updating this with the delta time, I will try that out. I also see how you can use gravity and raycast downwards. However I have a couple questions: how would the server be able to get the position of the enemy using this method, since the client is the one that is simulating the movement – unless I am missing something about what the server is doing? Also what about moving up slopes in terrain or up stacked parts? I would want the enemy to be able to climb up a part if it is barely any taller than the group, but if it is a certain height, the enemy should not be able to move up and instead just stop moving. I guess you could raycast downwards?? If you could show me the important bits of the code you are using to create the movement in the video you showed me both on the client and the server that would be greatly appreciated :slight_smile:

no actually in the system i made, the server is updating its version of the humanoid at a constant rate. custom physics and moveto checks here. and when i finish the system, the server will send the direction, magnitude, and estimated time of the moveto to the client (the client will handle their own physics by just using the default humanoid, making it moveto the replicated position, and then using that humanoid for physics). the server always knows the position of the enemy. the client has a guess based on the direction magnitude and estimated time of the moveto.

so in theory (i programmed it wrong and need to get it working properly) when the npc is moving on server, raycast from the npcs cframe * some forward cframe * up 1 stud, and raycast downwards .8 studs, to see if the npc needs to walk up steps or a slope for example.

hell nah you can def program it yourself

mental breakthrough: on server, parent a part to workspace.currentcamera. notice how it doesnt show on clientside, but shows on server side? now, if you wanted to parent a humanoid to there, you can, and you can create a fake humanoid on clientside to mimick the servers humanoid cframe. however, that comes with some expense on serverside probably.
instead, what if you created an unanchored part on the server in currentcamera, and instead of calculating physics and moveto yourself, you can just calculate the moveto.
this would save a bunch of inaccuracy, raycasting, and would give you collisions as well; the part in currentcamera wont replicate to clients, and thus you can just send the client (at minimum) the direction the npc must go, and the length it must go. easily compressible into 1 number
its absolutely genius what i came up w

Okay but are we not still simulating movement on the server side with humanoids? The whole point of making the system performant is to be able to give clients the sole responsiblity of rendering movement and the server just manages the movements via remote events. I mean you could still use a barebones humanoid with disabled states on the server but this must still drain performance. I still need to implement your previous idea of using custom physics but i have not got around to it yet.

Also i just want to ask, is it feasable to use align orientation and body velocitys? I know that one of those two prevent a part from clipping though the group or toppling over, which would be perfect instead of constantly ray casting. I looked in the vesteria source code and saw that they are using the depricated versions, and the performance of the enemy system in that game is what im trying to get to.

you dont necessarily need humanoids. i thought i made that clear in what i said. wtf is u talkin abt?, respectfully.
also if you WERE to have a humanoid in currentcamera on server, it would not render whatsoever. not visually at least.
but i was saying, if you were to parent a part to currentcamera on server, an unanchored one, and made it moveto a target position using a custom moveto function (using the unit formula i wrote in my first post in notes), not only do you get physics, but you also get collisions.

ive never really used roblox physics instances before. the most ive really done w them is a dash system with body velocity, and a swimmign system.
but you can definitely use that instead of constantly raycasting

Okay, but your idea of having a part in the workspace camera and using the movement logic to move that is still handling movement simulation on the server which is what im trying to avoid. in this post: How we reduced bandwidth usage by 60x in Astro Force (Roblox RTS) their first version of their client/server movement system was to parent a transparent part to the server camera and do it that way, but in the end they found a much better way to handle enemy movement by sending smaller vector values to the client and having the server not perform any movement tasks – a solution that i have tried to implement in my game.

they never said that in the post

they use a custom collision system (+a 2d grid), which is not plausible in your system. you don’t want movement on server apparently, which includes physics. so if one were to handle physics entirely on client there would be a lot of desync. if one were to moveto entirely on client there would be desync. HOWEVER, if someone, ON SERVER, was to apply a moveto to some humanoid in the servers camera, then the humanoid would, obviously, have its moveto cframe applied, but ALSO if its unanchored and cancollide, it would have physics and collisions.
now i see what you’re saying: “but how would i replicate the physics cframing?”
i shouldve expanded my thought process earlier, and im sorry. i missed something very crucial.

so, movement, at least in my head, is defined by a horizontal angle, that is left or right at a maximum of 360 deg, and a vertical angle, which is up and down with a max of 360 deg. to get the direction a humanoid moved over some unit of time, you subtract the previous position from the current position. with direction, you can use the atan2 function, which returns the number of degrees a position is on the graph, from the origin. say you have a cartesian graph, and theres a coordinate (1,1). the angle from the origin (0,0) will be 45 deg, for example.
take this logic on the horizontal and vertical of the humanids new cframe. you would do math.atan2(hum.x, hum.z) to get the angle the humanoid moved horizontally, and hum.y to get the vertical movement. and for the distance traveled you jsut do direction.magnitude.
compress those values into something small, say 1 number, and then send it to the client. the bitstream should look like vertical|horizontal|magnitude or something along the lines of that.
then the client decodes the bitstream, getting the vertical horizontal and magnitude of the new movement. then they multiply their version of the humanoids cframe with ion even know im assuming it would look like: cframe.new(vector3.new(math.cos(horizontal), vertical, math.sin(horizontal)) * magntiude) thats really just a rough guess, but thats the cframe the hunanoid changed by

I found a resource to help you. It answers alot of your questions and tells you how to properly make unlimited humanoids by making it client sided.

Hi there, thank you for sending me this resource. The only thing is that he is using path finding service, which I am not going to use for my game due to its performance issues, and also he is moving them exclusivley on the client side – I need to have some sort of system that at least checks the clients movement, or just tells the client what to do regarding an enemies position.

So im guessing you are tweening or lerping the characters movement? How will the AI know where to go? Are you using your own pathfinding service?

It likely has issues because of network ownership on the npc. If your trying to use a bunch of humanoids lerping or tweening the position also has its costs on the server probably even more than pathfinding. You could have movement on the client but the server sending the client where the npc should go. That way it could have track of where the npc is. Relying on the client on where the npc is, is a dangerous practice.

So in summary

Movement → Client
Path Predicting → Server which passes that info to the clients.

Yeah thats pretty much what im trying to get to. The only thing is I dont know how to keep the client and server synced so the server can get the clients position when needed. In my game, there will be lots of complex terrain, so using simple kenetic equations would not be enough (illustrated in post), and i’m not even sure how to go about terrain height adjustment and stuff like that (i’m trying to avoid using humanoid moveto on the client)

1 Like

Well in that case youll never be able to get all clients be perfectly synced. Simply the client or server will always be in front. On the server if its sending every client data in where to move every second. The client can check, if the npc is too far away then it can teleport them to that place. That way it can make sure and properly predict where the npc is.

The point of the npcs movement being on the client is for the bunch of moving npcs to be smoother and decrease the load on the server. MoveTo wouldnt be a good idea on the server or even lerping it wouldnt be a good ides either. Whatever way works for you but movement should stay on the client.

1 Like