Creating Your Own 3D Shooter Using the React and Three.js Stack — Part 2

Written by varlab | Published 2023/11/15
Tech Story Tags: three.js | r3f | tween.js | first-person-shooter | react-three-fiber-guide | create-your-own-3d-shooter | create-your-own-video-game | gaming

TLDRIn the era of active development of web technologies and interactive applications, 3D-graphics is becoming more and more relevant and in demand. But how to create a 3D application without losing the advantages of web development? In this article, we will look at how to combine the power of Three.js with the flexibility of React to create your own game right in the browser. This article will introduce you to the React Three Fiber library and teach you how to create interactive 3D games.via the TL;DR App

Introduction

In modern web development, the boundaries between classic and web applications are blurring every day. Today, we can create not only interactive websites but also full-fledged games right in the browser. One of the tools that makes this possible is the React Three Fiber library - a powerful tool for creating 3D graphics based on Three.js using React technology.

In today's article, we will implement:

  • weapon aiming animation;
  • flash animation when firing;
  • add a sound effect when firing a gun.

Repository on GitHub

https://youtu.be/fSihvQn_qWQ?embedable=true

https://codesandbox.io/p/github/JI0PATA/fps-game?embedable=true

Minor edits

Before we start, let's create a new images folder for images in the assets folder. And move the image of the floor surface to this folder.

Also, change the path to the image in the Ground.jsx file.

Section code

Aiming mechanics

As in most shooters, aiming is activated by right-clicking. But users can reassign this button to any other at any time in the game settings. Therefore, we will not set the condition of pressing this key directly in the code but will implement a separate config where we will set the control.

In React(Vite), it is already possible to create such a config without additional libraries. To do this, you need to create a .env file in the root of the project. Then, it is in the VITE_*** format that we can set environment variables that we can use anywhere in our project.

In the .env configuration file, let's add two variables that will contain the codes of mouse button presses. Namely, the code for the mouse button to activate firing and also for aiming.

Now, we need to rework the mouse button logic so that different actions are activated when different buttons are pressed.

But first, we need to fix some incorrect behavior when intercepting the mouse cursor on the canvas. At the moment, clicking the mouse on the screen immediately triggers a shot action, which looks a bit odd. So we'll add logic that until the cursor is intercepted by the application, then no click events will occur.

Now, we'll use the library to store the global state of Zustand. In the App.jsx file, we'll add the state and add event handlers for locking and unlocking the cursor.

In the Weapon.jsx file, we will add new logic that will differentiate between mouse keys pressed and also take into account the state of the intercepted cursor.

Let's create a function mouseButtonHandler. Inside, we will determine the current state of the intercepted cursor, and if the cursor has not been intercepted yet, we will not perform any actions. We also need to import from .env config the values for the mouse keys that activate the firing or aiming mode.

Let's also change the logic of the event handlers when pressing and releasing mouse keys.

Now, let's directly implement the aiming animation.

First, let's add a new state, useAimingStore, to store the aiming state.

Let's add a variable to be able to change the aiming state.

And in the mouseButtonHandler function, where we previously left empty space for the AIM_BUTTON button, we add a state change.

Let's go to the Player.jsx file and do the implementation inside it.

Firstly, we need to import the useAimingStore states from the Weapon.jsx file. Also, move the easing constant to the root of the file.

Let's add the isAiming state for further use in the file.

To save the state of the animation, let's add two states: aiming animation and returning to the original state.

Now, let's create the initAimingAnimation function, which will describe both states of the aiming animation.

In order for this animation to run, it is necessary to call the initAimingAnimation function when initializing the application. This is exactly when the object with the weapon model inside is ready for interaction.

When changing the isAiming state, it is necessary to trigger either the aiming animation or the weapon return to the initial position. For this purpose, let's add useEffect, within which one or another logic will be triggered according to the condition. So, for example, when starting the aiming process, it will be necessary to stop the "wiggle" animation and then start the aiming animation. When the mouse button is released, the animation of returning the weapon to the initial position is triggered, and when the onComplete event is triggered, the "wiggle" animation is re-run.

But now, when the application is initialized, the animation starts up, and it looks like the player was aiming and then exits the aiming mode. This happens because, by default, isAiming is set to false, and the "or" branch in the condition is immediately triggered during initialization. You can solve this by fixing the default value to null and then modifying the condition by adding a specific value to the condition.

So now we have an aiming animation when the right mouse button is clicked and an exit from this mode when it is released.

Section code

Refactoring the recoil animation

Before we get to the next part of our article, let's do some refactoring of the animation implementation.

In the Player.jsx file, let's change the function name from setAnimationParams to setSwayingAnimationParams. And also replace this function name in other places.

And from the initSwayingObjectAnimation function, we will remove the easing constant because earlier we moved it to the root of the file.

Let's move on to fixing the Weapon.jsx file.


Change the value forrecoilDuration to 50.

Let's remove recoilBackAnimation for unnecessary. Instead, let's now add isRecoilAnimationFinished.

For the generateNewPositionOfRecoil function, let's add a default value for the currentPosition parameter.

In the initRecoilAnimation function, let's remove the initialPosition constant for uselessness.

For Tween-animation, we will rework the logic, making it automatically-return to the initial position from which the animation started. We will also remove the return animation twRecoilBackAnimation.

Let's rework useEffect by splitting it into 2 different functions. The first will initialize the animation, and the second will run the weapon recoil animation.

Section code

Animation of the flash when shooting

Now, let's implement the flash display when the weapon is fired.

In the Weapon.jsx file import the useLoader function. And also, load the flash image into assets/images.

In the component, import this image as FlashShoot.

Next, let's use the useLoader function to load this image into the scene. We will also add a state to save the flashAnimation.

Let's create a new flashOpacity state to smoothly change the transparency of the flash. We will also create a new function initFlashAnimation, in which we will describe the animation sequence. And after that, we'll useEffect to initialize the animation.

Now, we need to display this image on the stage at the same level as the weapon model, setting the location so that the image is located directly on the end of the muzzle of the weapon.

And finally, let's add a call to the startShooting function for animation at each shot.

Section code

Adding the sound of a gunshot

Let's add the prepared sound file to the assets/sounds folder.

Import it into a file.

Using HTMLAudioElement, we can add a sound file for the current page (not for the scene).

When the startShooting function is called, we launch the given audio file. For several shots, the audio files to be launched will start and overlap with each other. And at the end just stop playing.

Section code

Conclusion

In this article, we have added an aiming animation, a flash animation when shooting, and a sound effect when shooting. In the next article, we will continue to refine our game by adding new functionality.

Thanks for reading, and I will be glad to respond to your comments!


Also published here.


Written by varlab | Full-stack developer
Published by HackerNoon on 2023/11/15