First Person Shooter (FPS) gaming engine (alpha version demo)

I decided to make an alpha-version of gaming engine that I am currently developing available to the public (temporarily).

A discrete graphics card is highly recommended. 8 years old (ish) gaming PC or laptop should be fine. Don’t open it on mobile devices. It may load fine, but controls are not designed for mobile anyway.

You will need to download about 28.6MB (2.7 for menu and the rest for the level). But there are just static files which are very browser cache friendly.

I recommend Chrome or Edge browsers for the best results.

Here is sandbox-demo: https://babylonjs-fps.com

And here is what you can do.

In the level:

Push light objects (like crates and white cylinders) by running into them (like in Half-Life 2).

Pick up pickable objects from the floor by pressing the ACTIVATE button (F.E.A.R. style).

Pickup floating objects by walking into them (Quake style).

Most interactive objects have tooltips.

Use a variety of weapons. Some weapons may have fire modes (“B”), melee attack (“MOUSE4”), aiming down the sights to reduce recoil (“MOUSE2”), tactic and full reloads. Not all weapons have all the features. Also refer to keyboard bindings for more.

You can hide your gun in the holster by pressing the corresponding gun key again.

Bullets are physical and they lose altitude with the distance.

Bullets can ricochet. Ricochets are not dangerous, but still physical and can slightly push objects, including yourself.

Guns have accuracy and recoil features. Recoil is huge for the rocket launcher.

Explosions are smart, they don’t hurt through walls like in GTA 3/Vice City.

Doors can be opened using ACTIVATE button or using remote switch (configurable). There are sliding doors (opens upwards) and rotating doors (like normal ones).

Ascending stairs is harder than descending, just like in real life. Honestly, it was made by happy accident.

There are different kinds of monsters. Red ones (except turrets) have only melee attacks. Blue ones have melee and range attack. There is also a stationary turret (which is quite deadly).

Monsters can detour each other and obstacles; they also can climb stairs. I use spatial partitioning to check for collisions between objects only in proximity. Technically the engine can handle huge crowds of monsters (AKA slaughter maps), but it depends on mesh complexity of course.

If monsters accidentally hurt each other, they start to in-fight (Doom style).

Monsters will attack you if they see you (after you stepped in their FOV) or if you hurt them. However, they are deaf for now, so, your noises don’t bother them unless you hurt them.

If monsters lose visual contact with the target, they still try to chase and find it.

Player drops gun on death.

There are 3 types of triggers: functional (triggers function, for example, remote explosion in this case), teleport and damage zone.

Monsters can activate triggers too (configurable).

Damage zone can damage only players, only monsters or both (configurable).

Monsters can collect floating medkits if they accidentally run into them. Thus, monsters can even exceed their initial health (80 HP), but no more than predefined maximum (200HP). If the monster already has maximum possible health, he will leave medkit in place.

You can break the glass with bullets, explosions or melee attacks.

Two floating red panels are switches. Press ACTIVATE next to them. One just permanently changes color. The second one opens the nearby door and can be used multiple times.

In the main menu:

Change keyboard bindings and sound settings.

Choose chapter. Now available only test level and placeholder demo-scene.

Load and save game. Saving game forces downloading the save-file. Saving is optimized, I don’t save the whole scene or meshes, instead I save only necessary in-game objects states, so save files are relatively small. To load the game, you need to choose the previously saved file on your drive. Saving will work only in the levels or scenes where it’s explicitly enabled. It’s enabled for Test Level 0. Save files are generated (and loaded) on the client, so they don’t add to network usage. Quick save is bounded to F5 and quick load is bounded to F9. Default behavior for those buttons is prevented.

Start new game (do it!).

Return to game or main menu. Available when playing level.

Keyboard navigation works.

Other funny things to do:

You can push objects with bullets, including pickable guns and med kits.

You can walk on top of the glass.

Explosive barrels!

Smart sound queue is used. If many explosions happen simultaneously, your ear drums will not explode (compare to explosion of huge cluster of Revenant missiles in Doom 2).

You can fool around with the main menu background a little on the first screen.

You can throw away your gun (“G”) with all the ammo in the magazine. It doesn’t affect the ammo saved in your backpack/ammo belt. You can pick up dropped gun again with the same ammo in magazine.

Implemented, but not included in demo:

Monsters can step on each other (can be configured individually).

Monsters can patrol the designated route (if set).

