Search

7월

7/2

지금까지 고생한 이유는 애니메이션 넣는것 때문이다 오브젝트 때문이 아니라
mixamo가 원흉인데.. 애니메이션 적용에 에러가 엄청많다 특히 텍스쳐 적용이 안되고 텍스쳐가 없어지고..
rodin.ai도 써봤고 meshy.ai도 써봤고 모델 만들어서 올려봤지만 텍스쳐 자꾸 없어지고 glb든 fbx든 zip파일을 올리든
오브젝트와 그 움직임은 그냥 threejs로 구현 가능하다 애니메이션 오토리깅같은것들이 어려워서 문제인건데
fbx파일로 올려도 리깅 안되어있다고 하고 sketchfab에서 리깅 된 모델 올려도 텍스쳐깨지고..
lowpoly는 더욱 구하기가 어렵다
그럴바에야는 애니메이션 적용하고 싶은 경우에는 그냥 mixamo에서 제공하는 모델 쓰는게 속편하다
그리고 fbx움직임같은것들 without skin으로 가져와서 그냥 fbx파일 별도로 있으면 그걸로 애니메이션 재생가능하다
처음부터 그냥 threejs mixamo 이런식으로 검색해봤으면 잘 나왔는데..
mixamo에는 lowpoly는 없다 굳이 lowpoly로 할 필요성도 없기는 한데
근데 lowpoly로 만들거나 비슷한 모델 찾고, 그거에다가 애니메이션 적용하는게 이렇게 어려운가.. 직접 적용하면 차라리 나을수도 있겠는데 mixamo에서 제공하는 모델인게 아니면 너무 어렵다
lowpoly를 포기하는게 낫겠다!!

7/3

bedtime storyteller 웹앱 만들기
clerk 로그인

7/4

여백공간 줄이기 너무 작다. 앱사이즈일때는 그냥 전체 채우게
로그아웃버튼 위치 수정
next버튼크기수정
api응답 파싱 제대로 안됨 제목이랑..
Story페이지 글씨체 멋있게
리스트 클릭하면 해당 소설 보여주도록
스토리 삭제 기능
favicon변경
Back Next UI등 기타 css 수정
AgeAndLengthStep으로 수정하기
생성중일때 loading spinner 넣기
genderstep에서 너무 비는데 좀더 크게 만들기
lesson or value 클릭하면 텍스트 채워주는 형식으로 하기 많이 사용될만한것
폰트수정
genderstep boy girl 뒷배경으로 아이콘 넣기
elevenlabs tts읽어주기 기능, 목소리 선택
react query zustand적용
toast적용
mp3파일 서버에 저장하도록 하기 오디오 업로드
storypage loading좀 예쁘게
속도 조절해서 굉장히 천천히 말하게, pause도 좀 넣게, 그리고 claude api요청글자수 줄이기 elvenlabs가 너무 비쌈..
No Stories yet 예쁘게 하기
gpt tts로 바꾸기
docker화 해서 railway호스팅
도메인 사기 bedtimestoryteller.rest
railway에서 환경변수 적용 안되는 문제 해결 ⇒ vite여서 그런것 arg로 전달해주기
랜딩페이지 만들기 threejs

7/9

elevenlabs가 너무 비싸다..
gpt-4o-mini-tts보다 16배정도 비쌈
3분짜리 만들었을때 0.03달러 드는것 확인함 100분에 1달러니까
30분에 5달러이던 elevenlabs보다 훨씬 싸다

7/10

도메인 적용해야 결제시스템 도입할 수 있다.

7/12

달에서 빛나도록 하기
결제내역 볼수있는 페이지, 로그아웃, 회원삭제 등을 위한 페이지 만들기
해시태그 적용해서 오른쪽에서 어떤건지 알수있게 하기 초록색 노란색
폴더기능 만들기 탭을 만들어서 전체보기탭이랑 폴더별로 보기 탭
threejs background너무 어두운 문제 해결

7/15

. ScrollControls 컴포넌트

<ScrollControls pages={4} damping={0.1}>
pages={4}: 총 4페이지 분량의 스크롤 영역 생성
damping={0.1}: 스크롤 감쇠 효과 (부드러운 스크롤)
<Scroll html> <h1 className="text-3xl font-bold underline">Hello, world!</h1> </Scroll>
Scroll html: 3D 씬 위에 HTML 요소를 오버레이
Tailwind CSS 클래스 사용 (text-3xl font-bold underline)
스크롤과 연동되어 움직이는 텍스트
좋은 질문입니다! 두 라이브러리의 역할을 명확히 설명해드리겠습니다.

@react-three/fiber

"Three.js를 React로 사용할 수 있게 해주는 렌더러"

주요 기능:

Three.js 객체들을 React 컴포넌트로 변환
React의 생명주기와 상태 관리를 3D에서 사용 가능
JSX로 3D 씬을 선언적으로 작성

예시:

import { Canvas } from '@react-three/fiber' // 순수 Three.js 방식 (복잡함) const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera() const renderer = new THREE.WebGLRenderer() const geometry = new THREE.BoxGeometry() const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) const cube = new THREE.Mesh(geometry, material) // Fiber 방식 (React스럽게) <Canvas> <mesh> <boxGeometry /> <meshBasicMaterial color="green" /> </mesh> </Canvas>
JavaScript
복사

@react-three/drei

"Three.js/Fiber 개발을 위한 유용한 헬퍼 컴포넌트 모음"

주요 제공 기능:

1. 카메라 컨트롤

import { OrbitControls, FlyControls } from '@react-three/drei' <OrbitControls /> // 마우스로 씬 회전/줌 <FlyControls /> // 1인칭 비행 컨트롤
JavaScript
복사

2. 조명 헬퍼

import { Environment, ContactShadows } from '@react-three/drei' <Environment preset="sunset" /> // 미리 설정된 환경 조명 <ContactShadows /> // 바닥 그림자
JavaScript
복사

3. 로더들

import { useGLTF, useTexture } from '@react-three/drei' const model = useGLTF('/model.glb') // 3D 모델 로드 const texture = useTexture('/texture.jpg') // 텍스처 로드
JavaScript
복사

4. 텍스트와 UI

import { Text, Html } from '@react-three/drei' <Text fontSize={1}>Hello World</Text> // 3D 텍스트 <Html><div>HTML in 3D</div></Html> // 3D 공간에 HTML
JavaScript
복사

5. 이펙트와 포스트프로세싱

import { Effects } from '@react-three/drei' <Effects> <bloomPass /> // 블룸 효과 <filmPass /> // 필름 효과 </Effects>
JavaScript
복사

