MotivaciĂłn
En el camino de todos los desarrolladores comerciales (no solo los programadores, sino, lo sĂ©, los diseñadores, por ejemplo, tambiĂ©n) tarde o temprano se encuentran con áreas pantanosas, lugares lĂşgubres y aburridos, vagando por los cuales generalmente se puede vagar por el desierto muerto del agotamiento profesional. y / o incluso a un psicoterapeuta para una cita para pastillas. Los empleadores de negocios obviamente usan tus habilidades más desarrolladas, exprimiĂ©ndolas al máximo, la pila de la mayorĂa de las vacantes está ocupada por las mismas herramientas empresariales, parece que no para todos los casos las más exitosas, convenientes e interesantes, y entiendes que tendrás para agravar una tonelada de tal legado... A menudo, las relaciones en el equipo no se desarrollan de la mejor manera para usted, y no obtiene una comprensiĂłn y retroalimentaciĂłn reales, impulsada por los colegas ... o por mĂ, tal vez, un campo relacionado], en mi humilde opiniĂłn, no lo es. solo una cualidad importante de un profesional, pero, de hecho, ayuda al desarrollador a sobrevivir en el capitalismo, manteniĂ©ndose no solo en demanda externa, competitiva con los jĂłvenes que avanzan pisándole los talones, sino, sobre todo, dando energĂa y movimiento desde adentro. A veces escuchas algo como: "pero mi ex dijo que si era posible no codificar, ¡él no codificarĂa!". SĂ, y los jĂłvenes de hoy se han dado cuenta de que en la situaciĂłn actual, "honestamente y normalmente" solo se puede ganar en TI, y ya están parados entre una multitud en la puerta del departamento de recursos humanos ... No sĂ©,Me gustĂł codificar desde la infancia, pero quiero codificar algo, si no es Ăştil, al menos interesante. En resumen, estoy lejos de ser un jugador, pero en mi vida ha habido varios perĂodos cortos en los que vergonzosamente "desperdiciĂ©". SĂ, la pasiĂłn por las computadoras en la infancia comenzĂł, por supuesto, con los juegos. Recuerdo cĂłmo en los noventa se trajo el Spectrum a la ciudad. A menudo no habĂa prácticamente nada para comer entonces, pero mi padre todavĂa tomĂł el Ăşltimo dinero del alijo, fue, defendiĂł una cola sin precedentes y nos comprĂł a mi hermano y a mĂ nuestro primer auto milagroso. Lo conectamos a travĂ©s de un cable con conectores SG-5 a un Record TV en blanco y negro, la imagen temblaba y parpadeaba, los juegos tenĂan que cargarse pacientemente en la RAM desde una grabadora de casete vieja [todavĂa escucho sonidos de carga venenosos], a menudo experimentando fallas ...A pesar de que los primeros programadores y diseñadores lograron colocar mundos enteros con una jugabilidad increĂble con su cĂłdigo en 48 kilobytes de RAM, rápidamente me cansĂ© de jugar y me dejĂ© llevar por la programaciĂłn en BASIC), dibujĂ© gráficos de sprites (y vectores "tridimensional" entonces, tambiĂ©n lo era, incluso compramos un libro complicado), escribimos mĂşsica simple en el editor ... Entonces, hace un tiempo me cansĂ© de todo otra vez, era un invierno pandĂ©mico y no podĂa montar una bicicleta, el grupo de rock no ensayĂł ... LeĂ los foros y me puse varios juegos populares más o menos frescos hechos en Unity o Unreal Engine, obviamente. Me gustan los juegos de rol, mundos abiertos, supervivencia, eso es todo ... DespuĂ©s del trabajo, comencĂ© a sumergirme en los mundos virtuales todas las noches y al hack-swing, pero no durĂł mucho. Todos los juegos son similares en mecánica,El juego monĂłtono se extiende sobre una pequeña trama en un montĂłn de tareas similares con batallas interminables ... Pero, lo curioso es que realmente se retrasa descaradamente en mecánicas importantes. Los productos comerciales que se venden por dinero se están quedando atrás ... Y cualquier "error", en mi humilde opiniĂłn, es una gran decepciĂłn: instantáneamente trae un cuento de hadas digital del entorno virtual al mundo real ... Por supuesto, excelentes gráficos, muy fresco dibujado. Pero, exagerando, me di cuenta de que todas estas artesanĂas en motores empresariales, de hecho, ni siquiera codifican. Son ensamblados por gerentes y diseñadores, simplemente "jugando con el color de los cubos", pero los cubos en sĂ, al mismo tiempo, prácticamente "no cambian" ... En general, cuando se volviĂł completamente aburrido, pensĂ© que "Yo tambiĂ©n puedo hacer eso", pero directamente en el navegadorLos productos comerciales que se venden por dinero se están quedando atrás ... Y cualquier "error", en mi humilde opiniĂłn, es una gran decepciĂłn: instantáneamente trae un cuento de hadas digital del entorno virtual al mundo real ... Por supuesto, excelentes gráficos, muy fresco dibujado. Pero, exagerando, me di cuenta de que todas estas artesanĂas en motores empresariales, de hecho, ni siquiera codifican. Son ensamblados por gerentes y diseñadores, simplemente "jugando con el color de los cubos", pero los cubos en sĂ, al mismo tiempo, prácticamente "no cambian" ... En general, cuando se volviĂł completamente aburrido, pensĂ© que "Yo tambiĂ©n puedo hacer eso", pero directamente en el navegadorProductos comerciales rezagados que se venden por dinero ... Y cualquier "error", en mi humilde opiniĂłn, esto es una gran decepciĂłn: instantáneamente trae un cuento de hadas digital del entorno virtual al mundo real ... Por supuesto, excelentes gráficos, muy fresco dibujado. Pero, exagerando, me di cuenta de que todas estas artesanĂas en motores empresariales, de hecho, ni siquiera codifican. Son ensamblados por gerentes y diseñadores, simplemente "jugando con el color de los cubos", pero los cubos en sĂ, al mismo tiempo, prácticamente "no cambian" ... En general, cuando se volviĂł completamente aburrido, pensĂ© que "Yo tambiĂ©n puedo hacer eso", pero directamente en el navegadorSon ensamblados por gerentes y diseñadores, simplemente "jugando con el color de los cubos", pero los cubos en sĂ, al mismo tiempo, prácticamente "no cambian" ... En general, cuando se volviĂł completamente aburrido, pensĂ© que "Yo tambiĂ©n puedo hacer eso", pero directamente en el navegadorSon ensamblados por gerentes y diseñadores, simplemente "jugando con el color de los cubos", pero los cubos en sĂ, al mismo tiempo, prácticamente "no cambian" ... En general, cuando se volviĂł completamente aburrido, pensĂ© que "Yo tambiĂ©n puedo hacer eso", pero directamente en el navegadorrepugnante no pretende ahorrar memoria de programaciĂłn seria javascript. Finalmente, decidĂ cumplir plenamente con el hecho de que todo el tiempo con una mirada inteligente le repito a mi hijo: “poder hacer juegos es mucho más interesante que jugarlos”. En resumen, me propuse escribir mi propio shooter de FPS basado en navegador personalizado basado en tecnologĂas abiertas.
Entonces, en este momento, el primer resultado para esta "tarea para uno mismo" de larga duraciĂłn, puede probar: http://robot-game.ru/
Pila y arquitectura
, - (… - quakejs WebAssembly), , , , . Three.js . , , , . .
, - «» — : , , , , . , Vue 2, , , , , Svelte. , , Three, , . , , , Vue, «» .
- 2D , 3D . , Linux Blender. , , UV- . ! , . «» « glTF»: .glb- « ». , , , «, ». , — . ( ) ( ) .glb ( — ). , «glTF »: .gltf- — . : - - . , .
- Express MongoDB. , . FPS-, . , - . , , , ( -). — . ( ). — — , — glb- — , «» — . : « SPA». Vue, , . , , , - «» — . : , , , , , , , - :
window.location.reload(true);
— — )) , , . , , — «» , , . ( ), (MP3, : 44100 16 , 128 / — ), - 100 — ... — « » — , — -, . , , «» . «» , , — ; …
. — , ! , « » Three (, , ). , . . . , . «» . , — , . , -.
«». , [ ] — ( ). : c « » scene.remove(object.mesh)
— — , :
// Object3D Three
object.mesh.visible = false;
//
object.isPicked = true;
, , id
: number mesh` uuid
: string . — Three , « » ( - - — uuid
).
.dispose()
, « ». « — , , — ». , « ».
:
. └─ /public // │ ├─ /audio // │ │ └─ ... │ ├─ /images // │ │ ├─ /favicons // │ │ │ └─ ... │ │ ├─ /modals // │ │ │ ├─ /level1 // 1 │ │ │ │ └─ ... │ │ │ └─ ... │ │ ├─ /models │ │ │ ├─ /Levels │ │ │ │ ├─ /level0 // - ( 0 - ) │ │ │ │ │ └─ Scene.glb │ │ │ │ └─ ... │ │ │ └─ /Objects │ │ │ ├─ Element.glb │ │ │ └─ ... │ │ └─ /textures │ │ ├─ texture1.jpg │ │ └─ ... │ ├─ favicon.ico // 16 16 │ ├─ index.html // │ ├─ manifest.json // │ └─ start.jpg // ) ├─ /src │ ├─ /assets // │ │ └─ optical.png // ))) │ ├─ /components // , │ │ ├─ /Layout // UI- │ │ │ ├─ Component1.vue // 1 │ │ │ ├─ mixin1.js // 1 │ │ │ └─ ... │ │ └─ /Three // │ │ ├─ /Modules // │ │ │ └─ ... │ │ └─ /Scene │ │ ├─ /Enemies // │ │ │ ├─ Enemy1.js │ │ │ └─ ... │ │ ├─ /Weapon // │ │ │ ├─ Explosions.js // │ │ │ ├─ HeroWeapon.js // │ │ │ └─ Shots.js // │ │ ├─ /World // │ │ │ ├─ Element1.js │ │ │ └─ ... │ │ ├─ Atmosphere.js // ( , , ) │ │ ├─ AudioBus.js // - │ │ ├─ Enemies.js // │ │ ├─ EventsBus.js // │ │ ├─ Hero.js // │ │ ├─ Scene.vue // │ │ └─ World.js // │ ├─ /store // Vuex │ │ └─ ... │ ├─ /styles // SCSS │ │ └─ ... │ ├─ /utils // js- │ │ ├─ api.js // │ │ ├─ constants.js // - │ │ ├─ i18n.js // │ │ ├─ screen-helper.js // " " │ │ ├─ storage.js // │ │ └─ utilities.js // - │ ├─ App.vue // "" │ └─ main.js // Vue └─ ... // , : , gitignore, README.md
« » — , GPU 60FPS Google Chrome ( Yandex Bro). Firefox , 2-3 . , , — «» . . « WebGL », - ))...
« » — FPS, «-, », . — - -: ... , , « »…
, . - - , , , , . . , , , , . , , , , . -, -. , , .
. . , .
- ... ... , , , ... — — , - …
, . , , . , , . ))
, ( — !), , «» . — — . , .
E :
. « », .
. — .
— — «» , — — , , « » — .
« » — 25 . : «» — — , «« .
— , ( — ) , — .
, :
. , - — . «» (, , — « » ).
— — : — . .
. — , . - — , . — - — - . , . : - …
, — .
2D- ( )
, , …
, .
, . . «», . , , , — , , . , . ( , ? React c CSS Modules — Flow, TS — , , !!! string… , ?). « » TDD, « GUI». — GUI, . — , «» , , .
, ( TDD). — , — , . . — .
( DESIGN
), - constants.js.
Three -, , . , , . , — — — «»- — gld- . ( ) «» Sphere
Ray
Three. FPS-: , .
, « » Pointer_Lock_API. Three -, :
// Controls
// In First Person
...
! — « » Esc . UI/UX — P — . — — — Esc, — . 27 , :
: Esc. — P. FPS-: . - . Three, , . — « ». . «» — . « » , . .
Three , . , , . — — ( ). : «» «» — , . — T.
.
Three: Renderer, Scene , Camera Audio listener , Controls
— mesh` —
— Vuex
( , ) ,
,
,
, , . - , mesh` . . « » — — — « » ( -?). — , ( ), . -.
— , , — :
import * as Three from 'three';
import { DESIGN } from '@/utils/constants';
function Module() {
let variable; // -
// ...
//
this.init = (
scope,
texture1,
material1,
// ...
) => {
// variable = ...
// ...
};
// - (, , )
this.animate = (scope) => {
// Scene.vue:
scope.moduleObjectsSore.filter(object => object.mode === DESIGN.ENEMIES.mode.active).forEach((object) => {
// scope.number = ...
// scope.direction = new Three.Vector3(...);
// variable = ... - , , , let variableNew;
// ...
});
};
}
export default Module;
Vuex 3 . layout.js : - , API-. hero.js — , /. , , setScale
setUser
.
preloader.js boolean- false
. isGameLoaded
— — — false
true
— . — : , , .
, , :
import * as Three from 'three';
import { loaderDispatchHelper } from '@/utils/utilities';
function Module() {
this.init = (
scope,
// ...
) => {
const sandTexture = new Three.TextureLoader().load(
'./images/textures/sand.jpg',
() => {
scope.render(); // " "
loaderDispatchHelper(scope.$store, 'isSandLoaded');
},
);
};
}
export default Module;
// @/utils/utilities.js:
export const loaderDispatchHelper = (store, field) => {
store.dispatch('preloader/preloadOrBuilt', field).then(() => {
store.dispatch('preloader/isAllLoadedAndBuilt');
}).catch((error) => { console.log(error); });
};
— - - « ?».
UI . , « ».
, , — . , ( ) LoadingManager`.
:
1) - PositionalAudio
2)
-API Three API . , . .
Hero [ ] :
// @/components/Three/Scene/Hero.js:
import * as Three from "three";
import {
DESIGN,
// ...
} from '@/utils/constants';
import {
loaderDispatchHelper,
// ...
} from '@/utils/utilities';
function Hero() {
const audioLoader = new Three.AudioLoader();
let steps;
let speed;
// ...
this.init = (
scope,
// ...
) => {
audioLoader.load('./audio/steps.mp3', (buffer) => {
steps = scope.audio.addAudioToHero(scope, buffer, 'steps', DESIGN.VOLUME.hero.step, false);
loaderDispatchHelper(scope.$store, 'isStepsLoaded');
});
};
this.setHidden = (scope, isHidden) => {
if (isHidden) {
// ...
steps.setPlaybackRate(0.5);
} else {
// ...
steps.setPlaybackRate(1);
}
};
this.setRun = (scope, isRun) => {
if (isRun && scope.keyStates['KeyW']) {
steps.setVolume(DESIGN.VOLUME.hero.run);
steps.setPlaybackRate(2);
} else {
steps.setVolume(DESIGN.VOLUME.hero.step);
steps.setPlaybackRate(1);
}
};
// ...
this.animate = (scope) => {
if (scope.playerOnFloor) {
if (!scope.isPause) {
// ...
// Steps sound
if (steps) {
if (scope.keyStates['KeyW']
|| scope.keyStates['KeyS']
|| scope.keyStates['KeyA']
|| scope.keyStates['KeyD']) {
if (!steps.isPlaying) {
speed = scope.isHidden ? 0.5 : scope.isRun ? 2 : 1;
steps.setPlaybackRate(speed);
steps.play();
}
}
}
} else {
if (steps && steps.isPlaying) steps.pause();
// ...
}
}
};
}
export default Module;
? — , . , , « » « » — . — — « ». — , . . — . . — .
if (!isLoop) audio.onEnded = () => audio.stop();
!
import * as Three from "three";
import { DESIGN, OBJECTS } from '@/utils/constants';
import { loaderDispatchHelper } from '@/utils/utilities';
function Module() {
const audioLoader = new Three.AudioLoader();
// ...
let material = null;
const geometry = new Three.SphereBufferGeometry(0.5, 8, 8);
let explosion;
let explosionClone;
let boom;
this.init = (
scope,
fireMaterial,
// ...
) => {
// -
audioLoader.load('./audio/mechanism.mp3', (buffer) => {
loaderDispatchHelper(scope.$store, 'isMechanismLoaded');
scope.array = scope.enemies.filter(enemy => enemy.name !== OBJECTS.DRONES.name);
scope.audio.addAudioToObjects(scope, scope.array, buffer, 'mesh', 'mechanism', DESIGN.VOLUME.mechanism, true);
});
// - - " " -
material = fireMaterial;
explosion = new Three.Mesh(geometry, material);
audioLoader.load('./audio/explosion.mp3', (buffer) => {
loaderDispatchHelper(scope.$store, 'isExplosionLoaded');
boom = buffer;
});
};
// ...
// ... - :
this.moduleFunction = (scope, enemy) => {
scope.audio.startObjectSound(enemy.id, 'mechanism');
// ...
scope.audio.stopObjectSound(enemy.id, 'mechanism');
// ...
};
// :
this.addExplosionToBus = (
scope,
// ...
) => {
explosionClone = explosion.clone();
// ..
scope.audio.playAudioOnObject(scope, explosionClone, boom, 'boom', DESIGN.VOLUME.explosion);
// ..
};
}
export default Module;
, ? ))
: — . , , — — Clock
Three. .
. : . , , . , . .
.
.
OBJECTS
«» , .
, — . - — .
. — «».
.
glb , , — , . . , , . . , . , Mandatory , — . - — «» — . :
room.geometry.computeBoundingBox();
room.visible = false;
— — «» :
// @/components/Three/Scene/World/Screens.js:
this.isHeroInRoomWithScreen = (scope, screen) => {
scope.box.copy(screen.room.geometry.boundingBox).applyMatrix4(screen.room.matrixWorld);
if (scope.box.containsPoint(scope.camera.position)) return true;
return false;
};
— «» , «» — , «mesh». «» « » — .
— — — — . . )
, — . « ».
: . , , -. , «mesh`». — — -. Sphere
. — () (). — .
«» — :
// @/components/Three/Scene/World.js:
const pseudoGeometry = new Three.SphereBufferGeometry(DESIGN.HERO.HEIGHT / 2, 4, 4);
const pseudoMaterial = new Three.MeshStandardMaterial({
color: DESIGN.COLORS.white,
side: Three.DoubleSide,
});
new Bottles().init(scope, pseudoGeometry, pseudoMaterial);
:
// @/components/Three/Scene/World/Thing.js:
import * as Three from 'three';
import { GLTFLoader } from '@/components/Three/Modules/Utils/GLTFLoader';
import { OBJECTS } from '@/utils/constants';
import { loaderDispatchHelper } from '@/utils/utilities';
function Thing() {
let thingClone;
let thingGroup;
let thingPseudo;
let thingPseudoClone;
this.init = (
scope,
pseudoGeometry,
pseudoMaterial,
) => {
thingPseudo = new Three.Mesh(pseudoGeometry, pseudoMaterial);
new GLTFLoader().load(
'./images/models/Objects/Thing.glb',
(thing) => {
loaderDispatchHelper(scope.$store, 'isThingLoaded'); //
for (let i = 0; i < OBJECTS.THINGS[scope.l].data.length; i++) {
// eslint-disable-next-line no-loop-func
thing.scene.traverse((child) => {
// ... - ""
});
//
thingClone = thing.scene.clone();
thingPseudoClone = thingPseudo.clone();
//
thingPseudoClone.name = OBJECTS.THINGS.name;
thingPseudoClone.position.y += 1.5; //
thingPseudoClone.visible = false; //
thingPseudoClone.updateMatrix(); //
thingPseudoClone.matrixAutoUpdate = false; //
//
thingGroup = new Three.Group();
thingGroup.add(thingClone);
thingGroup.add(thingPseudoClone);
//
thingGroup.position.set(
OBJECTS.THINGS[scope.l].data[i].x,
OBJECTS.THINGS[scope.l].data[i].y,
OBJECTS.THINGS[scope.l].data[i].z,
);
// " " -
scope.things.push({
id: thingPseudoClone.id,
group: thingGroup,
});
scope.objects.push(thingPseudoClone);
scope.scene.add(thingGroup); //
}
loaderDispatchHelper(scope.$store, 'isThingsBuilt'); //
},
);
};
}
export default Thing;
«» Hero.js:
// @/components/Three/Scene/Hero.js:
import { DESIGN, OBJECTS } from '@/utils/constants';
function Hero() {
// ...
this.animate = (scope) => {
// ...
// Raycasting
// Forward ray
scope.direction = scope.camera.getWorldDirection(scope.direction);
scope.raycaster.set(scope.camera.getWorldPosition(scope.position), scope.direction);
scope.intersections = scope.raycaster.intersectObjects(scope.objects);
scope.onForward = scope.intersections.length > 0 ? scope.intersections[0].distance < DESIGN.HERO.CAST : false;
if (scope.onForward) {
scope.object = scope.intersections[0].object;
// THINGS
if (scope.object.name.includes(OBJECTS.THINGS.name)) {
// ...
}
}
// ...
};
}
export default Hero;
. , - , , . :
// @/utils/utilities.js:
// let arrowHelper;
const fixNot = (value) => {
if (!value) return Number.MAX_SAFE_INTEGER;
return value;
};
export const isEnemyCanMoveForward = (scope, enemy) => {
scope.ray = new Three.Ray(enemy.collider.center, enemy.mesh.getWorldDirection(scope.direction).normalize());
scope.result = scope.octree.rayIntersect(scope.ray);
scope.resultDoors = scope.octreeDoors.rayIntersect(scope.ray);
scope.resultEnemies = scope.octreeEnemies.rayIntersect(scope.ray);
// arrowHelper = new Three.ArrowHelper(scope.direction, enemy.collider.center, 6, 0xffffff);
// scope.scene.add(arrowHelper);
if (scope.result || scope.resultDoors || scope.resultEnemies) {
scope.number = Math.min(fixNot(scope.result.distance), fixNot(scope.resultDoors.distance), fixNot(scope.resultEnemies.distance));
return scope.number > 6;
}
return true;
};
Three ArrowHelper
. :
« » — :
// @/utils/utilities.js:
export const isToHeroRayIntersectWorld = (scope, collider) => {
scope.direction.subVectors(collider.center, scope.camera.position).negate().normalize();
scope.ray = new Three.Ray(collider.center, scope.direction);
scope.result = scope.octree.rayIntersect(scope.ray);
scope.resultDoors = scope.octreeDoors.rayIntersect(scope.ray);
if (scope.result || scope.resultDoors) {
scope.number = Math.min(fixNot(scope.result.distance), fixNot(scope.resultDoors.distance));
scope.dictance = scope.camera.position.distanceTo(collider.center);
return scope.number < scope.dictance;
}
return false;
};
, Enemies.js . - :
// @/utils/constatnts.js:
export const DESIGN = {
DIFFICULTY: {
civil: 'civil',
anarchist: 'anarchist',
communist: 'communist',
},
ENEMIES: {
mode: {
idle: 'idle',
active: 'active',
dies: 'dies',
dead: 'dead',
},
spider: {
// ...
decision: {
enjoy: 60,
rotate: 25,
shot: {
civil: 40,
anarchist: 30,
communist: 25,
},
jump: 50,
speed: 20,
bend: 30,
},
},
drone: {
// ...
decision: {
enjoy: 50,
rotate: 25,
shot: {
civil: 50,
anarchist: 40,
communist: 30,
},
fly: 40,
speed: 20,
bend: 25,
},
},
},
// ...
};
// @/components/Three/Scene/Enemies.js:
import { DESIGN } from '@/utils/constants';
import {
randomInteger,
isEnemyCanShot,
// ...
} from "@/utils/utilities";
function Enemies() {
// ...
const idle = (scope, enemy) => {
// ...
};
const active = (scope, enemy) => {
// ...
// - : ( )
scope.decision = randomInteger(1, DESIGN.ENEMIES[enemy.name].decision.shot[scope.difficulty]) === 1;
if (scope.decision) {
if (isEnemyCanShot(scope, enemy)) {
scope.boolean = enemy.name === OBJECTS.DRONES.name;
scope.world.shots.addShotToBus(scope, enemy.mesh.position, scope.direction, scope.boolean);
scope.audio.replayObjectSound(enemy.id, 'shot');
}
}
};
const gravity = (scope, enemy) => {
// ...
};
this.animate = (scope) => {
scope.enemies.filter(enemy => enemy.mode !== DESIGN.ENEMIES.mode.dead).forEach((enemy) => {
switch (enemy.mode) {
case DESIGN.ENEMIES.mode.idle:
idle(scope, enemy);
break;
case DESIGN.ENEMIES.mode.active:
active(scope, enemy);
break;
case DESIGN.ENEMIES.mode.dies:
gravity(scope, enemy);
break;
}
});
};
}
export default Enemies;
, ( , , ) .
! : idle — — . — + . .
«» 3D- — , .
, — / . — — « » ( , ).
: : 1) , , , , 2) 3) . «» « ».
. - — . : / .
-. , : 1) 2) . «» .
— . , «», — , — «»: -. . )
— «» . , , . — . .
// @/utils/constatnts.js:
export const DESIGN = {
OCTREE_UPDATE_TIMEOUT: 0.5,
// ...
};
// @/utils/utilities.js:
//
import * as Three from "three";
import { Octree } from "../components/Three/Modules/Math/Octree";
export const updateEnemiesPersonalOctree = (scope, id) => {
scope.group = new Three.Group();
scope.enemies.filter(obj => obj.id !== id).forEach((enemy) => {
scope.group.add(enemy.pseudoLarge);
});
scope.octreeEnemies = new Octree();
scope.octreeEnemies.fromGraphNode(scope.group);
scope.scene.add(scope.group);
};
//
const enemyCollitions = (scope, enemy) => {
// c - , ,
scope.result = scope.octree.sphereIntersect(enemy.collider);
enemy.isOnFloor = false;
if (scope.result) {
enemy.isOnFloor = scope.result.normal.y > 0;
// ?
if (!enemy.isOnFloor) {
enemy.velocity.addScaledVector(scope.result.normal, -scope.result.normal.dot(enemy.velocity));
} else {
//
// ...
}
enemy.collider.translate(scope.result.normal.multiplyScalar(scope.result.depth));
}
// c
scope.resultDoors = scope.octreeDoors.sphereIntersect(enemy.collider);
if (scope.resultDoors) {
enemy.collider.translate(scope.resultDoors.normal.multiplyScalar(scope.resultDoors.depth));
}
// ,
if (scope.enemies.length > 1
&& !enemy.updateClock.running) {
if (!enemy.updateClock.running) enemy.updateClock.start();
updateEnemiesPersonalOctree(scope, enemy.id);
scope.resultEnemies = scope.octreeEnemies.sphereIntersect(enemy.collider);
if (scope.resultEnemies) {
result = scope.resultEnemies.normal.multiplyScalar(scope.resultEnemies.depth);
result.y = 0;
enemy.collider.translate(result);
}
}
if (enemy.updateClock.running) {
enemy.updateTime += enemy.updateClock.getDelta();
if (enemy.updateTime > DESIGN.OCTREE_UPDATE_TIMEOUT && enemy.updateClock.running) {
enemy.updateClock.stop();
enemy.updateTime = 0;
}
}
};
Atmosphere.js : , , — .
, : .
( 10 ) . . — , .
, React c TS !
FPS Three:
En todos los demás aspectos posibles, debemos optimizar el ciclo de animaciĂłn, el lanzamiento y el cálculo de las colisiones en el contexto del juego con el mayor cuidado posible, para mantener la conducciĂłn, pero evitar una caĂda en el rendimiento.
La mecanografĂa estática y las pruebas unitarias no ayudan en este experimento.
En principio, estoy satisfecho con lo que ya ha sucedido. Y quiero llevarlo a la máxima belleza. Por lo tanto, si conoce a alguien aficionado a la animaciĂłn esquelĂ©tica y puede aceptar agregar algunas pistas simples a mi glb, por favor, elimine el enlace al artĂculo para Ă©l.