Crowd management

Game developement is funny. Especially for the one who does regular frontend like me for example. For someone who thinks in other categories and architectural patterns.

Like, how do you move the players model and syncronize its movement with the steps animation so that the leg does not slide the surface but stays in place until the next step?

 We have the path to go, the model to animate and the ground. But how do you rotate the character in the right direction? If he went in the straight line you could just rotate him in the direction of the last point. But path a is broken curve and if you do so, he will go crab-like.

Some might say that you could rotate the player every time he reaches the next path control point. But that won't give you suitable result because he will be rotated too fast. After days of experiments and searching I found the solution (How to move objects along a spline): place a Besier curve through all the control points and re-face the player model as is moves. Now you can see smooth cool movement (with some bugs, but let's don't talk about it).

Yay! But there is a spoon of tar: three.js animations don't play by themselves, you should imperatively control them by using Clock object. And it works bad if there is a lot of work to be done in the scene: if you rotate the camera in the process, the animation will stuck. It definately great for animating unnesessary objects, but not the player object. In order to make this task super-duper-priority, I wrote custom implementation using webworker.
Full code.

Movement without worker
const ctx: Worker = self as any;
 
 let lastTime: number;
 let scheduleNextCall = true;
 
 function tick() {
   const currentTime = performance.now();
   ctx.postMessage({
     useFrame: {
       delta: (currentTime - lastTime) / 1000, // in seconds
     },
   });
   lastTime = currentTime;
   if (scheduleNextCall) {
     requestAnimationFrame(tick);
   }
 }
 
 // Respond to message from parent thread
 ctx.addEventListener('message', (e) => {
   if (!e.data.useFrame) {
     return;
   }
   if (e.data.useFrame.enabled) {
     scheduleNextCall = true;
     lastTime = performance.now();
     tick();
   } else {
     scheduleNextCall = false;
   }
 });
Movement with worker

After some confession I decided to add some more characters.

React architecture forces to use imperative management: when a component receives a new path it reacts with starting animations and moving the model along the way. This property is being passed by the parent component so if it changes during the movement, the will be big badaboom. I'm sure this doesn't take place in mature game engines but as long as I use react-three-fiber, I need to follow the rules. I needed some manager which could manipulate the models like a puppeteer. The most suitable solution is to use Redux.

const [activePlayer, setActivePlayer] = useState('oldman');
                 
 const [playersState, dispatch] = useReducer(playersReducer, {
   movement: playerIds.reduce((acc, id) => ({ ...acc, [id]: null }), {}),
   position: playerIds.reduce((acc, id, index) => ({ ...acc, [id]: [25 + index, 0, 25 + index] }), {}),
 });
 
 function handleFloorClick(e) {
   e.stopPropagation();
   if (isWorkerBusy) {
     return;
   }
   dispatch(movePlayer(activePlayer, path));
 }
 
 <Context.Provider value={{ dispatch, playersState }}> 
   {playerIds.map((id) => <Character 
     id={id} 
     position={playersState.position[id]} 
     model="player/Manequin" 
     isSelected={activePlayer === id} 
   />)} 
 </Context.Provider>                
Now the game controller is searching the path and passes it to selected unit. It, in turn, notifies the cotroller about the fact that it reached the end.

Finally, when each NPC can move it's own way, I should probably teach them to go together. Just find the path for each of the selected characters separately if the final point is reachable.

There is a demo scene where you can try it in action. Hold left shift button to select more than one NPC.

21 february 2021
Передвижение по отдельностиПередвижение группойВыделение группы