chore(controller): three-player-controller als Ordner statt Submodul
Das Submodul zeigte auf GitHub, der eingetragene Stand 028c652 lag aber nur lokal vor: GitHub-Master ac39682 plus Showcase mit Figuren und launch.json. Frische Klone konnten den Controller deshalb nicht laden. Jetzt liegt dieser Stand als normaler Ordner im Repo, ohne Verweis auf GitHub. dist/ und node_modules/ im Controller bleiben ignoriert und werden wie bisher lokal gebaut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
780e246fe5
commit
fb38652e7f
153 files changed
+42561
-4
No files matched your search
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
Matrix3,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
Object3D,
|
||||
PlaneGeometry,
|
||||
TextureLoader,
|
||||
} from "three";
|
||||
|
||||
const _normalMatrix = new Matrix3(); // 法线矩阵
|
||||
const _decalHelper = new Object3D(); // 弹孔朝向辅助体
|
||||
|
||||
// 弹孔贴花系统(对象池)
|
||||
export class DecalSystem {
|
||||
constructor(scene, maxDecals = 60, decalSize = 0.02) {
|
||||
// ==================== 场景引用 ====================
|
||||
this._scene = scene;
|
||||
|
||||
// ==================== 对象池配置 ====================
|
||||
this._maxDecals = maxDecals; // 最大弹孔数量,超出后移除最旧的
|
||||
this._pool = []; // 场景中现存的弹孔 Mesh
|
||||
this._mats = []; // 可用的弹痕材质(多张随机选取)
|
||||
this._geo = new PlaneGeometry(decalSize, decalSize); // 弹孔平面几何体
|
||||
this.onSpawn = null; // (hitPoint, hitNormal) => void,多人模式注入广播回调
|
||||
}
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
// 批量加载弹痕贴图,生成对应材质
|
||||
async loadMaterials(files, baseUrl) {
|
||||
const loader = new TextureLoader();
|
||||
await Promise.all(
|
||||
files.map(async (f) => {
|
||||
const tex = await loader.loadAsync(baseUrl + f);
|
||||
this._mats.push(
|
||||
new MeshBasicMaterial({
|
||||
map: tex,
|
||||
transparent: true,
|
||||
depthTest: true,
|
||||
depthWrite: false,
|
||||
polygonOffset: true,
|
||||
polygonOffsetFactor: -4, // 防止与墙面 Z-fighting
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 生成 / 清除 ====================
|
||||
|
||||
// 在射线命中点生成弹孔,并触发命中硝烟特效
|
||||
spawn(hit, effects) {
|
||||
if (!hit?.face || !hit?.object) return;
|
||||
if (!this._mats.length) return;
|
||||
|
||||
// 计算世界法线
|
||||
_normalMatrix.getNormalMatrix(hit.object.matrixWorld);
|
||||
const hitNormal = hit.face.normal.clone().applyMatrix3(_normalMatrix).normalize();
|
||||
const hitPoint = hit.point.clone();
|
||||
|
||||
// 计算弹孔朝向(面向法线 + 随机旋转)
|
||||
_decalHelper.position.copy(hitPoint);
|
||||
_decalHelper.lookAt(hitPoint.clone().add(hitNormal));
|
||||
_decalHelper.rotation.z = Math.random() * Math.PI * 2;
|
||||
|
||||
// 创建弹孔 Mesh,沿法线微偏移以避免 Z-fighting
|
||||
const mat = this._mats[Math.floor(Math.random() * this._mats.length)];
|
||||
const decal = new Mesh(this._geo, mat);
|
||||
decal.position.copy(hitPoint).addScaledVector(hitNormal, 0.001);
|
||||
decal.rotation.copy(_decalHelper.rotation);
|
||||
decal.renderOrder = 1;
|
||||
this._scene.add(decal);
|
||||
|
||||
// 超出上限时移除最旧弹孔
|
||||
this._pool.push(decal);
|
||||
if (this._pool.length > this._maxDecals) {
|
||||
this._scene.remove(this._pool.shift());
|
||||
}
|
||||
|
||||
// 命中硝烟特效
|
||||
effects?.triggerHitSmoke(hitPoint, hitNormal);
|
||||
|
||||
// 广播给其他客户端(多人模式注入)
|
||||
this.onSpawn?.(hitPoint, hitNormal);
|
||||
}
|
||||
|
||||
// 根据世界坐标和法线直接生成弹孔(接收远程弹痕时使用,不触发硝烟)
|
||||
spawnAtPoint(hitPoint, hitNormal) {
|
||||
if (!this._mats.length) return;
|
||||
|
||||
_decalHelper.position.copy(hitPoint);
|
||||
_decalHelper.lookAt(hitPoint.clone().add(hitNormal));
|
||||
_decalHelper.rotation.z = Math.random() * Math.PI * 2;
|
||||
|
||||
const mat = this._mats[Math.floor(Math.random() * this._mats.length)];
|
||||
const decal = new Mesh(this._geo, mat);
|
||||
decal.position.copy(hitPoint).addScaledVector(hitNormal, 0.001);
|
||||
decal.rotation.copy(_decalHelper.rotation);
|
||||
decal.renderOrder = 1;
|
||||
this._scene.add(decal);
|
||||
|
||||
this._pool.push(decal);
|
||||
if (this._pool.length > this._maxDecals) {
|
||||
this._scene.remove(this._pool.shift());
|
||||
}
|
||||
}
|
||||
|
||||
// 清空所有弹孔
|
||||
clear() {
|
||||
for (const d of this._pool) this._scene.remove(d);
|
||||
this._pool.length = 0;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user