Strong wind can push the player and objects. But you can create safe zones, protected from the wind. Functionality needs to be totally remade within new spatial partitioning architecture.

Server info:

I configured HTTP2 on the server, so all resources can be downloaded faster due to unlimited number of simultaneous requests. I also wanted to implement Server Push, but it appears to be deprecated in most of the browsers, while the new Early Hints are not useful for static websites, which is the case. So, I don’t use any of those. I don’t use any CDNs like CloudFront, because it costs money. But the download speed is not that bad anyway. Anywhere in the US it is 1-2 seconds literally. In remote locations, especially with worse internet, it may take some time (even up to 20-30 seconds). But again, the content is very browser cache friendly.

Everything runs on t2.micro EC2 instance in AWS, which is Free Tier Eligible, but I still have to pay for outbound traffic and public IP usage though :frowning:. I also installed free HTTPS certificates which will expire in about 50 days from now. HTTPS is required for HTTP2. I may update certificates, but everything will continue to work even after expiration (you will just start to see annoying browser warning). I configured automatic redirect HTTP->HTTPS. But use HTTPS link from above just to be sure.

If you know any free webhosting with HTTP2 + HTTPS for just static files: HTML/JS/images, please let me know.

Currently, I am desperately looking for a job in LA or remote anywhere in the US. Not necessarily BabylonJS related. I have more than 10 years of experience as a web-developer. Mostly frontend (Angular, React, Next.js) but experienced with backend and full stack (NodeJS, Python). AWS certified. My LinkedIn.

UPDATES from 2024-12-16: See the comment 18

13 Likes

This is so cool! I love the ricocheting bullets. Tons more features I gotta try out in your demo :slight_smile:

Cloudflare Pages can be a great option here

Sweet stuff splash27, and many cool features! :slight_smile:

I would reduce the time used on switching weapons. But of course it depends if your fps is more aimed at a slower tactical game play, and not fast paced (like doom).

What is your plans with the project? a licensed fps gaming engine? Multiplayer fps?

I am working on a similar project (no public demo available yet), and if you are interested here is some code for a simple crosshair using Babylon GUI:

Good luck with it! I will be looking forward to see how your project evolves over time :metal:

1 Like

Thank you, @Basic.

I plan to enrich the engine with features and create some small demo campaign to showcase main mechanics. I don’t think I will do an actual game, because in this case I will need serious collaborations. Game is first of all assets (graphics and other) which is mostly an art work rather than code. So most likely I will go the licensed engine path with free non-commercial use.

The engine is not limited only to games. I developed pretty solid scene switching and runtime control mechanics. Practically, you can copy/paste any playground scene and attach it as a separate level file, then the engine will display it in “Chapter Select” menu. You can also create a separate recourse pack for each level, which is just a JS array of strings defining the resources that you are going to use in the current level. Each resource string must have a corresponding “loader” function that may do some post-processing of loaded resource. Typical usage: upon loading weapon mesh a corresponding weapon class instance is being created that will control weapon animations and state.

Each level has a megastructure named “dependencies” that hosts processed resources and other logical objects of the level, so they can easily reference each other. Each level in definition has 3 “awaitable” load points: static, dynamic and “from save file”.
So, there are two ways to load the level:
a) Static data + dynamic data
b) Static data + save file
Static data is always being loaded upon level load, while dynamic data is a variable data that can be optionally saved and loaded from file.
Using wisely the dependencies and load points you can control the flow of the level achieving very small save files mentioned earlier.
But you don’t need to be overwhelmed by all of that if you don’t need to. If you always want to load the scene in only one possible original state, you can just define everything in static data as you would do in playground. So, the level doesn’t have to be a playable level. It can be a cutscene or funny cartoon. You can also put the scene on pause.

You can add helper classes to your codebase or use ones provided by me, which are mostly FPS centered: explosions, destruction, sector management (spatial partitioning), etc. You can add class instances into dependencies to make it available between load points and components.

Honestly, I didn’t have multiplayer in mind while making this engine. But some social features may appear later.

Yes, add this ASAP please :slight_smile:

Hi, a lot of work has gone into this. Could you tell me how you’re implementing collision detection? Are you using the Havok physics engine or CannonJS?

For physical collisions I initially used CannonJS, but then migrated to Havok.

