스터디 소개
- 그래픽스 엔진 등 공학적인 주제에 초점을 맞춤
- RPG만들기 에뮬레이터 스마트폰 버전
- 참고 위키: RPG만들기 한글, 영문
- Java로 만든 3D 물리 엔진으로 2D 엔진 만들기
- 툴이 아닌 에뮬레이터 위주
- UDK로 뭔가 삽질 하다가 멘붕하고 선회한 스터디 -ㅅ-
참가자
진행 시간
- 여름방학: 매 주 화, 목 저녁 7시 반 ~ 10시
목표
- RPGMaker VX Version을 Java로 -> Android용으로 -> Windows용으로... (겨울 방학 끝날 때 쯤엔 완성 되겠지...)
얻고 싶은 것
- 3D 그래픽스 지식 및 기본 그래픽스 개념
- 리버스 엔지니어링 노하우
- 게임 로직 및 흐름의 이해
- 설계 패턴
여름방학
2012.07.26
한 것
- jpct 설치 및 RPG 만들기가 어떻게 생겨 먹은 놈인지 확인
- jpct 튜토리얼 보고 따라 해 보기 - Hello World!
프로젝트 설정
- 이클립스 프로젝트 생성은 생략. 게다가 이미 svn repository에 등록해놓았음
- PC용 jPCT 엔진을 받는다
- 받은 엔진 안에는 jpct에 해당하는 jpct.jar과 또다른 그래픽 라이브러리인 lwjgl이 있다. lwjgl.jar까지 라이브러리로 등록한다
- lwjgl.jar은 내부적으로 native 함수를 호출한다. 이 native함수는 dll파일에 담겨져 있는데 이클립스에서 Window -> preferences -> java/installed JREs 에서 vm 설정에 -Djava.library.path=D:\Workspace\rma\libs 형식으로 lwjgl.dll 또는 lwjgl64.dll의 폴더 경로를 입력함으로써 참조하게 한다. 이와 관련된 오류는 java.lang.UnsatisfiedLinkError이다.
참고
2012.07.31
한 것
- 3D에서 정점, UV좌표, 삼각형의 표현에 대해 설명하고 실제로 객체를 제작해봄
- 파일 분석 정보를 기반으로 parser 제작 시작함. *.rvdata는 들어가는 정보의 종류는 다르지만 일정한 포맷이 있는듯함
예상보다 파일분석은 빨리 끝날지도.
2012.08.02
한 것
- 과제 검사(FAIL) - 코딩에 앞서 엄밀한 수학 모델링을 먼저 해보는 것이 필요할듯
- Actors 파일 해석법을 소개하고 다른 파일을 맛보기로 분석해봄
Simple rendering
직교투영 렌더링
- jPCT에서는 명시적으로 Orthographic rendering(직교투영 렌더링)을 지원하지 않는다
[1]
따라서 근사적으로라로 직교투영을 만든다
한줄요약: 카메라를 겁나 멀리 갖다두면 근사적으로 직교투영이 됨ㅋ
보정(Interpolation)
- 이를 이용하면 직교투영 좌표가 된 것처럼 보이긴 하지만 depth에 해당하는 z값이 바뀌면 조금이라도 오차가 생기기 때문에 이를 보정하여야 한다
특히 렌더링 순서를 z값을 조정함으로 결정시키기 때문에 정확한 렌더링을 위해서는 보정 테크닉을 아는 것이 필수이다.
- 위 그림에서 볼 수 있듯이 실제 Object의 중점과 사람이 느끼는 Object의 위치는 다르다. 크기 역시 마찬가지이다.
만약 위 그림처럼 Object가 스크린에서 delta만큼 떨어져 있다면 간단한 비례식으로 (length-delta)/length 만큼 크기가 조정되어야 한다
중심의 이동은 Object의 중심을 O, Projection의 중심을 P라고 할 때 벡터 PO만큼을 이동시키면 된다. 벡터 PO는 카메라 위치와 Object 중심을 잇는 직선의 연장선상에 있다는 것을 생각하면 계산이 간편해진다.
오브젝트 그리기
삼각형
- 모든 도형의 기초가 되는 도형이다. 모든 도형은 삼각형만으로 구성할 수 있고 또 쪼갤 수 있다.
- jpct에서 텍스쳐 있는 삼각형을 그리기 위해 필요한 정보는 다음과 같다
- 정점(vertex, 꼭지점) : 삼각형은 서로 다른 3개의 한 직선 위에 있지 않은 정점들로 구성된다.
- uv좌표 : 텍스쳐가 매핑되는 좌표계를 설정한다. (0, 0)부터 (1, 1)까지의 좌표에 텍스쳐 하나가 들어가게 된다
텍스쳐에 대해서는 나중에 할 말이 있을것.
- 정점들을 잇는 순서 : 이 순서에 따라 삼각형의 보이는 면과 보이지 않는 면이 결정된다.
다른 곳은 잘 모르겠으나 openGL에서는 오른나사의 법칙에 따라 엄지가 가리키는 방향의 면이 보이는 면이다.
CCW 판별 알고리즘을 이용해서 순서를 하드코딩하지 않고 자동으로 계산하게 할 수도 있다.
- Texture : 도형을 색칠하기 위한 정보가 들어있는 2차원 정사각형이라고 생각해도 좋다. 한 변의 길이는 속도를 위해 2의 제곱수여야 한다.
직사각형(FillRect) 그리기
- 왼쪽 위, 오른쪽 아래 좌표만 알면 그릴 수 있다.
- 사각형은 2개의 삼각형으로 구성된다.
- 실제 구현
직선(Line) 그리기
- 직선도 실제로는 너비를 가져야 렌더링이 가능하기 때문에 다음과 같은 구조를 가져야 한다
- vLine = vEnd - vStart
- normal(vLine과 수직인 벡터) = vLine × (-z방향 벡터) -> normalize하여 길이가 1인 벡터로 만듦.
- (u1, v1)과 (u1, v2)는 vStart ± (normal/2)
- (u2, v1)과 (u2, v2)는 vEnd ± (normal/2)
- 실제 구현
RPG Maker VX 분석
- .rvdata 파일 포맷은 차근차근 뜯어보니 예상외로 간단한 구조인 것 같다.
- 맨 처음 파일 시그니쳐에 해당하는 부분은 (04 08)이다.
- element간의 구분자는 0x3a 또는 0x3b이다.
0x3A로 나뉜 element는 element 이름이 있고 0x3B로 나뉜 element는 따로 element 이름이 없다(길이가 0)
| 구분
|
1 byte
|
n byte
|
1 byte
|
n byte
|
| 0x3A
|
element 이름 길이
|
element 이름
|
element 타입(optional)
|
element 값
|
|| 0x3B || element 출현 순서 || 생략 || element 타입(optional) || element 값 || ※타입이 클래스 시작, boolean, parameter인 경우는 생략된다.
ex> type이 int이고 값이 13인 @level 변수는 (0x10 @level i 0x12 0x3A) 으로 저장된다.
| 시그니쳐
|
구조
|
| RPG:Action RPG::Action등 class선언
|
다음 1바이트는 읽어들일 element 개수(테이블 제외)
|
| 'i'(int)
|
첫 1바이트는 숫자값+5, 255를 초과하는 수의 경우는 첫 1바이트가 정수의 크기이고 그다음 크기만큼의 정수를 읽어들이는 것으로 보임
|
| '"'(string)
|
첫 1바이트는 문자열의 길이(int형), 그 다음은 문자열 시작
|
| 'T' 또는 'F'(boolean)
|
boolean 값
|
| 'u'
|
다음에 테이블이 있음을 의미???
|
RPG::Actor(Actors.rvdata)
- (6F 3B)가 각 캐릭터 간 구분자의 역할을 하는 듯 보임
- 정보 테이블의 입력 순서는 매번 바뀌며 첫번째 캐릭터에서 제공함(구분자 0x3A)
두번째 캐릭터부터는 첫번째 캐릭터의 정보테이블 순서에 입각하여 입력하는 것으로 보임(구분자 0x3B)
- 첫번째 캐릭터의 데이터는 ID - @NAME - !TYPE - VALUE 식으로 저장되어 있음
TYPE이 0x69(i)이면 정수(항상 +5되어 저장됨), (22 0B)이면 문자열이다
그다음부터의 데이터는 INDEX - !TYPE - VALUE 식으로 저장되어 있다
| ID
|
TYPE
|
NAME
|
설명
|
| 0x08
|
int
|
@id
|
INDEX
|
| 0x0a
|
str
|
@name
|
이름
|
| 0x0a
|
Table
|
Table
|
Parameter 테이블(능력치 성장 곡선, 아래 Actor parameter table에서 설명)
|
| 0x0e
|
int
|
@class_id
|
직업 ID
|
| 0x0f
|
int
|
@weapon_id
|
무기 ID
|
| 0x0f
|
int
|
@armor1_id
|
방패 ID
|
| 0x0f
|
int
|
@armor2_id
|
투구 ID
|
| 0x0f
|
int
|
@armor3_id
|
갑옷 ID
|
| 0x0f
|
int
|
@armor4_id
|
장신구 ID
|
| 0x0f
|
int
|
@exp_basis
|
경험치(기본)
|
| 0x0f
|
str
|
@face_name
|
얼굴 그래픽 그룹 이름(확장자 제외 파일명)
|
| 0x0f
|
시그니쳐
|
RPG::Actor
|
다음 바이트가 0x1B, 새로운 캐릭터의 시작을 알림
|
| 0x10
|
table
|
@parameters
|
조사중
|
| 0x10
|
int
|
@face_index
|
얼굴 그래픽 ID
|
| 0x11
|
bool
|
@super_guard
|
강한 방어
|
| 0x11
|
bool
|
@auto_battle
|
자동 행동
|
| 0x12
|
bool
|
@pharmacology
|
회복약 효과 2배
|
| 0x13
|
int
|
@initial_level
|
초기 레벨
|
| 0x13
|
int
|
@exp_inflation
|
경험치 증가도
|
| 0x13
|
bool
|
@fix_equipment
|
장비 고정
|
| 0x14
|
str
|
@character_name
|
보행 그래픽 그룹 이름(확장자 제외 파일명)
|
| 0x14
|
bool
|
@critical_bonus
|
잦은 크리티컬
|
| 0x15
|
int
|
@character_index
|
보행 그래픽 ID
|
| 0x16
|
bool
|
@two_swords_style
|
이도류
|
- Actor Parameter table
- 첫 1바이트는 조사중. 그다음 2byte는 테이블의 크기가 byte단위로 저장되어 있다. 이 다음부터 offset이 시작된다고 가정하자.
- offset 0x00의 4byte는 테이블에서 사용하는 element의 크기(단위)
- offset 0x04의 4byte는 1차배열의 element수
- offset 0x08의 4byte는 2차배열의 element수
- offset 0x0C의 4byte는 3차배열의 element수
- offset 0x10의 4byte는 총 element 수로 추정(offset4 * offset8 * offset12)
- offset 0x14부터 테이블 시작.
- Actor에서 한 묶음은 short int형(2byte)의 배열인데, 결국 short int[1]![100][6]의 배열이 된다. (레벨0 ~ 99)
각 배열은 각 레벨별로 최대HP - 최대MP - 공격력 - 방어력 - 정신력 - 민첩성의 값을 가진다.
- 각 값의 최대치는 체마의 경우 9999, 그외 능력치는 999이다.
RPG::Map(Map001.rvdata)
| ID
|
TYPE
|
NAME
|
설명
|
| 0x0b
|
int
|
@width
|
맵 너비 (최대X좌표)
|
| 0x0c
|
int
|
@events
|
이벤트 개수
|
| 0x0c
|
int
|
@height
|
맵 높이 (최대Y좌표)
|
| 0x11
|
int
|
@scroll_type
|
스크롤 타입(0: 루프하지않음 1: 세로만 루프 2: 가로만 루프 3: 모두 루프)
|
| 0x11
|
int
|
@parallax_sy
|
세로 자동 스크롤
|
| 0x12
|
bool
|
@autoplay_bgm
|
BGM 자동변경
|
| 0x12
|
bool
|
@autoplay_bgs
|
BGS 자동변경
|
| 0x14
|
array(int)
|
@encounter_list
|
엔카운트(맵을 돌아다니다 만나는 몹 리스트)
|
| 0x15
|
bool
|
@parallax_loop_x
|
가로로 루프
|
| 0x15
|
bool
|
@parallax_loop_y
|
세로로 루프
|
| 0x15
|
bool
|
@disable_dashing
|
대쉬 금지
|
RPG::Event
2012년활동지도