{"id":356331,"date":"2024-05-20T23:27:23","date_gmt":"2024-05-20T23:27:23","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=356331"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-20T23:27:23","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=356331","title":{"rendered":"<span>React + Three.js. \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 3D \u0448\u0443\u0442\u0435\u0440. \u0427\u0430\u0441\u0442\u044c 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>\u041f\u0440\u0438\u0432\u0435\u0442, \u0443\u0432\u0430\u0436\u0430\u0435\u043c\u044b\u0435 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \u0425\u0430\u0431\u0440!\u00a0<\/p>\n<h3>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0432\u0435\u0431-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u043c\u0435\u0436\u0434\u0443 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u0438 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u0441\u0442\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0441 \u043a\u0430\u0436\u0434\u044b\u043c \u0434\u043d\u0451\u043c. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0430\u0439\u0442\u044b, \u043d\u043e \u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0435 \u0438\u0433\u0440\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0435\u043b\u0430\u0435\u0442 \u044d\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/docs.pmnd.rs\/react-three-fiber\/getting-started\/introduction\" rel=\"noopener noreferrer nofollow\"><strong>React Three Fiber<\/strong><\/a> &#8212; \u043c\u043e\u0449\u043d\u043e\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f 3D-\u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 <a href=\"https:\/\/threejs.org\/\" rel=\"noopener noreferrer nofollow\"><strong>Three.js<\/strong><\/a> \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 <strong>React<\/strong>.<\/p>\n<h3>\u041e \u0441\u0442\u0435\u043a\u0435 React Three Fiber<\/h3>\n<p><strong>React Three Fiber<\/strong> &#8212; \u044d\u0442\u043e \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 <strong>Three.js<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b <strong>React<\/strong> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f 3D-\u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u0432 \u0432\u0435\u0431\u0435. \u042d\u0442\u043e\u0442 \u0441\u0442\u0435\u043a \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u043c\u043e\u0449\u044c <strong>Three.js<\/strong> \u0441 \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u043e\u043c \u0438 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c\u044e <strong>React<\/strong>, \u0434\u0435\u043b\u0430\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c.<\/p>\n<p>\u0412 \u043e\u0441\u043d\u043e\u0432\u0435 <strong>React Three Fiber<\/strong> \u043b\u0435\u0436\u0438\u0442 \u0438\u0434\u0435\u044f, \u0447\u0442\u043e \u0432\u0441\u0451, \u0447\u0442\u043e \u0432\u044b \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0435 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438 <strong>React<\/strong>. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b \u0438 \u043c\u0435\u0442\u043e\u0434\u043e\u043b\u043e\u0433\u0438\u0438.<\/p>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u0433\u043b\u0430\u0432\u043d\u044b\u0445 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432 <strong>React Three Fiber<\/strong> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0435\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 <strong>React<\/strong>. \u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u044d\u0442\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0434\u0440\u0443\u0433\u0438\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b <strong>React<\/strong>.<\/p>\n<h3>\u0410\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c Web-GameDev  <\/h3>\n<p><strong>Web-GameDev<\/strong> \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0433\u043e\u0434\u044b \u043f\u0435\u0440\u0435\u0436\u0438\u043b \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0432\u0448\u0438\u0441\u044c \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 2D-\u0438\u0433\u0440 \u043d\u0430 <a href=\"https:\/\/www.adobe.com\/products\/flashplayer\/end-of-life.html\" rel=\"noopener noreferrer nofollow\"><strong>Flash<\/strong><\/a> \u0432 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 3D-\u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043c\u044b\u0435 \u0441 \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438. \u042d\u0442\u043e\u0442 \u0440\u043e\u0441\u0442 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u0438 \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u0435\u043b\u0430\u0435\u0442 <strong>Web-GameDev<\/strong> \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u043b\u044c\u0437\u044f \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u0433\u043b\u0430\u0432\u043d\u044b\u0445 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432 \u0432\u0435\u0431-\u0438\u0433\u0440 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c. \u0418\u0433\u0440\u043e\u043a\u0430\u043c \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b &#8212; \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u043f\u0440\u043e\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u0438\u0433\u0440, \u0434\u0435\u043b\u0430\u044f \u0438\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0439 \u0430\u0443\u0434\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443.<\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0435\u0431-\u0438\u0433\u0440 \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u0441\u0438\u043b\u044b \u0432 gamedev, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0435 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043c, \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 \u043e\u043f\u044b\u0442\u0430 \u0432 3D-\u0433\u0440\u0430\u0444\u0438\u043a\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b!<\/p>\n<h3>\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u0433\u0440 \u0432 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u0445  <\/h3>\n<p>\u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044b \u043f\u0440\u043e\u0448\u043b\u0438 \u0434\u043e\u043b\u0433\u0438\u0439 \u043f\u0443\u0442\u044c \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f, \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0432\u0448\u0438\u0441\u044c \u0438\u0437 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0432 \u043c\u043e\u0449\u043d\u044b\u0435 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u0438\u0433\u0440. \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044b, \u0442\u0430\u043a\u0438\u0435 \u043a\u0430\u043a <strong>Chrome<\/strong>,<strong> Firefox<\/strong>, <strong>Edge<\/strong> \u0438 <strong>\u0434\u0440\u0443\u0433\u0438\u0435<\/strong>, \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0438 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0438\u0445 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u043e\u0439 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439.<\/p>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0430\u043b \u0442\u043e\u043b\u0447\u043e\u043a \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044e \u0438\u0433\u0440 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <a href=\"https:\/\/www.khronos.org\/webgl\/\" rel=\"noopener noreferrer nofollow\"><strong>WebGL<\/strong><\/a>. \u042d\u0442\u043e\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u043b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u043e\u0435 \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435 \u0433\u0440\u0430\u0444\u0438\u043a\u0438, \u0447\u0442\u043e \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u0432\u044b\u0441\u0438\u043b\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c 3D-\u0438\u0433\u0440. \u0421\u043e\u0432\u043c\u0435\u0441\u0442\u043d\u043e \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0432\u0435\u0431-API, <strong>WebGL<\/strong> \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u044f\u044e\u0449\u0438\u0445 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.<\/p>\n<p>\u0422\u0435\u043c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0438\u0433\u0440 \u0434\u043b\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043a\u0440\u0430\u0439\u043d\u0435 \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438: \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u043c\u044f\u0442\u044c\u044e \u0438 \u0430\u0434\u0430\u043f\u0442\u0430\u0446\u0438\u044f \u043f\u043e\u0434 \u0440\u0430\u0437\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 &#8212; \u0432\u0441\u0451 \u044d\u0442\u043e \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<h3>\u041d\u0430 \u0441\u0442\u0430\u0440\u0442!  <\/h3>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u0441\u043b\u043e\u0432\u0430 \u0438 \u0442\u0435\u043e\u0440\u0438\u044f &#8212; \u044d\u0442\u043e \u043e\u0434\u043d\u043e, \u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u043f\u044b\u0442 &#8212; \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0438\u043d\u043e\u0435. \u0427\u0442\u043e\u0431\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c \u0438 \u043e\u0446\u0435\u043d\u0438\u0442\u044c \u0432\u0435\u0441\u044c \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b <strong>Web-GameDev<\/strong>, \u043b\u0443\u0447\u0448\u0438\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0435\u0431-\u0438\u0433\u0440 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u0438\u0433\u0440\u0443. \u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u043e\u0441\u0432\u043e\u0438\u0442\u044c \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u0442\u044c\u0441\u044f \u0441 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u0438 \u043d\u0430\u0439\u0442\u0438 \u043f\u0443\u0442\u0438 \u0438\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0449\u043d\u043e\u0439 \u0438 \u0433\u0438\u0431\u043a\u043e\u0439 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0432\u0435\u0431-\u0433\u0435\u0439\u043c\u0434\u0435\u0432\u0430.<\/p>\n<p>\u0412 \u0441\u0435\u0440\u0438\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0448\u0443\u0442\u0435\u0440 \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043b\u0438\u0446\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u0430\u043d\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u0438 \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u043c\u0441\u044f \u0432 \u0443\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u0438\u0440 \u0432\u0435\u0431-\u0438\u0433\u0440!<\/p>\n<p>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0434\u0435\u043c\u043e<\/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>\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043d\u0430 <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\" rel=\"noopener noreferrer nofollow\">GitHub<\/a><\/p>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c!<\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432  <\/h3>\n<p>\u0412 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0448\u0430\u0431\u043b\u043e\u043d \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 <strong>React<\/strong>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u0447\u043d\u0451\u043c \u0441 \u0435\u0433\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438.  <\/p>\n<pre><code class=\"bash\">npm create vite@latest<\/code><\/pre>\n<ul>\n<li>\n<p>\u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <strong>React<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c <strong>JavaScript<\/strong>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 npm-\u043f\u0430\u043a\u0435\u0442\u044b.<\/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>\u0417\u0430\u0442\u0435\u043c <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">\u0443\u0434\u0430\u043b\u0438\u043c<\/a> \u0432\u0441\u0451 \u043b\u0438\u0448\u043d\u0435\u0435 \u0438\u0437 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f Canvas  <\/h3>\n<p>\u0412 \u0444\u0430\u0439\u043b <strong>main.jsx <\/strong>\u0434\u043e\u0431\u0430\u0432\u0438\u043c div-\u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u0446\u0435\u043b\u0430. \u0412\u0441\u0442\u0430\u0432\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Canvas<\/strong> \u0438<strong> <\/strong>\u0437\u0430\u0434\u0430\u0434\u0438\u043c \u043f\u043e\u043b\u0435 \u0437\u0440\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u044b. \u0412\u043d\u0443\u0442\u0440\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Canvas<\/strong> \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App<\/strong>.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/96b\/5e9\/3e1\/96b5e93e1101717548f64928973af3ef.png\" alt=\"main.jsx\" title=\"main.jsx\" width=\"523\" height=\"315\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/96b\/5e9\/3e1\/96b5e93e1101717548f64928973af3ef.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>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0442\u0438\u043b\u0438, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043d\u0430 \u0432\u0441\u044e \u0432\u044b\u0441\u043e\u0442\u0443 \u044d\u043a\u0440\u0430\u043d\u0430 \u0438 \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043f\u0440\u0438\u0446\u0435\u043b\u0430 \u0432 \u0432\u0438\u0434\u0435 \u043a\u0440\u0443\u0433\u0430 \u043f\u043e \u0446\u0435\u043d\u0442\u0440\u0443 \u044d\u043a\u0440\u0430\u043d\u0430.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/894\/627\/221\/894627221e166f6b886357f9e5a22465.png\" alt=\"index.css\" title=\"index.css\" width=\"398\" height=\"397\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/894\/627\/221\/894627221e166f6b886357f9e5a22465.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>\u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App <\/strong>\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Sky<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0437\u0430\u0434\u043d\u0438\u043c \u0444\u043e\u043d\u043e\u043c \u0432 \u043d\u0430\u0448\u0435\u0439 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0441\u0446\u0435\u043d\u0435 \u0432 \u0432\u0438\u0434\u0435 \u043d\u0435\u0431\u0430.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4ee\/7e2\/e5c\/4ee7e2e5c1282124e79567362dc18463.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"387\" height=\"196\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4ee\/7e2\/e5c\/4ee7e2e5c1282124e79567362dc18463.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\/62d\/c11\/817\/62dc11817931b8bb087ce7b84eb93ca7.png\" alt=\"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0435\u0431\u0430 \u0432 \u0441\u0446\u0435\u043d\u0435\" title=\"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0435\u0431\u0430 \u0432 \u0441\u0446\u0435\u043d\u0435\" width=\"1600\" height=\"774\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/62d\/c11\/817\/62dc11817931b8bb087ce7b84eb93ca7.png\"\/><\/p>\n<div><figcaption><em>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0435\u0431\u0430 \u0432 \u0441\u0446\u0435\u043d\u0435<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/ede1b9eb617c1967aef2f09104c234a87b68bc97\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0430  <\/h3>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Ground<\/strong> \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0435\u0433\u043e \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c3d\/2f7\/165\/c3d2f71653bba0946ccfb05afaba4fd6.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"442\" height=\"358\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c3d\/2f7\/165\/c3d2f71653bba0946ccfb05afaba4fd6.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>\u0412 <strong>Ground<\/strong> \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043f\u043b\u043e\u0441\u043a\u043e\u0439 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438. \u041f\u043e \u043e\u0441\u0438 Y \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u043c \u0432\u043d\u0438\u0437, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u043f\u043e\u043b\u0435 \u0437\u0440\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u044b. \u0410 \u0442\u0430\u043a\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0451\u043c \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043f\u043e \u043e\u0441\u0438 \u0425, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0430 \u043f\u0440\u0438\u043d\u044f\u043b\u0430 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a0e\/4d3\/c8e\/a0e4d3c8e95a063ce670f8e89a95b3e3.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"484\" height=\"165\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a0e\/4d3\/c8e\/a0e4d3c8e95a063ce670f8e89a95b3e3.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>\u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e \u0447\u0442\u043e \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0446\u0432\u0435\u0442\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u043c\u044b \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u0441\u0435\u0440\u044b\u0439, \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0447\u0451\u0440\u043d\u043e\u0439.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/df4\/ffc\/336\/df4ffc3367eba8343dacdc672ad01604.png\" alt=\"\u041f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435  \" title=\"\u041f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435  \" width=\"1600\" height=\"750\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/df4\/ffc\/336\/df4ffc3367eba8343dacdc672ad01604.png\"\/><\/p>\n<div><figcaption><em>\u041f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/e9335eb32868ba48874b7461a6d79609a074f78b\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0411\u0430\u0437\u043e\u0432\u043e\u0435 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u0435 <\/h3>\n<p>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 \u0441\u0446\u0435\u043d\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0441\u0432\u0435\u0442\u0430 <strong>ambientLight<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0441\u0432\u0435\u0449\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e \u0432\u0441\u0435\u0445 \u0441\u0442\u043e\u0440\u043e\u043d \u0438 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043b\u0443\u0447\u0430. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0441\u0438\u043b\u0443 \u0441\u0432\u0435\u0447\u0435\u043d\u0438\u044f.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d58\/941\/72e\/d5894172e14e453adc275afba5a44e2e.png\" alt=\"\" title=\"\" width=\"434\" height=\"138\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d58\/941\/72e\/d5894172e14e453adc275afba5a44e2e.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\/7a1\/363\/724\/7a13637241c4bd103d198cc73dc335a2.png\" alt=\"\u041e\u0441\u0432\u0435\u0449\u0451\u043d\u043d\u0430\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c\" title=\"\u041e\u0441\u0432\u0435\u0449\u0451\u043d\u043d\u0430\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c\" width=\"1600\" height=\"647\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7a1\/363\/724\/7a13637241c4bd103d198cc73dc335a2.png\"\/><\/p>\n<div><figcaption><em>\u041e\u0441\u0432\u0435\u0449\u0451\u043d\u043d\u0430\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/385825283562b117c4b849c0c71832e0d634ec3e\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u0434\u043b\u044f \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043b\u0430  <\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0430 \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u0430 \u043e\u0434\u043d\u043e\u0440\u043e\u0434\u043d\u043e, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u0443. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043b\u0430 \u0432 \u0432\u0438\u0434\u0435 \u044f\u0447\u0435\u0435\u043a, \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0445\u0441\u044f \u043d\u0430 \u0432\u0441\u0451\u043c \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0443 <strong>assets<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 PNG \u0441 \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u043e\u0439.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a90\/1c1\/405\/a901c14055f79723f11aa597e2e445f8.png\" alt=\"\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u0434\u043b\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438\" title=\"\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u0434\u043b\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438\" width=\"1600\" height=\"1236\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a90\/1c1\/405\/a901c14055f79723f11aa597e2e445f8.png\"\/><\/p>\n<div><figcaption><em>\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430<\/em> \u0434\u043b\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438<\/figcaption><\/div>\n<\/figure>\n<p>\u0414\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0443 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0445\u0443\u043a\u043e\u043c <strong>useTexture<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/dre<\/strong>i. \u0410 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0434\u043b\u044f \u0445\u0443\u043a\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0432 \u0444\u0430\u0439\u043b \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u044b. \u0417\u0430\u0434\u0430\u0434\u0438\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u043c \u043e\u0441\u044f\u043c.  <\/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\/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=\"\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438\" width=\"1368\" height=\"1005\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8b9\/0e3\/517\/8b90e3517cc13fa425fa8d3063a14ce7.png\"\/><\/p>\n<div><figcaption><em>\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/f1c91e9106b77f6c45b5edb48e3b229ba0e1862c\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0414\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u043e\u0439  <\/h3>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>PointerLockControls<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/drei<\/strong> \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u043a\u0443\u0440\u0441\u043e\u0440 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u043d\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u043b\u0441\u044f \u043f\u0440\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043c\u044b\u0448\u044c\u044e, \u0430 \u043c\u0435\u043d\u044f\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0ed\/6f5\/bc3\/0ed6f5bc36d94e36b422e2eb9a8e4920.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"512\" height=\"219\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0ed\/6f5\/bc3\/0ed6f5bc36d94e36b422e2eb9a8e4920.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\/08a\/b61\/48e\/08ab6148ef45ee6c007a6ca9ef184997.GIF\" alt=\"\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u043e\u0439\" title=\"\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u043e\u0439\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u043e\u0439<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043f\u0440\u0430\u0432\u043a\u0443 \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Ground<\/strong>.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/696\/830\/d6a\/696830d6a1ba29ae6207fd80019b32b8.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"688\" height=\"160\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/696\/830\/d6a\/696830d6a1ba29ae6207fd80019b32b8.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\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u0438\u0437\u0438\u043a\u0443  <\/h3>\n<p>\u0414\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0443 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043a\u0443\u0431.  <\/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\/ab4\/ef4\/aad\/ab4ef4aad0120c8bc7f9edc58df77be4.png\" alt=\"\u041a\u0443\u0431 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435\" title=\"\u041a\u0443\u0431 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435\" width=\"1291\" height=\"935\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ab4\/ef4\/aad\/ab4ef4aad0120c8bc7f9edc58df77be4.png\"\/><\/p>\n<div><figcaption><em>\u041a\u0443\u0431 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u043e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0438\u0441\u0438\u0442 \u0432 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435.<\/p>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Physics<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/rapier <\/strong>\u0434\u043e\u0431\u0430\u0432\u0438\u043c \u201c\u0444\u0438\u0437\u0438\u043a\u0443\u201d \u043d\u0430 \u0441\u0446\u0435\u043d\u0443. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u043f\u043e\u043b\u0435 <strong>gravity<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0434\u0430\u0434\u0438\u043c \u0441\u0438\u043b\u044b \u043f\u0440\u0438\u0442\u044f\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u043e\u0441\u044f\u043c.<\/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>\u041e\u0434\u043d\u0430\u043a\u043e, \u043d\u0430\u0448 \u043a\u0443\u0431 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0441 \u0444\u0438\u0437\u0438\u043a\u043e\u0439, \u043d\u043e \u0441 \u043d\u0438\u043c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442. \u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043a\u0443\u0431 \u0432\u0451\u043b \u0441\u0435\u0431\u044f \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0435\u0433\u043e \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>RigidBody<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/rapier<\/strong>.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/48b\/142\/e4f\/48b142e4fb4e486638f1b6d6425714c5.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"528\" height=\"433\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/48b\/142\/e4f\/48b142e4fb4e486638f1b6d6425714c5.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>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 \u0443\u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u0443\u0431 \u043f\u043e\u0434 \u0432\u043e\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c \u0433\u0440\u0430\u0432\u0438\u0442\u0430\u0446\u0438\u0438 \u043f\u0430\u0434\u0430\u0435\u0442.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/29d\/5c8\/e47\/29d5c8e477813f32a77b693bbddfdbe8.GIF\" alt=\"\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0431\u0430\" title=\"\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0431\u0430\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0431\u0430<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u0434\u0440\u0443\u0433\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 &#8212; \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u043b \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043a\u0443\u0431, \u0438 \u0437\u0430 \u043f\u0440\u0435\u0434\u0435\u043b\u044b \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043e\u043d \u043d\u0435 \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0442\u0441\u044f.  <\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/1f564e9836554b644d43dd75d4644aa9cd5159b1\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u043e\u043b \u043a\u0430\u043a \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u0431\u044a\u0435\u043a\u0442  <\/h3>\n<p>\u0412\u0435\u0440\u043d\u0451\u043c\u0441\u044f \u043a \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 <strong>Ground<\/strong> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>RigidBody<\/strong> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043e\u0431\u0451\u0440\u0442\u043a\u0438 \u043d\u0430\u0434 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u043e\u043b\u0430.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/bf2\/2da\/ad4\/bf22daad47f081d3519509bc97176c39.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"721\" height=\"441\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bf2\/2da\/ad4\/bf22daad47f081d3519509bc97176c39.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>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043a\u0443\u0431 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043d\u0430 \u043f\u043e\u043b\u0443 \u043a\u0430\u043a \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u0435.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ff1\/0ff\/eaf\/ff10ffeafe4c696edabefe2be27481f9.GIF\" alt=\"\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0431\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c\" title=\"\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0431\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0431\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/31fe05543209c6828b7d5b69f06189e9f48a124a\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u043e\u0434\u0447\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0437\u0430\u043a\u043e\u043d\u0430\u043c \u0444\u0438\u0437\u0438\u043a\u0438  <\/h3>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Player<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.<\/p>\n<p>\u041f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 &#8212; \u044d\u0442\u043e \u0442\u0430\u043a\u043e\u0439 \u0436\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043a\u0430\u043a \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043a\u0443\u0431, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u043e\u043b\u0430, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441 \u043a\u0443\u0431\u043e\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>RigidBody<\/strong>. \u0410 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0432 \u0444\u043e\u0440\u043c\u0435 \u043a\u0430\u043f\u0441\u0443\u043b\u044b.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fa2\/649\/81f\/fa264981ff5a35b7135176b86ced8e9e.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"441\" height=\"260\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fa2\/649\/81f\/fa264981ff5a35b7135176b86ced8e9e.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>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Player<\/strong> \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Physics<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/648\/438\/ceb\/648438ceb07457ff14fda94bdd34ea6c.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"504\" height=\"307\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/648\/438\/ceb\/648438ceb07457ff14fda94bdd34ea6c.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>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u043d\u0430\u0448 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a35\/b52\/ca7\/a35b52ca74ef19e2afe9f9f8c4c569b2.png\" alt=\"\u041f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 \u0432 \u0444\u043e\u0440\u043c\u0435 \u043a\u0430\u043f\u0441\u0443\u043b\u044b\" title=\"\u041f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 \u0432 \u0444\u043e\u0440\u043c\u0435 \u043a\u0430\u043f\u0441\u0443\u043b\u044b\" width=\"1335\" height=\"860\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a35\/b52\/ca7\/a35b52ca74ef19e2afe9f9f8c4c569b2.png\"\/><\/p>\n<div><figcaption>\u041f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 \u0432 \u0444\u043e\u0440\u043c\u0435 \u043a\u0430\u043f\u0441\u0443\u043b\u044b<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/8877590a4767a281e71b0d0e4d04e49ad4660773\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 &#8212; \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0445\u0443\u043a\u0430  <\/h3>\n<p>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043b\u0430\u0432\u0438\u0448 <strong>WASD<\/strong>, \u0430 \u043f\u0440\u044b\u0436\u043e\u043a \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 <strong>\u041f\u0440\u043e\u0431\u0435\u043b<\/strong>.<\/p>\n<p>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e react-\u0445\u0443\u043a\u0430 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043b\u043e\u0433\u0438\u043a\u0443 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <strong>hooks.js<\/strong> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u0443\u0434\u0430 \u043d\u043e\u0432\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e <strong>usePersonControls<\/strong>.<\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 {\u201c\u043a\u043e\u0434 \u043a\u043b\u0430\u0432\u0438\u0448\u0438\u201d: \u201c\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u201d}. \u0414\u0430\u043b\u0435\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u0438 \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u0438 \u043a\u043b\u0430\u0432\u0438\u0448 \u043a\u043b\u0430\u0432\u0438\u0430\u0442\u0443\u0440\u044b. \u041f\u0440\u0438 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u0431\u0443\u0434\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0438\u0445 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u0412 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0438\u0437 \u0445\u0443\u043a\u0430 \u0432\u0435\u0440\u043d\u0451\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 {\u201c\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u201d: \u201c\u0441\u0442\u0430\u0442\u0443\u0441\u201d}.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8de\/019\/918\/8de019918db2afe8252bb15bbf453a06.png\" alt=\"hooks.js\" title=\"hooks.js\" width=\"516\" height=\"914\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8de\/019\/918\/8de019918db2afe8252bb15bbf453a06.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\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 &#8212; \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u0445\u0443\u043a\u0430  <\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0443\u043a\u0430 <strong>usePersonControls<\/strong> \u0435\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u043c. \u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 <strong>Player<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0438 \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430.\u00a0<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7d7\/30d\/7f1\/7d730d7f1b8a801d5d095c817ebf827a.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"336\" height=\"92\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d7\/30d\/7f1\/7d730d7f1b8a801d5d095c817ebf827a.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0414\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f <strong>useFrame<\/strong>, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u043c \u043f\u0430\u043a\u0435\u0442\u043e\u043c <strong>@react-three\/fiber<\/strong>. \u0414\u0430\u043d\u043d\u044b\u0439 \u0445\u0443\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e <strong>requestAnimationFrame<\/strong> \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u0435\u043b\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 60 \u0440\u0430\u0437 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0443.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/897\/27a\/4f3\/89727a4f3e1e237032ed9c9c88c0c6ce.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"717\" height=\"695\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/897\/27a\/4f3\/89727a4f3e1e237032ed9c9c88c0c6ce.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>\u041f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u0434\u0443<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const playerRef = useRef();<br \/><\/strong>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u0441\u044b\u043b\u043a\u0443 \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0438\u0433\u0440\u043e\u043a\u0430. \u042d\u0442\u0430 \u0441\u0441\u044b\u043b\u043a\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c \u0438\u0433\u0440\u043e\u043a\u0430 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>const { forward, backward, left, right, jump } = usePersonControls();<br \/><\/strong>\u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0445\u0443\u043a\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u0431\u0443\u043b\u0435\u0432\u044b\u043c\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u043c\u0438, \u043a\u0430\u043a\u0438\u0435 \u043a\u043d\u043e\u043f\u043a\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u0436\u0430\u0442\u044b \u0438\u0433\u0440\u043e\u043a\u043e\u043c.<\/p>\n<\/li>\n<li>\n<p><strong>useFrame((state) => { &#8230; });<br \/><\/strong>\u0425\u0443\u043a \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u043a\u0430\u0434\u0440\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 \u044d\u0442\u043e\u0433\u043e \u0445\u0443\u043a\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0438 \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0438\u0433\u0440\u043e\u043a\u0430.\u00a0<\/p>\n<\/li>\n<li>\n<p><strong>if (!playerRef.current) return;<br \/><\/strong>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0438\u0433\u0440\u043e\u043a\u0430. \u0415\u0441\u043b\u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0433\u0440\u043e\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442, \u0442\u043e, \u0432\u043e \u0438\u0437\u0431\u0435\u0436\u0430\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a, \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>const velocity = playerRef.current.linvel();<br \/><\/strong>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0438\u0433\u0440\u043e\u043a\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>frontVector.set(0, 0, backward &#8212; forward);<br \/><\/strong>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0432\u043f\u0435\u0440\u0451\u0434\/\u043d\u0430\u0437\u0430\u0434 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043d\u0430\u0436\u0430\u0442\u044b\u0445 \u043a\u043d\u043e\u043f\u043e\u043a.<\/p>\n<\/li>\n<li>\n<p><strong>sideVector.set(left &#8212; right, 0, 0);<br \/><\/strong>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0432\u043b\u0435\u0432\u043e\/\u0432\u043f\u0440\u0430\u0432\u043e.<\/p>\n<\/li>\n<li>\n<p><strong>direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED);<br \/><\/strong>\u0412\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0438\u0433\u0440\u043e\u043a\u0430 \u043f\u0443\u0442\u0451\u043c \u0432\u044b\u0447\u0438\u0442\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f, \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 (\u0447\u0442\u043e\u0431\u044b \u0434\u043b\u0438\u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0431\u044b\u043b\u0430 \u0440\u0430\u0432\u043d\u0430 1) \u0438 \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0443 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p><strong>playerRef.current.wakeUp();<br \/><\/strong>&#171;\u041f\u0440\u043e\u0431\u0443\u0436\u0434\u0435\u043d\u0438\u0435&#187; \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0438\u0433\u0440\u043e\u043a\u0430, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0435\u0442 \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434, \u0442\u043e \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u044a\u0435\u043a\u0442 \u201c\u0437\u0430\u0441\u043d\u0451\u0442\u201d \u0438 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>playerRef.current.setLinvel({ x: direction.x, y: velocity.y, z: direction.z });<br \/><\/strong>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043d\u043e\u0432\u043e\u0439 \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0438\u0433\u0440\u043e\u043a\u0430 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 (\u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u043f\u0440\u044b\u0436\u043a\u0438 \u0438\u043b\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u044f).<\/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>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u043a\u043b\u0430\u0432\u0438\u0448\u0438 <strong>WASD<\/strong> \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 \u043d\u0430\u0447\u0430\u043b \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0446\u0435\u043d\u0435. \u0422\u0430\u043a\u0436\u0435 \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u043a\u0443\u0431\u043e\u043c, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043d\u0438 \u043e\u0431\u0430 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/992\/a36\/a32\/992a36a32218df2f7165d0aaaeb79906.GIF\" alt=\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430\" title=\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/e385028ce949545cf58ade19dfdb8250643498d1\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 &#8212; \u043f\u0440\u044b\u0436\u043e\u043a  <\/h3>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u044b\u0436\u043e\u043a, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043e\u043c \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 <strong>@dimforge\/rapier3d-compat<\/strong> \u0438 <strong>@react-three\/rapier<\/strong>. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043d\u0430 \u0437\u0435\u043c\u043b\u0435 \u0438 \u0431\u044b\u043b\u0430 \u043d\u0430\u0436\u0430\u0442\u0430 \u043a\u043b\u0430\u0432\u0438\u0448\u0430 \u0434\u043b\u044f \u043f\u0440\u044b\u0436\u043a\u0430. \u0412 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u0437\u0430\u0434\u0430\u0451\u043c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0443 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0438 \u0441\u0438\u043b\u0443 \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u044f \u043f\u043e \u043e\u0441\u0438 Y.  <\/p>\n<p>\u0415\u0449\u0451 \u0434\u043b\u044f <strong>Player<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0430\u0441\u0441\u0443 \u0438 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u0432\u0441\u0435\u043c \u043e\u0441\u044f\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u043d\u0435 \u0437\u0430\u0432\u0430\u043b\u0438\u0432\u0430\u043b\u0441\u044f \u0432 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u0440\u0438 \u0441\u0442\u043e\u043b\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u0438 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/1c9\/ffa\/247\/1c9ffa247b09725215af19fa6e42a032.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"801\" height=\"892\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1c9\/ffa\/247\/1c9ffa247b09725215af19fa6e42a032.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>\u041f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u0434\u0443<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const world = rapier.world;<br \/><\/strong>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u0446\u0435\u043d\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u0432\u0438\u0436\u043a\u0430 <strong>Rapier<\/strong>. \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432\u0441\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0445 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c.<\/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>\u0412 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u201c\u043b\u0443\u0447\u0435\u0432\u043e\u0435 \u043f\u0440\u043e\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435\u201d (raycasting). \u0421\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043b\u0443\u0447, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0438\u0433\u0440\u043e\u043a\u0430 \u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0432\u043d\u0438\u0437 \u043f\u043e \u043e\u0441\u0438 Y. \u042d\u0442\u043e\u0442 \u043b\u0443\u0447 \u201c\u0431\u0440\u043e\u0441\u0430\u0435\u0442\u0441\u044f\u201d \u0432 \u0441\u0446\u0435\u043d\u0443, \u0447\u0442\u043e\u0431\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u043f\u0435\u0440\u0435\u0441\u0435\u043a\u0430\u0435\u0442\u0441\u044f \u043b\u0438 \u043e\u043d \u0441 \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.<\/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>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0443\u0441\u043b\u043e\u0432\u0438\u0435, \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438 \u0438\u0433\u0440\u043e\u043a \u043d\u0430 \u0437\u0435\u043c\u043b\u0435:<\/p>\n<ul>\n<li>\n<p><strong>ray <\/strong>&#8212; \u0431\u044b\u043b \u043b\u0438 \u043b\u0443\u0447 \u0441\u043e\u0437\u0434\u0430\u043d;<\/p>\n<\/li>\n<li>\n<p><strong>ray.collider <\/strong>&#8212; \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u043b\u0438 \u043b\u0443\u0447 \u0441 \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435;<\/p>\n<\/li>\n<li>\n<p><strong>Math.abs(ray.toi)<\/strong> &#8212; \u201c\u0432\u0440\u0435\u043c\u044f \u0432\u043e\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u201d \u043b\u0443\u0447\u0430. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043c\u0435\u043d\u044c\u0448\u0435 \u0438\u043b\u0438 \u0440\u0430\u0432\u043d\u043e \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u043c\u0443, \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0438\u0433\u0440\u043e\u043a \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u043b\u0438\u0437\u043a\u043e \u043a \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438, \u0447\u0442\u043e\u0431\u044b \u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u201c\u043d\u0430 \u0437\u0435\u043c\u043b\u0435\u201d.<\/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>\u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Ground<\/strong>, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0441 \u043b\u0443\u0447\u0430\u043c\u0438 \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u201c\u043f\u0440\u0438\u0437\u0435\u043c\u043b\u0435\u043d\u0438\u044f\u201d, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043d\u0435\u0433\u043e \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9e2\/684\/af1\/9e2684af1712f1a5e4a814b22eaa84be.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"716\" height=\"444\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9e2\/684\/af1\/9e2684af1712f1a5e4a814b22eaa84be.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>\u041f\u043e\u0434\u043d\u0438\u043c\u0435\u043c \u043a\u0430\u043c\u0435\u0440\u0443 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0432\u044b\u0448\u0435 \u0434\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0441\u0446\u0435\u043d\u044b.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/669\/ca4\/7b8\/669ca47b81e097586aaf0b788acb4588.png\" alt=\"main.jsx\" title=\"main.jsx\" width=\"516\" height=\"161\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/669\/ca4\/7b8\/669ca47b81e097586aaf0b788acb4588.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\/018\/54b\/0ff\/01854b0ff1e4ccc9e163f4cd3f93fc04.gif\" alt=\"\u041f\u0440\u044b\u0436\u043a\u0438 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430\" title=\"\u041f\u0440\u044b\u0436\u043a\u0438 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430\" width=\"848\" height=\"478\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/018\/54b\/0ff\/01854b0ff1e4ccc9e163f4cd3f93fc04.gif\"\/><\/p>\n<div><figcaption><em>\u041f\u0440\u044b\u0436\u043a\u0438 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/bc2c22d9661defb4e645fac17e63457505604ac9\" rel=\"noopener noreferrer nofollow\">\u041f\u0435\u0440\u0432\u044b\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/f5bad070b78677e9924fc4599a2025baf7dffb09\" rel=\"noopener noreferrer nofollow\">\u0412\u0442\u043e\u0440\u043e\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b \u0437\u0430 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0435\u043c  <\/h3>\n<p>\u0414\u043b\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u044b \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043f\u043e\u0437\u0438\u0446\u0438\u044e \u0438\u0433\u0440\u043e\u043a\u0430 \u0438 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u043a\u0430\u0434\u0440\u0430 \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0437\u0438\u0446\u0438\u044e \u043a\u0430\u043c\u0435\u0440\u044b. \u0410 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436 \u0434\u0432\u0438\u0433\u0430\u043b\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438, \u043a\u0443\u0434\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430 \u043a\u0430\u043c\u0435\u0440\u0430, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <strong>applyEuler<\/strong>.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/02e\/80b\/89c\/02e80b89c480eae23afc538c0076b566.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"953\" height=\"386\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/02e\/80b\/89c\/02e80b89c480eae23afc538c0076b566.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>\u041f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u0434\u0443<\/strong><\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 <strong>applyEuler<\/strong> \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a \u0432\u0435\u043a\u0442\u043e\u0440\u0443 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0433\u043b\u043e\u0432 \u042d\u0439\u043b\u0435\u0440\u0430. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043a \u0432\u0435\u043a\u0442\u043e\u0440\u0443 <strong>direction<\/strong>. \u042d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u043a\u0430\u043c\u0435\u0440\u044b, \u0447\u0442\u043e\u0431\u044b \u0438\u0433\u0440\u043e\u043a \u0434\u0432\u0438\u0433\u0430\u043b\u0441\u044f \u0432 \u0442\u043e\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0438, \u043a\u0443\u0434\u0430 \u043f\u043e\u0432\u0451\u0440\u043d\u0443\u0442\u0430 \u043a\u0430\u043c\u0435\u0440\u0430.<\/p>\n<\/blockquote>\n<p>\u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u043f\u0440\u0430\u0432\u0438\u043c \u0440\u0430\u0437\u043c\u0435\u0440 <strong>Player<\/strong> \u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0433\u043e \u0432\u044b\u0448\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a\u0443\u0431\u0430, \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432 \u0440\u0430\u0437\u043c\u0435\u0440 <strong>CapsuleCollider<\/strong> \u0438 \u043f\u043e\u043f\u0440\u0430\u0432\u0438\u0432 \u043b\u043e\u0433\u0438\u043a\u0443 \u201c\u043f\u0440\u044b\u0436\u043a\u0430\u201d.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d20\/2dd\/f21\/d202ddf21d2faa290a0ba06856e7df22.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"832\" height=\"347\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d20\/2dd\/f21\/d202ddf21d2faa290a0ba06856e7df22.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\/88a\/795\/757\/88a79575741963e30906f417c6792f1f.GIF\" alt=\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b\" title=\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/74e2206a9d16d78268224d9d451cd2405ec162ab\" rel=\"noopener noreferrer nofollow\">\u041f\u0435\u0440\u0432\u044b\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/0063ea91b5981e7ce2be89b71d73a9f2d69a624a\" rel=\"noopener noreferrer nofollow\">\u0412\u0442\u043e\u0440\u043e\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u0443\u0431\u043e\u0432<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0441\u0446\u0435\u043d\u0430 \u043d\u0435 \u043e\u0449\u0443\u0449\u0430\u043b\u0430\u0441\u044c \u0441\u043e\u0432\u0441\u0435\u043c \u043f\u0443\u0441\u0442\u043e\u0439, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u043a\u0443\u0431\u043e\u0432. \u0412 \u0444\u0430\u0439\u043b\u0435 json \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0438\u043c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u0437 \u043a\u0443\u0431\u043e\u0432 \u0438 \u0437\u0430\u0442\u0435\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u043c \u0438\u0445 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <strong>cubes.json<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0438\u043c \u043c\u0430\u0441\u0441\u0438\u0432 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442.  <\/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>\u0412 \u0444\u0430\u0439\u043b\u0435 <strong>Cube.jsx<\/strong> \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Cubes<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u0443\u0431\u043e\u0432 \u0432 \u0446\u0438\u043a\u043b\u0435. \u0410 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Cube<\/strong> \u0431\u0443\u0434\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u044b\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c.  <\/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>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Cubes<\/strong> \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App<\/strong>, \u0443\u0434\u0430\u043b\u0438\u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439 \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439 \u043a\u0443\u0431.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/824\/840\/58e\/82484058e17fd25504c6c0ad94e137d2.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"506\" height=\"447\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/824\/840\/58e\/82484058e17fd25504c6c0ad94e137d2.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\/fbf\/943\/bfb\/fbf943bfb4847033c40431974aafd1cf.GIF\" alt=\"\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u0443\u0431\u043e\u0432\" title=\"\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u0443\u0431\u043e\u0432\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u0443\u0431\u043e\u0432<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/357405aab1cf8f49699397c7bcf07fa9ca7b667d\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442  <\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u043c\u0451\u043c\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c 3D-\u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0430 \u0441\u0446\u0435\u043d\u0443. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u043e\u0440\u0443\u0436\u0438\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0432\u043e\u0442 <a href=\"https:\/\/sketchfab.com\/3d-models\/low-poly-aks-74u-4aa2f92e016c434eafdba67b238bb7fb\" rel=\"noopener noreferrer nofollow\">\u0442\u0430\u043a\u0443\u044e<\/a>.<\/p>\n<p>\u0421\u043a\u0430\u0447\u0430\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 GLTF \u0438 \u0440\u0430\u0441\u043f\u0430\u043a\u0443\u0435\u043c \u0430\u0440\u0445\u0438\u0432 \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.\u00a0<\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 \u043d\u0430\u043c \u0444\u043e\u0440\u043c\u0430\u0442 \u0434\u043b\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0430 \u0441\u0446\u0435\u043d\u0443, \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 <strong>gltf-pipeline<\/strong>.<\/p>\n<pre><code class=\"bash\">npm i -D gltf-pipeline<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>gltf-pipeline <\/strong>\u0440\u0435\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 <strong>GLTF<\/strong> \u0432 \u0444\u043e\u0440\u043c\u0430\u0442 <strong>GLB<\/strong>, \u0442\u0430\u043a \u043a\u0430\u043a \u0432 \u044d\u0442\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435 \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u043e\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u0432 \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u044b\u0445\u043e\u0434\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0434\u043b\u044f \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 <strong>public<\/strong>.  <\/p>\n<pre><code class=\"bash\">gltf-pipeline -i weapon\/scene.gltf -o public\/weapon.glb<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c react-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443 \u044d\u0442\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438, \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0435\u0451 \u043d\u0430 \u0441\u0446\u0435\u043d\u0443. \u0412\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u043c <a href=\"https:\/\/gltf.pmnd.rs\/\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0441\u0443\u0440\u0441\u043e\u043c <\/a>\u043e\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 <strong>@react-three\/fiber<\/strong>.<\/p>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u044f \u0432 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440, \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b <strong>weapon.glb<\/strong>. \u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u043b\u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u041f\u0440\u043e\u0432\u043e\u0434\u043d\u0438\u043a \u043d\u0430\u0439\u0434\u0451\u043c \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c \u0435\u0433\u043e.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d66\/2bf\/e64\/d662bfe640f4d443a5e47b3529b12782.png\" alt=\"\u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c\" title=\"\u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c\" width=\"1123\" height=\"1184\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d66\/2bf\/e64\/d662bfe640f4d443a5e47b3529b12782.png\"\/><\/p>\n<div><figcaption><em>\u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u0412 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440\u0435 \u043c\u044b \u0443\u0432\u0438\u0434\u0438\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 react-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442, \u043a\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0451\u043c \u0432 \u043d\u0430\u0448 \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 \u043d\u043e\u0432\u044b\u0439 \u0444\u0430\u0439\u043b <strong>WeaponModel.jsx<\/strong>, \u0438\u0437\u043c\u0435\u043d\u0438\u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0430\u043c\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043d\u0430 \u043e\u0434\u043d\u043e\u0438\u043c\u0451\u043d\u043d\u043e\u0435 \u0441 \u0444\u0430\u0439\u043b\u043e\u043c.<\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/0621b9ac3ab267dbc94a99264662d9d896f80207\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0440\u0443\u0436\u0438\u044f \u043d\u0430 \u0441\u0446\u0435\u043d\u0435<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430 \u0441\u0446\u0435\u043d\u0443. \u0412 \u0444\u0430\u0439\u043b\u0435 <strong>App.jsx <\/strong>\u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>WeaponModel<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/828\/8c4\/148\/8288c4148bad0a5bd8c3d30df1ee94b1.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"428\" height=\"363\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/828\/8c4\/148\/8288c4148bad0a5bd8c3d30df1ee94b1.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\/2cd\/a94\/27d\/2cda9427ddfa68b960e2b41fe8f224dc.GIF\" alt=\"\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438\" title=\"\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/2f3aae04743b0f87724c723bb68ad71e5cbca53b\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0435\u043d\u0435\u0439  <\/h3>\n<p>\u0412 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430 \u043d\u0430\u0448\u0435\u0439 \u0441\u0446\u0435\u043d\u0435 \u043d\u0438 \u043e\u0434\u0438\u043d \u0438\u0437 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043e\u0432 \u043d\u0435 \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0442\u0435\u043d\u0438.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0442\u0435\u043d\u0438 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0430\u0442\u0440\u0438\u0431\u0443\u0442 <strong>shadows<\/strong> \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Canvas<\/strong>.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c3e\/04a\/bef\/c3e04abef685874d59a723b1fd56c3a3.png\" alt=\"main.jsx\" title=\"main.jsx\" width=\"415\" height=\"160\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c3e\/04a\/bef\/c3e04abef685874d59a723b1fd56c3a3.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>\u0414\u0430\u043b\u0435\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f. \u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e \u0447\u0442\u043e \u043c\u044b \u0443\u0436\u0435 \u0438\u043c\u0435\u0435\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 <strong>ambientLight<\/strong>, \u043e\u043d \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0435\u043d\u0438 \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u0442\u0430\u043a \u043a\u0430\u043a \u0443 \u043d\u0435\u0433\u043e \u043d\u0435\u0442 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043b\u0443\u0447\u0430 \u0441\u0432\u0435\u0442\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f <strong>directionalLight<\/strong> \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0435\u0433\u043e. \u0410\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u043c \u0434\u043b\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0430 \u201c\u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043d\u0438\u0435\u201d \u0442\u0435\u043d\u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <strong>castShadow<\/strong>. \u0418\u043c\u0435\u043d\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0442\u0435\u043d\u044c \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u044b.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4c5\/daf\/e06\/4c5dafe0615e829ef0c9dfe79a31c49a.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"421\" height=\"358\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4c5\/daf\/e06\/4c5dafe0615e829ef0c9dfe79a31c49a.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>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0434\u0440\u0443\u0433\u043e\u0439 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 <strong>receiveShadow<\/strong> \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 <strong>Ground<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0442\u0435\u043d\u0438 \u043d\u0430 \u0441\u0435\u0431\u0435.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/da6\/823\/d3b\/da6823d3bec74dabb56e2aabc44b4d37.png\" alt=\"Ground.jsx\" title=\"Ground.jsx\" width=\"715\" height=\"162\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/da6\/823\/d3b\/da6823d3bec74dabb56e2aabc44b4d37.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\/448\/822\/c87\/448822c8793f9b962334ba0615080db9.png\" alt=\"\u041c\u043e\u0434\u0435\u043b\u044c \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0442\u0435\u043d\u044c\" title=\"\u041c\u043e\u0434\u0435\u043b\u044c \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0442\u0435\u043d\u044c\" width=\"1600\" height=\"839\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/448\/822\/c87\/448822c8793f9b962334ba0615080db9.png\"\/><\/p>\n<div><figcaption><em>\u041c\u043e\u0434\u0435\u043b\u044c \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0442\u0435\u043d\u044c<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435: \u043a\u0443\u0431\u0430\u043c \u0438 \u0438\u0433\u0440\u043e\u043a\u0443. \u0414\u043b\u044f \u043a\u0443\u0431\u043e\u0432 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c <strong>castShadow<\/strong> \u0438 <strong>receiveShadow<\/strong>, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u043a\u0430\u043a \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c, \u0442\u0430\u043a \u0438 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0442\u0435\u043d\u0438, \u0430 \u0434\u043b\u044f \u0438\u0433\u0440\u043e\u043a\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e <strong>castShadow<\/strong>.  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c <strong>castShadow<\/strong> \u0434\u043b\u044f <strong>Player<\/strong>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/536\/255\/1eb\/5362551eba18930abb481b75c7dfbb88.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"640\" height=\"158\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/536\/255\/1eb\/5362551eba18930abb481b75c7dfbb88.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>\u0414\u043e\u0431\u0430\u0432\u0438\u043c <strong>castShadow<\/strong> \u0438 <strong>receiveShadow<\/strong> \u0434\u043b\u044f <strong>Cube.<\/strong><\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/233\/0c1\/c80\/2330c1c8002589d6f174b92abd6a5404.png\" alt=\"Cube.jsx\" title=\"Cube.jsx\" width=\"471\" height=\"159\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/233\/0c1\/c80\/2330c1c8002589d6f174b92abd6a5404.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\/d94\/362\/204\/d9436220450e36f9abea2f4096282c78.GIF\" alt=\"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044e\u0442 \u0442\u0435\u043d\u044c\" title=\"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044e\u0442 \u0442\u0435\u043d\u044c\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0435 \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044e\u0442 \u0442\u0435\u043d\u044c<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/9a1c30993bf65f8dfdb7230f42d03c38d0682bb8\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0435\u043d\u0435\u0439 &#8212; \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0431\u0440\u0435\u0437\u043a\u0438 \u0442\u0435\u043d\u0438  <\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043f\u0440\u0438\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c\u0441\u044f, \u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c, \u0447\u0442\u043e \u043f\u043b\u043e\u0449\u0430\u0434\u044c \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043d\u044c \u0441\u043e\u0432\u0441\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f. \u0418 \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435 \u0437\u0430 \u043f\u0440\u0435\u0434\u0435\u043b\u044b \u044d\u0442\u043e\u0439 \u043f\u043b\u043e\u0449\u0430\u0434\u0438 \u0442\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u0440\u0435\u0437\u0430\u0435\u0442\u0441\u044f.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/366\/2b0\/e57\/3662b0e5784dff2483c8df0eff9770be.GIF\" alt=\"\u041e\u0431\u0440\u0435\u0437\u0430\u043d\u0438\u0435 \u0442\u0435\u043d\u0438\" title=\"\u041e\u0431\u0440\u0435\u0437\u0430\u043d\u0438\u0435 \u0442\u0435\u043d\u0438\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u041e\u0431\u0440\u0435\u0437\u0430\u043d\u0438\u0435 \u0442\u0435\u043d\u0438<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u041f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u044d\u0442\u043e \u043f\u043e \u0442\u043e\u0439 \u043f\u0440\u0438\u0447\u0438\u043d\u0435, \u0447\u0442\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043a\u0430\u043c\u0435\u0440\u0430 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043b\u0438\u0448\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0445 \u0442\u0435\u043d\u0435\u0439 \u043e\u0442 <strong>directionalLight<\/strong>. \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>directionalLight<\/strong> \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 <strong>shadow-camera-(top, bottom, left, right)<\/strong> \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442\u044c \u044d\u0442\u0443 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438. \u041f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u044d\u0442\u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432, \u0442\u0435\u043d\u044c \u0441\u0442\u0430\u043d\u0435\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u044b\u0442\u043e\u0439. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442 <strong>shadow-mapSize<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/487\/37e\/68a\/48737e68ad508838d5a113d47cba07c2.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"479\" height=\"507\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/487\/37e\/68a\/48737e68ad508838d5a113d47cba07c2.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\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043e\u0440\u0443\u0436\u0438\u044f \u043a \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0443  <\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043e\u0440\u0443\u0436\u0438\u044f \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043b\u0438\u0446\u0430. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Weapon<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u043b\u043e\u0433\u0438\u043a\u0443 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043e\u0440\u0443\u0436\u0438\u044f \u0438 \u0441\u0430\u043c\u0443 3D-\u043c\u043e\u0434\u0435\u043b\u044c.  <\/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>\u041f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u044d\u0442\u043e\u0442 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435 \u0441 <strong>RigidBody<\/strong> \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0438 \u0432 \u0445\u0443\u043a\u0435 <strong>useFrame<\/strong> \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u043e\u0437\u0438\u0446\u0438\u044e \u0438 \u0443\u0433\u043e\u043b \u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043e\u0442 \u043a\u0430\u043c\u0435\u0440\u044b.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cf0\/358\/da5\/cf0358da5ba38721bf708444fed56aca.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"865\" height=\"797\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cf0\/358\/da5\/cf0358da5ba38721bf708444fed56aca.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\/003\/75a\/0d5\/00375a0d5eacf579c0b6aa9ab7feb7f0.GIF\" alt=\"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0440\u0443\u0436\u0438\u044f \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043b\u0438\u0446\u0430\" title=\"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0440\u0443\u0436\u0438\u044f \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043b\u0438\u0446\u0430\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0440\u0443\u0436\u0438\u044f \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043b\u0438\u0446\u0430<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/fbe4dccdfe6de98e97aad043e7083b34f3a81a7f\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u043e\u0440\u0443\u0436\u0438\u044f \u043f\u0440\u0438 \u0445\u043e\u0434\u044c\u0431\u0435  <\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0445\u043e\u0434\u043a\u0430 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u0431\u044b\u043b\u0430 \u0431\u043e\u043b\u0435\u0435 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439, \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043e\u0440\u0443\u0436\u0438\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439<strong> tween.js<\/strong>.<\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Weapon<\/strong> \u043e\u0431\u0435\u0440\u043d\u0451\u043c \u0432 \u0442\u0435\u0433 <strong>group<\/strong>, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043d\u0435\u0433\u043e \u0447\u0435\u0440\u0435\u0437 \u0445\u0443\u043a <strong>useRef<\/strong>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/14f\/5a8\/8b3\/14f5a88b39acf86fe92c2e9f168c6c9d.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"605\" height=\"122\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/14f\/5a8\/8b3\/14f5a88b39acf86fe92c2e9f168c6c9d.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 <strong>useState<\/strong> \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fb5\/09d\/24c\/fb509d24c39d83672cd08ad4b51a0297.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"639\" height=\"86\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fb5\/09d\/24c\/fb509d24c39d83672cd08ad4b51a0297.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7a9\/b2b\/5de\/7a9b2b5def5830ab09e0e96a9286e002.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"551\" height=\"582\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7a9\/b2b\/5de\/7a9b2b5def5830ab09e0e96a9286e002.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>\u041f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u0434\u0443<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const twSwayingAnimation = new TWEEN.Tween(currentPosition) \u2026<br \/><\/strong>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 &#171;\u043a\u0430\u0447\u0430\u043d\u0438\u044f&#187; \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043e\u0442 \u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u043a \u043d\u043e\u0432\u043e\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>const twSwayingBackAnimation = new TWEEN.Tween(currentPosition) \u2026<br \/><\/strong>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u043a \u0435\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0432\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>twSwayingAnimation.chain(twSwayingBackAnimation);<br \/><\/strong>\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0434\u0432\u0443\u0445 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0432\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u043b\u0430\u0441\u044c \u0432\u0442\u043e\u0440\u0430\u044f.<\/p>\n<\/li>\n<\/ol>\n<\/blockquote>\n<p>\u0412 <strong>useEffect<\/strong> \u0432\u044b\u0437\u043e\u0432\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c1c\/a87\/b0c\/c1ca87b0c701844088a861485b21b615.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"307\" height=\"62\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c1c\/a87\/b0c\/c1ca87b0c701844088a861485b21b615.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043c\u043e\u043c\u0435\u043d\u0442, \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435. \u0421\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u0442\u043e \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u0438 \u043f\u0440\u0438 \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0449\u0451 \u0440\u0430\u0437.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/871\/3db\/b8f\/8713dbb8fd67aa1d049b2c2d6b67dd70.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"418\" height=\"123\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/871\/3db\/b8f\/8713dbb8fd67aa1d049b2c2d6b67dd70.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>\u041f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u0434\u0443<\/strong><\/p>\n<ol>\n<li>\n<p><strong>const isMoving = direction.length() > 0;<br \/><\/strong>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430. \u0415\u0441\u043b\u0438 \u0432\u0435\u043a\u0442\u043e\u0440 <strong>direction<\/strong> \u0438\u043c\u0435\u0435\u0442 \u0434\u043b\u0438\u043d\u0443 \u0431\u043e\u043b\u044c\u0448\u0435 0, \u0442\u043e \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0435\u0441\u0442\u044c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p><strong>if (isMoving &amp;&amp; isSwayingAnimationFinished) { &#8230; }<br \/><\/strong>\u042d\u0442\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f, \u0430 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u201c\u043a\u0430\u0447\u0430\u043d\u0438\u044f\u201d \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0430\u0441\u044c.<\/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 * 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>\u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c <strong>useFrame<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c tween-\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e. <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ad6\/ec2\/1b3\/ad6ec21b31e4b7c6fb23867f2924c3e9.png\" alt=\"App.jsx\" title=\"App.jsx\" width=\"245\" height=\"103\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ad6\/ec2\/1b3\/ad6ec21b31e4b7c6fb23867f2924c3e9.png\"\/><\/p>\n<div><figcaption>App.jsx<\/figcaption><\/div>\n<\/figure>\n<blockquote>\n<p><strong>TWEEN.update()<\/strong> \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0432\u0441\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 <strong>TWEEN.js<\/strong>. \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u043a\u0430\u0434\u0440\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0439.<\/p>\n<\/blockquote>\n<details class=\"spoiler\">\n<summary>App.jsx<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">import * as TWEEN from \"@tweenjs\/tween.js\"; 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 {useFrame} from \"@react-three\/fiber\";  const shadowOffset = 50;  export const App = () => {     useFrame(() => {         TWEEN.update();     });      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;\/>     ) }  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\/868\/99e\/e45\/86899ee45385c5560e735c3b54e0450a.GIF\" alt=\"\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u043e\u0440\u0443\u0436\u0438\u044f\" title=\"\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u043e\u0440\u0443\u0436\u0438\u044f\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption><em>\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u043e\u0440\u0443\u0436\u0438\u044f<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/3e986e87f1daa1ed9e20fabc7675ca83142d1d78\" rel=\"noopener noreferrer nofollow\">\u041f\u0435\u0440\u0432\u044b\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/12abbcd9d5b922e8766b859ff82272de08099c06\" rel=\"noopener noreferrer nofollow\">\u0412\u0442\u043e\u0440\u043e\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043e\u0442\u0434\u0430\u0447\u0438  <\/h3>\n<p>\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043c\u043e\u043c\u0435\u043d\u0442, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u044b\u0441\u0442\u0440\u0435\u043b \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u043c\u044b\u0448\u0438. \u0414\u043e\u0431\u0430\u0432\u0438\u043c <strong>useState<\/strong> \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f, <strong>useRef<\/strong> \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442 \u043e\u0440\u0443\u0436\u0438\u044f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0432\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0434\u043b\u044f \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u0438 \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u043a\u043b\u0430\u0432\u0438\u0448\u0438 \u043c\u044b\u0448\u0438.   <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e1f\/3d9\/1d8\/e1f3d91d830c894385232e9087ca1699.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"442\" height=\"47\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e1f\/3d9\/1d8\/e1f3d91d830c894385232e9087ca1699.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\/78d\/3d9\/8b1\/78d3d98b19f5f1a1a624bb7c638e1b3f.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"433\" height=\"184\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/78d\/3d9\/8b1\/78d3d98b19f5f1a1a624bb7c638e1b3f.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\/c24\/6bd\/0fd\/c246bd0fdd5fd7170ba50031ed4b1329.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"304\" height=\"161\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c24\/6bd\/0fd\/c246bd0fdd5fd7170ba50031ed4b1329.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043e\u0442\u0434\u0430\u0447\u0438 \u043f\u0440\u0438 \u043a\u043b\u0438\u043a\u0435 \u043a\u043d\u043e\u043f\u043a\u043e\u0439 \u043c\u044b\u0448\u0438. \u0411\u0443\u0434\u0435\u043c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <strong>tween.js<\/strong>.\u00a0<\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u0434\u043b\u044f \u0441\u0438\u043b\u044b \u043e\u0442\u0434\u0430\u0447\u0438 \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9e3\/9f5\/9e7\/9e39f59e74acbb0abea5571390031691.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"341\" height=\"69\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9e3\/9f5\/9e7\/9e39f59e74acbb0abea5571390031691.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u041a\u0430\u043a \u0438 \u043f\u0440\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u043e\u0440\u0443\u0436\u0438\u044f, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0434\u0432\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f <strong>useState<\/strong> \u0434\u043b\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043e\u0442\u0434\u0430\u0447\u0438 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0432 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b1\/5e9\/186\/4b15e91865401c71f0ba07ffd09ecbfb.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"554\" height=\"44\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4b1\/5e9\/186\/4b15e91865401c71f0ba07ffd09ecbfb.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0433\u043e \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043e\u0442\u0434\u0430\u0447\u0438 &#8212; <strong>generateRecoilOffset<\/strong> \u0438 <strong>generateNewPositionOfRecoil<\/strong>.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6ac\/b1b\/ef1\/6acb1bef186dd7a3ef1144ee1fe17324.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"475\" height=\"240\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6ac\/b1b\/ef1\/6acb1bef186dd7a3ef1144ee1fe17324.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043e\u0442\u0434\u0430\u0447\u0438. \u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c <strong>useEffect<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0443\u043a\u0430\u0436\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u201c\u0432\u044b\u0441\u0442\u0440\u0435\u043b\u0430\u201d, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0432\u044b\u0441\u0442\u0440\u0435\u043b\u0435 \u0437\u0430\u043d\u043e\u0432\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u043b\u0430 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u043d\u043e\u0432\u044b\u0435 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/473\/8c9\/e8a\/4738c9e8a1c5cd047f41a2187eeb2c21.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"558\" height=\"481\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/473\/8c9\/e8a\/4738c9e8a1c5cd047f41a2187eeb2c21.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\/309\/93e\/173\/30993e1733df2ae7494ddce2f8f7593c.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"292\" height=\"225\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/309\/93e\/173\/30993e1733df2ae7494ddce2f8f7593c.png\"\/><\/p>\n<div><figcaption>Weapon.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u0410 \u0432 <strong>useFrame<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u201c\u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f\u201d \u043a\u043b\u0430\u0432\u0438\u0448\u0438 \u043c\u044b\u0448\u0438 \u0434\u043b\u044f \u0441\u0442\u0440\u0435\u043b\u044c\u0431\u044b, \u0447\u0442\u043e\u0431\u044b \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0441\u0442\u0440\u0435\u043b\u044c\u0431\u044b \u043d\u0435 \u043f\u0440\u0435\u043a\u0440\u0430\u0449\u0430\u043b\u0430\u0441\u044c \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440, \u043f\u043e\u043a\u0430 \u043a\u043b\u0430\u0432\u0438\u0448\u0430 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0443\u0449\u0435\u043d\u0430.  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e1c\/4f1\/f9b\/e1c4f1f9b3426afc441c5a925322176f.png\" alt=\"Weapon.jsx\" title=\"Weapon.jsx\" width=\"258\" height=\"104\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e1c\/4f1\/f9b\/e1c4f1f9b3426afc441c5a925322176f.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\/15c\/8ad\/4cf\/15c8ad4cf42403c198934242c87a112f.GIF\" alt=\"\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043e\u0442\u0434\u0430\u0447\u0438\" title=\"\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043e\u0442\u0434\u0430\u0447\u0438\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043e\u0442\u0434\u0430\u0447\u0438<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/8a1a38b6402503d87b05dfc4d83834d5d9ed3a9c\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u0431\u0435\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438  <\/h3>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u201c\u0431\u0435\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u201d \u0434\u043b\u044f \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u043e\u0441\u044c \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u044f \u201c\u0437\u0430\u0432\u0438\u0441\u043d\u0443\u0432\u0448\u0435\u0439\u201d \u0438\u0433\u0440\u044b.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 <strong>useState<\/strong>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/041\/c12\/334\/041c12334c8935ddfab0b711fc95b34f.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"746\" height=\"63\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/041\/c12\/334\/041c12334c8935ddfab0b711fc95b34f.png\"\/><\/p>\n<div><figcaption>Player.jsx<\/figcaption><\/div>\n<\/figure>\n<p>\u041f\u043e\u043f\u0440\u0430\u0432\u0438\u043c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u201c\u043f\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f\u201d, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f. \u0418\u0434\u0435\u044f \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043f\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u0445: \u0445\u043e\u0434\u044c\u0431\u0430 \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430, &#8212; \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u044b \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0441\u043d\u0430\u0447\u0430\u043b\u0430.  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/977\/013\/93c\/97701393c17c7a2b1bfadf2709bac378.png\" alt=\"Player.jsx\" title=\"Player.jsx\" width=\"561\" height=\"506\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/977\/013\/93c\/97701393c17c7a2b1bfadf2709bac378.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\/7bf\/132\/484\/7bf1324846f305fc27892486a3a74422.GIF\" alt=\"Idle \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f\" title=\"Idle \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f\" width=\"500\" height=\"282\"\/><\/p>\n<div><figcaption>Idle \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/635b8619e303aa0456fcde15300032b2633f1c10\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435  <\/h3>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u0441\u0446\u0435\u043d\u044b \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u0436\u0430. \u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u043c\u043e\u0434\u0435\u043b\u044c \u043e\u0440\u0443\u0436\u0438\u044f, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043e\u0442\u0434\u0430\u0447\u0438 \u043f\u0440\u0438 \u0441\u0442\u0440\u0435\u043b\u044c\u0431\u0435 \u0438 \u043f\u0440\u0438 \u0431\u0435\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438. \u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c \u0434\u043e\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0443 \u0438\u0433\u0440\u0443, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u043d\u043e\u0432\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b.   <\/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\/764554\/\"> https:\/\/habr.com\/ru\/articles\/764554\/<\/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>\u041f\u0440\u0438\u0432\u0435\u0442, \u0443\u0432\u0430\u0436\u0430\u0435\u043c\u044b\u0435 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \u0425\u0430\u0431\u0440!\u00a0<\/p>\n<h3>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0432\u0435\u0431-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u043c\u0435\u0436\u0434\u0443 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u0438 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u0441\u0442\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0441 \u043a\u0430\u0436\u0434\u044b\u043c \u0434\u043d\u0451\u043c. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0430\u0439\u0442\u044b, \u043d\u043e \u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0435 \u0438\u0433\u0440\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0435\u043b\u0430\u0435\u0442 \u044d\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/docs.pmnd.rs\/react-three-fiber\/getting-started\/introduction\" rel=\"noopener noreferrer nofollow\"><strong>React Three Fiber<\/strong><\/a> &#8212; \u043c\u043e\u0449\u043d\u043e\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f 3D-\u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 <a href=\"https:\/\/threejs.org\/\" rel=\"noopener noreferrer nofollow\"><strong>Three.js<\/strong><\/a> \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438 <strong>React<\/strong>.<\/p>\n<h3>\u041e \u0441\u0442\u0435\u043a\u0435 React Three Fiber<\/h3>\n<p><strong>React Three Fiber<\/strong> &#8212; \u044d\u0442\u043e \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 <strong>Three.js<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b <strong>React<\/strong> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f 3D-\u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u0432 \u0432\u0435\u0431\u0435. \u042d\u0442\u043e\u0442 \u0441\u0442\u0435\u043a \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u043c\u043e\u0449\u044c <strong>Three.js<\/strong> \u0441 \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u043e\u043c \u0438 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c\u044e <strong>React<\/strong>, \u0434\u0435\u043b\u0430\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c.<\/p>\n<p>\u0412 \u043e\u0441\u043d\u043e\u0432\u0435 <strong>React Three Fiber<\/strong> \u043b\u0435\u0436\u0438\u0442 \u0438\u0434\u0435\u044f, \u0447\u0442\u043e \u0432\u0441\u0451, \u0447\u0442\u043e \u0432\u044b \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0435 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438 <strong>React<\/strong>. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b \u0438 \u043c\u0435\u0442\u043e\u0434\u043e\u043b\u043e\u0433\u0438\u0438.<\/p>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u0433\u043b\u0430\u0432\u043d\u044b\u0445 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432 <strong>React Three Fiber<\/strong> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0435\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 <strong>React<\/strong>. \u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u044d\u0442\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0434\u0440\u0443\u0433\u0438\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b <strong>React<\/strong>.<\/p>\n<h3>\u0410\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c Web-GameDev  <\/h3>\n<p><strong>Web-GameDev<\/strong> \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0433\u043e\u0434\u044b \u043f\u0435\u0440\u0435\u0436\u0438\u043b \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0432\u0448\u0438\u0441\u044c \u0438\u0437 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 2D-\u0438\u0433\u0440 \u043d\u0430 <a href=\"https:\/\/www.adobe.com\/products\/flashplayer\/end-of-life.html\" rel=\"noopener noreferrer nofollow\"><strong>Flash<\/strong><\/a> \u0432 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 3D-\u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043c\u044b\u0435 \u0441 \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438. \u042d\u0442\u043e\u0442 \u0440\u043e\u0441\u0442 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u0438 \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u0435\u043b\u0430\u0435\u0442 <strong>Web-GameDev<\/strong> \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u043b\u044c\u0437\u044f \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u0433\u043b\u0430\u0432\u043d\u044b\u0445 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432 \u0432\u0435\u0431-\u0438\u0433\u0440 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c. \u0418\u0433\u0440\u043e\u043a\u0430\u043c \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b &#8212; \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u043f\u0440\u043e\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u0438\u0433\u0440, \u0434\u0435\u043b\u0430\u044f \u0438\u0445 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0439 \u0430\u0443\u0434\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443.<\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0435\u0431-\u0438\u0433\u0440 \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u0441\u0438\u043b\u044b \u0432 gamedev, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0435 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043c, \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 \u043e\u043f\u044b\u0442\u0430 \u0432 3D-\u0433\u0440\u0430\u0444\u0438\u043a\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b!<\/p>\n<h3>\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u0433\u0440 \u0432 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430\u0445  <\/h3>\n<p>\u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044b \u043f\u0440\u043e\u0448\u043b\u0438 \u0434\u043e\u043b\u0433\u0438\u0439 \u043f\u0443\u0442\u044c \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f, \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0432\u0448\u0438\u0441\u044c \u0438\u0437 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0432 \u043c\u043e\u0449\u043d\u044b\u0435 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u0438\u0433\u0440. \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044b, \u0442\u0430\u043a\u0438\u0435 \u043a\u0430\u043a <strong>Chrome<\/strong>,<strong> Firefox<\/strong>, <strong>Edge<\/strong> \u0438 <strong>\u0434\u0440\u0443\u0433\u0438\u0435<\/strong>, \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0438 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0438\u0445 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u043e\u0439 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439.<\/p>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0430\u043b \u0442\u043e\u043b\u0447\u043e\u043a \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044e \u0438\u0433\u0440 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <a href=\"https:\/\/www.khronos.org\/webgl\/\" rel=\"noopener noreferrer nofollow\"><strong>WebGL<\/strong><\/a>. \u042d\u0442\u043e\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u043b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u043e\u0435 \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435 \u0433\u0440\u0430\u0444\u0438\u043a\u0438, \u0447\u0442\u043e \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u0432\u044b\u0441\u0438\u043b\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c 3D-\u0438\u0433\u0440. \u0421\u043e\u0432\u043c\u0435\u0441\u0442\u043d\u043e \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0432\u0435\u0431-API, <strong>WebGL<\/strong> \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u044f\u044e\u0449\u0438\u0445 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.<\/p>\n<p>\u0422\u0435\u043c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0438\u0433\u0440 \u0434\u043b\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043a\u0440\u0430\u0439\u043d\u0435 \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438: \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u043c\u044f\u0442\u044c\u044e \u0438 \u0430\u0434\u0430\u043f\u0442\u0430\u0446\u0438\u044f \u043f\u043e\u0434 \u0440\u0430\u0437\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 &#8212; \u0432\u0441\u0451 \u044d\u0442\u043e \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<h3>\u041d\u0430 \u0441\u0442\u0430\u0440\u0442!  <\/h3>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u0441\u043b\u043e\u0432\u0430 \u0438 \u0442\u0435\u043e\u0440\u0438\u044f &#8212; \u044d\u0442\u043e \u043e\u0434\u043d\u043e, \u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u043f\u044b\u0442 &#8212; \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0438\u043d\u043e\u0435. \u0427\u0442\u043e\u0431\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c \u0438 \u043e\u0446\u0435\u043d\u0438\u0442\u044c \u0432\u0435\u0441\u044c \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b <strong>Web-GameDev<\/strong>, \u043b\u0443\u0447\u0448\u0438\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0435\u0431-\u0438\u0433\u0440 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u0438\u0433\u0440\u0443. \u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u043e\u0441\u0432\u043e\u0438\u0442\u044c \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u0442\u044c\u0441\u044f \u0441 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u0438 \u043d\u0430\u0439\u0442\u0438 \u043f\u0443\u0442\u0438 \u0438\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0449\u043d\u043e\u0439 \u0438 \u0433\u0438\u0431\u043a\u043e\u0439 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0432\u0435\u0431-\u0433\u0435\u0439\u043c\u0434\u0435\u0432\u0430.<\/p>\n<p>\u0412 \u0441\u0435\u0440\u0438\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0448\u0443\u0442\u0435\u0440 \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043b\u0438\u0446\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u0430\u043d\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u0438 \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u043c\u0441\u044f \u0432 \u0443\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u0438\u0440 \u0432\u0435\u0431-\u0438\u0433\u0440!<\/p>\n<p>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0434\u0435\u043c\u043e<\/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>\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043d\u0430 <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\" rel=\"noopener noreferrer nofollow\">GitHub<\/a><\/p>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c!<\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432  <\/h3>\n<p>\u0412 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0448\u0430\u0431\u043b\u043e\u043d \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 <strong>React<\/strong>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u0447\u043d\u0451\u043c \u0441 \u0435\u0433\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438.  <\/p>\n<pre><code class=\"bash\">npm create vite@latest<\/code><\/pre>\n<ul>\n<li>\n<p>\u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <strong>React<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c <strong>JavaScript<\/strong>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 npm-\u043f\u0430\u043a\u0435\u0442\u044b.<\/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>\u0417\u0430\u0442\u0435\u043c <a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">\u0443\u0434\u0430\u043b\u0438\u043c<\/a> \u0432\u0441\u0451 \u043b\u0438\u0448\u043d\u0435\u0435 \u0438\u0437 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/4b55195e929d877d9231f35442e66346675522ed\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f Canvas  <\/h3>\n<p>\u0412 \u0444\u0430\u0439\u043b <strong>main.jsx <\/strong>\u0434\u043e\u0431\u0430\u0432\u0438\u043c div-\u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u0446\u0435\u043b\u0430. \u0412\u0441\u0442\u0430\u0432\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Canvas<\/strong> \u0438<strong> <\/strong>\u0437\u0430\u0434\u0430\u0434\u0438\u043c \u043f\u043e\u043b\u0435 \u0437\u0440\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u044b. \u0412\u043d\u0443\u0442\u0440\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Canvas<\/strong> \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App<\/strong>.  <\/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>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0442\u0438\u043b\u0438, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043d\u0430 \u0432\u0441\u044e \u0432\u044b\u0441\u043e\u0442\u0443 \u044d\u043a\u0440\u0430\u043d\u0430 \u0438 \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043f\u0440\u0438\u0446\u0435\u043b\u0430 \u0432 \u0432\u0438\u0434\u0435 \u043a\u0440\u0443\u0433\u0430 \u043f\u043e \u0446\u0435\u043d\u0442\u0440\u0443 \u044d\u043a\u0440\u0430\u043d\u0430.  <\/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>\u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App <\/strong>\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Sky<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0437\u0430\u0434\u043d\u0438\u043c \u0444\u043e\u043d\u043e\u043c \u0432 \u043d\u0430\u0448\u0435\u0439 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u0441\u0446\u0435\u043d\u0435 \u0432 \u0432\u0438\u0434\u0435 \u043d\u0435\u0431\u0430.  <\/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>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0435\u0431\u0430 \u0432 \u0441\u0446\u0435\u043d\u0435<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/ede1b9eb617c1967aef2f09104c234a87b68bc97\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u041f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0430  <\/h3>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>Ground<\/strong> \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0435\u0433\u043e \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>App<\/strong>.  <\/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>\u0412 <strong>Ground<\/strong> \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043f\u043b\u043e\u0441\u043a\u043e\u0439 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438. \u041f\u043e \u043e\u0441\u0438 Y \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u043c \u0432\u043d\u0438\u0437, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043f\u043e\u043f\u0430\u043b\u0430 \u0432 \u043f\u043e\u043b\u0435 \u0437\u0440\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u044b. \u0410 \u0442\u0430\u043a\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0451\u043c \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043f\u043e \u043e\u0441\u0438 \u0425, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0430 \u043f\u0440\u0438\u043d\u044f\u043b\u0430 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435.  <\/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>\u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e \u0447\u0442\u043e \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0446\u0432\u0435\u0442\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u043c\u044b \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u0441\u0435\u0440\u044b\u0439, \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0447\u0451\u0440\u043d\u043e\u0439.  <\/p>\n<figure class=\"full-width\">\n<div><figcaption><em>\u041f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c \u043d\u0430 \u0441\u0446\u0435\u043d\u0435<\/em>  <\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/e9335eb32868ba48874b7461a6d79609a074f78b\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0411\u0430\u0437\u043e\u0432\u043e\u0435 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u0435 <\/h3>\n<p>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 \u0441\u0446\u0435\u043d\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0441\u0432\u0435\u0442\u0430 <strong>ambientLight<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0441\u0432\u0435\u0449\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e \u0432\u0441\u0435\u0445 \u0441\u0442\u043e\u0440\u043e\u043d \u0438 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043b\u0443\u0447\u0430. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0441\u0438\u043b\u0443 \u0441\u0432\u0435\u0447\u0435\u043d\u0438\u044f.  <\/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>\u041e\u0441\u0432\u0435\u0449\u0451\u043d\u043d\u0430\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u044c<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/385825283562b117c4b849c0c71832e0d634ec3e\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u0434\u043b\u044f \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043b\u0430  <\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0430 \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u0430 \u043e\u0434\u043d\u043e\u0440\u043e\u0434\u043d\u043e, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u0443. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043b\u0430 \u0432 \u0432\u0438\u0434\u0435 \u044f\u0447\u0435\u0435\u043a, \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0445\u0441\u044f \u043d\u0430 \u0432\u0441\u0451\u043c \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0443 <strong>assets<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 PNG \u0441 \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u043e\u0439.<\/p>\n<figure class=\"full-width\">\n<div><figcaption><em>\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430<\/em> \u0434\u043b\u044f \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438<\/figcaption><\/div>\n<\/figure>\n<p>\u0414\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0443 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0445\u0443\u043a\u043e\u043c <strong>useTexture<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/dre<\/strong>i. \u0410 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0434\u043b\u044f \u0445\u0443\u043a\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0432 \u0444\u0430\u0439\u043b \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0443\u0440\u044b. \u0417\u0430\u0434\u0430\u0434\u0438\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u043c \u043e\u0441\u044f\u043c.  <\/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>\u0422\u0435\u043a\u0441\u0442\u0443\u0440\u0430 \u043d\u0430 \u043f\u043b\u043e\u0441\u043a\u043e\u0441\u0442\u0438<\/em><\/figcaption><\/div>\n<\/figure>\n<p><a href=\"https:\/\/github.com\/JI0PATA\/fps-game\/commit\/f1c91e9106b77f6c45b5edb48e3b229ba0e1862c\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0414\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u043e\u0439  <\/h3>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>PointerLockControls<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/drei<\/strong> \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u043a\u0443\u0440\u0441\u043e\u0440 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u043d\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u043b\u0441\u044f \u043f\u0440\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043c\u044b\u0448\u044c\u044e, \u0430 \u043c\u0435\u043d\u044f\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043c\u0435\u0440\u044b \u043d\u0430 \u0441\u0446\u0435\u043d\u0435.  <\/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><em>\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u043c\u0435\u0440\u043e\u0439<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043f\u0440\u0430\u0432\u043a\u0443 \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Ground<\/strong>.  <\/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\">\u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0430<\/a><\/p>\n<h3>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u0438\u0437\u0438\u043a\u0443  <\/h3>\n<p>\u0414\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0430 \u0441\u0446\u0435\u043d\u0443 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043a\u0443\u0431.  <\/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>\u041a\u0443\u0431 \u043d\u0430 \u0441\u0446\u0435\u043d\u0435<\/em><\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u043e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0438\u0441\u0438\u0442 \u0432 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435.<\/p>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 <strong>Physics<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/rapier <\/strong>\u0434\u043e\u0431\u0430\u0432\u0438\u043c \u201c\u0444\u0438\u0437\u0438\u043a\u0443\u201d \u043d\u0430 \u0441\u0446\u0435\u043d\u0443. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u043f\u043e\u043b\u0435 <strong>gravity<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0434\u0430\u0434\u0438\u043c \u0441\u0438\u043b\u044b \u043f\u0440\u0438\u0442\u044f\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u043e\u0441\u044f\u043c.<\/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>\u041e\u0434\u043d\u0430\u043a\u043e, \u043d\u0430\u0448 \u043a\u0443\u0431 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0441 \u0444\u0438\u0437\u0438\u043a\u043e\u0439, \u043d\u043e \u0441 \u043d\u0438\u043c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442. \u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043a\u0443\u0431 \u0432\u0451\u043b \u0441\u0435\u0431\u044f \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0435\u0433\u043e \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <strong>RigidBody<\/strong> \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>@react-three\/rapier<\/strong>.  <\/p>\n<figure class=\"full-width\">\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<\/code><\/pre>\n<\/div>\n<\/details>\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-356331","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/356331","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=356331"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/356331\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=356331"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=356331"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=356331"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}