If you ask about collision prevention for moving monsters, I wrote about it here.
However, I updated it a little bit in my code since then. I started to use Thin Instances for the debugging grid to improve performance and also fixed some other minor issues.

2 Likes

Understandable why you focus on the engine, and not making a game.
Sounds like a great plan with a licensed engine with free non-commercial use!

Are you switching out the entire scene, or unloading/loading assets into a single scene?
My project has XR as a focus, and I ran into the problem of staying in XR mode when switching scenes.
I ended up using a single scene and loading/unloading assets as needed.
See more here WebXR across multiple scenes - #5 by Basic

Your sector-management (spatial partitioning) is a great idea too to keep the scene performant :slight_smile:
Does your projectiles and the player also use the same sector-management?

I am switching scenes. I actually had one problem with that before. But after I started calling native dispose method on the scene, before assigning the new scene my problem got resolved.

Physical projectile (ballistics) is fully managed by Havok. I believe Havok uses spatial partitioning itself under the hood. When a physical collision is detected, I check the other object to see if it can be damaged (has DestructibleObject in metadata). If so then the damage is applied.

Player and any other object that can move (be moved) and might be considered as an obstacle for others is a part of sector manager dynamic grid. Even pushable physical meshes like crates or barrels are parts of sector manager dynamic grid.
In order to be added to dynamic grid correctly an entity should extend the ChainEntity abstract class. I call it ChainEntity, because entities are stored as Linked List in dynamic grid sectors. Thus, it’s easier to go through all entities in the sector and move entity between sectors. So, the player, crate, barrel and every monster are a ChainEntity. ChainEntity has dimensional properties, that may refer to bounding box or an actual mesh (for extreme precision), current sector number and an actual class name.
Explosions also use sector manager to figure out the destructible objects in proximity.

Triggers, however, are not parts of sector manager yet. I assume that trigger collection is not supposed to be huge per level, so I can afford to store it separately for now.

I am currently working on the projectile implementation in my game.
Right now I just move my projectiles using a velocity vector, multiplier and applying it to my transform component position.

const position = this.transformComponent.position;
    const updatedPosition = position.add(
      this.velocity.scale(deltaTime * this.multiplier),
    );
    this.transformComponent.setPosition(
      updatedPosition.x,
      updatedPosition.y,
      updatedPosition.z,
    );

I’ve been thinking on how I should handle the collision, but I think I might also go with Havok :slight_smile:
My game is going to be multiplayer-only, so my plan is to have the clients calculate the collision and report it to the server, which can in a later implementation verify it, either by doing the calculation itself, or request verification from another client.

Instead of a DestructibleObject my plan for the MVP is to use a Damage (for projectile) and Health (for player) components to determine when a player is hit and apply the damage.

For collision, the projectile will most likely just use a Sphere shape, and the player will (for now) use a Cylinder shape (later on a PhysicsShapeContainer with multiple shapes)

Good idea of using linked lists to store the (chained)-entities in the dynamic grid sector, especially for performance.

Do you plan on adding arms to the player, or are you going to keep it simple with a floating gun?

It’s practically the same. DestructibleObject contains health information. But I also add maxHealth, applyDamageHandler and dieHandler. This gives me flexibility in how to deduct health, how to heal and what to do when health is 0. One very basic example is that you can implement immunity or armor for certain type of incoming damage. Also, having a DestructibleObject in mesh metadata means the difference between regular barrel and explosive barrel. :slight_smile:

Arms are just continuation of the weapon mesh. My engine can handle it, no problem. Each gun may have nonPickedMesh (for example a gun that lies on the floor) and pickedMesh (a gun while you are holding it). This enables difference in detalization for picked and non-picked gun meshes for performance purposes. if your pickedMesh has animated arms, it will work right away in my engine. But as I mentioned before, making animated arms is more like an artwork rather than coding.

Soft particles could be a nice addition for the explosion sprites. See e.g. here.

2 Likes

Good idea with the flexibility of how the health will be deducted.

Since my plan is to have my game always connected, it will be up to the server to check up on what is going to happen when there is an collision registered between two entities.

I have also done some initial unfinished implementation of an item system, and I have the same goal of letting the user equip an armor/shield, which would reduce the damage taken,
but all that calculation will be done server-side.

Makes sense that the arms are just a continuation of the weapon mesh. I plan to have various player models, and therefore various player arms, so I am not sure if I could use the
same approach.

