character.tsx 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. "use client";
  2. import { useRef, useState } from "react";
  3. import { useFrame, useThree } from "@react-three/fiber";
  4. import { useGLTF } from "@react-three/drei";
  5. import * as THREE from "three";
  6. import { useEditorStore } from "@/lib/store";
  7. interface GameCharacterProps {
  8. position?: [number, number, number] | null;
  9. rotation?: [number, number, number] | null;
  10. scale?: [number, number, number] | null;
  11. castShadow?: boolean | null;
  12. receiveShadow?: boolean | null;
  13. modelUrl: string;
  14. role?: string | null;
  15. physics?: {
  16. mass?: number | null;
  17. isStatic?: boolean | null;
  18. restitution?: number | null;
  19. friction?: number | null;
  20. colliderType?: string | null;
  21. } | null;
  22. objectId?: string | null;
  23. }
  24. export function GameCharacter({
  25. position,
  26. rotation,
  27. scale,
  28. modelUrl,
  29. }: GameCharacterProps) {
  30. const isPlaying = useEditorStore((s) => s.isPlaying);
  31. const pos: [number, number, number] = position ?? [0, 0, 0];
  32. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  33. const scl: [number, number, number] = scale ?? [1, 1, 1];
  34. const groupRef = useRef<THREE.Group>(null);
  35. const { camera } = useThree();
  36. const [isNear, setIsNear] = useState(false);
  37. useFrame(() => {
  38. if (!isPlaying || !groupRef.current) return;
  39. const dist = groupRef.current.position.distanceTo(camera.position);
  40. setIsNear(dist < 3);
  41. });
  42. if (!modelUrl) {
  43. return (
  44. <group position={pos} rotation={rot} scale={scl} ref={groupRef}>
  45. <mesh castShadow>
  46. <capsuleGeometry args={[0.3, 0.8, 8, 16]} />
  47. <meshStandardMaterial color="#e74c3c" />
  48. </mesh>
  49. </group>
  50. );
  51. }
  52. return (
  53. <group ref={groupRef} position={pos} rotation={rot} scale={scl}>
  54. <CharacterModel url={modelUrl} />
  55. {isPlaying && isNear && (
  56. <sprite position={[0, 2.2, 0]} scale={[0.5, 0.5, 0.5]}>
  57. <spriteMaterial color="#ffffff" opacity={0.8} transparent />
  58. </sprite>
  59. )}
  60. </group>
  61. );
  62. }
  63. function CharacterModel({ url }: { url: string }) {
  64. const { scene } = useGLTF(url);
  65. return <primitive object={scene.clone()} />;
  66. }