Motivasi
Dalam perjalanan setiap pengembang komersial (tidak hanya pembuat kode, tapi, saya tahu, desainer, misalnya, juga) cepat atau lambat akan menemukan daerah berawa, tempat suram yang suram, berkeliaran di mana Anda biasanya dapat berjalan ke gurun mati kelelahan profesional dan / atau bahkan ke psikoterapis untuk membuat janji bertemu pil. Pengusaha bisnis jelas menggunakan keterampilan Anda yang paling berkembang, memeras hingga maksimum, tumpukan sebagian besar lowongan ditempati oleh alat perusahaan yang sama, tampaknya tidak untuk semua kasus yang paling sukses, nyaman dan menarik, dan Anda memahami bahwa Anda akan memilikinya untuk memperburuk ton seperti warisan... Seringkali, hubungan dalam tim tidak berkembang dengan cara terbaik untuk Anda, dan Anda tidak mendapatkan pemahaman dan umpan balik yang nyata, dorongan dari kolega ... atau untuk saya sendiri, mungkin - bidang terkait], IMHO, tidak hanya kualitas penting dari seorang profesional, tetapi, pada kenyataannya, membantu pengembang untuk bertahan dalam kapitalisme, tidak hanya tetap diminati secara eksternal, bersaing dengan orang-orang muda yang semakin maju, tetapi, yang terpenting, memberikan energi dan gerakan dari dalam. Kadang-kadang Anda mendengar sesuatu seperti: "tetapi mantan saya mengatakan bahwa jika mungkin untuk tidak membuat kode, dia tidak akan membuat kode!". Ya, dan anak muda saat ini telah menyadari bahwa dalam situasi saat ini, "secara jujur ββdan normal" Anda hanya dapat menghasilkan dari IT, dan mereka sudah berdiri di tengah kerumunan di depan pintu departemen HR ... Saya tidak tahu,Saya suka coding sejak kecil, tetapi saya ingin membuat kode sesuatu, jika tidak berguna, setidaknya menarik. Singkatnya, saya jauh dari seorang gamer, tetapi dalam hidup saya ada beberapa periode singkat ketika saya secara memalukan "menyia-nyiakan". Ya, kecintaan terhadap komputer di masa kanak-kanak dimulai, tentu saja, dengan game. Saya ingat bagaimana pada tahun sembilan puluhan Spectrum dibawa ke kota. Saat itu praktis hampir tidak ada yang bisa dimakan, tetapi ayah saya masih mengambil uang terakhir dari simpanan, pergi, mempertahankan antrian besar yang belum pernah terjadi sebelumnya dan membelikan saya dan saudara lelaki saya mobil ajaib pertama kami. Kami menghubungkannya melalui kabel dengan konektor SG-5 ke Rekam TV hitam putih, gambar bergetar dan berkedip, game harus dengan sabar dimuat ke dalam RAM dari perekam kaset lama [Saya masih mendengar suara memuat yang beracun], sering mengalami kegagalan ...Terlepas dari kenyataan bahwa programmer dan desainer awal berhasil menempatkan seluruh dunia dengan gameplay luar biasa dengan kode mereka dalam 48 kilobyte RAM, saya cepat bosan bermain dan saya terbawa dengan pemrograman dalam BASIC)), saya menggambar grafik sprite (dan vektor) "tiga dimensi" kemudian, juga, kami bahkan membeli sebuah buku yang rumit), menulis musik sederhana di editor ... Jadi, beberapa waktu yang lalu saya bosan dengan semuanya lagi, itu adalah musim dingin pandemi dan saya tidak bisa berkendara sebuah sepeda, grup rock tidak berlatih ... Saya membaca forum dan menetapkan sendiri beberapa game populer yang kurang lebih segar yang dibuat di Unity atau Unreal Engine, jelas. Saya suka RPG-open worlds-survival game, itu saja ... Setelah bekerja, saya mulai terjun ke dunia virtual setiap malam dan melakukan hack-swing, tetapi itu tidak berlangsung lama. Gim ini semuanya serupa dalam mekanika,Gameplay monoton diolesi di atas plot kecil menjadi sekumpulan tugas serupa dengan pertempuran tanpa akhir ... Tapi, lucunya, itu benar-benar tanpa malu-malu tertinggal dalam mekanika penting. Produk komersial yang dijual untuk mendapatkan uang sedang tertinggal ... Dan "bug" apa pun, IMHO, adalah kekecewaan yang kuat - ini secara instan membawa dongeng digital dari lingkungan virtual ke dunia nyata ... Tentu saja, grafik yang luar biasa, digambar sangat keren. Tapi, melebih-lebihkan, saya menyadari bahwa semua kerajinan ini pada mesin perusahaan, pada kenyataannya, bahkan tidak membuat kode. Mereka dirakit oleh manajer dan desainer, hanya "bermain-main dengan warna kubus", tetapi kubus itu sendiri, pada saat yang sama, secara praktis "tidak berubah" ... Secara umum, ketika menjadi sangat membosankan, saya pikir itu "Saya bisa melakukannya juga," tapi langsung di browser aktifProduk komersial yang dijual untuk mendapatkan uang sedang tertinggal ... Dan "bug" apa pun, IMHO, adalah kekecewaan yang kuat - ini secara instan membawa dongeng digital dari lingkungan virtual ke dunia nyata ... Tentu saja, grafik yang luar biasa, digambar sangat keren. Tapi, melebih-lebihkan, saya menyadari bahwa semua kerajinan ini pada mesin perusahaan, pada kenyataannya, bahkan tidak membuat kode. Mereka dirakit oleh manajer dan desainer, hanya "bermain-main dengan warna kubus", tetapi kubus itu sendiri, pada saat yang sama, secara praktis "tidak berubah" ... Secara umum, ketika menjadi sangat membosankan, saya pikir itu "Saya bisa melakukannya juga," tapi langsung di browser aktifProduk komersial yang dijual untuk mendapatkan uang sedang tertinggal ... Dan "bug" apa pun, IMHO, adalah kekecewaan yang kuat - ini secara instan membawa dongeng digital dari lingkungan virtual ke dunia nyata ... Tentu saja, grafik yang luar biasa, digambar sangat keren. Tapi, melebih-lebihkan, saya menyadari bahwa semua kerajinan ini pada mesin perusahaan, pada kenyataannya, bahkan tidak membuat kode. Mereka dirakit oleh manajer dan desainer, hanya "bermain dengan warna kubus", tetapi kubus itu sendiri, pada saat yang sama, secara praktis "tidak berubah" ... Secara umum, ketika menjadi sangat membosankan, saya pikir itu "Saya bisa melakukannya juga," tapi langsung di browser aktifMereka dirakit oleh manajer dan desainer, hanya "bermain-main dengan warna kubus", tetapi kubus itu sendiri, pada saat yang sama, secara praktis "tidak berubah" ... Secara umum, ketika menjadi sangat membosankan, saya pikir itu "Saya bisa melakukannya juga," tapi langsung di browser aktifMereka dirakit oleh manajer dan desainer, hanya "bermain-main dengan warna kubus", tetapi kubus itu sendiri, pada saat yang sama, secara praktis "tidak berubah" ... Secara umum, ketika menjadi sangat membosankan, saya pikir itu "Saya bisa melakukannya juga," tapi langsung di browser aktifmenjijikkan tidak dimaksudkan untuk menghemat memori javascript pemrograman serius. Akhirnya, saya memutuskan untuk sepenuhnya mematuhi fakta bahwa sepanjang waktu dengan tampilan yang cerdas saya ulangi kepada anak saya: βbisa membuat game jauh lebih menarik daripada memainkannyaβ. Singkatnya, saya mulai menulis FPS shooter berbasis browser kustom saya sendiri berdasarkan teknologi terbuka.
Jadi, saat ini, hasil pertama untuk "tugas untuk diri sendiri" yang sudah lama berlangsung ini - Anda dapat menguji: http://robot-game.ru/
Tumpukan dan arsitektur
, - (β¦ - 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:
Dalam semua aspek lain yang memungkinkan, kita harus mengoptimalkan siklus animasi, casting, dan penghitungan tabrakan di dalamnya dalam konteks gameplay secermat mungkin, untuk mempertahankan drive, tetapi menghindari penurunan performa.
Pengetikan statis dan pengujian unit tidak membantu dalam eksperimen ini.
Pada prinsipnya, saya senang dengan apa yang telah terjadi. Dan saya ingin membuatnya menjadi sangat cantik. Oleh karena itu, jika Anda mengenal seseorang yang menyukai animasi kerangka dan mungkin setuju untuk menambahkan beberapa trek sederhana ke glb saya - tolong buang tautan ke artikel untuknya?