Also because I want my game to support VR, the arms/hands should follow the tracking of the player, and I would instead attach the weapon to the player hand instead.
If run on PC+Mobile, the arms would be controlled by an animation, and the weapon would be attached to the hand bone.

Thanks for the conversation @splash27, it is very helpful sparring to get some ideas and thoughts on how to implement things :slight_smile:

1 Like

I slightly updated the demo. It’s still available under the same link in the first post.

Now flying rockets, collectable medkits and triggers also rely on SectorManager (aka spatial partition) which improves performance and makes the engine runtime/state more uniform. It actually enabled some new features that I just added to the demo, particularly:

  • Monsters can activate triggers too (configurable).
  • Damage zone can damage only players, only monsters, or both (configurable).
  • Monsters can collect floating medkits if they accidentally run into them. Thus, monsters can even exceed their initial health (80 HP), but no more than predefined maximum (200HP). If the monster already has maximum possible health, he will leave medkit in place.

Also updated BabylonJS up to 7.25.1.

I also added this info to the original post.

2 Likes

Hey @splash27 is there any chance you will be open-sourcing this some day? I’d love to add it awesome-babylonjs.

1 Like

It will be open-sourced when I make it look more finished. It will take a while however.

By the way, I stream the development process on my YouTube channel: https://www.youtube.com/@splash27/streams
And couple of streams ago I switched to English language, so more people can understand what I am talking about. :slight_smile:

Updated the demo. Link is still the same: https://babylonjs-fps.com/
Here’s what’s new.

New Monster:
I introduced a new monster that I called CatDemon. Any similarity to existing monsters, living or dead, is purely coincidental. :slight_smile:
It’s my first 3D model made end-to-end and it looks… awesome :smiley:
Seriously, it turned out to be much cooler than I expected.
On the side note, gun models were also made by me for 90%. I downloaded some open-source pack, then heavily edited, rigged and animated by myself, but at least I had some starting geometry there.
What is most important here is that I initiated some sort of code framework in the project to import external monsters.
The CatDemon is quite rich in behavior. It has idle, angry, chasing animations as well as range and melee attacks and dying animation. It also makes funny noises along the way.
For range attack it shoots fireball arcs. The projectile is still physical and slightly loses altitude as it goes, also if you are extremely good at reactions, you may hit projectile with a bullet and dismantle it.
On collision projectiles create small explosion which is purely cosmetical (no splash damage like for rockets and grenades).
For melee attack it tries to bite the target.
CatDemon is beefier compared to existing debugging monsters (400 HP compared to just 80 HP).
It can in-fight with other monsters.
I also added dubugMode for monster instances (shows physical bodies and BabylonJS bounding boxes).

Optimizations:
I heavily optimized ray casting calls.

  • Now rays originate outside of the source mesh instead of its origin. It excludes at least one mesh (potentially heavy) from consideration. Given the number of meshes and frequency of ray casts it gives the highest performance boost.
  • Caching raycast results. If raycast results between same objects are needed multiple times during the same frame, I don’t cast ray again anymore, I use cached result instead.
  • Changed the raycast result processing algorithm from O(n*m) to O(n + m). It can be improved even further up to O(m), but isn’t worth it, because m and n are quite low anyway. This improvement gives barely noticeable effect, since I already use spacial partitioning to minimize interactions only to local areas.

Still looking for further performance improvements. Ray casts give the major slowdown so far according to profiler. Does GPU boosted rays exist?

Bug fixes:

  • Removed obsolete code that caused synced animations to throttle. Now gun animations run smoother.
  • Fixed bugs with life time of throwable projectiles.
  • Fixed some crashes on loading of save files.
  • Fixed crashes when monster is attacked by natural disaster (like triggered explosions) which doesn’t assign a target to the monster.

Back-end:

  • Changed underlying OS on hosting which allowed to install certbot without the pain. Now HTTPS certificates should be updated automatically. HTTPS is crucial for HTTP2.0 which is crucial for multiple resource fetching.
  • Changed the minifying tool. Downloadable package was reduced to 28.6MB (2.7MB for the menu and the rest for the Test Level 0). It also fixed some crashes while loading from save file.

Despite the fact that I solved the problem with free HTTPS updates I still may consider taking the demo down soon, because my Free Tier on AWS is almost expired. Or maybe I will migrate everything to other hosting. Anyway, the demo will be available for at least 3 more weeks from now.

6 Likes