2018/01/02 - [Programming/Unity] - [유니티/C#/리듬게임] 2. BPM이란?


이전 포스팅에서는 BMS파일 구조와 BPM에 관하여 살펴보았으니 이번 포스팅부턴 실제로 개발해봅시다.

 코드에 아직 미숙한 부분이 많아도 차차 개선할 예정이니 알고리즘 참고로는 괜찮지 않을까 싶습니다.


시작해봅시다!


일단 먼저 UI 틀부터 잡았습니다. 물론 구조부터 짠 다음, 진행하는게 수월합니다만. 

이것저것 신경쓸 부분이 많더군요. 

저는 기능 모듈별로 코딩 한다음, 모듈끼리 연동 하면서 디버깅 하는 방식으로 진행해 볼까 합니다. 사실 시간이 부족한 것도 있는데, 귀찮은게 더 큰건 함정.



곡 선택 Scene의 하이어라키 뷰 구조입니다.  마스터 패널 내부에서 오브젝트를 한꺼번에 관리합니다. 개발 도중에 포스팅 하는 상황이라 UI는 변경될 수 있습니다.





마스터 패널 내부에서 캔버스로 UI를 그려주는데,

곡 정보 뷰인 Info와 곡 리스트 뷰인 Song List로 나누었습니다.


Info에서는 말 그대로 곡 정보와 썸네일 이미지를 BMS파일의 헤더를 파싱하여 

오른쪽 상단에 Title, Artist, Genre, BPM, Playlevel, Rank 등 곡 정보와 

왼쪽 상단에 해당 곡 이미지를 파싱하여 UI에 뿌려줍니다.


Song List에서는 하단 패널에서 좌, 우 스크롤링하여 List로 구현할 예정입니다.

타깃은 모바일, PC 둘다 타깃으로 잡아 버튼 형식으로 List를 스크롤할 예정입니다.


일단은 현재까진 곡 정보를 불러오는 파싱작업부터 완료하였기 때문에 하단 스크롤 List는 나중에 포스팅 하겠습니다.


일단 헤더 파일 부분만 파싱하는 이유는 곡을 선택할때마다 데이터 파일까지 파싱 할 경우, 프레임 드랍이 되는 경우를 막기 위해 헤더 파일 부분만 파싱합니다.

헤더 파일에서도, 현재 Scene에서는 선택한 곡의 정보만 필요하기 때문에 필요한 부분만 파싱합시다.




위 사진에서 빨간색 박스 내부의 내용만 필요하기 때문에 아래 WAV 파일을 지정하는 부분은 지금 파싱하지 않습니다.


아래는 코드입니다. 아직 수정할 부분이 많아서 좀 더럽긴 하지만 참고하는 용도 정도는 되지 않을까 싶습니다. 코드 보시고 궁금한점이 있으시면 댓글달아 주세요.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
using UnityEngine.UI;
 
public class SongInfoLoading : MonoBehaviour {
 
    public Transform ParentObj; // 파싱한 데이터를 저장하기 위한 부모 오브젝트
    public Transform[] tpList;  // 자식 오브젝트를 저장하기 위한 배열
    public string[] songList;   // 곡 이름을 저장하는 문자열 배열
    public int index;   // 몇번째 곡이 선택되었는지 표시하는 인덱스 변수
 
 
    //=================================== Data Parse
    public void ParseData(int index)    // 파싱한 곡의 정보를 입력하는 메소드, 정수형 파라미터로 오브젝트를 구분함.
    {
        string temp = "";   // 임시코드, Title, Artist 등 헤더 파일 값을 저장.
        tempStr = "";
        for (int i = 1; i < tempSplit.Length; i++)  // #TITLE ~ [#TITLE] 을 자른 나머지 데이터를 취합한다. (곡 제목, 아티스트 등) 데이터 저장 
        {
            tempStr += tempSplit[i] + " ";  // 공백 혹은 콜론 구분자로 문자열을 잘랐으나, 콜론의 경우 데이터 파싱 부분에서만 사용하기 때문에 공백을 추가하여 누적한다.
        }
        temp = tempSplit[0].Substring(1, tempSplit[0].Length-1);    // 18번 라인에서 언급한 [Title]의 문자열 저장.
        //Debug.Log(temp);
        tpList[index].GetComponent<Text>().text = temp + " : " + tempStr;   // 파싱한 데이터를 각 자식 오브젝트의 Text에 뿌려줌.
    }
    //=================================== Data Parse
 
    //=================================== Image Parse
    public void ParseBackImg(int index) // 파싱한 곡의 이미지를 뿌려주는 메소드, 이 또한 정수형 파라미터로 오브젝트 구분
    {
        SpriteRenderer spriteRenderer;  // 이미지 오브젝트의 spriteRenderer component를 사용하여 이미지를 변경한다.
        spriteRenderer = tpList[index].GetComponent<SpriteRenderer>();  // 오브젝트의 spriteRenderer component를 가져옴.
 
        Sprite sp = Resources.Load<Sprite>("Song/"+SongName+"/BG"); //이미지 리소스파일 이름은 BG로 통일 할 예정이며, 47번 라인에서 선언한 음악 파일 이름으로 이미지를 구분.
 
        spriteRenderer.sprite = sp; // sprite 변경
    }
    //=================================== Image Parse
 
 
    protected FileInfo fileNmae = null;     // BMS 파일을 열기위한 파일 제어 클래스
    protected StreamReader reader = null;   // 파일 스트림을 읽기 위한 StreamReader
    protected string path;       // 곡 파일 path
    protected string StrText;   // 파일을 한줄씩 읽었을때 저장하는 문자열
    protected string SongName;  // 이미지 파일을 파싱할 때 상대경로로 지정하는 임시 변수, 추후 수정 할 수 있음.
 
 
    private char[] seps;                    // 구분자, [공백과 콜론]으로 구분.
    private string[] tempSplit;      // 위 구분자로 파싱한 문자열을 잘라 담는 임시 문자열 배열
    private string tempStr;                 // 자른 문자열 데이터에서 자식 오브젝트의 Text에 저장할 임시 문자열, 추후 수정할 수 있음.
 
    // Use this for initialization
 
    void Start ()        // 스크립트 호출시 시작.
    {
        //========== initializing
        index = 0;
        tempSplit = null;
        tempStr = "";   
        StrText = "";
        SongName = "";
        path = "Assets/Resources/Song/";
        seps = new char[] { ' '':' };
        //========== initializing
 
        SongName = songList[index]; // 곡 이름은 List에서 담은 값으로 저장, 추후 변경 가능
        path = path + songList[index] + "/";    // 42번 라인에서 저장한 root path에서 해당하는 곡의 경로를 저장
        fileNmae = new FileInfo(path + songList[index]+".bms"); // 해당 곡의 BMS 파일을 파싱하기 위한 코드, BMS파일을 지정
 
        if (fileNmae != null)   //파일이 NULL이 아닐 경우 실행
        {
            reader = fileNmae.OpenText();   // BMS 파일을 Open한다.
        }
 
        else // 파일이 NULL일 경우, 디버그로 에러 검출
        {
            Debug.Log("65Line, BMS Parse Error");
        }
        
        tpList = ParentObj.gameObject.GetComponentsInChildren<Transform>(); // ParentObj의 Transform components가 있는 자식들을 가져옴 / 자식들 전체를 가져옴.
 
    }
    
    // Update is called once per frame
    void Update ()      // Update문은 추후 코루틴(Coroutines)을 사용하여 변경 예정
    {    
        if(StrText != null && StrText != "*---------------------- HEADER FIELD END")    // BMS파일에서 읽어온 문자열이 null이 아니면서 [*---------------------- HEADER FIELD END] 이 아닐때.
            // 원하는 헤더파일 부분만 읽기위해 작성.
        {
 
            StrText = reader.ReadLine();    // BMS파일을 한줄씩 읽음.
            tempSplit = StrText.Split(seps);    // 구분자(공백, 콜론)으로 읽어온 문자열을 자름
            
            if (tempSplit[0].Equals("#TITLE"))  // 자른 문자열이 #Title일때
            {
                ParseData(2);
            }else if (tempSplit[0].Equals("#ARTIST"))
            {
                ParseData(3);
            }else if (tempSplit[0].Equals("#GENRE"))
            {
                ParseData(4);
            }else if (tempSplit[0].Equals("#BPM"))
            {
                ParseData(5);
            }else if (tempSplit[0].Equals("#PLAYLEVEL"))
            {
                ParseData(6);
            }else if (tempSplit[0].Equals("#RANK"))
            {
                ParseData(7);
            }else if (tempSplit[0].Equals("#STAGEFILE")) // 자른 문자열이 이미지 파일 데이터일때
            {
                ParseBackImg(10);
            }
            else
            {
                Debug.Log("[NotErr]Nothing : " + StrText);    //에러X 확인용 디버그 코드
            }
        }
    }
}
 
cs



코드 부분을 살펴보면 무식하게 Update 함수에 코딩한 것을 볼 수 있습니다.

이 부분은 추후 코루틴(Coroutines)을 다루면서 수정하겠습니다.


다음 포스팅에서 UI와 함께 다시 살펴봅시다.



'Programming > [Unity]' 카테고리의 다른 글

[유니티/C#/리듬게임] 2. BPM이란?  (0) 2018.01.02
[유니티/C#/리듬게임] 1. BMS에 관하여.  (0) 2018.01.02
블로그 이미지

덕배님

5년차 S/W 개발자입니다. Android, Unity, JAVA, C, C++, C# 정보를 공유합니다

,


2018/01/02 - [Programming/Unity] - [UNITY/C#/리듬게임] 1. BMS에 관하여.


지난 포스팅에서는 BMS에 대해 알아보았으니 이번 포스팅은 개발 전 BPM에 관하여 써볼까 합니다.


출처 : http://smilejsu.tistory.com/380, http://blog.insane.pe.kr/1384


BPM은 Beat Per Minute의 약자로 분당 비트수로서 1분당 1/4박자의 갯수입니다.


BPM을 60으로 나누어 주면 BPS가 될 것인데, 4로 또 나누는 이유는 한 비트는 4개의 박자로 이루어져 있기 때문에 4로 나누어줍니다(4/4박자). 

한 박자의 길이(1/4beat)는 bpm/60/4이며, 이 값으로 1을 나누어 주면 박자 당 초(spb)가 나옵니다.


- 1분당 BPM별 비트 갯수

 BPM

1/4 박자

1/8박자 

1/16박자 

1/32박자 

 60 BPM

 60 개

 120 개

 240 개

 480 개

 100 BPM

 100 개

 200 개

 400 개

 800 개

 130 BPM

 130 개

 260 개

 520 개

 1040 개

 155 BPM

 155 개

 310 개

 620 개

 1240 개


우리가 만드는 게임에서 BPM과 박자에 따라 비트 갯수가 달라지는데.

1/32박자 (32비트) 게임에서 130 BPM시 1분당 최대 1040 노트가 생성됩니다.


이때 노트 한개당 간격은

60bpm에서 4비트는 1초마다 1비트씩 올라가므로 최대 60개가 생성되는데,

1/8박자는 0.5초, 16박자는 0.25초, 32박자는 0.125초 마다 1비트가 올라갑니다.


155 bpm의 32비트는 최대 생성 노트의 갯수는 1240개이므로 비트 하나당 간격을 계산해 봅시다. 소숫점은 총 8자리까지 사용하며, 이하 소숫점은 반올림하여 계산합니다.

155 / 60 = 2.58333333


0.125 / 2.58333333 = 0.04838710



노트는 0.04838710초마다 나오며 이 값이 맞는지 최대 비트 1240개와 계산을 해봅시다.


0.04838710 * 1240 = 60.000004


60에 근접하게 값이 나오는것을 알 수 있습니다.

더 낮은 자리에서 계산한다면 더욱 정확한 값이 나옵니다.

이 값이 정확하지 않으면 음악 재생 시간이 길면 길수록 비트가 조금씩 밀리고 당겨지는 현상이 나올수 있으므로 주의가 필요합니다.


이상 BPM에 관한 포스팅을 마칩니다.


상기 출처 글을 읽으며 도움이 많이되어 작성하였습니다.

하단에도 링크 남기오니 참고하시면 좋을것 같습니다.

BPM 계산 방식과 배속의 구현 방법


블로그 이미지

덕배님

5년차 S/W 개발자입니다. Android, Unity, JAVA, C, C++, C# 정보를 공유합니다

,

안녕하세요! 첫 포스팅이군요. Unity 카테고리의 첫 포스팅은 리듬게임을 만들어볼까 합니다.


일단 리듬게임을 만들기 위해서 BMS, BPM, 배속 등 알아야 할게 몇가지가 있습니다. 이 글을 쓰면서 공부하고 있는 입장이라. 틀린 내용이 있다면 지적해주시면 감사하겠습니다. 


시작해봅시다!


BMS란?


BMS란 Be-Music Source 의 약자로 BM98을 시작으로 한 프리웨어 리듬게임 혹은 리듬게임의 파일 형식입니다.

Be-Music용 음악을 제작하고 연주하기 위한 포맷으로 BMS의 경우에는 음악을 듣는 것 뿐만 아닌, 간단한 애니메이션을 표시함과 더불어 자신이 연주 및 게임까지 진행 가능한 음악 형식입니다.


BMS들의 확장자는 세가지 형태가 있습니다.


1. BMS - Be-Music Script 

- 플레이어 당 6키의 키 (건반 5개와 턴테이블, 보통 5키로 통칭)를 사용할 수 있습니다.


2. BME - Be-Music Extended

- BMS 확장자와 갔지만 플레이어 당 최대 8개의 키 (건반 7개와 턴테이블, 보통 7키로 통칭)을 지원한다. 7키를 지원하면서 확장자가 BMS인 것도 있기 때문에 5키, 7키의 구분은 6/7 번 건반이 등장하느냐로 판단하는 것이 일반적입니다.


3. BML - Be-Music Longnote

- BME와 같습니다만. 롱노트 오브젝트를 지원합니다. 옛날 프로그램들은 롱노트를 아예 인식하지 못할 수 있기 때문에 롱노트를 사용함을 알리기 위해 확장자를 바꿔서 사용하는 경우가 많습니다.





BMS의 구성요소


일반적으로 BMS파일은 단 하나만 있는것이 아닌 아래와 같은 요소로 구성되어 있습니다.


- 해당 곡의 메타 데이터(게임 플레이와 상관 없든 데이터): 제목, 제작자, 장르 등.

- 게임 플레이를 좌우하는 요소들 : BPM, 판정 난이도 등.

- 사용할 사운드/이미지 데이터 등의 위치와 파일 이름.

- 배경음 (BackGroundMusic, BGM; 오브젝트를 누르는 지 여부와 상관없이 항상 재생되는 소리)들이 재생될 순서.

- 배경 동영상(BGA)들이 등장할 순서.

- 오브젝트들의 배열 방법 (패턴, 채보 등으로도 불림.)


1. BMS파일(확장자 *.BMS)

BMS음악의 핵심이며 내용은 우리가 메모장으로 볼수 있는 텍스트 파일로 되어있습니다.
여기에는 곡제목, 제작자, 곡난이도 등의 기본정보.
게임상에서의 음악 출력과 그림 출력형태, 떨어지는 오브젝트 노트의 배치, 어떤 것을 누르면 어떤 소리가 난다는 등의 정보들이 배치되어 있습니다.
일반적으로 BMS파일은 별도의 제작기로 만들지만 그 포맷을 알고 있다면 텍스트 파일로 만드는 것도 가능합니다.
(이는 HTML문서를 텍스트상에서 만드느냐 웹에디터로 만드느냐의 이치와 비슷합니다.)
BMS이외에 BME라는 파일도 있는데 이것은 7건반 이상을 지원한다는 뜻이며 그 이외에는 BMS와 완전히 같은 것입니다.


2. WAV파일(확장자 *.WAV)
윈도우에서 쓰는 표준 사운드 파일이며 BMS에서 음을 출력하는 기본 요소이기도 합니다.
이 WAV화일들은 크게 백그라운드에 출력하는 배경음과 버튼을 누를 때 출력하는 건반음으로 나뉩니다.
건반음들도 건반 위치에 따라, 혹은 곡의 진행에 따라 계속 바뀌기 때문에 거기에 해당하는 WAV들이 따로따로 있어야 합니다.
BMS자료안에 WAV파일이 하나가 아닌 많이 있는 것도 그러한 이유입니다.
간혹 WAV 대신에 MP3를 사용하는 경우도 있지만 BMS에서는 표준이라고는 볼수 없습니다.

3. 미디화일(확장자 *.MID)
미디음악을 출력하기 위한 음악파일이며 BMS에서는 백그라운드에 출력하는 배경음악용으로 쓰입니다.
하지만 PC가 미디를 지원해야 하며 제대로 지원되며 사운드카드 혹은 미디음원의 종류에 따라서 소리가 다르게 나는 수도 있어서
최근의 BMS에서는 거의 쓰이지 않는 추세이기도 합니다.

4. BMP파일(확장자 *.BMP)
윈도우에서 쓰는 표준 그림 파일이며 BMS에서는 플레이중에 화면 애니메이션(BGA:Back Ground Animation)을 출력하기 위해서 사용합니다.
앞에서도 말씀드렸지만 BMS에서는 애니메이션을 동영상 등을 사용하지 않고 그림 한장한장을 교대 혹은 반복해서 보여주는 방식이므로.
그러한 애니메이션의 한컷한컷을 BMP한 장씩으로 해서 만들게 됩니다.
물론 그림이 아예 안나오는 BMS에서는 이러한 BMP파일들이 없습니다.
간혹 BMP대신에 JPG를 사용하는 경우도 있는데
이 경우에는 용량에서 이득을 보지만 게임에 따라서 특수한 플러그인을 사용해야 제대로 지원할수 있습니다.


모든 프로그램에서 똑같이 동작하는 BMS 파일을 만들려면, bmp/wav파일만을 사용해야 합니다. 대부분 프로그램들은 기본적으로 지원하지않는 jpg등 다른 포맷들을 Susie32 플러그인이나 Window Media 등 외부 라이브러리를 사용하여 처리하지만. 별도의 라이브러리 없이 자체적으로 이러한 파일 포맷들을 지원하는 경우도 있습니다.




기본구조


BMS 파일은 일반적인 텍스트 파일입니다. 개행 문자는 보통 윈도우즈의 개행 문자인 0D 0A(C 형식으로 표시하면 "\r\n")를 사용합니다.


BMS 파일에서 실제로 인식되는 줄은 #으로 시작하는 줄 뿐입니다. 명령 이름이나 파일 이름등은 대소문자를 구별하지 않습니다.

#으로 시작되지 않는 줄은 모두 무시되며, 주석으로 사용합니다.

#으로 시작하는 줄들은 그 줄에 들어 있는 데이터의 종류에 따라서 헤더 섹션과 데이터 섹션으로 나뉩니다.

보통 헤더 섹션이 데이터 섹션 앞에 나오는 경우가 대부분이지만 항상 그런것은 아니며, 두 섹션이 섞여 나오거나 위치가 바뀌어도 같은 뜻을 가져야 합니다.


아직 공부가 많이 필요한 부분이니 부족한 점이 많습니다.

글은 꾸준히 업데이트 할 예정이며, 작업 진행 사항 관련하여 포스팅 할 예정입니다.


이 외에 BMS, 리듬게임 관련 정보는 관련글 링크 참고하시면 될 것 같습니다.



BMS란 무엇인가? 

Guide to understand BMS format

BMS 스크립트 분석 - 기본 속성 안내

BMS 플레이어 만들기 1 - BMS 파일 구조 분석

블로그 이미지

덕배님

5년차 S/W 개발자입니다. Android, Unity, JAVA, C, C++, C# 정보를 공유합니다

,