관계 정리

Three.js (순수 자바스크립트 3D 라이브러리) ↓ @react-three/fiber (Three.js를 React로 감싸는 렌더러) ↓ @react-three/drei (개발을 편하게 해주는 헬퍼 컴포넌트들)
Plain Text
복사
요약:
Fiber = Three.js를 React로 사용할 수 있게 해주는 핵심 도구
Drei = 개발을 빠르고 편하게 해주는 유용한 컴포넌트들

framer motion

whileInView를 더 큰 요소에 넣는 이유는 하나하나 반응하지 않도록 하기 위해서
전체 큰 요소가 들어왔을때 variant를 세팅하고 동작하도록 함
gsap의 자동스크롤 쓰면 유저가 쓱쓱 지나갈 고민 안해도 되고 자동으로 되는데.. 웹사이트도 이렇게 구현했으면 될것같다

7/16

formspree 설정
설명 내용 바꾸기
project스타일 약간 수정
vercel배포
css layout해결
결제시스템 곧 출시한다고 수정
에러해결. 음성생성 중간에 나갔을때 정지하기
반응형 확인
clerk production으로 돌리기
clerk production
api키를 프로덕션 인스턴스로 바꾸기
publishable과 비밀키 둘다 live로 바꾸기
DNS CNAME 세팅

7/17

vocasimple

호출 너무 많은 문제

supabase를 사용하고 있는데, api gateway의 tatl request수가 비정상적으로 높다. 앱을 몇번 reload했을 뿐인데 요청 수가 /auth/v1/user get은 6200번, /rest/v1/vocabulary도 거의 6200번이다. 왜 이렇게 여러번의 호출이 일어나는건지 확인하고 해결
Supabase에서는 auth.users 테이블이 시스템 테이블이라서 직접 컬럼을 추가할 수 없어요. 그래서 별도의 테이블을 만들어야 합니다.
Supabase의 구조를 설명드리면:
Supabase 테이블 구조
auth.users 테이블 (시스템 테이블)
Supabase가 자동으로 관리하는 인증 전용 테이블
id, email, created_at 등 기본 인증 정보만 저장
사용자가 직접 수정할 수 없음 (컬럼 추가/삭제 불가)
public.user_profiles 테이블 (사용자 정의 테이블)
사용자가 생성하는 추가 정보용 테이블
nickname, avatar, preferences 등 커스텀 데이터 저장
auth.users.id와 Foreign Key로 연결
왜 이런 구조를 사용하는가?
1.
보안: 인증 데이터와 사용자 데이터 분리
2.
시스템 안정성: Supabase 업데이트가 사용자 데이터에 영향 안줌

Todos

supabase RLS policy 추가
플래시카드 기능
쓰기 퀴즈 기능
결과페이지 만들기 - 오늘 처음인 경우 불꽃 받았다는 표시도 함께.
단어 수정 기능 왼쪽으로 스와이프하면 편집 아이콘 나와서
퀴즈 하나를 완료하면 불꽃 주기
유저별로 며칠며칠에 완수했는지 저장해야하고
그걸기반으로 이번달에는 며칠완수했는지 저장
닉네임 생성 및 변경기능
회원탈퇴 기능 만들기
가입한 이메일로 json파일 보내주기 기능
미암기에서 암기로 넣는 기능 ⇒ 퀴즈에서 풀었을 때 자동으로 외운 단어장으로 넘어가기
티어 기능 1000개 이상 외웠으면 sage 500개 이상 knight 그 이하는 apprentice
외운단어수 체크 기능 ⇒ 단어중에 isMemorized true인것 갯수
퀴즈풀때 미암기만 / 전체 선택기능 만들기
전체일때 암기였는데 틀린건 미암기로 다시 isMemorized: false로 바꾸기
단어장 선택기능 왼쪽 위에서 전체/암기/미암기 선택할수 있는 버튼 생성 primary color에<Octicons name="stack" size={24} color="black" />를 사용하는 원형 vocabulary라고 적혀있는것 상단, ‘+’아이콘과 동일한 높이에
티어표 볼수있도록 하기 아직 랭킹에 진입한 사람이 없으면 없다고 첫번째가 되어보라고 하기
Achievement로 연속공부기록, 암기한 단어를 UI구현해놓은걸 실제로 사용하도록 구현하기
AI로 예문생성 기능
VocabularyStore 생성 및 중복 API 호출 개선
UserProfileStore 생성 및 Profile 탭 API 호출 최적화
VocabularyCard에 암기 상태 표시 추가
tanstack query로 생성 혹은 수정 혹은 삭제하면 invalidate해서 다시 가져오도록 하기
예문은 뜻과 동일한 상황에서만보이도록 수정

7/21

algorithmplayground

하노이의 탑 (실제 탑 형태로 3D 구현)
군체 알고리즘 시각화
texture로 사각형 + 이름 표시 바닥에
초기화 안되는 문제 해결
처음 생성되는 공간에서 Welcome to Algorithm Playground. Feel free to walk around!
캐릭터 변경하기
알고리즘 설명 패널, 시간복잡도

7/22

Third-person controller

rapier
rigid body를 넣어야함 object들에
캐릭터는 capsule colider가 있으면 경사면을 올라갈 수 있음
캐릭터가 반드시 camera가 lookAt하는대상일필요는 없다 센터보다 약간 아래에 있는게 좋다

Rapier가 하는 일

Rapier = 물리 엔진이에요. 현실 세계처럼 중력, 충돌, 관성 등을 계산해줍니다.
import { CapsuleCollider, RigidBody } from "@react-three/rapier"; <RigidBody colliders={false} lockRotations ref={rb}> // 이 안의 모든 것이 물리법칙을 따라 움직여요 <CapsuleCollider args={[0.08, 0.15]} /> </RigidBody>
JavaScript
복사
RigidBody: 물리적인 몸체 (떨어뜨리면 중력으로 떨어져요)
CapsuleCollider: 충돌 감지용 캡슐 (벽에 부딪히면 못 지나가요)

움직이게 하는 핵심 코드

// 1. 현재 속도 가져오기 const vel = rb.current.linvel(); // Rapier의 선형 속도 // 2. 새로운 속도 계산 vel.x = Math.sin(회전각도) * 속도; vel.z = Math.cos(회전각도) * 속도; // 3. 실제로 움직이기 rb.current.setLinvel(vel, true); // Rapier에게 "이 속도로 움직여!"
JavaScript
복사
이게 전부예요! 나머지는 다 부가적인 계산들입니다.

함수들의 정체

