paint-brush
Creating Your Own 3D Shooter Using the React and Three.js Stack — Part 2by@varlab
701 reads
701 reads

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

by Ivan ZhukovNovember 15th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In 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.
featured image - Creating Your Own 3D Shooter Using the React and Three.js Stack — Part 2
Ivan Zhukov HackerNoon profile picture

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


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.


Ground.jsx file with the changed path to the image


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.


.env with added variables


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.


Changes to the App.jsx file


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.



Getting values from .env


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


Modified event handler for mouse click events


Now, let's directly implement the aiming animation.


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


Import Zustand

State in Weapon.jsx


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


Getting the setIsAiming function


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


Changing the state in mouseButtonHandler


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.


Importing useAimingStore and the easing constant


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


The isAiming state


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


Adding the aimingAnimation and aimingBackAnimation states


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


Function initAmingAnimation


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.


useEffect for initAimingAnimation initialisation


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.


useEffect to activate the animation effect


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.


Condition with the changed isAiming value to null


UseEffect to activate the animation with the corrected 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.


Aiming animation


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.


Player.jsx file with the changed function name


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


Removed easing constant


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


Change the value forrecoilDuration to 50.


Changed recoilDuration value


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


The added state isRecoilAnimationFinished


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


Default value for the function generateNewPositionOfRecoil


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


The initialPosition variable is removed


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.


Modified initRecoilAnimation function


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.


Modified logic of useEffect


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.


Flash image when the weapon is fired


In the component, import this image as FlashShoot.


Importing the image


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


Importing useLoader

Loading the image on the scene and the flash animation state


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.


Realisation of the flash animation when the shot is fired


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.


Adding the image to the scene


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


Finalised startShooting function


Animation of the flash when fired


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.


Import audio file


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


Adding audio to the document


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.


Playing audio in the startShooting function


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.