- 메인 페이지에서 배경, 말, 주사위면을 커스텀하게 지정하도록 변경.
- 모든 데이터를 로컬에 저장하고, 가져오기 및 내보내기를 할 수 있도록 수정.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Suspense, useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Suspense, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Canvas, useFrame, useLoader, useThree } from '@react-three/fiber';
|
||||
import {
|
||||
Physics,
|
||||
@@ -15,25 +15,26 @@ import {
|
||||
Vector3,
|
||||
type OrthographicCamera,
|
||||
} from 'three';
|
||||
import { getDiceFaceTextureList } from './asset-storage';
|
||||
|
||||
const FLOOR_SIZE = 10;
|
||||
const FLOOR_SIZE = 6;
|
||||
const FLOOR_HALF = FLOOR_SIZE / 2;
|
||||
const WALL_THICKNESS = 0.2;
|
||||
const WALL_HEIGHT = 10;
|
||||
const WALL_THICKNESS = 0.25;
|
||||
const WALL_HEIGHT = 8;
|
||||
const WALL_HALF_T = WALL_THICKNESS / 2;
|
||||
const WALL_HALF_H = WALL_HEIGHT / 2;
|
||||
|
||||
const STILL_SPEED_SQ = 0.05;
|
||||
const STILL_FRAMES = 18;
|
||||
|
||||
// boxGeometry material 순서: +X, -X, +Y, -Y, +Z, -Z
|
||||
const DICE_FACE_TEXTURES = [
|
||||
'/dice/2.png', // +X
|
||||
'/dice/5.png', // -X
|
||||
'/dice/1.png', // +Y
|
||||
'/dice/6.png', // -Y
|
||||
'/dice/3.png', // +Z
|
||||
'/dice/4.png', // -Z
|
||||
// boxGeometry material 순서: +X, -X, +Y, -Y, +Z, -Z (기본 경로, 런타임에 localStorage로 교체)
|
||||
const DEFAULT_DICE_FACE_TEXTURES = [
|
||||
'/dice/2.png',
|
||||
'/dice/5.png',
|
||||
'/dice/1.png',
|
||||
'/dice/6.png',
|
||||
'/dice/3.png',
|
||||
'/dice/4.png',
|
||||
] as const;
|
||||
|
||||
// 결과 판정: 로컬 법선이 월드 +Y에 가장 가까운 면
|
||||
@@ -99,8 +100,8 @@ function InvisibleWalls() {
|
||||
);
|
||||
}
|
||||
|
||||
function DiceMesh() {
|
||||
const textures = useLoader(TextureLoader, [...DICE_FACE_TEXTURES]);
|
||||
function DiceMesh({ faceUrls }: { faceUrls: string[] }) {
|
||||
const textures = useLoader(TextureLoader, faceUrls);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
for (const texture of textures) {
|
||||
@@ -115,7 +116,7 @@ function DiceMesh() {
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
{textures.map((texture, index) => (
|
||||
<meshStandardMaterial
|
||||
key={texture.uuid}
|
||||
key={`${texture.uuid}-${index}`}
|
||||
attach={`material-${index}`}
|
||||
map={texture}
|
||||
roughness={0.55}
|
||||
@@ -169,10 +170,12 @@ function RapierDiceExample({
|
||||
diceRef,
|
||||
rolling,
|
||||
onSettled,
|
||||
faceUrls,
|
||||
}: {
|
||||
diceRef: React.RefObject<RapierRigidBody | null>;
|
||||
rolling: boolean;
|
||||
onSettled: (value: number) => void;
|
||||
faceUrls: string[];
|
||||
}) {
|
||||
return (
|
||||
<Canvas
|
||||
@@ -203,7 +206,7 @@ function RapierDiceExample({
|
||||
friction={0.6}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<DiceMesh />
|
||||
<DiceMesh key={faceUrls.join('|')} faceUrls={faceUrls} />
|
||||
</Suspense>
|
||||
</RigidBody>
|
||||
|
||||
@@ -237,6 +240,11 @@ export default function Dice({
|
||||
const diceRef = useRef<RapierRigidBody>(null);
|
||||
const [rolling, setRolling] = useState(false);
|
||||
const [result, setResult] = useState<number | null>(null);
|
||||
const [faceUrls, setFaceUrls] = useState<string[]>([...DEFAULT_DICE_FACE_TEXTURES]);
|
||||
|
||||
useEffect(() => {
|
||||
setFaceUrls(getDiceFaceTextureList());
|
||||
}, []);
|
||||
|
||||
const onSettled = useCallback(
|
||||
(value: number) => {
|
||||
@@ -252,7 +260,12 @@ export default function Dice({
|
||||
return (
|
||||
<div className="relative h-full w-full" style={{ containerType: 'size' }}>
|
||||
<div className="absolute top-0 left-0 aspect-square w-[min(100%,100cqh)]">
|
||||
<RapierDiceExample diceRef={diceRef} rolling={rolling} onSettled={onSettled} />
|
||||
<RapierDiceExample
|
||||
diceRef={diceRef}
|
||||
rolling={rolling}
|
||||
onSettled={onSettled}
|
||||
faceUrls={faceUrls}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute bottom-2 left-2 z-10 rounded bg-black/70 px-3 py-1 text-sm text-white disabled:opacity-50"
|
||||
|
||||
Reference in New Issue
Block a user