diff --git a/src/App.jsx b/src/App.jsx index 3efef2f..9f1d03d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -20,8 +20,8 @@ export default function App() { const [timeScale, setTimeScale] = useState(100); const [camSpeed, setCamSpeed] = useState(0.05); const [vrmModel, setVrmModel] = useState('ai.vrm'); - const [lang, setLang] = useState('ja'); - const langRef = useRef('ja'); + const [lang, setLang] = useState('en'); + const langRef = useRef('en'); const voiceIndexRef = useRef(0); const voicePattern = ['normal','normal','normal','normal','normal','normal','normal','normal','skill','skill']; const actionIndexRef = useRef(0); diff --git a/src/AvatarScene.jsx b/src/AvatarScene.jsx index a6c738c..9336bb1 100644 --- a/src/AvatarScene.jsx +++ b/src/AvatarScene.jsx @@ -124,7 +124,7 @@ export default function AvatarScene({ selectedAnimation: animState, onZoomOut, c const timer = setTimeout(() => setEffect(null), SKILL_DURATION); return () => clearTimeout(timer); } else if (animName === 'fly_dodge') { - setEffect({ type: 'moon' }); + setEffect({ type: 'earth' }); const timer = setTimeout(() => setEffect(null), SKILL_DURATION); return () => clearTimeout(timer); } diff --git a/src/SkillEffects.jsx b/src/SkillEffects.jsx index 78968fe..d3904cd 100644 --- a/src/SkillEffects.jsx +++ b/src/SkillEffects.jsx @@ -2,113 +2,141 @@ import React, { useRef, useMemo } from 'react'; import { useFrame } from '@react-three/fiber'; import * as THREE from 'three'; -const SPHERE_PRESETS = { +// --- Galaxy Effect (skill) --- + +const GALAXY_COUNT = 600; +const ARM_COUNT = 4; +const MAX_RADIUS = 0.6; + +const GALAXY_PRESETS = { sun: { - layers: [ - { radius: 0.3, segments: 64, size: 0.015, opacity: 0.5 }, - { radius: 0.22, segments: 48, size: 0.02, opacity: 0.3 }, - { radius: 0.38, segments: 40, size: 0.008, opacity: 0.2 }, - ], + coreColor: [1.0, 0.8, 0.3], + armColor: [1.0, 0.5, 0.1], + tipColor: [1.0, 1.0, 0.7], lightColor: '#ffaa22', lightIntensity: 1.5, - rotateSpeed: 1.5, - pulseSpeed: 2.0, - pulseRange: 0.25, - colorFn: (normDist, layerIdx) => { - if (layerIdx === 0) return [1.0, 0.7 + normDist * 0.3, 0.1]; - if (layerIdx === 1) return [1.0, 0.4, 0.05]; - return [1.0, 0.9, 0.5]; - }, - }, - moon: { - layers: [ - { radius: 0.2, segments: 48, size: 0.01, opacity: 0.4 }, - { radius: 0.26, segments: 32, size: 0.006, opacity: 0.15 }, - ], - lightColor: '#aaccff', - lightIntensity: 1.0, rotateSpeed: 0.8, - pulseSpeed: 1.0, - pulseRange: 0.12, - colorFn: (normDist, layerIdx) => { - if (layerIdx === 0) return [0.7, 0.8, 1.0]; - return [0.5, 0.6, 1.0]; - }, - }, - earth: { - layers: [ - { radius: 0.25, segments: 56, size: 0.012, opacity: 0.7 }, - { radius: 0.32, segments: 36, size: 0.007, opacity: 0.25 }, - ], - lightColor: '#44aaff', - lightIntensity: 2.5, - rotateSpeed: 1.0, - pulseSpeed: 1.5, - pulseRange: 0.18, - colorFn: (normDist, layerIdx) => { - if (layerIdx === 0) return [0.2, 0.6 + normDist * 0.4, 1.0]; - return [0.1, 0.8, 0.6]; - }, - }, - neutron: { - layers: [ - { radius: 0.15, segments: 72, size: 0.008, opacity: 0.9 }, - { radius: 0.1, segments: 48, size: 0.015, opacity: 0.6 }, - { radius: 0.22, segments: 32, size: 0.005, opacity: 0.3 }, - ], - lightColor: '#8844ff', - lightIntensity: 4, - rotateSpeed: 3.0, - pulseSpeed: 3.0, - pulseRange: 0.35, - colorFn: (normDist, layerIdx) => { - if (layerIdx === 0) return [0.9, 0.3, 1.0]; - if (layerIdx === 1) return [1.0, 1.0, 1.0]; - return [0.5, 0.1, 1.0]; - }, + spread: 1.0, }, }; -function ParticleLayer({ radius, segments, size, opacity, colorFn, layerIdx }) { - const data = useMemo(() => { - const geo = new THREE.SphereGeometry(radius, segments, segments); - const posArr = new Float32Array(geo.attributes.position.array); - const colArr = new Float32Array(posArr.length); - for (let i = 0; i < posArr.length; i += 3) { - const x = posArr[i], y = posArr[i + 1], z = posArr[i + 2]; - const dist = Math.sqrt(x * x + y * y + z * z); - const normDist = dist / (radius || 1); - const [r, g, b] = colorFn(normDist, layerIdx); - colArr[i] = r; - colArr[i + 1] = g; - colArr[i + 2] = b; +function buildGalaxy(preset) { + const positions = new Float32Array(GALAXY_COUNT * 3); + const colors = new Float32Array(GALAXY_COUNT * 3); + const randoms = new Float32Array(GALAXY_COUNT); + + for (let i = 0; i < GALAXY_COUNT; i++) { + const i3 = i * 3; + const arm = i % ARM_COUNT; + const armAngle = (arm / ARM_COUNT) * Math.PI * 2; + const t = Math.random(); + const r = t * MAX_RADIUS; + const twist = t * Math.PI * 2.5; + const angle = armAngle + twist; + const scatter = (1 - t * 0.5) * 0.08 * preset.spread; + + positions[i3] = Math.cos(angle) * r + (Math.random() - 0.5) * scatter; + positions[i3 + 1] = (Math.random() - 0.5) * 0.04 * (1 - t * 0.5); + positions[i3 + 2] = Math.sin(angle) * r + (Math.random() - 0.5) * scatter; + + const core = preset.coreColor; + const arm_ = preset.armColor; + const tip = preset.tipColor; + const c = t < 0.3 + ? core.map((v, j) => v + (arm_[j] - v) * (t / 0.3)) + : arm_.map((v, j) => v + (tip[j] - v) * ((t - 0.3) / 0.7)); + colors[i3] = c[0]; + colors[i3 + 1] = c[1]; + colors[i3 + 2] = c[2]; + randoms[i] = Math.random(); + } + return { positions, colors, randoms }; +} + +function GalaxyEffect({ position = [0, 1.0, 0], scale = 1 }) { + const preset = GALAXY_PRESETS.sun; + const groupRef = useRef(); + const pointsRef = useRef(); + const { positions, colors, randoms } = useMemo(() => buildGalaxy(preset), []); + const basePositions = useRef(positions); + + useFrame(({ clock }) => { + if (!groupRef.current || !pointsRef.current) return; + const t = clock.getElapsedTime(); + groupRef.current.rotation.y = t * preset.rotateSpeed; + groupRef.current.rotation.x = Math.sin(t * 0.3) * 0.15; + const pulse = 1 + Math.sin(t * 2.0) * 0.1; + groupRef.current.scale.setScalar(scale * pulse); + + const pos = pointsRef.current.geometry.attributes.position.array; + const base = basePositions.current; + for (let i = 0; i < GALAXY_COUNT; i++) { + const i3 = i * 3; + const drift = Math.sin(t * 1.5 + randoms[i] * 10) * 0.02; + pos[i3] = base[i3] + drift; + pos[i3 + 1] = base[i3 + 1] + Math.sin(t * 2 + randoms[i] * 6) * 0.01; + pos[i3 + 2] = base[i3 + 2] + drift * 0.7; } - geo.dispose(); - return { positions: posArr, colors: colArr }; - }, [radius, segments, colorFn, layerIdx]); + pointsRef.current.geometry.attributes.position.needsUpdate = true; + }); return ( - - - - - - - + + + + + + + + + + ); } -function EnergySphere({ type = 'sun', position = [0, 1.5, 0], scale = 1 }) { +// --- Sphere Effect (teleport) --- + +const SPHERE_PRESET = { + layers: [ + { radius: 0.2, segments: 48, size: 0.01, opacity: 0.4 }, + { radius: 0.26, segments: 32, size: 0.006, opacity: 0.15 }, + ], + lightColor: '#aaccff', + lightIntensity: 0.3, + rotateSpeed: 0.8, + pulseSpeed: 1.0, + pulseRange: 0.12, + colorFn: (normDist, layerIdx) => { + if (layerIdx === 0) return [1.0, 0.8 + normDist * 0.15, 0.2]; + return [1.0, 0.7, 0.1]; + }, +}; + +function buildSphereLayer(radius, segments, colorFn, layerIdx) { + const geo = new THREE.SphereGeometry(radius, segments, segments); + const posArr = new Float32Array(geo.attributes.position.array); + const colArr = new Float32Array(posArr.length); + for (let i = 0; i < posArr.length; i += 3) { + const x = posArr[i], y = posArr[i + 1], z = posArr[i + 2]; + const dist = Math.sqrt(x * x + y * y + z * z); + const normDist = dist / (radius || 1); + const [r, g, b] = colorFn(normDist, layerIdx); + colArr[i] = r; + colArr[i + 1] = g; + colArr[i + 2] = b; + } + geo.dispose(); + return { positions: posArr, colors: colArr }; +} + +function SphereEffect({ position = [0, 1.0, 0], scale = 1 }) { const groupRef = useRef(); const layerRefs = useRef([]); - const preset = SPHERE_PRESETS[type] || SPHERE_PRESETS.sun; + const preset = SPHERE_PRESET; + + const layerData = useMemo(() => + preset.layers.map((l, i) => buildSphereLayer(l.radius, l.segments, preset.colorFn, i)), + []); useFrame(({ clock }) => { if (!groupRef.current) return; @@ -130,20 +158,27 @@ function EnergySphere({ type = 'sun', position = [0, 1.5, 0], scale = 1 }) { {preset.layers.map((layer, i) => ( layerRefs.current[i] = el}> - + + + + + + + ))} - ); } -export { EnergySphere, SPHERE_PRESETS }; +// --- Unified export --- + +function EnergySphere({ type = 'sun', position = [0, 1.0, 0], scale = 1 }) { + if (type === 'earth' || type === 'moon') { + return ; + } + return ; +} + +export { EnergySphere }; export default EnergySphere;