Rapier 함수들

rb.current.linvel() // 현재 linear velocity(선형 속도) 가져오기 rb.current.setLinvel() // linear velocity 설정하기
JavaScript
복사

Three.js 함수들

object.getWorldPosition(vector) // Three.js Object3D의 메소드 // 3D 객체의 "월드 좌표계에서의 실제 위치"를 가져옴 camera.position.lerp(target, 0.1) // Vector3의 메소드 MathUtils.lerp(start, end, 0.1) // Three.js 유틸리티
JavaScript
복사

직접 만든 함수

const lerpAngle = (start, end, t) => { // 각도 전용 보간 (360도 넘나들 때 최단경로로 회전) }
JavaScript
복사

WorldPosition의 비밀

<group ref={container}> // 이 그룹이 회전하면 <group ref={cameraPosition} position-y={4} position-z={-4} /> // 이 위치도 함께 회전 </group>
JavaScript
복사
문제: position-y={4} 이건 그룹 내부의 상대적 위치예요. 해결: getWorldPosition()으로 실제 월드에서의 절대 위치를 구해요.
// 예시 그룹이 90도 회전했다면: - 상대위치: (0, 4, -4) - 실제위치: (4, 4, 0)getWorldPosition()이 알려주는 값
JavaScript
복사

get() 함수

const [, get] = useKeyboardControls(); // get()을 호출하면 현재 키보드 상태를 알려줌 if (get().forward) { // W키가 눌려있나? movement.z = 1; } if (get().backward) { // S키가 눌려있나? movement.z = -1; }
JavaScript
복사

Lerp 함수들 비교

// 1. 일반 숫자 보간 MathUtils.lerp(0, 100, 0.1)10 // 2. 3D 좌표 보간 camera.position.lerp(targetPosition, 0.1) // Vector3 → Vector3 // 3. 각도 보간 (특별 처리 필요) lerpAngle(350, 10, 0.1) // 360도를 넘나들 때 최단경로로
JavaScript
복사
왜 각도는 특별할까요?
일반 lerp: 350도 → 10도 = 긴 길로 돌아감 (340도 회전) lerpAngle: 350도 → 10도 = 짧은 길로 감 (20도 회전)
Plain Text
복사

전체 흐름 요약

useFrame(() => { // 1. 키보드 확인 const 움직임방향 = get().forward ? 1 : 0; // 2. Rapier로 실제 움직이기 const vel = rb.current.linvel(); vel.z = 움직임방향 * 속도; rb.current.setLinvel(vel, true); // ← 이게 실제 움직임! // 3. 카메라가 따라오기 cameraPosition.current.getWorldPosition(worldPos); // 실제 위치 계산 camera.position.lerp(worldPos, 0.1); // 부드럽게 따라가기 });
JavaScript
복사

핵심만 뽑으면

1.
Rapier: 물리 엔진 (setLinvel로 실제 움직임)
2.
get(): 키보드 상태 확인
3.
getWorldPosition: 그룹 회전 고려한 실제 위치
4.
lerp: 부드러운 이동/회전을 위한 보간 linear interpolation
실제로 캐릭터를 움직이는 건 rb.current.setLinvel(vel, true) 이 한 줄뿐이에요!
나머지는 다 "부드럽고 자연스럽게 보이게" 하는 장식들입니다!
useFrame은 React Three Fiber의 렌더링 루프 훅입니다 (보통 60fps)
매개변수로 { camera, mouse }를 받는데, 이는 현재 활성 카메라와 정규화된 마우스 좌표를 제공합니다
mouse.x, mouse.y는 -1 ~ 1 사이의 값 (화면 중앙이 0,0)
// 우리가 아는 것: "45도 방향으로 이동하고 싶다"
// 물리 엔진이 원하는 것: "x속도 = ?, z속도 = ?"

Math.atan2()가 하는 일

javascript
characterRotationTarget.current = Math.atan2(movement.x, movement.z);
atan2는 "방향 벡터를 각도로 변환"하는 함수예요:
javascript
// 예시들: Math.atan2(0, 1) = 0° // 순수하게 앞으로 (z=1, x=0) Math.atan2(1, 1) = 45° // 왼쪽 앞 대각선 (z=1, x=1) Math.atan2(1, 0) = 90° // 순수하게 왼쪽 (z=0, x=1) Math.atan2(-1, 1) = -45° // 오른쪽 앞 대각선 (z=1, x=-1)

sin/cos가 하는 일 (반대 변환!)

atan2와 반대로 **"각도를 방향 벡터로 변환"**해요:
javascript
vel.x = Math.sin(각도) * speed; vel.z = Math.cos(각도) * speed;

문제: 각도를 실제 이동으로 변환하기

// 우리가 아는 것: "45도 방향으로 이동하고 싶다" // 물리 엔진이 원하는 것: "x속도 = ?, z속도 = ?"
JavaScript
복사

Math.atan2()가 하는 일

characterRotationTarget.current = Math.atan2(movement.x, movement.z);
JavaScript
복사
atan2는 "방향 벡터를 각도로 변환"하는 함수예요:
// 예시들: Math.atan2(0, 1) = 0° // 순수하게 앞으로 (z=1, x=0) Math.atan2(1, 1) = 45° // 왼쪽 앞 대각선 (z=1, x=1) Math.atan2(1, 0) = 90° // 순수하게 왼쪽 (z=0, x=1) Math.atan2(-1, 1) = -45° // 오른쪽 앞 대각선 (z=1, x=-1)
JavaScript
복사
시각적으로 보면:
앞(z=1) ↑ | 왼쪽 ----+---- 오른쪽 (x=1) | (x=-1) | ↓ 뒤(z=-1)
Plain Text
복사

sin/cos가 하는 일 (반대 변환!)

atan2와 반대로 **"각도를 방향 벡터로 변환"**해요:
vel.x = Math.sin(각도) * speed; vel.z = Math.cos(각도) * speed;
JavaScript
복사

구체적인 예시로 이해하기

시나리오: W키 + D키 (앞 + 오른쪽)

1단계: movement 수집

movement.z = 1; // 앞으로 movement.x = -1; // 오른쪽 (D키는 -1)
JavaScript
복사

2단계: atan2로 각도 구하기

characterRotationTarget.current = Math.atan2(-1, 1) = -45° // "오른쪽 앞 대각선" 방향
JavaScript
복사

3단계: 전체 각도 계산

총각도 = rotationTarget.current + characterRotationTarget.current // 예: 현재 시점이 30° 돌려져 있다면 총각도 = 30° + (-45°) = -15°
JavaScript
복사

4단계: sin/cos로 실제 속도 계산

vel.x = Math.sin(-15°) * speed = -0.26 * speed // 살짝 오른쪽 vel.z = Math.cos(-15°) * speed = 0.97 * speed // 거의 앞으로
JavaScript
복사

왜 sin이 x축, cos이 z축인가?

Three.js 좌표계에서:
0° = 정면(+z 방향) cos(0°) = 1, sin(0°) = 0 (x=0, z=1)90° = 왼쪽(+x 방향) cos(90°) = 0, sin(90°) = 1 (x=1, z=0)180° = 뒤쪽(-z 방향) cos(180°) = -1, sin(180°) = 0 (x=0, z=-1)
JavaScript
복사

전체 과정 시각화

입력: W + D키 ↓ movement: {x: -1, z: 1} (오른쪽 앞) ↓ atan2(-1, 1) = -45° (각도로 변환) ↓ 총각도: 시점회전 + (-45°) ↓ sin/cos로 실제 속도 계산 ↓ vel: {x: ?, z: ?} (물리엔진이 이해하는 속도)
Plain Text
복사

왜 이런 복잡한 과정이 필요한가?

방법 1: 직접 계산 (문제 많음)

// 시점이 회전되지 않은 상태에서만 작동 if (get().forward) vel.z = speed; if (get().right) vel.x = -speed; // 문제: 카메라가 회전하면 엉망이 됨 // W키 누르는데 캐릭터가 옆으로 감 😱
JavaScript
복사

방법 2: 현재 방식 (완벽함)

// 1. 입력을 "의도"로 해석 // 2. 현재 시점을 고려해서 // 3. 실제 월드 좌표계 속도로 변환 // 결과: 카메라가 어떻게 회전해도 // W키는 항상 "화면 기준 앞으로" 이동! ✅
JavaScript
복사

핵심 개념

// atan2: 방향을 각도로 변환 "대각선으로 가고 싶다""45도" // sin/cos: 각도를 속도로 변환 "45도""vel.x = 0.7, vel.z = 0.7"
JavaScript
복사
비유:
atan2: "나침반으로 방향 확인하기"
sin/cos: "나침반 각도를 실제 걸음으로 바꾸기"

디버깅으로 확인해보기

// 콘솔에 찍어보면: console.log("Movement:", movement.x, movement.z); console.log("Character angle:", characterRotationTarget.current * 180/Math.PI, "도"); console.log("Final velocity:", vel.x, vel.z); // 예시 결과: // Movement: -1 1 (오른쪽 앞) // Character angle: -45 도 // Final velocity: -0.7 0.7
JavaScript
복사

movement 객체가 하는 일

const movement = { x: 0, // 좌우 이동 "의도" z: 0, // 앞뒤 이동 "의도" };
JavaScript
복사
중요한 점: movement는 실제 이동이 아니라 **"이동하고 싶은 방향"**을 나타냅니다!

입력 수집 단계

// 키보드 입력 if (get().forward) { movement.z = 1; // "앞으로 가고 싶다" } if (get().backward) { movement.z = -1; // "뒤로 가고 싶다" } // 좌우키 (이건 이동이 아님!) if (get().left) { movement.x = 1; // "왼쪽으로 회전하고 싶다" } if (get().right) { movement.x = -1; // "오른쪽으로 회전하고 싶다" }
JavaScript
복사

마우스 입력 (더 복잡함)

if (isClicking.current) { if (Math.abs(mouse.x) > 0.1) { movement.x = -mouse.x; // 마우스 좌우 → 회전 의도 } movement.z = mouse.y + 0.4; // 마우스 상하 → 이동 의도 }
JavaScript
복사
예시로 이해해보기:
// 마우스가 오른쪽 위에 있다면: mouse.x = 0.5 // 오른쪽 mouse.y = -0.3 // 위쪽 // 결과: movement.x = -0.5 // 왼쪽으로 회전하려는 의도 movement.z = -0.3 + 0.4 = 0.1 // 살짝 앞으로 가려는 의도
JavaScript
복사

movement를 실제 동작으로 변환

1단계: 회전 처리

if (movement.x !== 0) { rotationTarget.current += ROTATION_SPEED * movement.x; }
JavaScript
복사
movement.x가 양수면 → 왼쪽 회전
movement.x가 음수면 → 오른쪽 회전
실제 회전은 나중에 container가 함

2단계: 캐릭터가 바라볼 방향 결정

if (movement.x !== 0 || movement.z !== 0) { characterRotationTarget.current = Math.atan2(movement.x, movement.z);
JavaScript
복사
Math.atan2 예시:
// 앞으로만 이동 Math.atan2(0, 1) = 0// 정면 Math.atan2(0, -1) = 180// 뒤쪽 // 옆으로만 이동 Math.atan2(1, 0) = 90// 왼쪽 Math.atan2(-1, 0) = -90// 오른쪽 // 대각선 이동 Math.atan2(1, 1) = 45// 왼쪽 앞 Math.atan2(-1, 1) = -45// 오른쪽 앞
JavaScript
복사

3단계: 실제 velocity 계산

vel.x = Math.sin(rotationTarget.current + characterRotationTarget.current) * speed; vel.z = Math.cos(rotationTarget.current + characterRotationTarget.current) * speed;
JavaScript
복사

구체적인 예시

시나리오: W키와 A키를 동시에 눌렀다고 가정
// 1. 입력 수집 movement.z = 1; // W키 (앞으로) movement.x = 1; // A키 (왼쪽 회전) // 2. 회전 업데이트 rotationTarget.current += ROTATION_SPEED * 1; // 왼쪽으로 회전 // 3. 캐릭터 방향 계산 characterRotationTarget.current = Math.atan2(1, 1) = 45// 왼쪽 앞 45도 // 4. 최종 각도 총 각도 = rotationTarget.current + 45// 5. velocity 계산 vel.x = Math.sin(총각도) * speed // x방향 속도 vel.z = Math.cos(총각도) * speed // z방향 속도
JavaScript
복사

핵심: 두 가지 회전의 차이

rotationTarget.current // 전체 시점 회전 (카메라도 함께) characterRotationTarget.current // 캐릭터만의 이동 방향
JavaScript
복사
비유로 설명:
rotationTarget: 당신이 몸 전체를 돌리는 것
characterRotationTarget: 돌린 상태에서 걸어갈 방향을 정하는 것

마우스 드래그 예시

// 마우스를 오른쪽 아래로 드래그 mouse.x = 0.7 // 오른쪽 mouse.y = 0.5 // 아래 // movement 계산 movement.x = -0.7 // 왼쪽으로 회전 movement.z = 0.5 + 0.4 = 0.9 // 앞으로 이동 // 결과: // - 시점이 왼쪽으로 회전하면서 // - 캐릭터가 앞으로 빠르게 이동 // - 큰 움직임이라 달리기 모드 활성화
JavaScript
복사

왜 이렇게 복잡한가?

단순한 방법:

// 키 하나당 하나의 동작 if (get().forward) vel.z = speed; if (get().left) vel.x = speed;
JavaScript
복사

현재 방법의 장점:

// 1. 여러 입력의 조합 가능 W + A = 왼쪽 앞 대각선 + 시점 회전 // 2. 마우스 드래그로 자유로운 이동 // 3. 캐릭터가 이동 방향을 바라봄 // 4. 시점과 캐릭터 방향 독립적 제어
JavaScript
복사

전체 흐름 정리

입력 → movement 객체 → 두 종류의 회전각 → 실제 velocity
Plain Text
복사
1.
입력 수집: 키보드/마우스 → movement.x, movement.z
2.
시점 회전: movement.xrotationTarget (카메라도 함께 회전)
3.
캐릭터 방향: movement.x + movement.zcharacterRotationTarget
4.
최종 이동: 두 각도 합쳐서 → vel.x, vel.z
movement는 "의도"이고, velocity는 "실제 동작"입니다!
정확합니다! 두 개의 독립적인 회전 시스템이 있어요!

회전 구조 이해하기

<RigidBody ref={rb}> <group ref={container}> // ← rotationTarget이 회전시키는 그룹 <group ref={cameraTarget} /> // 카메라 관련 <group ref={cameraPosition} /> // 카메라 관련 <group ref={character}> // ← characterRotationTarget이 회전시키는 그룹 <Character /> // 실제 캐릭터 모델 </group> </group> </RigidBody>
JavaScript
복사

두 가지 회전의 차이점

rotationTarget (container 그룹)

if (movement.x !== 0) { rotationTarget.current += ROTATION_SPEED * movement.x; } // 나중에 적용: container.current.rotation.y = MathUtils.lerp( container.current.rotation.y, rotationTarget.current, 0.1 );
JavaScript
복사
회전하는 것들:
카메라 (cameraPosition, cameraTarget)
캐릭터 (character 그룹도 포함)
전체 시점!
언제 회전하나:
A/D 키를 누를 때
마우스를 좌우로 드래그할 때

characterRotationTarget (character 그룹만)

if (movement.x !== 0 || movement.z !== 0) { characterRotationTarget.current = Math.atan2(movement.x, movement.z); } // 나중에 적용: character.current.rotation.y = lerpAngle( character.current.rotation.y, characterRotationTarget.current, 0.1 );
JavaScript
복사
회전하는 것:
캐릭터 모델만! (카메라는 그대로)
언제 회전하나:
이동 방향이 바뀔 때마다 (W,A,S,D 조합)
캐릭터가 걸어가는 방향을 바라보게 하기 위해

실제 동작 예시

시나리오: A키를 누르고 있다가 W키를 추가로 누름

Phase 1: A키만 누름

// 입력 movement.x = 1; // 왼쪽 movement.z = 0; // 이동 없음 // 결과 rotationTarget += ROTATION_SPEED; // 전체 시점이 왼쪽으로 회전 characterRotationTarget = atan2(1, 0) = 90°; // 캐릭터가 왼쪽을 바라봄 // 실제 속도 vel.x = sin(rotationTarget + 90°) * speed; // 실제 이동 vel.z = cos(rotationTarget + 90°) * speed;
JavaScript
복사

Phase 2: A + W키 함께 누름

// 입력 movement.x = 1; // 왼쪽 (시점 회전 계속) movement.z = 1; // 앞으로 // 결과 rotationTarget += ROTATION_SPEED; // 시점은 계속 왼쪽으로 회전 characterRotationTarget = atan2(1, 1) = 45°; // 캐릭터가 왼쪽-앞을 바라봄 // 실제 속도 vel.x = sin(rotationTarget + 45°) * speed; // 대각선 이동 vel.z = cos(rotationTarget + 45°) * speed;
JavaScript
복사

회전 기준 (원점)

rotationTarget의 기준점

// container 그룹의 중심 = 캐릭터 위치 // 카메라가 캐릭터를 중심으로 공전
JavaScript
복사

characterRotationTarget의 기준점

// character 그룹의 중심 = 캐릭터 발 위치 // 캐릭터가 제자리에서 몸만 회전
JavaScript
복사

왜 두 개로 분리했을까?

하나로 합친다면:

// 문제: 캐릭터가 옆으로 걸을 때 // 카메라도 함께 회전해서 시점이 흔들림 😵‍💫
JavaScript
복사

분리된 시스템:

// ✅ 시점 회전: 플레이어가 직접 제어 (A/D키) // ✅ 캐릭터 회전: 이동 방향에 따라 자동 // 결과: 안정적인 카메라 + 자연스러운 캐릭터 애니메이션
JavaScript
복사

7/23

실제 디바이스에서 테스트하는 방법

1. Development Build로 실제 디바이스 테스트

bash
# Development Build 생성 (클라우드) eas build --profile development --platform ios # 빌드 완료 후 QR 코드나 링크로 디바이스에 설치

2. 또는 로컬 빌드 후 디바이스 연결

bash
# 실제 디바이스를 USB로 연결하고 npx expo run:ios --device
expo-dev-client는 expo go에서 안되는 기능 위해서
eas build는 진짜 앱 빌드
vocasimple 출시
애플 ⇒ 앱 다운로드하면 안됨 ⇒ 애플 개발자 계정 필요.. 옛날에는 팀 계정 있어서 가능했던것..
안드로이드 ⇒ 다운로드 되지만 expo-notifications때문인지 실행해도 멈춤 ⇒ 디바이스 필요
npx expo run:android로 에뮬레이터에 다운받아놓고 (혹은 usb연결한 디바이스에 다운받고)
그리고 npx expo start —dev-client하면 다운받은 상태에서 expo go 없이 실행되는거임
expo go에서 안되는 기능도 많기 때문에 빌드해야하는것
eas build는 클라우드로 빌드하는거고..
expo go에서 안되는것 외에 시뮬레이터에서 안되는것도 있고..
결국은 디바이스에서 테스트해보는게 제일인데 디바이스에 다운로드하려면
애플 안드로이드 둘다 개발자계정 필요하고
일단 애플 개발자계정 신청해놨고 안드로이드는 기기있으면 바로 가능할것!!

1. Development Build 앱이 폰에 설치되어 있어야 함

이미 설치했다면:

eas build로 받은 앱이 설치되어 있어야 함
또는 npx expo run:ios --device로 설치된 앱

아직 설치 안했다면:

# USB로 폰 연결 후 npx expo run:ios --device
Shell
복사

2. 개발 서버 시작

npx expo start --dev-client
Shell
복사

3. 폰에서 접속하는 방법

방법 1: QR 코드 스캔

Development Build 앱 실행 (Expo Go 아님!)
터미널에 뜨는 QR 코드를 앱에서 스캔

방법 2: URL 직접 입력

Development Build 앱 실행
터미널에 표시된 exp://192.168.x.x:8081 주소 입력

방법 3: 자동 연결 (같은 WiFi)

개발 서버 시작하면 자동으로 디바이스 목록에 표시
클릭해서 연결

주의사항:

반드시 Development Build 앱 사용 (Expo Go )
같은 WiFi 네트워크에 연결되어 있어야 함
안되면 -tunnel 옵션 사용
npx expo start --dev-client --tunnel
Shell
복사
핵심: Development Build 앱을 먼저 폰에 설치한 후, 그 앱으로 QR 코드를 스캔하거나 URL을 입력해야 합니다.

7/24

Apple developer program

결제 수단 변경
비밀번호 업데이트
전화해보니까 애플 개발자 계정에서 신원확인이 필요하다고 함
신원확인 할 수 있도록 처리해주고 영문으로 주소 입력하라고 함
결국 직접 신분증 사진같은거 계속 보내준끝에 허가받고 성공!!

발생한 문제 원인

development build가 아니었음..
expo dev client가 앱 번들에 포함되려면 developmentClient가 true로 설정되어있어야 하고
eas build한 다음에 qr로 다운받고 dev client포함되어있으면 번들 사이즈도 꽤 크다
그다음 npx expo start —dev-client하면 그제서야 내 디바이스가 dev client에 연결된다
이때는 js만 수정하는걸로는 빌드 다시 안해도 되고
처음에 splashscreen 못 벗어나는건 notification때문이었던거같고
나중에 흰 화면 뜨던건 폰트가 로딩되기 전에 렌더링되어서 그랬던것..
해결했던 문제와 원인 정리
1.
초기 문제: 앱 실행 시 "isn't responding" 오류
원인:
패키지 버전 충돌: Expo SDK와 호환되지 않는 패키지 버전들
설정 파일 중복: app.json과 app.config.js 동시 존재로 인한 충돌
Supabase 초기화 블로킹: 타임아웃 없는 무한 대기 상태
환경 변수 로딩 실패: 프로덕션 빌드에서 process.env 접근 불가
해결책:

패키지 버전 수정

npx expo install --fix

app.json 제거

rm app.json

타임아웃 및 fallback 추가

10초 타임아웃 설정
Constants를 통한 환경 변수 fallback
try-catch 에러 처리 강화
1.
흰 화면 문제 (에뮬레이터는 정상, 실제 기기만 문제)
원인:
네비게이션 타이밍 이슈: 컴포넌트 마운트 전 네비게이션 시도
JavaScript 에러 미처리: 크래시 시 흰 화면만 표시
폰트 로딩과 인증 초기화 순서 문제
해결책:
// 네비게이션 지연 처리 setTimeout(() => { router.replace(session ? "/(tabs)" : "/(auth)"); }, 500);
// ErrorBoundary 추가 <ErrorBoundary> <QueryClientProvider> // 앱 컴포넌트들 </QueryClientProvider> </ErrorBoundary>
// 로딩 상태 UI 제공 if (!loaded || !initialized || loading) { return <LoadingScreen />; }
1.
Development Build 연결 실패
원인:
잘못된 EAS 설정: developmentClient: true 누락
Development build vs 일반 빌드 혼동
해결책:
// eas.json "development": { "distribution": "internal", "developmentClient": true, // 이것이 핵심! "android": { "buildType": "apk" } }
1.
"No apps connected" 문제
원인:
Development build가 제대로 생성되지 않음
QR 코드를 잘못된 앱에서 스캔 (Expo Go vs Development build)
해결책:
올바른 EAS 설정으로 다시 빌드
Development build 앱에서 QR 스캔 (Expo Go 아님)
1.
네트워크 요청 실패 ("Network request failed")
원인:
Android 네트워크 보안 정책: HTTPS 요청 차단
실제 기기와 개발 서버 간 네트워크 문제
해결책:
// app.config.js android: { usesCleartextTraffic: true, networkSecurityConfig: { domain: ["krxessvmecpbjsqyrqgw.supabase.co", "api.anthropic.com"], includeSubdomains: true } }
// 터널 모드 사용 npx expo start --dev-client --tunnel
1.
작은 화면에서 UI 요소 잘림
원인:
고정 크기 이미지: 393x504px 고정값 사용
화면 크기 대응 부족
해결책:
// 반응형 이미지 크기 계산 const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); const imageWidth = Math.min(screenWidth * 0.9, 393); const maxImageHeight = screenHeight * 0.5; const finalImageHeight = Math.min(imageHeight, maxImageHeight);
핵심 교훈:
1.
에뮬레이터 ≠ 실제 기기: 네트워크, 보안 정책, 성능 차이 존재
2.
Development Build의 중요성: 실제 기기에서 디버깅하려면 필수
3.
환경 변수 처리: 프로덕션 빌드에서는 다른 접근 방식 필요
4.
에러 처리의 중요성: 예상치 못한 상황에 대한 대비 필수
5.
네트워크 보안: Android에서 HTTPS 요청 시 추가 설정 필요
bash
# Apple ID 연결 eas build:configure # 기기 등록 eas device:create
Expo를 사용해 iOS Internal Distribution 빌드를 시도할 때 겪는 일반적인 문제입니다. 앱이 바로 설치되지 않고 파일만 다운로드되는 이유는, 빌드된 앱(.ipa 파일)에 테스트하려는 기기의 고유 식별자(UDID)가 포함되지 않았기 때문입니다.
eas build로 생성된 .ipa 파일을 다운로드하는 것은 정상적인 과정이며, 이 파일을 UDID가 등록된 기기에 직접 설치해야 합니다.
아래 단계별 가이드를 따라 문제를 해결해 보세요.

## 1단계: 기기 UDID 찾기

가장 먼저, 앱을 설치할 아이폰이나 아이패드의 UDID를 알아내야 합니다.
macOS Finder (또는 구버전 iTunes) 사용:
1.
iPhone/iPad를 Mac에 케이블로 연결합니다.
2.
Finder를 열고 사이드바에서 연결된 기기를 선택합니다.
3.
기기 이름 아래에 있는 모델명, 배터리 정보 등이 적힌 텍스트를 클릭하세요. 클릭할 때마다 정보가 바뀌며, UDID가 나타납니다. (일련번호 -> UDID 순으로 보입니다.)
4.
UDID 값을 복사합니다.

## 2단계: Apple 개발자 계정에 UDID 등록하기 登録

복사한 UDID를 Apple 개발자 계정에 등록해야 합니다.
1.
Apple Developer 사이트에 로그인합니다.
2.
Certificates, Identifiers & Profiles 섹션으로 이동합니다.
3.
왼쪽 메뉴에서 Devices를 클릭합니다.
4.
파란색 '+' 버튼을 눌러 새 기기를 추가합니다.
5.
PlatformiOS, tvOS, watchOS로 선택합니다.
6.
Device Name (예: "홍길동의 아이폰 15")과 **Device ID (UDID)**를 붙여넣고 Continue를 눌러 등록을 완료합니다.
중요: 이 과정을 거쳐야만 Apple이 해당 기기에서 앱을 실행하도록 허용하는 'Provisioning Profile'을 만들 수 있습니다.

## 3단계: eas.json 파일 설정하기

프로젝트의 루트에 있는 eas.json 파일에 internal 배포를 위한 프로필을 설정해야 합니다. 이 프로필은 Apple 개발자 계정에 등록된 기기들에서만 앱을 테스트할 수 있게 해줍니다.
JSON
{ "cli": { "version": ">= 7.6.0" }, "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal" }, "production": {}, "internal": { "distribution": "internal", "ios": { "enterpriseProvisioning": "adhoc" } } }, "submit": { "production": {} } }
위 예시처럼 build 섹션 안에 internal 프로필을 추가하세요.
"distribution": "internal": 내부 배포용 빌드임을 명시합니다.
"ios": { "enterpriseProvisioning": "adhoc" }: Ad Hoc 방식(등록된 기기 대상)으로 배포할 것을 지정합니다.
EAS는 빌드 과정에서 Apple 개발자 계정의 정보를 바탕으로 등록된 기기 목록을 포함하는 새로운 Provisioning Profile을 자동으로 생성하거나 업데이트합니다.

## 4단계: EAS로 다시 빌드하기

이제 설정한 internal 프로필을 사용하여 다시 빌드합니다.
Bash
eas build --platform ios --profile internal
이 명령을 실행하면 EAS가 다음을 수행합니다.
1.
Apple 개발자 계정에 로그인합니다.
2.
2단계에서 등록한 UDID를 포함하여 새로운 Ad Hoc Provisioning Profile을 생성합니다.
3.
이 Profile을 사용하여 앱을 빌드하고 서명합니다.

## 5단계: 앱 설치하기

빌드가 완료되면 EAS에서 제공하는 링크나 QR 코드를 통해 앱을 설치할 수 있습니다.
1.
EAS 빌드 대시보드에서 완료된 빌드 항목으로 이동합니다.
2.
Install 버튼이나 QR 코드를 찾을 수 있습니다.
3.
앱을 설치하려는 iPhone/iPad의 카메라로 QR 코드를 스캔하세요.
4.
화면에 나타나는 안내에 따라 앱을 설치합니다.
이제 UDID가 정상적으로 등록되었기 때문에 "파일만 다운로드"되는 현상 없이 앱이 기기에 직접 설치될 것입니다.

## 요약 및 핵심 체크리스트

문제 원인: 빌드에 기기 UDID가 포함되지 않아서 설치 권한이 없는 것입니다.
해결 순서:
1.
UDID 확인: 내 아이폰/아이패드의 UDID를 정확히 찾는다.
2.
UDID 등록: Apple 개발자 사이트의 'Devices'에 UDID를 추가한다.
3.
eas.json 설정: internal 프로필을 distribution: 'internal'로 설정한다.
4.
재빌드: eas build --profile internal 명령으로 새로 빌드한다.
5.
설치: 빌드 완료 후 생성된 QR코드를 통해 기기에 설치한다.
OS 16 버전부터는 App Store가 아닌 다른 경로(예: QR 코드, 테스트 링크)로 설치한 앱을 실행하려면 '개발자 모드'를 반드시 켜야 합니다. 이는 사용자를 악성 소프트웨어로부터 보호하기 위한 Apple의 보안 정책입니다.
개발자 모드를 활성화하는 방법은 간단합니다.

## 개발자 모드 활성화 방법 켜기

1.
아이폰에서 설정 앱을 엽니다.
2.
개인정보 보호 및 보안 메뉴로 들어갑니다.
3.
화면을 가장 아래로 스크롤하여 개발자 모드를 선택합니다.
4.
개발자 모드 토글 스위치를 켭니다.
5.
기기를 재시동해야 한다는 경고창이 나타나면, '재시동'을 탭합니다.
6.
기기가 재시동된 후, 개발자 모드를 켜겠냐고 다시 묻는 팝업창이 나타납니다. 여기서 **'켜기'**를 선택하고 기기 암호를 입력하면 모든 과정이 완료됩니다.
eas build --profile internal —platform ios
지금현재 ios 내부빌드, devclient포함
eas build —profile development —platform android
안드로이드 개발 빌드, devclient포함
앱 아이콘 및 스크린샷 가이드
1.
앱 아이콘
EAS Build가 자동으로 처리: 1024x1024 아이콘을 제공하면 EAS가 모든 사이즈 생성
현재 140x140은 너무 작음 → 1024x1024로 교체 필요
App Store Connect에서 별도 업로드하지 않아도 됨
1.
스크린샷 시뮬레이터 가이드
6.5인치 (iPhone 14 Pro Max, 15 Plus 등) iPhone 15 Pro Max 시뮬레이터 사용 해상도: 1290 x 2796
6.9인치 (iPhone 15 Pro Max 등) iPhone 15 Pro Max 시뮬레이터 사용 해상도: 1320 x 2868
스크린샷 촬영 방법:
1.
Xcode → Window → Devices and Simulators
2.
해당 시뮬레이터 선택
3.
Device → Screenshot (Cmd+S)
4.
또는 시뮬레이터에서 Cmd+S
5.
Support URL
간단한 GitHub Pages나 Notion 페이지로 충분: 또는 간단한 HTML 페이지 (연락처, FAQ 포함)
꿀팁: 보통 가장 큰 사이즈인 6.7인치용 스크린샷을 먼저 준비하면, 앱 스토어 커넥트에서 이 스크린샷을 6.5인치나 5.5인치 등 다른 작은 디스플레이용으로도 제출할 수 있도록 허용해 줍니다. 따라서 가장 큰 사이즈(iPhone 15 Pro Max)를 기준으로 먼저 작업하시면 시간을 크게 절약할 수 있습니다.
Unable to Add for Review
The items below are required to start the review process:
You must upload a screenshot for 13-inch iPad displays.
You must enter a Privacy Policy URL in App Privacy.
You must select a primary category for your app.
You must set up Content Rights Information in App Information.
You must select the level of frequency for each Apple content description in the Age Rating section.
기존 저장 방식의 문제점:
1.
임시 저장소 사용: 컨테이너 내부 파일시스템에 저장
2.
데이터 손실: 컨테이너 재시작/재배포 시 모든 데이터 삭제
3.
확장성 부족: 컨테이너가 다시 생성될 때마다 초기화
Volume 저장의 장점:
1.
영구 데이터 보존: 컨테이너 재시작/업데이트 시에도 데이터 유지
2.
안정성: Railway가 볼륨을 별도로 관리하여 안전함
3.
백업/복구: 볼륨 단위로 백업 가능

앱스토어 반려

이 앱스토어 리뷰는 앱이 불필요하게 회원가입을 강요하고 있다고 지적하는 거예요.
문제점:
계정이 필요하지 않은 기능들도 회원가입/로그인을 해야만 사용할 수 있게 되어 있음
앱의 핵심 기능과 직접적으로 관련없는데도 개인정보 입력을 요구하고 있음
해결방법: 계정 기반이 아닌 기능들은 회원가입 없이도 자유롭게 접근할 수 있도록 앱을 수정해야 합니다.
예를 들어:
콘텐츠 읽기, 기본 검색 등 → 회원가입 없이 사용 가능하게
개인 설정 저장, 구매 내역, 개인화된 추천 등 → 회원가입 필요한 기능으로 유지
즉, 꼭 필요한 경우가 아니면 회원가입을 강제하지 말고, 사용자가 앱을 먼저 체험해볼 수 있도록 진입장벽을 낮추라는 의미입니다.
Retry
HK
그럼 지금현재 supabase로 단어 저장 불러오기 등을 하고있는데 전부 없애고 디바이스에 저장하는걸로 바꿔야 해?
Edit
아니요! 전부 없앨 필요는 없어요. 선택적으로 접근하면 됩니다.
추천하는 방식:
1단계: 게스트 모드 추가
회원가입 없이도 앱 사용 가능
단어들을 로컬 스토리지(디바이스)에 저장
기본 기능들 모두 사용 가능
2단계: 회원가입 시 혜택 제공
"계정을 만들면 데이터를 클라우드에 백업하고 다른 기기에서도 사용할 수 있어요"
로컬 데이터를 Supabase로 동기화 옵션 제공
구현 방법:
앱 시작 → [게스트로 시작] [로그인] ↓ 로컬 스토리지 사용 ↓ 나중에 "백업하시겠어요?" 팝업
장점:
앱스토어 가이드라인 준수
기존 Supabase 코드 재활용 가능
사용자 경험 개선 (진입장벽 제거)
회원가입 전환율도 더 높아질 수 있음
결국 로컬 + 클라우드 하이브리드 방식으로 가는 게 최선의 해결책이에요!
버전 번호빌드 넘버 모두 이전보다 높아야 함
리젝 응답할 때는 **"Resolution Center"**에서 수정사항 설명도 작성
보통 리뷰는 다시 1-3일 정도 소요
팁: 앱스토어 커넥트에서 이전 제출을 **"Developer Rejected"**로 철회하고 새 버전을 올리는 것도 가능해요. 이러면 리뷰 대기열에서 더 빨리 처리될 수 있습니다.
네, 그 문제는 매우 흔한 상황입니다. 결론부터 말하면 git commit 및 push를 하지 않아서 발생했을 가능성이 99%입니다.
EAS Build는 로컬 컴퓨터가 아닌 EAS 서버에서 빌드를 진행합니다. EAS 서버는 git 저장소에 있는 최신 코드를 가져와서 빌드하기 때문에, buildNumber를 수정했더라도 해당 변경사항을 git commit하고 git push하지 않으면 EAS 서버는 변경된 내용을 알 수 없습니다.

## 원인

EAS의 빌드 프로세스는 다음과 같습니다.
1.
사용자가 eas build 명령어를 실행합니다.
2.
EAS CLI가 현재 프로젝트의 git commit 내역을 확인합니다.
3.
git 저장소에 push된 최신 코드를 EAS의 빌드 서버로 보냅니다.
4.
EAS 빌드 서버는 그 코드를 기반으로 앱을 빌드합니다.
따라서 app.json이나 app.config.js 파일에서 buildNumber를 수정하고 저장만 한 상태는 내 로컬 컴퓨터에만 적용된 것이지, 빌드 서버는 예전 buildNumber를 그대로 사용하게 됩니다.
캐시 지우고 새로 빌드 이제 가장 중요한 단계입니다. -clear-cache 옵션을 붙여서 빌드를 새로 시작하세요.Bash
eas build --platform ios --profile production --clear-cache
새 빌드 제출 위 빌드가 성공적으로 완료되면, 가장 최신 빌드를 제출합니다.Bash
eas submit --platform ios --latest
I had a similar experience updating the app.json file in my-expo-project. I kept the version string the same 0.9.9 and only changed the build number string from 124 to 125.
Running eas build --profile production --platform iOS resulted in the same console error.
You've already submitted this version of the app.
The build summary on expo.dev showed the old build number was delivered.
Older build number delivered to expo.dev1
Solution
Turns out I had to use the expo prebuild command to resolve this.
➜ my-expo-project git:(v125) $ expo prebuild ✔ Created native projects | /android, /ios already created | gitignore already synced ✔ Updated package.json and added index.js entry point for iOS and Android 📦 Using npm to install packages. ✔ Cleaned JavaScript dependencies 3698ms ✔ Installed JavaScript dependencies 11150ms ✔ Config synced
Plain Text
복사
Now that the app config is synced to the ./iOS and ./Android folders eas build --profile production --platform iOS now delivers the correct bundle and build number to expo.dev

7/31

Review of your submission has been completed. It is now eligible for distribution.
Submission ID: 0be8c5e5-e2a4-4af7-bd10-de36c1c4fc80
App Name: vocasimple

드디어 앱스토어 배포 성공!!