{"id":356374,"date":"2024-05-20T23:28:19","date_gmt":"2024-05-20T23:28:19","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=356374"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-20T23:28:19","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=356374","title":{"rendered":"<span>React + Three.js. Creating your own 3D shooter. Part 1<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Hello, esteemed Habr contributors!<\/p>\n<h3>Introduction <\/h3>\n<p>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 <a href=\"https:\/\/docs.pmnd.rs\/react-three-fiber\/getting-started\/introduction\" rel=\"noopener noreferrer nofollow\"><strong>React Three Fiber<\/strong><\/a><strong> <\/strong>library &#8212; a powerful tool for creating 3D graphics based on <a href=\"https:\/\/threejs.org\/\" rel=\"noopener noreferrer nofollow\"><strong>Three.js<\/strong><\/a> using <strong>React <\/strong>technology.  <\/p>\n<h3>About the React Three Fiber stack  <\/h3>\n<p><strong>React Three Fiber is <\/strong>a wrapper over <strong>Three.js <\/strong>that uses the structure and principles of <strong>React <\/strong>to create 3D graphics on the web. This stack allows developers to combine the power of <strong>Three.js <\/strong>with the convenience and flexibility of <strong>React<\/strong>, making the process of creating an application more intuitive and organised.<\/p>\n<p>At the heart of <strong>React Three Fiber is <\/strong>the idea that everything you create in a scene is a <strong>React<\/strong> component. This allows developers to apply familiar patterns and methodologies.<\/p>\n<p>One of the main advantages of <strong>React Three Fiber <\/strong>is its ease of integration with the <strong>React <\/strong>ecosystem. Any other <strong>React<\/strong> tools can still be easily integrated when using this library.<\/p>\n<h3>Relevance of Web-GameDev  <\/h3>\n<p><strong>Web-GameDev <\/strong>has undergone major changes in recent years, evolving from simple 2D <a href=\"https:\/\/www.adobe.com\/products\/flashplayer\/end-of-life.html\" rel=\"noopener noreferrer nofollow\"><strong>Flash<\/strong><\/a><strong> <\/strong>games to complex 3D projects comparable to desktop applications. This growth in popularity and capabilities makes Web-GameDev an area that cannot be ignored.<\/p>\n<p>One of the main advantages of web gaming is its accessibility. Players do not need to download and install any additional software &#8212; just click on the link in their browser. This simplifies the distribution and promotion of games, making them available to a wide audience around the world.<\/p>\n<p>Finally, web game development can be a great way for developers to try their hand at gamedev using familiar technologies. Thanks to the available tools and libraries, even without experience in 3D graphics, it is possible to create interesting and high-quality projects!<\/p>\n<h3>Game performance in modern browsers  <\/h3>\n<p>Modern browsers have come a long way, evolving from fairly simple web browsing tools to powerful platforms for running complex applications and games. Major browsers such as <strong>Chrome<\/strong>, <strong>Firefox<\/strong>, <strong>Edge <\/strong>and <strong>others are <\/strong>constantly being optimised and developed to ensure high performance, making them an ideal platform for developing complex applications.<\/p>\n<p>One of the key tools that has fuelled the development of browser-based gaming is <a href=\"https:\/\/www.khronos.org\/webgl\/\" rel=\"noopener noreferrer nofollow\"><strong><u>WebGL<\/u><\/strong><\/a>. This standard allowed developers to use hardware graphics acceleration, which significantly improved the performance of 3D games. Together with other webAPIs, <strong>WebGL <\/strong>opens up new possibilities for creating impressive web applications directly in the browser.<\/p>\n<p>Nevertheless, when developing games for the browser, it is crucial to consider various performance aspects: resource optimisation, memory management and adaptation for different devices are all key points that can affect the success of a project.<\/p>\n<h3>On your mark!  <\/h3>\n<p>However, words and theory are one thing, but practical experience is quite another. To really understand and appreciate the full potential of web game development, the best way is to immerse yourself in the development process. Therefore, as an example of successful web game development, we will create our own game. This process will allow us to learn key aspects of development, face real problems and find solutions to them, and see how powerful and flexible a web game development platform can be.<\/p>\n<p>In a series of articles, we&#8217;ll look at how to create a first-person shooter using the features of this library, and dive into the exciting world of web-gamedev!<\/p>\n<p>Final demo<\/p>\n<div class=\"embed_link\">\n<div class=\"embed__thumb\" style=\"background-image: url(&quot;undefined&quot;);\"><\/div>\n<div class=\"embed__caption\">\n<div class=\"embed__caption-title\"><span>FPS Game<\/span><\/div>\n<p><a href=\"https:\/\/zy45x4-5173.csb.app\/\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"embed__caption-link\">zy45x4-5173.csb.app<\/a><\/div>\n<\/div>\n<div class=\"embed_link\">\n<div class=\"embed__thumb\" style=\"background-image: url(&quot;undefined&quot;);\"><\/div>\n<div class=\"embed__caption\">\n<div class=\"embed__caption-title\"><span>fps-game\/master &#8212; CodeSandbox<\/span><\/div>\n<p><a href=\"https:\/\/codesandbox.io\/p\/github\/JI0PATA\/fps-game\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"embed__caption-link\">codesandbox.io<\/a><\/div>\n<\/div>\n<p>Repository on <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\" rel=\"noopener noreferrer nofollow\"><u>GitHub<\/u><\/a><\/p>\n<p>Now, let&#8217;s get started!<\/p>\n<h3>Setting up the project and installing packages  <\/h3>\n<p>First of all, we will need a <strong>React <\/strong>project template. So let&#8217;s start by installing it.  <\/p>\n<pre><code class=\"bash\">npm create vite@latest<\/code><\/pre>\n<ul>\n<li>\n<p>select the <strong>React <\/strong>library; <\/p>\n<\/li>\n<li>\n<p>select <strong>JavaScript<\/strong>. <\/p>\n<\/li>\n<\/ul>\n<p>Install additional npm packages.<\/p>\n<pre><code class=\"bash\">npm install three @react-three\/fiber @react-three\/drei @react-three\/rapier zustand @tweenjs\/tween.js<\/code><\/pre>\n<p>Then <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">delete<\/a> everything unnecessary from our project.  <\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Customising the Canvas display  <\/h3>\n<p>In the <strong>main.jsx <\/strong>file, add a div element that will be displayed on the page as a scope. Insert a <strong>Canvas <\/strong>component and set the field of view of the camera. Inside the <strong>Canvas <\/strong>component place the <strong>App <\/strong>component.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/334\/4b4\/e32\/3344b4e3259afedc48176bab83c22381.png\" alt=\"main.jsx\" title=\"main.jsx\" width=\"523\" height=\"315\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/334\/4b4\/e32\/3344b4e3259afedc48176bab83c22381.png\"\/><\/p>\n<div><figcaption>main.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>main.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import React from 'react' import ReactDOM from 'react-dom\/client' import App from '.\/App.jsx' import '.\/index.css' import {Canvas} from \"@react-three\/fiber\";  ReactDOM.createRoot(document.getElementById('root')).render(   &lt;React.StrictMode>     &lt;div id=\"container\">         &lt;div className=\"aim\">&lt;\/div>         &lt;Canvas camera={{ fov: 45 }}>             &lt;App \/>         &lt;\/Canvas>     &lt;\/div>   &lt;\/React.StrictMode>, )<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Let&#8217;s add styles to stretch the UI elements to the full height of the screen and display the scope as a circle in the centre of the screen.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/904\/e29\/5c0\/904e295c07bb417497deed91fdb9a306.png\" alt=\"index.css\" title=\"index.css\" width=\"398\" height=\"397\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/904\/e29\/5c0\/904e295c07bb417497deed91fdb9a306.png\"\/><\/p>\n<div><figcaption>index.css<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>index.css<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"css\">* {     margin: 0;     padding: 0; }  html, body, #root, #container {     height: 100%; }  .aim {     position: absolute;     top: 50%;     left: 50%;     width: 10px;     height: 10px;     border-radius: 50%;     transform: translate3d(-50%, -50%, 0);     border: 2px solid white;     z-index: 2; }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>In the <strong>App component we <\/strong>add a <strong>Sky <\/strong>component, which will be displayed as the background in our game scene in the form of a sky.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/5fb\/9f8\/477\/5fb9f847724edb67c0372e83a204358b.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"387\" height=\"196\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5fb\/9f8\/477\/5fb9f847724edb67c0372e83a204358b.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {Sky} from \"@react-three\/drei\";  export const App = () => {   return (     &lt;>       &lt;Sky sunPosition={[100, 20, 100]} \/>     &lt;\/>   ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aaf\/e38\/23f\/aafe3823f5019721b181bcf064ef6eb1.png\" alt=\"Displaying the sky in the scene  \" title=\"Displaying the sky in the scene  \" width=\"1560\" height=\"754\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/aaf\/e38\/23f\/aafe3823f5019721b181bcf064ef6eb1.png\"\/><\/p>\n<div><figcaption><em>Displaying the sky in the scene<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/ede1b9eb617c1967aef2f09104c234a87b68bc97\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Floor surface  <\/h3>\n<p>Let&#8217;s create a <strong>Ground<\/strong> component<strong> <\/strong>and place it in the <strong>App <\/strong>component.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8b3\/557\/ff4\/8b3557ff42475e8013aed2b5197ab02f.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"442\" height=\"358\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8b3\/557\/ff4\/8b3557ff42475e8013aed2b5197ab02f.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\";  export const App = () => {     return (         &lt;>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;Ground \/>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>In <strong>Ground, <\/strong>create a flat surface element. On the Y axis move it downwards so that this plane is in the field of view of the camera. And also flip the plane on the X axis to make it horizontal.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/273\/0b0\/7b2\/2730b07b23cf4929b7d3fe170db20b0e.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"484\" height=\"165\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/273\/0b0\/7b2\/2730b07b23cf4929b7d3fe170db20b0e.png\"\/><\/p>\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">export const Ground = () => {     return (         &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>             &lt;planeGeometry args={[500, 500]} \/>             &lt;meshStandardMaterial color=\"gray\" \/>         &lt;\/mesh>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Even though we specified grey as the material colour, the plane appears completely black.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cb5\/2b8\/cac\/cb52b8cac64139395bd26c43fd785cf7.png\" alt=\"Flat on the scene  \" title=\"Flat on the scene  \" width=\"1560\" height=\"731\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cb5\/2b8\/cac\/cb52b8cac64139395bd26c43fd785cf7.png\"\/><\/p>\n<div><figcaption><em>Flat on the scene<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/e9335eb32868ba48874b7461a6d79609a074f78b\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Basic lighting  <\/h3>\n<p>By default, there is no lighting in the scene, so let&#8217;s add a light source <strong>ambientLight, <\/strong>which illuminates the object from all sides and does not have a directed beam. As a parameter set the intensity of the glow.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3bd\/35b\/648\/3bd35b64808e018028703940ca3203d0.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"434\" height=\"138\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3bd\/35b\/648\/3bd35b64808e018028703940ca3203d0.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\";  export const App = () => {     return (         &lt;>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Ground \/>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b9f\/2d3\/348\/b9f2d3348e1a856fb3e7cce981351afe.png\" alt=\"Illuminated plane  \" title=\"Illuminated plane  \" width=\"1600\" height=\"647\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b9f\/2d3\/348\/b9f2d3348e1a856fb3e7cce981351afe.png\"\/><\/p>\n<div><figcaption><em>Illuminated plane<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/385825283562b117c4b849c0c71832e0d634ec3e\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Texture for the floor surface  <\/h3>\n<p>To make the floor surface not look homogeneous, we will add texture. Make a pattern of the floor surface in the form of cells repeating all along the surface.<\/p>\n<p>In the <strong>assets <\/strong>folder add a PNG image with a texture.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e21\/276\/4a1\/e212764a1fe17587db1a2b75dd530ac7.png\" alt=\"Added texture  \" title=\"Added texture  \" width=\"1600\" height=\"1236\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e21\/276\/4a1\/e212764a1fe17587db1a2b75dd530ac7.png\"\/><\/p>\n<div><figcaption><em>Added texture<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p>To load a texture on the scene, let&#8217;s <strong>use the useTexture <\/strong>hook from the <strong>@react-three\/drei <\/strong>package. And as a parameter for the hook we will pass the texture image imported into the file. Set the repetition of the image in the horizontal axes.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e34\/132\/cf5\/e34132cf51bff62e106791125b9b998a.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"686\" height=\"318\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e34\/132\/cf5\/e34132cf51bff62e106791125b9b998a.png\"\/><\/p>\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>             &lt;planeGeometry args={[500, 500]} \/>             &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[360, 360]} \/>         &lt;\/mesh>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8b9\/0e3\/517\/8b90e3517cc13fa425fa8d3063a14ce7.png\" alt=\"\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438\" title=\"Texture on a plane  \" width=\"1368\" height=\"1005\" data-src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8b9\/0e3\/517\/8b90e3517cc13fa425fa8d3063a14ce7.png\"\/><\/p>\n<div><figcaption><em>Texture on a plane<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/f1c91e9106b77f6c45b5edb48e3b229ba0e1862c\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Camera movement  <\/h3>\n<p>Using the<strong> PointerLockControls <\/strong>component from the <strong>@react-three\/drei <\/strong>package, fix the cursor on the screen so that it does not move when you move the mouse, but changes the position of the camera on the scene.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8fa\/e95\/d5c\/8fae95d5c782be315c4367846a877b81.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"512\" height=\"219\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8fa\/e95\/d5c\/8fae95d5c782be315c4367846a877b81.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Ground \/>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0c7\/a45\/abe\/0c7a45abeb2a653f77647431b5174d11.GIF\" alt=\"Camera motion demonstration\" title=\"Camera motion demonstration\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Camera motion demonstration<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s make a small edit for the <strong>Ground <\/strong>component.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fd7\/91c\/468\/fd791c4686828c524fc32cc6fb8baada.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"688\" height=\"160\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fd7\/91c\/468\/fd791c4686828c524fc32cc6fb8baada.png\"\/><\/p>\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>             &lt;planeGeometry args={[500, 500]} \/>             &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[100, 100]} \/>         &lt;\/mesh>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/3eb9816f82157b88647d185ec57bb7f9de959f62\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Adding physics  <\/h3>\n<p>For clarity, let&#8217;s add a simple cube to the scene.  <\/p>\n<pre><code class=\"xml\">&lt;mesh position={[0, 3, -5]}>     &lt;boxGeometry \/> &lt;\/mesh><\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/777\/04d\/1e7\/77704d1e74ae71ce494f2fd008413538.png\" alt=\"The cube on the scene \" title=\"The cube on the scene \" width=\"1291\" height=\"935\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/777\/04d\/1e7\/77704d1e74ae71ce494f2fd008413538.png\"\/><\/p>\n<div><figcaption><em>The cube on the scene<\/em> <\/figcaption><\/div>\n<\/figure>\n<p>Right now he&#8217;s just hanging in space.<\/p>\n<p>Use the <strong>Physics <\/strong>component from the <strong>@react-three\/rapier <\/strong>package to add &#171;physics&#187; to the scene. As a parameter, configure the gravity field, where we set the gravitational forces along the axes.<\/p>\n<pre><code class=\"xml\">&lt;Physics gravity={[0, -20, 0]}>     &lt;Ground \/>     &lt;mesh position={[0, 3, -5]}>         &lt;boxGeometry \/>     &lt;\/mesh> &lt;\/Physics><\/code><\/pre>\n<p>However, our cube is inside the physics component, but nothing happens to it. To make the cube behave like a real physical object, we need to wrap it in the <strong>RigidBody <\/strong>component from the <strong>@react-three\/rapier <\/strong>package.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f63\/fe8\/669\/f63fe8669691e9b3ed5140803ca5d2b4.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"528\" height=\"433\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f63\/fe8\/669\/f63fe8669691e9b3ed5140803ca5d2b4.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\"; import {Physics, RigidBody} from \"@react-three\/rapier\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Physics gravity={[0, -20, 0]}>                 &lt;Ground \/>                 &lt;RigidBody>                     &lt;mesh position={[0, 3, -5]}>                         &lt;boxGeometry \/>                     &lt;\/mesh>                 &lt;\/RigidBody>             &lt;\/Physics>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>After that, we will immediately see that every time the page reloads, the cube falls down under the influence of gravity.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/dca\/ec4\/24e\/dcaec424ed100198e4d8ba02f124381a.GIF\" alt=\"\u0421ube fall\" title=\"\u0421ube fall\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>\u0421ube fall<\/figcaption><\/div>\n<\/figure>\n<p>But now there is another task &#8212; it is necessary to make the floor an object with which the cube can interact, and beyond which it will not fall.  <\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/1f564e9836554b644d43dd75d4644aa9cd5159b1\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>The floor as a physical object  <\/h3>\n<p>Let&#8217;s go back to the <strong>Ground component <\/strong>and add a <strong>RigidBody <\/strong>component as a wrapper over the floor surface.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/bf6\/062\/8d9\/bf60628d90fb1ea00523a4f3810c70a3.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"721\" height=\"441\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bf6\/062\/8d9\/bf60628d90fb1ea00523a4f3810c70a3.png\"\/><\/p>\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\"; import {RigidBody} from \"@react-three\/rapier\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;RigidBody>             &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>                 &lt;planeGeometry args={[500, 500]} \/>                 &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[100, 100]} \/>             &lt;\/mesh>         &lt;\/RigidBody>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Now when falling, the cube stays on the floor like a real physical object.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b09\/4b5\/665\/b094b5665aa31de3ec78bf5a95bdaaae.GIF\" alt=\"Falling cube on a plane\" title=\"Falling cube on a plane\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Falling cube on a plane<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/31fe05543209c6828b7d5b69f06189e9f48a124a\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Subjecting a character to the laws of physics  <\/h3>\n<p>Let&#8217;s create a <strong>Player <\/strong>component that will control the character on the scene.<\/p>\n<p>The character is the same physical object as the added cube, so it must interact with the floor surface as well as the cube on the scene. That&#8217;s why we add the <strong>RigidBody <\/strong>component. And let&#8217;s make the character in the form of a capsule.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/71a\/037\/1c1\/71a0371c194f6e4a73f51d763c9c66f3.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"441\" height=\"260\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/71a\/037\/1c1\/71a0371c194f6e4a73f51d763c9c66f3.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {RigidBody} from \"@react-three\/rapier\";  export const Player = () => {     return (         &lt;>             &lt;RigidBody position={[0, 1, -2]}>                 &lt;mesh>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                 &lt;\/mesh>             &lt;\/RigidBody>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Place the <strong>Player <\/strong>component inside the <strong>Physics <\/strong>component.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6f0\/75e\/ee9\/6f075eee97e136c2e91c822054f0ea37.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"504\" height=\"307\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6f0\/75e\/ee9\/6f075eee97e136c2e91c822054f0ea37.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\"; import {Physics, RigidBody} from \"@react-three\/rapier\"; import {Player} from \".\/Player.jsx\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Physics gravity={[0, -20, 0]}>                 &lt;Ground \/>                 &lt;Player \/>                 &lt;RigidBody>                     &lt;mesh position={[0, 3, -5]}>                         &lt;boxGeometry \/>                     &lt;\/mesh>                 &lt;\/RigidBody>             &lt;\/Physics>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Now our character has appeared on the scene.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e54\/22a\/f30\/e5422af30d3964d120c2a6404b5e82f8.png\" alt=\"A character in capsule form  \" title=\"A character in capsule form  \" width=\"1335\" height=\"860\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e54\/22a\/f30\/e5422af30d3964d120c2a6404b5e82f8.png\"\/><\/p>\n<div><figcaption><em>A character in capsule form<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/8877590a4767a281e71b0d0e4d04e49ad4660773\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Moving a character &#8212; creating a hook  <\/h3>\n<p>The character will be controlled using the <strong>WASD <\/strong>keys, and jump using the <strong>Spacebar<\/strong>.<\/p>\n<p>With our own react-hook, we implement the logic of moving the character.<\/p>\n<p>Let&#8217;s create a <strong>hooks.js <\/strong>file and add a new <strong>usePersonControls <\/strong>function there.<\/p>\n<p>Let&#8217;s define an object in the format {&#171;keycode&#187;: &#171;action to be performed&#187;}. Next, add event handlers for pressing and releasing keyboard keys. When the handlers are triggered, we will determine the current actions being performed and update their active state. As a final result, the hook will return an object in the format {&#171;action in progress&#187;: &#171;status&#187;}.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c8b\/f1c\/84d\/c8bf1c84dc835e7e977b64c9345722fc.png\" alt=\"hooks.js\" title=\"hooks.js\" width=\"516\" height=\"914\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c8b\/f1c\/84d\/c8bf1c84dc835e7e977b64c9345722fc.png\"\/><\/p>\n<div><figcaption>hooks.js<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>hooks.js<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import { useEffect, useState } from \"react\";  export const usePersonControls = () => {     const keys = {         KeyW: \"forward\",         KeyS: \"backward\",         KeyA: \"left\",         KeyD: \"right\",         Space: \"jump\"     }      const moveFieldByKey = (key) => keys[key];      const [movement, setMovement] = useState({         forward: false,         backward: false,         left: false,         right: false,         jump: false     });      const setMovementStatus = (code, status) => {         setMovement((m) => ({...m, [code]: status}))     }      useEffect(() => {         const handleKeyDown = (ev) => {             setMovementStatus(moveFieldByKey(ev.code), true);         }          const handleKeyUp = (ev) => {             setMovementStatus(moveFieldByKey(ev.code), false);         }          document.addEventListener('keydown', handleKeyDown);         document.addEventListener('keyup', handleKeyUp);          return () => {             document.removeEventListener('keydown', handleKeyDown);             document.removeEventListener('keyup', handleKeyUp);         }     }, []);      return movement; }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4e83142bdd6e843e7bc6f434801d3a0817d0cd4e\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Moving a character &#8212; implementing a hook  <\/h3>\n<p>After implementing the <strong>usePersonControls <\/strong>hook, it should be <strong>used <\/strong>when controlling the character. In the <strong>Player <\/strong>component we will add motion state tracking and update the vector of the character&#8217;s movement direction.\u00a0<\/p>\n<p>We will also define variables that will store the states of the movement directions.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7a2\/e4e\/10a\/7a2e4e10a6e06060005dc879b2958a42.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"336\" height=\"92\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7a2\/e4e\/10a\/7a2e4e10a6e06060005dc879b2958a42.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>To update the character&#8217;s position, let&#8217;s <strong>useFrame <\/strong>provided by the <strong>@react-three\/fiber <\/strong>package. This hook works similarly to <strong>requestAnimationFrame <\/strong>and executes the body of the function about 60 times per second.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c8a\/c93\/4c5\/c8ac934c596ea1f6ec3d214125760459.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"717\" height=\"695\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c8a\/c93\/4c5\/c8ac934c596ea1f6ec3d214125760459.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>Code Explanation:<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const playerRef = useRef();<br \/><\/strong>Create a link for the player object. This link will allow direct interaction with the player object on the scene.<\/p>\n<\/li>\n<li>\n<p><strong>const { forward, backward, left, right, jump } = usePersonControls();<br \/><\/strong>When a hook is used, an object with boolean values indicating which control buttons are currently pressed by the player is returned.<\/p>\n<\/li>\n<li>\n<p><strong>useFrame((state) => { &#8230; });<br \/><\/strong>The hook is called on each frame of the animation. Inside this hook, the player&#8217;s position and linear velocity are updated.\u00a0<\/p>\n<\/li>\n<li>\n<p><strong>if (!playerRef.current) return; <br \/><\/strong>Checks for the presence of a player object. If there is no player object, the function will stop execution to avoid errors.<\/p>\n<\/li>\n<li>\n<p><strong>const velocity = playerRef.current.linvel(); <br \/><\/strong>Get the current linear velocity of the player.<\/p>\n<\/li>\n<li>\n<p><strong>frontVector.set(0, 0, backward &#8212; forward); <br \/><\/strong>Set the forward\/backward motion vector based on the pressed buttons.<\/p>\n<\/li>\n<li>\n<p><strong>sideVector.set(left &#8212; right, 0, 0); <br \/><\/strong>Set the left\/right movement vector.<\/p>\n<\/li>\n<li>\n<p><strong>direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED); <br \/><\/strong>Calculate the final vector of player movement by subtracting the movement vectors, normalising the result (so that the vector length is 1) and multiplying by the movement speed constant.<\/p>\n<\/li>\n<li>\n<p><strong>playerRef.current.wakeUp(); <br \/><\/strong>&#171;Wakes up&#187; the player object to make sure it reacts to changes. If you don&#8217;t use this method, after some time the object will &#171;sleep&#187; and will not react to position changes.<\/p>\n<\/li>\n<li>\n<p><strong>playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z }); <br \/><\/strong>Set the player&#8217;s new linear velocity based on the calculated direction of movement and keep the current vertical velocity (so as not to affect jumps or falls).<\/p>\n<\/li>\n<\/ol>\n<\/blockquote>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import {RigidBody} from \"@react-three\/rapier\"; import {useRef} from \"react\"; import {usePersonControls} from \".\/hooks.js\"; import {useFrame} from \"@react-three\/fiber\";  const MOVE_SPEED = 5; const direction = new THREE.Vector3(); const frontVector = new THREE.Vector3(); const sideVector = new THREE.Vector3();  export const Player = () => {     const playerRef = useRef();     const { forward, backward, left, right, jump } = usePersonControls();      useFrame((state) => {         if (!playerRef.current) return;          const velocity = playerRef.current.linvel();          frontVector.set(0, 0, backward - forward);         sideVector.set(left - right, 0, 0);         direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED);          playerRef.current.wakeUp();         playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });     });      return (         &lt;>             &lt;RigidBody position={[0, 1, -2]} ref={playerRef}>                 &lt;mesh>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                 &lt;\/mesh>             &lt;\/RigidBody>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>As a result, when pressing the <strong>WASD <\/strong>keys, the character started moving around the scene. He can also interact with the cube, because they are both physical objects.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/dbc\/fb9\/ba1\/dbcfb9ba16bcf555e994f0b170193f34.GIF\" alt=\"Character movement\" title=\"Character movement\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Character movement<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/e385028ce949545cf58ade19dfdb8250643498d1\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Moving a character &#8212; jump<\/h3>\n<p>In order to implement the jump, let&#8217;s use the functionality from the <strong>@dimforge\/rapier3d-compat <\/strong>and <strong>@react-three\/rapier <\/strong>packages. In this example, let&#8217;s check that the character is on the ground and the jump key has been pressed. In this case, we set the character&#8217;s direction and acceleration force on the Y-axis.  <\/p>\n<p>For <strong>Player <\/strong>we will add mass and block rotation on all axes, so that he will not fall over in different directions when colliding with other objects on the scene.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2ba\/50a\/2ce\/2ba50a2cee50e3711cb05b0e1a570646.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"801\" height=\"892\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2ba\/50a\/2ce\/2ba50a2cee50e3711cb05b0e1a570646.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>Code Explanation:<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const world = rapier.world; <br \/><\/strong>Gaining access to the <strong>Rapier <\/strong>physics engine scene. It contains all physical objects and manages their interaction.<\/p>\n<\/li>\n<li>\n<p><strong>const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x: 0, y: -1, z: 0 })); <br \/><\/strong>This is where &#171;raycasting&#187; (raycasting) takes place. A ray is created that starts at the player&#8217;s current position and points down the y-axis. This ray is &#171;cast&#187; into the scene to determine if it intersects with any object in the scene.<\/p>\n<\/li>\n<li>\n<p><strong>const grounded = ray &amp;&amp; ray.collider &amp;&amp; Math.abs(ray.toi) &lt;= 1.5;<br \/><\/strong>The condition is checked if the player is on the ground:<\/p>\n<ul>\n<li>\n<p><strong>ray <\/strong>&#8212; whether the ray was created;<\/p>\n<\/li>\n<li>\n<p><strong>ray.collider <\/strong>&#8212; whether the <strong>ray <\/strong>collided with any object on the scene;<\/p>\n<\/li>\n<li>\n<p><strong>Math.abs(ray.toi) <\/strong>&#8212; the &#171;exposure time&#187; of the ray. If this value is less than or equal to the given value, it may indicate that the player is close enough to the surface to be considered &#171;on the ground&#187;.<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<\/blockquote>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import * as RAPIER from \"@dimforge\/rapier3d-compat\" import {RigidBody, useRapier} from \"@react-three\/rapier\"; import {useRef} from \"react\"; import {usePersonControls} from \".\/hooks.js\"; import {useFrame} from \"@react-three\/fiber\";  const MOVE_SPEED = 5; const direction = new THREE.Vector3(); const frontVector = new THREE.Vector3(); const sideVector = new THREE.Vector3();  export const Player = () => {     const playerRef = useRef();     const { forward, backward, left, right, jump } = usePersonControls();      const rapier = useRapier();      useFrame((state) => {         if (!playerRef.current) return;          \/\/ moving player         const velocity = playerRef.current.linvel();          frontVector.set(0, 0, backward - forward);         sideVector.set(left - right, 0, 0);         direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED);          playerRef.current.wakeUp();         playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });          \/\/ jumping         const world = rapier.world;         const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x: 0, y: -1, z: 0 }));         const grounded = ray &amp;&amp; ray.collider &amp;&amp; Math.abs(ray.toi) &lt;= 1;          if (jump &amp;&amp; grounded) doJump();     });      const doJump = () => {         playerRef.current.setLinvel({x: 0, y: 8, z: 0});     }      return (         &lt;>             &lt;RigidBody position={[0, 1, -2]} mass={1} ref={playerRef} lockRotations>                 &lt;mesh>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                 &lt;\/mesh>             &lt;\/RigidBody>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>You also need to modify the <strong>Ground <\/strong>component so that the raytraced algorithm for determining the &#171;landing&#187; status works correctly, by adding a physical object that will interact with other objects in the scene.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f1c\/5ed\/34d\/f1c5ed34dbe10491aa70663305ea858f.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"716\" height=\"444\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f1c\/5ed\/34d\/f1c5ed34dbe10491aa70663305ea858f.png\"\/><\/p>\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\"; import {CuboidCollider, RigidBody} from \"@react-three\/rapier\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;RigidBody type=\"fixed\" colliders={false}>             &lt;mesh position={[0, 0, 0]} rotation-x={-Math.PI \/ 2}>                 &lt;planeGeometry args={[500, 500]} \/>                 &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[100, 100]} \/>             &lt;\/mesh>             &lt;CuboidCollider args={[500, 2, 500]} position={[0, -2, 0]}\/>         &lt;\/RigidBody>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Let&#8217;s raise the camera a little higher for a better view of the scene. <\/p>\n<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a60\/ade\/e9b\/a60adee9bb1bb9d5902e30464cd1e998.png\" alt=\"main.jsx\" title=\"main.jsx\" width=\"516\" height=\"161\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a60\/ade\/e9b\/a60adee9bb1bb9d5902e30464cd1e998.png\"\/><\/p>\n<div><figcaption>main.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>main.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import React from 'react' import ReactDOM from 'react-dom\/client' import App from '.\/App.jsx' import '.\/index.css' import {Canvas} from \"@react-three\/fiber\";  ReactDOM.createRoot(document.getElementById('root')).render(   &lt;React.StrictMode>     &lt;div id=\"container\">         &lt;div className=\"aim\">&lt;\/div>         &lt;Canvas camera={{ fov: 45, position: [0, 5, 0] }}>             &lt;App \/>         &lt;\/Canvas>     &lt;\/div>   &lt;\/React.StrictMode>, )<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/34c\/d5d\/20a\/34cd5d20a1f11fc7f0091ccc03d5d33c.gif\" alt=\"Character jumps\" title=\"Character jumps\" width=\"848\" height=\"478\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/34c\/d5d\/20a\/34cd5d20a1f11fc7f0091ccc03d5d33c.gif\"\/><\/p>\n<div><figcaption>Character jumps<\/figcaption><\/div>\n<\/figure>\n<p>Section code<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/bc2c22d9661defb4e645fac17e63457505604ac9\" rel=\"noopener noreferrer nofollow\">First commit<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/f5bad070b78677e9924fc4599a2025baf7dffb09\" rel=\"noopener noreferrer nofollow\">Second commit<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>Moving the camera behind the character  <\/h3>\n<p>To move the camera, we will get the current position of the player and change the position of the camera every time the frame is refreshed. And for the character to move exactly along the trajectory, where the camera is directed, we need to add <strong>applyEuler<\/strong>.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e03\/f85\/79b\/e03f8579bafa6451494d97e8e076f77a.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"953\" height=\"386\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e03\/f85\/79b\/e03f8579bafa6451494d97e8e076f77a.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>Code Explanation:<\/strong><\/p>\n<p>The <strong>applyEuler <\/strong>method applies rotation to a vector based on specified Euler angles. In this case, the camera rotation is applied to the <strong>direction <\/strong>vector. This is used to match the motion relative to the camera orientation, so that the player moves in the direction the camera is rotated.<\/p>\n<\/blockquote>\n<p>Let&#8217;s slightly adjust the size of <strong>Player <\/strong>and make it taller relative to the cube, increasing the size of <strong>CapsuleCollider <\/strong>and fixing the &#171;jump&#187; logic.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/55e\/2c4\/a2d\/55e2c4a2dcf08e3f153e29789d4933e6.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"832\" height=\"347\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/55e\/2c4\/a2d\/55e2c4a2dcf08e3f153e29789d4933e6.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/867\/037\/245\/86703724539cde9f627c2b57e07dc051.GIF\" alt=\"Moving the camera\" title=\"Moving the camera\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Moving the camera<\/figcaption><\/div>\n<\/figure>\n<p>Section code<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/74e2206a9d16d78268224d9d451cd2405ec162ab\" rel=\"noopener noreferrer nofollow\">First commit<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/0063ea91b5981e7ce2be89b71d73a9f2d69a624a\" rel=\"noopener noreferrer nofollow\">Second commit<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>Generation of cubes<\/h3>\n<p>To make the scene not feel completely empty, let&#8217;s add cube generation. In the json file, list the coordinates of each of the cubes and then display them on the scene. To do this, create a file <strong>cubes.json, <\/strong>in which we will list an array of coordinates.   <\/p>\n<pre><code class=\"json\">[   [0, 0, -7],   [2, 0, -7],   [4, 0, -7],   [6, 0, -7],   [8, 0, -7],   [10, 0, -7] ]<\/code><\/pre>\n<p>In the <strong>Cube.jsx <\/strong>file, create a <strong>Cubes <\/strong>component, which will generate cubes in a loop. And <strong>Cube component will <\/strong>be directly generated object.  <\/p>\n<pre><code class=\"javascript\">import {RigidBody} from \"@react-three\/rapier\"; import cubes from \".\/cubes.json\";  export const Cubes = () => {     return cubes.map((coords, index) => &lt;Cube key={index} position={coords} \/>); }  const Cube = (props) => {     return (         &lt;RigidBody {...props}>             &lt;mesh castShadow receiveShadow>                 &lt;meshStandardMaterial color=\"white\" \/>                 &lt;boxGeometry \/>             &lt;\/mesh>         &lt;\/RigidBody>     ); }<\/code><\/pre>\n<p>Let&#8217;s add the created <strong>Cubes component <\/strong>to the <strong>App <\/strong>component by deleting the previous single cube.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0d2\/bf8\/24f\/0d2bf824f45a785fb947c702390a86fc.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"506\" height=\"447\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0d2\/bf8\/24f\/0d2bf824f45a785fb947c702390a86fc.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\"; import {Physics} from \"@react-three\/rapier\"; import {Player} from \".\/Player.jsx\"; import {Cubes} from \".\/Cube.jsx\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Physics gravity={[0, -20, 0]}>                 &lt;Ground \/>                 &lt;Player \/>                 &lt;Cubes \/>             &lt;\/Physics>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0f3\/f8b\/d8e\/0f3f8bd8e899c19a00b641bdc2bf6e2b.GIF\" alt=\"Generation of cubes\" title=\"Generation of cubes\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Generation of cubes<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/357405aab1cf8f49699397c7bcf07fa9ca7b667d\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Importing the model into the project  <\/h3>\n<p>Now let&#8217;s add a 3D model to the scene. Let&#8217;s add a weapon model for the character. Let&#8217;s start by looking for a 3D model. For example, let&#8217;s take <a href=\"https:\/\/sketchfab.com\/3d-models\/low-poly-aks-74u-4aa2f92e016c434eafdba67b238bb7fb\" rel=\"noopener noreferrer nofollow\">this one<\/a>.<\/p>\n<p>Download the model in GLTF format and unpack the archive in the root of the project. <\/p>\n<p>In order to get the format we need to import the model into the scene, we will need to install the <strong>gltf-pipeline <\/strong>add-on package.  <\/p>\n<pre><code class=\"bash\">npm i -D gltf-pipeline<\/code><\/pre>\n<p>Using the<strong> gltf-pipeline <\/strong>package, reconvert the model from the <strong>GLTF format <\/strong>to the <strong>GLB format, <\/strong>since in this format all model data are placed in one file. As an output directory for the generated file we specify the <strong>public <\/strong>folder.  <\/p>\n<pre><code class=\"bash\">gltf-pipeline -i weapon\/scene.gltf -o public\/weapon.glb<\/code><\/pre>\n<p>Then we need to generate a react component that will contain the markup of this model to add it to the scene. Let&#8217;s use the<a href=\"https:\/\/gltf.pmnd.rs\/\" rel=\"noopener noreferrer nofollow\"> official resource<\/a> from the <strong>@react-three\/fiber <\/strong>developers.\u00a0<\/p>\n<p>Going to the converter will require you to load the converted <strong>weapon.glb <\/strong>file.<\/p>\n<p>Using drag and drop or Explorer search, find this file and download it.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c44\/a74\/4a9\/c44a744a94785aebf97d782b12f38605.png\" alt=\"Converted model\" title=\"Converted model\" width=\"1123\" height=\"1184\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c44\/a74\/4a9\/c44a744a94785aebf97d782b12f38605.png\"\/><\/p>\n<div><figcaption>Converted model<\/figcaption><\/div>\n<\/figure>\n<p>In the converter we will see the generated react-component, the code of which <strong>we <\/strong>will transfer to our project in a new file <strong>WeaponModel.jsx, <\/strong>changing the name of the component to the same name as the file.  <\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/0621b9ac3ab267dbc94a99264662d9d896f80207\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Displaying the weapon model on the scene  <\/h3>\n<p>Now let&#8217;s import the created model to the scene. In <strong>App.jsx <\/strong>file add <strong>WeaponModel <\/strong>component.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/485\/759\/c98\/485759c98d0a9f13917bb65d34cb0ad4.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"428\" height=\"363\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/485\/759\/c98\/485759c98d0a9f13917bb65d34cb0ad4.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\"; import {Physics} from \"@react-three\/rapier\"; import {Player} from \".\/Player.jsx\"; import {Cubes} from \".\/Cube.jsx\"; import {WeaponModel} from \".\/WeaponModel.jsx\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Physics gravity={[0, -20, 0]}>                 &lt;Ground \/>                 &lt;Player \/>                 &lt;Cubes \/>             &lt;\/Physics>              &lt;group position={[0, 3, 0]}>                 &lt;WeaponModel \/>             &lt;\/group>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9ef\/e47\/aa8\/9efe47aa8e4ec4e75d0eb7c11e4bbe16.GIF\" alt=\"Demonstration of the imported model  \" title=\"Demonstration of the imported model  \" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Demonstration of the imported model  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/2f3aae04743b0f87724c723bb68ad71e5cbca53b\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Adding shadows  <\/h3>\n<p>At this point in our scene, none of the objects are casting shadows. <\/p>\n<p>To enable <strong>shadows <\/strong>on the scene you need to add the <strong>shadows <\/strong>attribute to the <strong>Canvas <\/strong>component.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c3a\/998\/95f\/c3a99895f2ebe54987f9a3dd75378562.png\" alt=\"main.jsx\" title=\"main.jsx\" width=\"415\" height=\"160\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c3a\/998\/95f\/c3a99895f2ebe54987f9a3dd75378562.png\"\/><\/p>\n<div><figcaption>main.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>main.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import React from 'react' import ReactDOM from 'react-dom\/client' import App from '.\/App.jsx' import '.\/index.css' import {Canvas} from \"@react-three\/fiber\";  ReactDOM.createRoot(document.getElementById('root')).render(   &lt;React.StrictMode>     &lt;div id=\"container\">         &lt;div className=\"aim\">&lt;\/div>         &lt;Canvas camera={{ fov: 45 }} shadows>             &lt;App \/>         &lt;\/Canvas>     &lt;\/div>   &lt;\/React.StrictMode>, )<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Next, we need to add a new light source. Despite the fact that we already have <strong>ambientLight <\/strong>on the scene, it cannot create shadows for objects, because it does not have a directional light beam. So let&#8217;s add a new light source called <strong>directionalLight <\/strong>and configure it. The attribute to enable the &#171;<strong>cast<\/strong>&#187; shadow mode is <strong>castShadow<\/strong>. It is the addition of this parameter that indicates that this object can cast a shadow on other objects.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ac3\/1ca\/058\/ac31ca0588abcd3ef1c64757785bb482.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"421\" height=\"358\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ac3\/1ca\/058\/ac31ca0588abcd3ef1c64757785bb482.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\"; import {Physics} from \"@react-three\/rapier\"; import {Player} from \".\/Player.jsx\"; import {Cubes} from \".\/Cube.jsx\"; import {WeaponModel} from \".\/WeaponModel.jsx\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;directionalLight                 castShadow                 intensity={.8}                 position={[50, 50, 0]} \/>             &lt;Physics gravity={[0, -20, 0]}>                 &lt;Ground \/>                 &lt;Player \/>                 &lt;Cubes \/>             &lt;\/Physics>              &lt;group position={[3, 1, -2]}>                 &lt;WeaponModel \/>             &lt;\/group>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>After that, let&#8217;s add another attribute <strong>receiveShadow to the Ground <\/strong>component, which means that the component in the scene can receive and display shadows on itself.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cb3\/8e4\/11b\/cb38e411bac9510c545cbe0cf601cf27.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"715\" height=\"162\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cb3\/8e4\/11b\/cb38e411bac9510c545cbe0cf601cf27.png\"\/><\/p>\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\"; import {CuboidCollider, RigidBody} from \"@react-three\/rapier\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;RigidBody type=\"fixed\" colliders={false}>             &lt;mesh receiveShadow position={[0, 0, 0]} rotation-x={-Math.PI \/ 2}>                 &lt;planeGeometry args={[500, 500]} \/>                 &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[100, 100]} \/>             &lt;\/mesh>             &lt;CuboidCollider args={[500, 2, 500]} position={[0, -2, 0]}\/>         &lt;\/RigidBody>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4ca\/7a2\/965\/4ca7a29658968b4d3d9dfc54bcb5390a.png\" alt=\"The model casts a shadow  \" title=\"The model casts a shadow  \" width=\"1560\" height=\"818\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4ca\/7a2\/965\/4ca7a29658968b4d3d9dfc54bcb5390a.png\"\/><\/p>\n<div><figcaption><em>The model casts a shadow<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p>Similar attributes should be added to other objects on the scene: cubes and player. For the cubes we will add <strong>castShadow <\/strong>and <strong>receiveShadow, <\/strong>because they can both cast and <strong>receive <\/strong>shadows, and for the player we will add only <strong>castShadow<\/strong>.  <\/p>\n<p>Let&#8217;s add <strong>castShadow<\/strong> for <strong>Player<\/strong>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/53c\/1b5\/a97\/53c1b5a97cde138c387fdfd495376619.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"640\" height=\"158\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/53c\/1b5\/a97\/53c1b5a97cde138c387fdfd495376619.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import * as RAPIER from \"@dimforge\/rapier3d-compat\" import {CapsuleCollider, RigidBody, useRapier} from \"@react-three\/rapier\"; import {useRef} from \"react\"; import {usePersonControls} from \".\/hooks.js\"; import {useFrame} from \"@react-three\/fiber\";  const MOVE_SPEED = 5; const direction = new THREE.Vector3(); const frontVector = new THREE.Vector3(); const sideVector = new THREE.Vector3();  export const Player = () => {     const playerRef = useRef();     const { forward, backward, left, right, jump } = usePersonControls();      const rapier = useRapier();      useFrame((state) => {         if (!playerRef.current) return;          \/\/ moving player         const velocity = playerRef.current.linvel();          frontVector.set(0, 0, backward - forward);         sideVector.set(left - right, 0, 0);         direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED).applyEuler(state.camera.rotation);          playerRef.current.wakeUp();         playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });          \/\/ jumping         const world = rapier.world;         const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x: 0, y: -1, z: 0 }));         const grounded = ray &amp;&amp; ray.collider &amp;&amp; Math.abs(ray.toi) &lt;= 1.5;          if (jump &amp;&amp; grounded) doJump();          \/\/ moving camera         const {x, y, z} = playerRef.current.translation();         state.camera.position.set(x, y, z);     });      const doJump = () => {         playerRef.current.setLinvel({x: 0, y: 8, z: 0});     }      return (         &lt;>             &lt;RigidBody colliders={false} mass={1} ref={playerRef} lockRotations>                 &lt;mesh castShadow>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                     &lt;CapsuleCollider args={[0.75, 0.5]} \/>                 &lt;\/mesh>             &lt;\/RigidBody>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Add <strong>castShadow<\/strong> and <strong>receiveShadow<\/strong> for <strong>Cube<\/strong>.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/55c\/858\/933\/55c858933f39030c0ba587b6bb74e365.png\" alt=\"Cube.jsx\" title=\"Cube.jsx\" width=\"471\" height=\"159\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/55c\/858\/933\/55c858933f39030c0ba587b6bb74e365.png\"\/><\/p>\n<div><figcaption>Cube.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Cube.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {RigidBody} from \"@react-three\/rapier\"; import cubes from \".\/cubes.json\";  export const Cubes = () => {     return cubes.map((coords, index) => &lt;Cube key={index} position={coords} \/>); }  const Cube = (props) => {     return (         &lt;RigidBody {...props}>             &lt;mesh castShadow receiveShadow>                 &lt;meshStandardMaterial color=\"white\" \/>                 &lt;boxGeometry \/>             &lt;\/mesh>         &lt;\/RigidBody>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5d9\/523\/f66\/5d9523f66c74c57685be7956483444a4.GIF\" alt=\"All objects on scene cast a shadow\" title=\"All objects on scene cast a shadow\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>All objects on scene cast a shadow<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/9a1c30993bf65f8dfdb7230f42d03c38d0682bb8\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Adding shadows &#8212; correcting shadow clipping  <\/h3>\n<p>If you look closely now, you will find that the surface area on which the shadow is cast is quite small. And when going beyond this area, the shadow is simply cut off.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bc1\/d85\/b6b\/bc1d85b6b69d728194313f167faf91aa.GIF\" alt=\"Shadow cropping\" title=\"Shadow cropping\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Shadow cropping<\/figcaption><\/div>\n<\/figure>\n<p>The reason for this is that by default the camera captures only a small area of the displayed shadows from <strong>directionalLight<\/strong>. We can for the <strong>directionalLight <\/strong>component by adding additional attributes <strong>shadow-camera-(top, bottom, left, right) to <\/strong>expand this area of visibility. After adding these attributes, the shadow will become slightly blurred. To improve the quality, we will add the <strong>shadow-mapSize attribute<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/19f\/0ec\/fc7\/19f0ecfc7ffa08513c826028d76f0c28.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"479\" height=\"507\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/19f\/0ec\/fc7\/19f0ecfc7ffa08513c826028d76f0c28.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\"; import {Physics} from \"@react-three\/rapier\"; import {Player} from \".\/Player.jsx\"; import {Cubes} from \".\/Cube.jsx\"; import {WeaponModel} from \".\/WeaponModel.jsx\";  const shadowOffset = 50;  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;directionalLight                 castShadow                 intensity={1.5}                 shadow-mapSize={4096}                 shadow-camera-top={shadowOffset}                 shadow-camera-bottom={-shadowOffset}                 shadow-camera-left={shadowOffset}                 shadow-camera-right={-shadowOffset}                 position={[100, 100, 0]}             \/>             &lt;Physics gravity={[0, -20, 0]}>                 &lt;Ground \/>                 &lt;Player \/>                 &lt;Cubes \/>             &lt;\/Physics>              &lt;group position={[3, 1, -2]}>                 &lt;WeaponModel \/>             &lt;\/group>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/6fb490223ef97707fa4c465ae478c5f415ee6661\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Binding weapons to a character  <\/h3>\n<p>Now let&#8217;s add first-person weapon display. Create a new <strong>Weapon <\/strong>component, which will contain the weapon behaviour logic and the 3D model itself.<\/p>\n<pre><code class=\"javascript\">import {WeaponModel} from \".\/WeaponModel.jsx\";  export const Weapon = (props) => {     return (         &lt;group {...props}>             &lt;WeaponModel \/>         &lt;\/group>     ); }<\/code><\/pre>\n<p>Let&#8217;s place this component on the same level as the <strong>RigidBody of the <\/strong>character and in the <strong>useFrame <\/strong>hook we will set the position and rotation angle based on the position of the values from the camera.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/53b\/665\/2b6\/53b6652b6fe1e3e0d4edf3ca16cfb42e.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"865\" height=\"797\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/53b\/665\/2b6\/53b6652b6fe1e3e0d4edf3ca16cfb42e.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import * as RAPIER from \"@dimforge\/rapier3d-compat\" import {CapsuleCollider, RigidBody, useRapier} from \"@react-three\/rapier\"; import {useRef} from \"react\"; import {usePersonControls} from \".\/hooks.js\"; import {useFrame} from \"@react-three\/fiber\"; import {Weapon} from \".\/Weapon.jsx\";  const MOVE_SPEED = 5; const direction = new THREE.Vector3(); const frontVector = new THREE.Vector3(); const sideVector = new THREE.Vector3(); const rotation = new THREE.Vector3();  export const Player = () => {     const playerRef = useRef();     const { forward, backward, left, right, jump } = usePersonControls();     const objectInHandRef = useRef();      const rapier = useRapier();      useFrame((state) => {         if (!playerRef.current) return;          \/\/ moving player         const velocity = playerRef.current.linvel();          frontVector.set(0, 0, backward - forward);         sideVector.set(left - right, 0, 0);         direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED).applyEuler(state.camera.rotation);          playerRef.current.wakeUp();         playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });          \/\/ jumping         const world = rapier.world;         const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x: 0, y: -1, z: 0 }));         const grounded = ray &amp;&amp; ray.collider &amp;&amp; Math.abs(ray.toi) &lt;= 1.5;          if (jump &amp;&amp; grounded) doJump();          \/\/ moving camera         const {x, y, z} = playerRef.current.translation();         state.camera.position.set(x, y, z);          \/\/ moving object in hand for the player         objectInHandRef.current.rotation.copy(state.camera.rotation);         objectInHandRef.current.position.copy(state.camera.position).add(state.camera.getWorldDirection(rotation));     });      const doJump = () => {         playerRef.current.setLinvel({x: 0, y: 8, z: 0});     }      return (         &lt;>             &lt;RigidBody colliders={false} mass={1} ref={playerRef} lockRotations>                 &lt;mesh castShadow>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                     &lt;CapsuleCollider args={[0.75, 0.5]} \/>                 &lt;\/mesh>             &lt;\/RigidBody>             &lt;group ref={objectInHandRef}>                 &lt;Weapon position={[0.3, -0.1, 0.3]} scale={0.3} \/>             &lt;\/group>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8ab\/ce2\/68c\/8abce268c8a697d3e5bb8bf4ef9d7ab2.GIF\" alt=\"First-person weapon model display\" title=\"First-person weapon model display\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>First-person weapon model display<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/fbe4dccdfe6de98e97aad043e7083b34f3a81a7f\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Animation of weapon swinging while walking  <\/h3>\n<p>To make the character&#8217;s gait more natural, we will add a slight wiggle of the weapon while moving. To create the animation we will use the installed <strong>tween.js <\/strong>library.<\/p>\n<p><strong>The Weapon <\/strong>component will be wrapped in a <strong>group <\/strong>tag so that you can add a reference to it via the <strong>useRef <\/strong>hook.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/051\/e68\/c30\/051e68c30971a9fb6ae06594a4cabd46.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"605\" height=\"122\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/051\/e68\/c30\/051e68c30971a9fb6ae06594a4cabd46.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s add some <strong>useState <\/strong>to save the animation.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6d6\/450\/147\/6d64501478ee684cea2420834b023d38.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"639\" height=\"86\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6d6\/450\/147\/6d64501478ee684cea2420834b023d38.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s create a function to initialise the animation.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f68\/798\/ac8\/f68798ac8d9f459a6f3d8de92142c24f.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"551\" height=\"582\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f68\/798\/ac8\/f68798ac8d9f459a6f3d8de92142c24f.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>Code Explanation:<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const twSwayingAnimation = new TWEEN.Tween(currentPosition) &#8230; <br \/><\/strong>Creating an animation of an object &#171;swinging&#187; from its current position to a new position.<\/p>\n<\/li>\n<li>\n<p><strong>const twSwayingBackAnimation = new TWEEN.Tween(currentPosition) &#8230; <br \/><\/strong>Creating an animation of the object returning back to its starting position after the first animation has completed.<\/p>\n<\/li>\n<li>\n<p><strong>twSwayingAnimation.chain(twSwayingBackAnimation); <br \/><\/strong>Connecting two animations so that when the first animation completes, the second animation automatically starts.<\/p>\n<\/li>\n<\/ol>\n<\/blockquote>\n<p>In <strong>useEffect <\/strong>we call the animation initialisation function.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/dff\/f70\/ede\/dfff70ede434d155ba0d8ce1fd247acb.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"307\" height=\"62\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/dff\/f70\/ede\/dfff70ede434d155ba0d8ce1fd247acb.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Now it is necessary to determine the moment during which the movement occurs. This can be done by determining the current vector of the character&#8217;s direction.<\/p>\n<p>If character movement occurs, we will refresh the animation and run it again when finished.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/593\/6af\/3fa\/5936af3faa6eb00daa503d0ecc47af4a.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"418\" height=\"123\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/593\/6af\/3fa\/5936af3faa6eb00daa503d0ecc47af4a.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>Code Explanation:<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const isMoving = direction.length() > 0; <br \/><\/strong>Here the object&#8217;s movement state <strong>is <br \/><\/strong>checked. If the <strong>direction<\/strong> vector has a length greater than 0, it means that the object has a direction of movement.<\/p>\n<\/li>\n<li>\n<p><strong>if (isMoving &amp;&amp; isSwayingAnimationFinished) { &#8230; } <br \/><\/strong>This state is executed if the object is moving and the &#171;swinging&#187; animation has finished.<\/p>\n<\/li>\n<\/ol>\n<\/blockquote>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code>import * as THREE from \"three\"; import * as RAPIER from \"@dimforge\/rapier3d-compat\" import * as TWEEN from \"@tweenjs\/tween.js\"; import {CapsuleCollider, RigidBody, useRapier} from \"@react-three\/rapier\"; import {useEffect, useRef, useState} from \"react\"; import {usePersonControls} from \".\/hooks.js\"; import {useFrame} from \"@react-three\/fiber\"; import {Weapon} from \".\/Weapon.jsx\";  const MOVE_SPEED = 5; const direction = new THREE.Vector3(); const frontVector = new THREE.Vector3(); const sideVector = new THREE.Vector3(); const rotation = new THREE.Vector3();  export const Player = () => {     const playerRef = useRef();     const { forward, backward, left, right, jump } = usePersonControls();     const objectInHandRef = useRef();      const swayingObjectRef = useRef();     const [swayingAnimation, setSwayingAnimation] = useState(null);     const [swayingBackAnimation, setSwayingBackAnimation] = useState(null);     const [isSwayingAnimationFinished, setIsSwayingAnimationFinished] = useState(true);      const rapier = useRapier();      useFrame((state) => {         if (!playerRef.current) return;          \/\/ moving player         const velocity = playerRef.current.linvel();          frontVector.set(0, 0, backward - forward);         sideVector.set(left - right, 0, 0);         direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED).applyEuler(state.camera.rotation);          playerRef.current.wakeUp();         playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });          \/\/ jumping         const world = rapier.world;         const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x: 0, y: -1, z: 0 }));         const grounded = ray &amp;&amp; ray.collider &amp;&amp; Math.abs(ray.toi) &lt;= 1.5;          if (jump &amp;&amp; grounded) doJump();          \/\/ moving camera         const {x, y, z} = playerRef.current.translation();         state.camera.position.set(x, y, z);          \/\/ moving object in hand for the player         objectInHandRef.current.rotation.copy(state.camera.rotation);         objectInHandRef.current.position.copy(state.camera.position).add(state.camera.getWorldDirection(rotation));          const isMoving = direction.length() > 0;          if (isMoving &amp;&amp; isSwayingAnimationFinished) {             setIsSwayingAnimationFinished(false);             swayingAnimation.start();         }          TWEEN.update();     });      const doJump = () => {         playerRef.current.setLinvel({x: 0, y: 8, z: 0});     }      const initSwayingObjectAnimation = () => {         const currentPosition = new THREE.Vector3(0, 0, 0);         const initialPosition = new THREE.Vector3(0, 0, 0);         const newPosition = new THREE.Vector3(-0.05, 0, 0);         const animationDuration = 300;         const easing = TWEEN.Easing.Quadratic.Out;          const twSwayingAnimation = new TWEEN.Tween(currentPosition)             .to(newPosition, animationDuration)             .easing(easing)             .onUpdate(() => {                 swayingObjectRef.current.position.copy(currentPosition);             });          const twSwayingBackAnimation = new TWEEN.Tween(currentPosition)             .to(initialPosition, animationDuration)             .easing(easing)             .onUpdate(() => {                 swayingObjectRef.current.position.copy(currentPosition);             })             .onComplete(() => {                 setIsSwayingAnimationFinished(true);             });          twSwayingAnimation.chain(twSwayingBackAnimation);          setSwayingAnimation(twSwayingAnimation);         setSwayingBackAnimation(twSwayingBackAnimation);     }      useEffect(() => {         initSwayingObjectAnimation();     }, []);      return (         &lt;>             &lt;RigidBody colliders={false} mass={1} ref={playerRef} lockRotations>                 &lt;mesh castShadow>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                     &lt;CapsuleCollider args={[0.75, 0.5]} \/>                 &lt;\/mesh>             &lt;\/RigidBody>             &lt;group ref={objectInHandRef}>                 &lt;group ref={swayingObjectRef}>                     &lt;Weapon position={[0.3, -0.1, 0.3]} scale={0.3} \/>                 &lt;\/group>             &lt;\/group>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>In the <strong>App <\/strong>component, let&#8217;s add a <strong>useFrame <\/strong>where we will update the tween animation.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/494\/e8c\/919\/494e8c919f8dddb52c025b40191e5d1f.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"245\" height=\"103\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/494\/e8c\/919\/494e8c919f8dddb52c025b40191e5d1f.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>TWEEN.update() updates <\/strong>all active animations in the <strong>TWEEN.js <\/strong>library. This method is called on each animation frame to ensure that all animations run smoothly.  <\/p>\n<\/blockquote>\n<p>Section code<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/3e986e87f1daa1ed9e20fabc7675ca83142d1d78\" rel=\"noopener noreferrer nofollow\">First commit<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/12abbcd9d5b922e8766b859ff82272de08099c06\" rel=\"noopener noreferrer nofollow\">Second commit<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>Recoil animation <\/h3>\n<p>We need to define the moment when a shot is fired &#8212; that is, when the mouse button is pressed. Let&#8217;s add <strong>useState <\/strong>to store this state, <strong>useRef <\/strong>to store a reference to the weapon object, and two event handlers for pressing and releasing the mouse button.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b2d\/554\/e8f\/b2d554e8f913e0abb31b82795aff6ac2.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"442\" height=\"47\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b2d\/554\/e8f\/b2d554e8f913e0abb31b82795aff6ac2.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0e8\/122\/139\/0e81221394abd02208c970c58b2a7a24.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"433\" height=\"184\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0e8\/122\/139\/0e81221394abd02208c970c58b2a7a24.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b8\/433\/94d\/4b843394d92415de03c7b70e865ca1d6.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"304\" height=\"161\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4b8\/433\/94d\/4b843394d92415de03c7b70e865ca1d6.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s implement a recoil animation when clicking the mouse button. We will use <strong>tween.js <\/strong>library for this purpose.\u00a0<\/p>\n<p>Let us define constants for recoil force and animation duration.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/431\/812\/d06\/431812d0657df0237dc26c7e90e810fd.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"341\" height=\"69\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/431\/812\/d06\/431812d0657df0237dc26c7e90e810fd.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>As with the weapon wiggle animation, we add two <strong>useState <\/strong>states for the recoil and return to home position animation and a state with the animation end status.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3ae\/6d1\/3d7\/3ae6d13d70a5a2ee7b55a00ca5942d39.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"554\" height=\"44\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3ae\/6d1\/3d7\/3ae6d13d70a5a2ee7b55a00ca5942d39.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s make functions to get a random vector of recoil animation &#8212; <strong>generateRecoilOffset <\/strong>and <strong>generateNewPositionOfRecoil<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/65f\/d3b\/55d\/65fd3b55d1c4f06aa333a5075a420a54.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"475\" height=\"240\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/65f\/d3b\/55d\/65fd3b55d1c4f06aa333a5075a420a54.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Create a function to initialise the recoil animation. We will also add <strong>useEffect<\/strong>, in which we will specify the &#171;shot&#187; state as a dependency, so that at each shot the animation is initialised again and new end coordinates are generated.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b57\/7fa\/d5d\/b577fad5d7b063d1c7a4e257d1737c2c.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"558\" height=\"481\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b57\/7fa\/d5d\/b577fad5d7b063d1c7a4e257d1737c2c.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/424\/31e\/569\/42431e56910bdc518105a4f0822d6581.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"292\" height=\"225\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/424\/31e\/569\/42431e56910bdc518105a4f0822d6581.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>And in <strong>useFrame, <\/strong>let&#8217;s add a check for &#171;holding&#187; the mouse key for firing, so that the firing animation doesn&#8217;t stop until the key is released.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/dfc\/b5f\/a06\/dfcb5fa06fe4a8f74bc5d04c236b32e6.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"258\" height=\"104\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/dfc\/b5f\/a06\/dfcb5fa06fe4a8f74bc5d04c236b32e6.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Weapon.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import * as TWEEN from \"@tweenjs\/tween.js\"; import {WeaponModel} from \".\/WeaponModel.jsx\"; import {useEffect, useRef, useState} from \"react\"; import {useFrame} from \"@react-three\/fiber\";  const recoilAmount = 0.03; const recoilDuration = 100; const easing = TWEEN.Easing.Quadratic.Out;  export const Weapon = (props) => {     const [recoilAnimation, setRecoilAnimation] = useState(null);     const [recoilBackAnimation, setRecoilBackAnimation] = useState(null);     const [isShooting, setIsShooting] = useState(false);     const weaponRef = useRef();      useEffect(() => {         document.addEventListener('mousedown', () => {             setIsShooting(true);         });          document.addEventListener('mouseup', () => {             setIsShooting(false);         });     }, []);      const generateRecoilOffset = () => {         return new THREE.Vector3(             Math.random() * recoilAmount,             Math.random() * recoilAmount,             Math.random() * recoilAmount,         )     }      const generateNewPositionOfRecoil = (currentPosition) => {         const recoilOffset = generateRecoilOffset();         return currentPosition.clone().add(recoilOffset);     }      const initRecoilAnimation = () => {         const currentPosition = new THREE.Vector3(0, 0, 0);         const initialPosition = new THREE.Vector3(0, 0, 0);         const newPosition = generateNewPositionOfRecoil(currentPosition);          const twRecoilAnimation = new TWEEN.Tween(currentPosition)             .to(newPosition, recoilDuration)             .easing(easing)             .onUpdate(() => {                 weaponRef.current.position.copy(currentPosition);             });          const twRecoilBackAnimation = new TWEEN.Tween(currentPosition)             .to(initialPosition, recoilDuration)             .easing(easing)             .onUpdate(() => {                 weaponRef.current.position.copy(currentPosition);             });          twRecoilAnimation.chain(twRecoilBackAnimation);          setRecoilAnimation(twRecoilAnimation);         setRecoilBackAnimation(twRecoilBackAnimation);     }      const startShooting = () => {         recoilAnimation.start();     }      useEffect(() => {         initRecoilAnimation();          if (isShooting) {             startShooting();         }     }, [isShooting]);      useFrame(() => {         TWEEN.update();          if (isShooting) {             startShooting();         }     });      return (         &lt;group {...props}>             &lt;group ref={weaponRef}>                 &lt;WeaponModel \/>             &lt;\/group>         &lt;\/group>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1b3\/b83\/d6a\/1b3b83d6a6901e908ff2c57be17c2d14.GIF\" alt=\"Recoil animation\" title=\"Recoil animation\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Recoil animation<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/8a1a38b6402503d87b05dfc4d83834d5d9ed3a9c\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Animation during inactivity  <\/h3>\n<p>Realise the animation of &#171;inactivity&#187; for the character, so that there is no feeling of the game &#171;hanging&#187;.<\/p>\n<p>To do this, let&#8217;s add some new states via <strong>useState<\/strong>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/50c\/da8\/3a5\/50cda83a58d929d9c279760cbaf4f8ea.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"746\" height=\"63\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/50c\/da8\/3a5\/50cda83a58d929d9c279760cbaf4f8ea.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s fix the initialisation of the &#171;wiggle&#187; animation to use values from the state. The idea is that different states: walking or stopping, will use different values for the animation and each time the animation will be initialised first.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7a3\/bf3\/884\/7a3bf38847366694d374646d907bebf3.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"561\" height=\"506\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7a3\/bf3\/884\/7a3bf38847366694d374646d907bebf3.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Player.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import * as RAPIER from \"@dimforge\/rapier3d-compat\" import * as TWEEN from \"@tweenjs\/tween.js\"; import {CapsuleCollider, RigidBody, useRapier} from \"@react-three\/rapier\"; import {useEffect, useRef, useState} from \"react\"; import {usePersonControls} from \".\/hooks.js\"; import {useFrame} from \"@react-three\/fiber\"; import {Weapon} from \".\/Weapon.jsx\";  const MOVE_SPEED = 5; const direction = new THREE.Vector3(); const frontVector = new THREE.Vector3(); const sideVector = new THREE.Vector3(); const rotation = new THREE.Vector3();  export const Player = () => {     const playerRef = useRef();     const { forward, backward, left, right, jump } = usePersonControls();     const objectInHandRef = useRef();      const swayingObjectRef = useRef();     const [swayingAnimation, setSwayingAnimation] = useState(null);     const [swayingBackAnimation, setSwayingBackAnimation] = useState(null);     const [isSwayingAnimationFinished, setIsSwayingAnimationFinished] = useState(true);     const [swayingNewPosition, setSwayingNewPosition] = useState(new THREE.Vector3(-0.005, 0.005, 0));     const [swayingDuration, setSwayingDuration] = useState(1000);     const [isMoving, setIsMoving] = useState(false);      const rapier = useRapier();      useFrame((state) => {         if (!playerRef.current) return;          \/\/ moving player         const velocity = playerRef.current.linvel();          frontVector.set(0, 0, backward - forward);         sideVector.set(left - right, 0, 0);         direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED).applyEuler(state.camera.rotation);          playerRef.current.wakeUp();         playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });          \/\/ jumping         const world = rapier.world;         const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x: 0, y: -1, z: 0 }));         const grounded = ray &amp;&amp; ray.collider &amp;&amp; Math.abs(ray.toi) &lt;= 1.5;          if (jump &amp;&amp; grounded) doJump();          \/\/ moving camera         const {x, y, z} = playerRef.current.translation();         state.camera.position.set(x, y, z);          \/\/ moving object in hand for the player         objectInHandRef.current.rotation.copy(state.camera.rotation);         objectInHandRef.current.position.copy(state.camera.position).add(state.camera.getWorldDirection(rotation));          setIsMoving(direction.length() > 0);          if (swayingAnimation &amp;&amp; isSwayingAnimationFinished) {             setIsSwayingAnimationFinished(false);             swayingAnimation.start();         }     });      const doJump = () => {         playerRef.current.setLinvel({x: 0, y: 8, z: 0});     }      const setAnimationParams = () => {         if (!swayingAnimation) return;          swayingAnimation.stop();         setIsSwayingAnimationFinished(true);          if (isMoving) {             setSwayingDuration(() => 300);             setSwayingNewPosition(() => new THREE.Vector3(-0.05, 0, 0));         } else {             setSwayingDuration(() => 1000);             setSwayingNewPosition(() => new THREE.Vector3(-0.01, 0, 0));         }     }      const initSwayingObjectAnimation = () => {         const currentPosition = new THREE.Vector3(0, 0, 0);         const initialPosition = new THREE.Vector3(0, 0, 0);         const newPosition = swayingNewPosition;         const animationDuration = swayingDuration;         const easing = TWEEN.Easing.Quadratic.Out;          const twSwayingAnimation = new TWEEN.Tween(currentPosition)             .to(newPosition, animationDuration)             .easing(easing)             .onUpdate(() => {                 swayingObjectRef.current.position.copy(currentPosition);             });          const twSwayingBackAnimation = new TWEEN.Tween(currentPosition)             .to(initialPosition, animationDuration)             .easing(easing)             .onUpdate(() => {                 swayingObjectRef.current.position.copy(currentPosition);             })             .onComplete(() => {                 setIsSwayingAnimationFinished(true);             });          twSwayingAnimation.chain(twSwayingBackAnimation);          setSwayingAnimation(twSwayingAnimation);         setSwayingBackAnimation(twSwayingBackAnimation);     }      useEffect(() => {         setAnimationParams();     }, [isMoving]);      useEffect(() => {         initSwayingObjectAnimation();     }, [swayingNewPosition, swayingDuration]);      return (         &lt;>             &lt;RigidBody colliders={false} mass={1} ref={playerRef} lockRotations>                 &lt;mesh castShadow>                     &lt;capsuleGeometry args={[0.5, 0.5]}\/>                     &lt;CapsuleCollider args={[0.75, 0.5]} \/>                 &lt;\/mesh>             &lt;\/RigidBody>             &lt;group ref={objectInHandRef}>                 &lt;group ref={swayingObjectRef}>                     &lt;Weapon position={[0.3, -0.1, 0.3]} scale={0.3} \/>                 &lt;\/group>             &lt;\/group>         &lt;\/>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/04e\/a9b\/85a\/04ea9b85a2accf541d88302cd717b885.GIF\" alt=\"Idle animation\" title=\"Idle animation\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Idle animation<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/635b8619e303aa0456fcde15300032b2633f1c10\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Conclusion  <\/h3>\n<p>In this part we have implemented scene generation and character movement. We also added a weapon model, recoil animation when firing and at idle. In the next part we will continue to refine our game, adding new functionality.  <\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/764692\/\"> https:\/\/habr.com\/ru\/articles\/764692\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Hello, esteemed Habr contributors!<\/p>\n<h3>Introduction <\/h3>\n<p>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 <a href=\"https:\/\/docs.pmnd.rs\/react-three-fiber\/getting-started\/introduction\" rel=\"noopener noreferrer nofollow\"><strong>React Three Fiber<\/strong><\/a><strong> <\/strong>library &#8212; a powerful tool for creating 3D graphics based on <a href=\"https:\/\/threejs.org\/\" rel=\"noopener noreferrer nofollow\"><strong>Three.js<\/strong><\/a> using <strong>React <\/strong>technology.  <\/p>\n<h3>About the React Three Fiber stack  <\/h3>\n<p><strong>React Three Fiber is <\/strong>a wrapper over <strong>Three.js <\/strong>that uses the structure and principles of <strong>React <\/strong>to create 3D graphics on the web. This stack allows developers to combine the power of <strong>Three.js <\/strong>with the convenience and flexibility of <strong>React<\/strong>, making the process of creating an application more intuitive and organised.<\/p>\n<p>At the heart of <strong>React Three Fiber is <\/strong>the idea that everything you create in a scene is a <strong>React<\/strong> component. This allows developers to apply familiar patterns and methodologies.<\/p>\n<p>One of the main advantages of <strong>React Three Fiber <\/strong>is its ease of integration with the <strong>React <\/strong>ecosystem. Any other <strong>React<\/strong> tools can still be easily integrated when using this library.<\/p>\n<h3>Relevance of Web-GameDev  <\/h3>\n<p><strong>Web-GameDev <\/strong>has undergone major changes in recent years, evolving from simple 2D <a href=\"https:\/\/www.adobe.com\/products\/flashplayer\/end-of-life.html\" rel=\"noopener noreferrer nofollow\"><strong>Flash<\/strong><\/a><strong> <\/strong>games to complex 3D projects comparable to desktop applications. This growth in popularity and capabilities makes Web-GameDev an area that cannot be ignored.<\/p>\n<p>One of the main advantages of web gaming is its accessibility. Players do not need to download and install any additional software &#8212; just click on the link in their browser. This simplifies the distribution and promotion of games, making them available to a wide audience around the world.<\/p>\n<p>Finally, web game development can be a great way for developers to try their hand at gamedev using familiar technologies. Thanks to the available tools and libraries, even without experience in 3D graphics, it is possible to create interesting and high-quality projects!<\/p>\n<h3>Game performance in modern browsers  <\/h3>\n<p>Modern browsers have come a long way, evolving from fairly simple web browsing tools to powerful platforms for running complex applications and games. Major browsers such as <strong>Chrome<\/strong>, <strong>Firefox<\/strong>, <strong>Edge <\/strong>and <strong>others are <\/strong>constantly being optimised and developed to ensure high performance, making them an ideal platform for developing complex applications.<\/p>\n<p>One of the key tools that has fuelled the development of browser-based gaming is <a href=\"https:\/\/www.khronos.org\/webgl\/\" rel=\"noopener noreferrer nofollow\"><strong><u>WebGL<\/u><\/strong><\/a>. This standard allowed developers to use hardware graphics acceleration, which significantly improved the performance of 3D games. Together with other webAPIs, <strong>WebGL <\/strong>opens up new possibilities for creating impressive web applications directly in the browser.<\/p>\n<p>Nevertheless, when developing games for the browser, it is crucial to consider various performance aspects: resource optimisation, memory management and adaptation for different devices are all key points that can affect the success of a project.<\/p>\n<h3>On your mark!  <\/h3>\n<p>However, words and theory are one thing, but practical experience is quite another. To really understand and appreciate the full potential of web game development, the best way is to immerse yourself in the development process. Therefore, as an example of successful web game development, we will create our own game. This process will allow us to learn key aspects of development, face real problems and find solutions to them, and see how powerful and flexible a web game development platform can be.<\/p>\n<p>In a series of articles, we&#8217;ll look at how to create a first-person shooter using the features of this library, and dive into the exciting world of web-gamedev!<\/p>\n<p>Final demo<\/p>\n<div class=\"embed_link\">\n<div class=\"embed__thumb\" style=\"background-image: url(&quot;undefined&quot;);\"><\/div>\n<div class=\"embed__caption\">\n<div class=\"embed__caption-title\"><span>FPS Game<\/span><\/div>\n<p><a href=\"https:\/\/zy45x4-5173.csb.app\/\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"embed__caption-link\">zy45x4-5173.csb.app<\/a><\/div>\n<\/div>\n<div class=\"embed_link\">\n<div class=\"embed__thumb\" style=\"background-image: url(&quot;undefined&quot;);\"><\/div>\n<div class=\"embed__caption\">\n<div class=\"embed__caption-title\"><span>fps-game\/master &#8212; CodeSandbox<\/span><\/div>\n<p><a href=\"https:\/\/codesandbox.io\/p\/github\/JI0PATA\/fps-game\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"embed__caption-link\">codesandbox.io<\/a><\/div>\n<\/div>\n<p>Repository on <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\" rel=\"noopener noreferrer nofollow\"><u>GitHub<\/u><\/a><\/p>\n<p>Now, let&#8217;s get started!<\/p>\n<h3>Setting up the project and installing packages  <\/h3>\n<p>First of all, we will need a <strong>React <\/strong>project template. So let&#8217;s start by installing it.  <\/p>\n<pre><code class=\"bash\">npm create vite@latest<\/code><\/pre>\n<ul>\n<li>\n<p>select the <strong>React <\/strong>library; <\/p>\n<\/li>\n<li>\n<p>select <strong>JavaScript<\/strong>. <\/p>\n<\/li>\n<\/ul>\n<p>Install additional npm packages.<\/p>\n<pre><code class=\"bash\">npm install three @react-three\/fiber @react-three\/drei @react-three\/rapier zustand @tweenjs\/tween.js<\/code><\/pre>\n<p>Then <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">delete<\/a> everything unnecessary from our project.  <\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Customising the Canvas display  <\/h3>\n<p>In the <strong>main.jsx <\/strong>file, add a div element that will be displayed on the page as a scope. Insert a <strong>Canvas <\/strong>component and set the field of view of the camera. Inside the <strong>Canvas <\/strong>component place the <strong>App <\/strong>component.  <\/p>\n<figure class=\"full-width\">\n<div><figcaption>main.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>main.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import React from 'react' import ReactDOM from 'react-dom\/client' import App from '.\/App.jsx' import '.\/index.css' import {Canvas} from \"@react-three\/fiber\";  ReactDOM.createRoot(document.getElementById('root')).render(   &lt;React.StrictMode>     &lt;div id=\"container\">         &lt;div className=\"aim\">&lt;\/div>         &lt;Canvas camera={{ fov: 45 }}>             &lt;App \/>         &lt;\/Canvas>     &lt;\/div>   &lt;\/React.StrictMode>, )<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Let&#8217;s add styles to stretch the UI elements to the full height of the screen and display the scope as a circle in the centre of the screen.  <\/p>\n<figure class=\"\">\n<div><figcaption>index.css<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>index.css<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"css\">* {     margin: 0;     padding: 0; }  html, body, #root, #container {     height: 100%; }  .aim {     position: absolute;     top: 50%;     left: 50%;     width: 10px;     height: 10px;     border-radius: 50%;     transform: translate3d(-50%, -50%, 0);     border: 2px solid white;     z-index: 2; }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>In the <strong>App component we <\/strong>add a <strong>Sky <\/strong>component, which will be displayed as the background in our game scene in the form of a sky.  <\/p>\n<figure class=\"\">\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {Sky} from \"@react-three\/drei\";  export const App = () => {   return (     &lt;>       &lt;Sky sunPosition={[100, 20, 100]} \/>     &lt;\/>   ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\">\n<div><figcaption><em>Displaying the sky in the scene<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/ede1b9eb617c1967aef2f09104c234a87b68bc97\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Floor surface  <\/h3>\n<p>Let&#8217;s create a <strong>Ground<\/strong> component<strong> <\/strong>and place it in the <strong>App <\/strong>component.  <\/p>\n<figure class=\"\">\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\";  export const App = () => {     return (         &lt;>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;Ground \/>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>In <strong>Ground, <\/strong>create a flat surface element. On the Y axis move it downwards so that this plane is in the field of view of the camera. And also flip the plane on the X axis to make it horizontal.  <\/p>\n<figure class=\"\">\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">export const Ground = () => {     return (         &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>             &lt;planeGeometry args={[500, 500]} \/>             &lt;meshStandardMaterial color=\"gray\" \/>         &lt;\/mesh>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>Even though we specified grey as the material colour, the plane appears completely black.  <\/p>\n<figure class=\"full-width\">\n<div><figcaption><em>Flat on the scene<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/e9335eb32868ba48874b7461a6d79609a074f78b\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Basic lighting  <\/h3>\n<p>By default, there is no lighting in the scene, so let&#8217;s add a light source <strong>ambientLight, <\/strong>which illuminates the object from all sides and does not have a directed beam. As a parameter set the intensity of the glow.  <\/p>\n<figure class=\"\">\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\";  export const App = () => {     return (         &lt;>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Ground \/>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\">\n<div><figcaption><em>Illuminated plane<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/385825283562b117c4b849c0c71832e0d634ec3e\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Texture for the floor surface  <\/h3>\n<p>To make the floor surface not look homogeneous, we will add texture. Make a pattern of the floor surface in the form of cells repeating all along the surface.<\/p>\n<p>In the <strong>assets <\/strong>folder add a PNG image with a texture.<\/p>\n<figure class=\"full-width\">\n<div><figcaption><em>Added texture<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p>To load a texture on the scene, let&#8217;s <strong>use the useTexture <\/strong>hook from the <strong>@react-three\/drei <\/strong>package. And as a parameter for the hook we will pass the texture image imported into the file. Set the repetition of the image in the horizontal axes.  <\/p>\n<figure class=\"full-width\">\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>             &lt;planeGeometry args={[500, 500]} \/>             &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[360, 360]} \/>         &lt;\/mesh>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\">\n<div><figcaption><em>Texture on a plane<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/f1c91e9106b77f6c45b5edb48e3b229ba0e1862c\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Camera movement  <\/h3>\n<p>Using the<strong> PointerLockControls <\/strong>component from the <strong>@react-three\/drei <\/strong>package, fix the cursor on the screen so that it does not move when you move the mouse, but changes the position of the camera on the scene.  <\/p>\n<figure class=\"\">\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import {PointerLockControls, Sky} from \"@react-three\/drei\"; import {Ground} from \".\/Ground.jsx\";  export const App = () => {     return (         &lt;>             &lt;PointerLockControls \/>             &lt;Sky sunPosition={[100, 20, 100]}\/>             &lt;ambientLight intensity={1.5} \/>             &lt;Ground \/>         &lt;\/>     ) }  export default App<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"\">\n<div><figcaption>Camera motion demonstration<\/figcaption><\/div>\n<\/figure>\n<p>Let&#8217;s make a small edit for the <strong>Ground <\/strong>component.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>Ground.jsx<\/figcaption><\/div>\n<\/figure>\n<details class=\"spoiler\">\n<summary>Ground.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as THREE from \"three\"; import { useTexture } from \"@react-three\/drei\"; import floorTexture from \".\/assets\/floor.png\";  export const Ground = () => {     const texture = useTexture(floorTexture);     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;      return (         &lt;mesh position={[0, -5, 0]} rotation-x={-Math.PI \/ 2}>             &lt;planeGeometry args={[500, 500]} \/>             &lt;meshStandardMaterial color=\"gray\" map={texture} map-repeat={[100, 100]} \/>         &lt;\/mesh>     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/3eb9816f82157b88647d185ec57bb7f9de959f62\" rel=\"noopener noreferrer nofollow\">Section code<\/a><\/p>\n<h3>Adding physics  <\/h3>\n<p>For clarity, let&#8217;s add a simple cube to the scene.  <\/p>\n<pre><code class=\"xml\">&lt;mesh position={[0, 3, -5]}>     &lt;boxGeometry \/> &lt;\/mesh><\/code><\/pre>\n<figure class=\"full-width\">\n<div><figcaption><em>The cube on the scene<\/em> <\/figcaption><\/div>\n<\/figure>\n<p>Right now he&#8217;s just hanging in space.<\/p>\n<p>Use the <strong>Physics <\/strong>component from the <strong>@react-three\/rapier <\/strong>package to add &#171;physics&#187; to the scene. As a parameter, configure the gravity field, where we set the gravitational forces along the axes.<\/p>\n<pre><code class=\"xml\">&lt;Physics gravity={[0, -20, 0]}>     &lt;Ground \/>     &lt;mesh position={[0, 3, -5]}>         &lt;boxGeometry \/>     &lt;\/mesh> &lt;\/Physics><\/code><\/pre>\n<p>However, our cube is inside the physics component, but nothing happens to it. To make the cube behave like a real physical object, we need to wrap it in the <strong>RigidBody <\/strong>component from the <strong>@react-three\/ra<\/strong><\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-356374","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/356374","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=356374"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/356374\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=356374"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=356374"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=356374"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}