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;