Files
Hommie_RPG_Game/js/game/Dog.js
Maxim Dolgolyov fb5f09212b Initial commit: 3D Hommie RPG game
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:09 +03:00

195 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as THREE from 'three';
export class Dog {
constructor(game) {
this.game = game;
this.mesh = null;
this.position = new THREE.Vector3(-25, 0, 30); // default, обновляется из конфига в spawn()
this.adopted = false;
this.followDistance = 3;
this.speed = 4;
this.moodTimer = 0;
this.tailWag = 0;
this.tail = null;
}
spawn() {
// Позиция из конфига (рядом с парком)
const parkCfg = this.game.world.mapConfig?.structures?.park || {};
this.position.set(
(parkCfg.x ?? -30) + 5,
0,
(parkCfg.z ?? 25) + 5
);
const group = new THREE.Group();
// Тело
const bodyGeo = new THREE.BoxGeometry(0.8, 0.45, 0.4);
const bodyMat = new THREE.MeshStandardMaterial({ color: 0x8B6914 });
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.y = 0.45;
body.castShadow = true;
group.add(body);
// Голова
const headGeo = new THREE.BoxGeometry(0.3, 0.3, 0.35);
const head = new THREE.Mesh(headGeo, bodyMat);
head.position.set(0.45, 0.6, 0);
head.castShadow = true;
group.add(head);
// Морда
const noseGeo = new THREE.BoxGeometry(0.12, 0.12, 0.2);
const noseMat = new THREE.MeshStandardMaterial({ color: 0x4a3010 });
const nose = new THREE.Mesh(noseGeo, noseMat);
nose.position.set(0.6, 0.55, 0);
group.add(nose);
// Нос
const noseTip = new THREE.Mesh(
new THREE.SphereGeometry(0.04, 6, 4),
new THREE.MeshStandardMaterial({ color: 0x222222 })
);
noseTip.position.set(0.67, 0.56, 0);
group.add(noseTip);
// Уши
[-0.14, 0.14].forEach(side => {
const earGeo = new THREE.BoxGeometry(0.08, 0.15, 0.06);
const ear = new THREE.Mesh(earGeo, bodyMat);
ear.position.set(0.4, 0.8, side);
ear.rotation.z = side > 0 ? 0.3 : -0.3;
group.add(ear);
});
// Глаза
const eyeMat = new THREE.MeshStandardMaterial({ color: 0x222222 });
[-0.1, 0.1].forEach(side => {
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.03, 6, 4), eyeMat);
eye.position.set(0.55, 0.65, side);
group.add(eye);
});
// Ноги
const legGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.3, 6);
const legMat = new THREE.MeshStandardMaterial({ color: 0x7a5a10 });
[[-0.25, -0.14], [-0.25, 0.14], [0.25, -0.14], [0.25, 0.14]].forEach(([lx, lz]) => {
const leg = new THREE.Mesh(legGeo, legMat);
leg.position.set(lx, 0.15, lz);
group.add(leg);
});
// Хвост
const tailGeo = new THREE.CylinderGeometry(0.03, 0.02, 0.3, 6);
const tail = new THREE.Mesh(tailGeo, bodyMat);
tail.position.set(-0.5, 0.6, 0);
tail.rotation.z = Math.PI / 4;
group.add(tail);
this.tail = tail;
group.position.copy(this.position);
this.mesh = group;
this.game.scene.add(group);
}
adopt() {
this.adopted = true;
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 20);
}
update(dt) {
if (!this.mesh) return;
// Виляние хвостом
this.tailWag += dt * 8;
if (this.tail) {
this.tail.rotation.x = Math.sin(this.tailWag) * 0.4;
}
if (this.adopted) {
this.followPlayer(dt);
this.applyMoodBoost(dt);
} else {
this.wander(dt);
}
this.mesh.position.copy(this.position);
}
followPlayer(dt) {
const playerPos = this.game.player.position;
const dir = new THREE.Vector3().subVectors(playerPos, this.position);
dir.y = 0;
const dist = dir.length();
if (dist > this.followDistance) {
dir.normalize();
const speed = dist > 8 ? this.speed * 2 : this.speed;
this.position.add(dir.multiplyScalar(speed * dt));
// Поворот к игроку
this.mesh.rotation.y = Math.atan2(dir.x, dir.z);
}
// Телепортация если слишком далеко
if (dist > 25) {
const behind = new THREE.Vector3();
this.game.camera.getWorldDirection(behind);
behind.y = 0;
behind.normalize().multiplyScalar(-3);
this.position.copy(playerPos).add(behind);
}
}
wander(dt) {
// Бродит рядом с парком
if (!this._wanderTarget || this.position.distanceTo(this._wanderTarget) < 1) {
const parkCfg = this.game.world.mapConfig?.structures?.park || {};
const cx = parkCfg.x ?? -30;
const cz = parkCfg.z ?? 25;
this._wanderTarget = new THREE.Vector3(
cx + (Math.random() - 0.5) * 20,
0,
cz + (Math.random() - 0.5) * 16
);
this._wanderWait = 2 + Math.random() * 3;
}
if (this._wanderWait > 0) {
this._wanderWait -= dt;
return;
}
const dir = new THREE.Vector3().subVectors(this._wanderTarget, this.position);
dir.y = 0;
if (dir.length() > 0.5) {
dir.normalize();
this.position.add(dir.multiplyScalar(1.5 * dt));
this.mesh.rotation.y = Math.atan2(dir.x, dir.z);
}
}
applyMoodBoost(dt) {
this.moodTimer += dt;
if (this.moodTimer >= 10) {
this.moodTimer = 0;
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 1);
}
}
reset() {
if (this.mesh) {
this.game.scene.remove(this.mesh);
this.mesh = null;
}
this.adopted = false;
const parkCfg = this.game.world.mapConfig?.structures?.park || {};
this.position.set(
(parkCfg.x ?? -30) + 5,
0,
(parkCfg.z ?? 25) + 5
);
}
}