NotePad++ Plugin Manager 설치법

 

notepad++ 프로그램을 처음 설치하고 나면 아래 사진과 같이 상단 메뉴에 

플러그인 -> Plugin Manager 메뉴가 없습니다.





그래서 Plugin Manager를 사용하려면, 수동으로 설치해줘야 한다.


아래 링크로 가서

https://github.com/bruderstein/nppPluginManager/releases


32비트 일 경우

PluginManager_v1.4.9_UNI.zip 


64비트 일 경우

PluginManager_v1.4.9_x64.zip


해당 파일을 다운로드 한 후,


Notepad++ 설치 경로 [경로 변경하지 않았을 경우, C:\Program Files (x86)\]에 


plugins 폴더와 updater 폴더를 복사해주고 notepad++ 프로그램을 재실행하면 


아래 사진과 같이 Plugin Manager 메뉴가 나옵니다.








출처: http://sooin01.tistory.com/entry/Notepad-Plugin-Manager-설치 [수앙]

블로그 이미지

덕배님

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

,


[MFC/기초] entry point

 

 

글 순서

1.  MFC AppWizard에 대해서
2.  실습 환경

3.  가장 간단한 MFC 프로그램

4.  Line By Line Walkthrough

5.  WinMain()은 어디에 있을까 ?

 

1.  MFC AppWizard 에 대해서

 

대부분 MFC를 처음 배울 때 MFC AppWizard를 사용해서 생성된 코드를 가지고 시작합니다. 또한 처음부터 Document/View 구조를 가지고 시작하곤 하지요. 하지만 이런 방식은 몇 가지 문제가 있습니다.

 

(A)   Wizard가 생성해준 많은 양은 코드는 초보자에게 MFC를 더욱 어렵게 느끼게 합니다.

(B)   특정 형태의 프로그램에서는 Document/View 구조가 적합할 수 있지만, 반대로 Document/View 구조가 필요 없는 경우도 많이 있습니다. MFC를 사용함에 있어 Document/View 구조는 필수가 아닌 선택입니다. 하지만 대부분의 서적이 Document/View 구조로만 설명하고 있습니다.

 

그래서 본 Tutorial 에서는 Wizard를 사용하지 않고 MFC의 기본 요소들을 직접 코딩 해서 구현해 보도록 하겠습니다.

물론 어느정고 MFC의 감을 잡았으면 Wizard를 사용해서 학습하는 것이 좋습니다.

Wizard를 사용하지 않기 때문에 코드의 양도 많고 불편하겠지만 오히려 MFC의 구조를 이해하고 활용 하는데 에는 많은 도움이 될 것입니다.

 

 

2. 실습 환경

 

1장에 나오는 예제를 실습해 보려면 다음 단계에 따라 프로젝트를 만들어야 합니다.

 

VC2005/2008의 경우

  • 1. 프로젝트 타입은 Win32 Application 을 선택한다.

  • 2. 응용프로그램 설정에서 “빈 프로젝트”항목을 체크한다.

  • 3. “프로젝트”메뉴에서 제일 밑에 있는 ”속성”을 선택한다.

    • A. 왼쪽 트리의 항목중 구성속성에서 일반을 선택한 후 오른쪽 설정에서 아래 2가지를 변경한다.

      • i. MFC 사용 항목을 “공유 DLL에서 MFC 사용”으로 변경한다.

      • ii. 문자 집합 항목을 “멀티바이트 문자집합사용”으로 변경한다.

  • 4. 이제 .cpp 소스를 추가한 후 교재에 있는 소스를 가지고 실습하면 된다

 

Visual studio 6.0 의 경우

  • 1.     프로젝트 타입은 Win32 Application 을 선택한다.

  • 2.     응용프로그램 설정에서 “빈 프로젝트” 항목을 체크한다.

  • 3.     “프로젝트” 메뉴에서 “속성”을 선택한다.

    • A.      “General Tab” 에서 가운데 콤보 박스에서 “Using MFC DLL Shared”를 선택한다.

  • 4.     이제 .cpp 소스 파일을 추가한 후 교재에 있는 소스를 가지고 실습하면 된다.

 

3. 가장 간단한 MFC 프로그램

 

MFC를 사용해서 만들수 있는 가장 간단한 프로그램으로 시작해 봅시다.

항목 1-01의 내용을 참고 해서 프로젝트를 만들고 아래 코드를 입력한 후 실행해 보도록 하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // HelloMFC.cpp
#include<afxwin.h>
 
class CHelloApp : public CWinApp
{
public:
  virtual BOOL InitInstance();
};
BOOL CHelloApp::InitInstance()
{
  CWinApp::InitInstance();
 
  AfxMessageBox("Hello, MFC");
  return TRUE;
}
CHelloApp theApp;
cs

 

 

 

4. Line by Line Walkthrough

 

이제 한 줄 한 줄씩 살펴 보도록 하겠습니다..

 

1
#include <afxwin.h>
cs



 

MFC를 사용해서 윈도우 프로그램을 작성할 경우 반드시 <afxwin.h> 헤더를 포함해야 합니다.

 

 

 


1
2
3
4
5
class CHelloApp : public CWinApp
{
public:
  virtual BOOL InitInstance();
};
cs
 

MFC는 CWinApp 라는 클래스를 제공하는데, MFC를 사용하는 모든 윈도우 프로그램은 CWinApp 클래스의 파생클래스를 반드시 1개 만들어야 합니다.

 

 

1
2
3
4
5
6
7
BOOL CHelloApp::InitInstance()
{
  CWinApp::InitInstance();
 
  AfxMessageBox("Hello, MFC");
  return TRUE;
}
cs

 

CWinApp 클래스는 InitInstance() 라는 가상함수를 제공하는데 사용자는 반드시 이 기상함수를 재정의해서 응용프로그램의 초기화 코드를 만듭니다. 초기화에 성공한 경우는 TRUE를 초기화에 실패한 경우는 FALSE 를 리턴 합니다. 또한, InitInstance() 함수를 재정의 할 때는 CWinApp::InitInstance()를 먼저 한번 호출해야 하는데 CWinApp::InitInstance() 함수 안에서는 언어 관련 리소스를 초기화 하고 있습니다.

 

 

 


1
CHelloApp theApp;
cs
 

마지막으로 사용자가 만든 파생클래스에 대해서 객체를 전역적으로 한 개만 생성 해야 합니다.

 

 

 

결국은 MFC로 윈도우 프로그램을 만들기 위해서는 다음의 4단계의 과정을 따라야 합니다. 

1.  MFC의 기본헤더인 <afxwin.h> 를 포함한다.

2.  CWinApp 의 파생클래스를 만든다.

3.  CWinApp 클래스의 InitInstance() 가상함수를 재정의 한다.

4.  사용자가 만든 클래스의 객체를 전역적으로 1개 생성한다.

 

 

5. WinMain()은 어디에 있을까 ?

 

C/C++ 로 만든 프로그램은 main( ) 함수부터 실행 됩니다. 특히 윈도우 프로그램은 main( ) 함수 대신 WinMain() 함수를 만들어야 합니다. 그럼 위 예제에서 WinMain () 함수는 도대체 어디 있을까요 ?

 

MFC는 내부적으로 WinMain() 함수를 제공 합니다. MFC가 제공하는 WinMain() 함수의 대략적인 내용이 아래에 있습니다. 지금 아래 코드의 전체를 이해 할 필요는 없습니다. 단지, WinMain()함수는 MFC가 제공한다는 사실과 전체적인 흐름만 파악하고 있으면 됩니다. 시간이 지나면 저절로 모든 내용을 이해 하게 될 것입니다.(단, 열심히 해야 겠지요)


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
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                LPTSTR lpCmdLine, int nCmdShow)
{
  // AfxWinMain을 호출합니다. 결국AfxWinMain()에서모든일을수행합니다.
  return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); 
}
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
            _In_ LPTSTR lpCmdLine, int nCmdShow)
{
  //......
  // (A) 사용자가 전역적 으로 만든 객체의 주소를 얻어 옵니다. 
  CWinThread* pThread = AfxGetThread();
  CWinApp* pApp = AfxGetApp();
  // (B) MFC 라이브러리의 초기화 과정을 수행 합니다.
  if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
    goto InitFailure;
  // App global initializations (rare)
  if (pApp != NULL && !pApp->InitApplication())
    goto InitFailure;
  // (C) 여기서 사용자가 재정의한 가상함수가 호출 됩니다.
  if (!pThread->InitInstance())
  {
    if (pThread->m_pMainWnd != NULL)
    {
      TRACE(traceAppMsg, 0"Warning: Destroying non-NULL m_pMainWnd\n");
      pThread->m_pMainWnd->DestroyWindow();
    }
    // (D) 사용자가 FALSE를 리턴한 경우 종료 되기 전에 아래 함수가 호출 됩니다.
    nReturnCode = pThread->ExitInstance();
    goto InitFailure;
  }
  // (E) 사용자가 TRUE를 리턴한경우 아래 함수가 호출됩니다.
  nReturnCode = pThread->Run();
  // (F) 종료 작업을 수행하고 종료됩니다.
InitFailure:
#ifdef _DEBUG
  // Check for missing AfxLockTempMap calls
  if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
  {
    TRACE(traceAppMsg, 0"Warning: Temp map lock count non-zero (%ld).\n",
      AfxGetModuleThreadState()->m_nTempMapLock);
  }
  AfxLockTempMaps();
  AfxUnlockTempMaps(-1);
#endif
  AfxWinTerm();
  return nReturnCode;
}
cs


 자세한 설명은 생략 하도록 하겠습니다. (A)   ~ (F) 까지의 주석을 한번만 읽어 보세요.

 

이 처럼 MFC는 내부적으로는 프로그램의 전체 적인 흐름을 만들어 놓고 있습다. 사용자는 그 흐름에 끼어 맞추어 가면서 프로그램을 만드는 것입니다. 이런 기법을 흔히 “Application Framework” 라고 부릅니다. 흔히 MFC관련 서적이나 글을 읽다 보면 “MFC의 Framework 에 의하면” 이라는 말을 자주 보게 되는데 결국은 “MFC가 미리 만들어둔 내부 코드에 의하면” 이라고 생각하면 됩니다. 이처럼 MFC를 정확히 이해하려면 내부구조도 어느 정도 잘 이해 하고 있어야 합니다.

 

 

[ 보충 설명 ]

 

MFC의 Entry Point 부분을 간단히 흉내 내어 보도록 하겠습니다. 완전히 동일 하려면 너무 복잡하니까 이해를 위해서 간단하게 만들어 보도록 하겠습니다. 먼저 다음의 2가지 내용을 정확히 알고 있어야 합니다.

 

1. C++에서는  전역 객체의 생성자가 main()함수 보다 먼저 실행됩니다.

2. 파생 클래스의 객체를 생성하면 기본 클래스의 생성자가 먼저 호출되고 자신의 생성자가 호출됩니다.

[참고] 정확히는 mainCRTStartup(), 전역객체 생성자, main()함수의 순서이다.

먼저 console application 형태로 프로젝트를 만든 후에 프로젝트 속성에서 빈 프로젝트를 선택합니다.

 

먼저 MFC와 비슷한 라이브러리를 코드를 만들어 보겠습니다. 좋은 방법은 아니지만 간단하게 실험해 보기 위해서 헤더 파일에 구현부도 제공하도록 하겠습니다.

 

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
// afxwin.h
typedef int BOOL;
 
#define TRUE  1
#define FALSE 0
 
class CWinApp
{
public:
  CWinApp();
  virtual BOOL InitInstance();
  virtual int  Run();
  virtual int  ExitInstance();
};
 
CWinApp* g_app = 0// 사용자 객체의 주소를 보관할 전역변수.
 
CWinApp::CWinApp()
{
  g_app = this;    // 자신의 주소를 전역변수에 보관한다.
}
BOOL CWinApp::InitInstance()
{
  // 어떤초기화작업을수행한다.
  return TRUE;
}
int CWinApp::Run()
{
  return 0;
}
int CWinApp::ExitInstance()
{
  return 0;
}
 
// 사용자가 만든 객체의 주소를 리턴한다. 
CWinApp* AfxGetApp()
{
  return g_app;
}
 
int main()
{
  CWinApp* pThread = AfxGetApp(); // 사용자 객체의 주소를 리턴한다.
 
  if ( ! pThread->InitInstance() )
  {
    pThread->ExitInstance();
    goto Failure;
  }
  pThread->Run();
 
Failure:
  return 0;
}
cs

 

 

이제 위의 라이브러리는 사용하는 사용자의 코드 입니다. 우리가 만든 코드와 비슷하지요 ?

 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.cpp
#include "afxwin.h"
#include <iostream>
using namespace std;
 
class CMyApp : public CWinApp
{
public:
  CMyApp();
  virtual BOOL InitInstance();
};
CMyApp::CMyApp()
{
}
BOOL CMyApp::InitInstance()
{
  CWinApp::InitInstance();
  cout << "Hello, MFC" << endl;
  return TRUE;
}
 
CMyApp theApp;
cs

  

이제 간단히 실행되는 순서를 생각해 보도록 하겠습니다.

 

(A)   전역객체 theApp가 있으므로 생성자가 제일 먼저 호출되어야 합니다. 그런데, 기본 클래스가 있으므로 기본 클래스의 생성자인 CWinApp::CWinApp()가 실행됩니다. theApp의 주소를 전역 변수 g_app에 보관하고 있군요.

 

(B)   이제 CMyApp::CMyApp() 생성자가 호출 됩니다.. 우리의 경우 특별히 하는 일은 없습니다.

 

(C)   이제 main()함수가 호출됩니다.

 

(D)   main 함수에서는 AfxGetApp() 함수를 호출해서 g_app에 보관된 주소를 얻어서 pThread라는 변수에 보관하고 있습니다.

 

(E)    다음으로 pThread->InitInstance()를 호출 합니다. 드디어 사용자가 만든 코드가 수행되는 군요.

 

(F)    TRUE를 리턴 했으므로 CWinApp::Run()을 수행후 종료됩니다.

 

결국 MFC도 이와 비슷하게 구현되어 있습니다. 물론  완전히 동일 하지는 않다. MFC는 내부적으로 전역객체의 주소를 보관하기 위해서 AFX_MODULE_THREAD_STATE 라는 복잡한 구조체를 사용합니다.  또한, 사용자 객체의주소를 얻기 위해 MFC는 내부적으로 AfxGetApp(), AfxGetThread()라는 두개의 함수를 제공하는데 두 함수의 차이는 뒤에서 시간나면 이야기를 해보도록 하지요. MFC의 내부적인 정확한 동작 방식은 앞으로 계속 이야기 하겠습니다. 지금 예제는 단지 흐름을 살펴 보기 위한 예제일 뿐입니다.

 

다음 글에서는 윈도우를 만들어 보겠습니다.





[출처] MFC 기초 첫번째 이야기 - entry point (C++ Master) 

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

MFC 프로그래밍 기초  (0) 2018.02.19
[MFC/기초] AfxGetapp() 이란?  (0) 2018.02.06
블로그 이미지

덕배님

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

,

AfxGetapp() 이란?


우선 MFC로 프로그램을 만들면, 다음과 같이 클래스가 생성되며, 프로젝트 명을 Test라고 가정해 봅시다.

  • CTestApp - CWinApp 클래스를 상속, 프로그램 초기화 클래스 (InitInstance)

  • CMainFrame - CFrameWnd 클래스를 상속, 전체 윈도우 관련 클래스

  • CTestDoc - CDocument 클래스를 상속, 문서 관련 클래스(Open, Save, Serialize)

  • CTestView - CView 클래스를 상속, 사용자 화면 클래스(OnPaint, OnDraw)

  • CAboutDlg - CDialog 클래스를 상속, 도움말 대화 상자 클래스(DoModal)


어느 곳에서나 CTestApp의 포인터를 얻고자 한다면 AfxGetApp()를 호출합니다.

AfxGetApp()은 전역 함수이므로 어느 곳에서나 호출이 가능합니다.


CTestApp* pApp = AfxGetApp();

위와 같이 코딩을 하게 되면, 에러가 발생하오니 아래와 같이 코딩합시다.

CTestApp* pApp = (CTestApp*)AfxGetApp();

AfxGetApp() 전역 함수를 호출하게 되면 MFC 응용프로그램의 최초에 생성된 스레드의 app 를 반환해 줍니다.

반환 타입이 CWinApp * 이므로 사용할때는 사용할 타입의 타입 캐스팅을 해주어야 합니다.


CTestApp 클래스의 선언 부분에 포함(include)을 시키지 않으면 에러가 발생할 수 있는데, 이럴 때는 CTestApp 클래스의 선언을 다음처럼 포함시켜야 합니다.


#include "Test.h"


 메인 스레드의 메인 윈도우 객체 포인터를 얻기 위해선

::AfxGetApp()->m_pMainWnd;

와 같이 m_pMainWnd 멤버 변수 값을 참조 하면 됩니다.


또는

::AfxGetApp()->GetMainWnd(); 

멤버 함수를 호출하여 값을 얻을 수도 있습니다.





출처


AfxGetApp() 와 AfxGetMainWnd()

MFC 클래스 포인터 얻어오기


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

MFC 프로그래밍 기초  (0) 2018.02.19
[MFC/기초] entry point  (0) 2018.02.07
블로그 이미지

덕배님

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

,

전처리문이란?

 

컴파일 이전에 미리 처리되는 문장을 말합니다또한 선행처리기라고도 말합니다. 여기서 컴파일러는 여러분들이 작성하신 코드를 컴파일하기 전에 전처리문에서 정의해 놓은 코드들을 우선적으로 수행합니다.

전처리문들의 종류로는 #include, #if, #define, #ifdef, #defined, #ifndef, #undef 등이 있습니다. 기존에 작성한 소스 코드들을 건드리지 않고, 부분적인 컴파일을 수행합니다.

 

 

C, C++의 전처리문이 시작하는 Line의 첫 글자는 ‘#’으로 시작합니다.

ANSI 표준에 따른 C의 전처리문의 종류는 아래와 같습니다.

파일 처리 : #include

형태 정의 : #define, #undef

조건 처리 : #if, #ifdef, #ifndef, #else, #elif, #endif

에러 처리 : #error

디버깅 : #line

컴파일 옵션 처리 : #pragma

 

조건 처리를 위한 전처리문은 조건에 대한 검사를 수행하며, 그 결과를 True, False로 반환합니다.

#if : …이 참이라면

#ifdef : …이 정의되어 있다면

#else : #if #ifdef에 대응.

#elif : else + if”의 의미

#endif : #if, #ifdef, #infdef 이 끝났음을 알림.

 


#include

헤더 파일(외부파일)을 읽어서 함수를 포함시키고자 할 때 사용됩니다. 

이때 외부파일은 C의 소스파일과 같은 형태의 일반 문서파일을 말합니다.


1

2

#include <stdio.h>    // 이 소스(위치)에 stdio.h의 파일의 내용을 포함시킨다. 

#include "text.h"       // 이 소스(위치)에 text.h라는 파일의 내용을 포함시킨다. 

cs


#include<> 를 사용할 때는 컴파일러의 라이브러리 폴더를 참조할 때 '<>'를 사용합니다.

#include"" 를 사용할 때는 현재 디렉토리에서 헤더파일을 참조할 때, 주로 사용자가 생성한 헤더파일을 참조할때 ' "" '를 사용합니다.


#define

상수값을 지정하기 위한 예약어. 전처리기 지시자라고 하며, #define으로 선언하면 구문을 상수로 변환합니다.
또한 #define은 상수선언과 더불어 함수로도 사용이 가능합니다.


1

2

#define PI 3.14

#define SUM (x,y) { x + y }

cs


위와 같이 #define 까지의 구문을 전처리기 지시자 라고 하며, PI는 상수라고 칭합니다.


1

2

3

#define PI 3.14

 

double PI = 3.14;

cs


위와 같이 두 코드가 있다고 가정하면 결과는 비슷해 보이지만 엄밀히 말하면 다릅니다.

#define 구문은 '단순한 치환' 입니다. 예를들어 가정해봅시다.

예를 들어 '내 생일'이 31일 이라고 가정한다면 '#define 내생일 31' 이라고 선언할 수 있을겁니다.

또한 '내생일 - 1'을 하면 31 - 1로 30이 나올것입니다.

'내생일' 이라는건 문자임에도 불구하고 실제로는 31이라는 정수를 대신하고 있는것이죠.

별명의 개념이라고 생각하시면 될 것 같습니다.

제 이름과 별명이 있지만 둘 중 하나만 불러도 알 수 있는것 처럼요.

따라서 위 코드를 보게되면 PI도 3.14를 대신하고있는 '상수' 입니다.


상수의 값은 변경할 수 없으며, 이렇게 상수에 이름을 붙여주는 것을 매크로 상수 라고 합니다.

매크로 상수는 컴파일 후에 'PI'의 값을 3.14로 아래 주석부분처럼 치환한다고 이해하시면 될 것 같습니다.


1

2

3

4

5

6

7

8

9

#include <stdio.h>

 

#define PI 3.14

 

int main(){

    

    printf("PI = %.2lf", PI);

    //printf("PI = %.2lf", 3.14);

}


cs


또한 #define으로 함수 또한 정의할 수 있습니다. 이는 '매크로 함수' 라고 칭합니다.

아래 코드를 살펴봅시다.


1

2

3

4

5

6

7

8

9

10

11

12

13

#include <stdio.h>

 

#define DuckBae(x,y) { x + y }

 

int main(){

    int num1 = 1;

    int num2 = 2;

    int result = 0;

 

    result = DuckBae(num1, num2);

 

    printf("%d\n",result);

}

cs


여기서 보면, result = DuckBae(num1, num2); 가

result = num1 + num2; 로 바뀌게 됩니다. 


#undef

앞에서 #define으로 이미 정의된 매크로를 무효화합니다.


1

2

#define DuckBae(x,y) { x + y }

#undef DuckBae(x,y)

cs


라고 하면 앞으로 사용되는 DuckBae(x,y) 매크로 함수는 ‘undefined symbol’이 되어 에러처리합니다.


#ifdef

#ifdef 헤더명_H__  ~ #endif 구문으로 실행하는 매크로입니다.


헤더파일이 겹치는 것을 막기위해 사용합니다. 



1

2

#include <stdio.h> //====(1)

#include <stdio.h> //====(2)

cs



위와같이 동일한 구문을 두번 썼다고 가정해봅시다.

1번 Line에서 이미 include를 했는데, 밑에서도 include한다면 문제가 되겠지요?

또한 컴파일러가 검사하는 코드도 늘어납니다. 

그래서 stdio,h에는 아래와 같은 코드가 선언되어 있습니다.



1

2

#ifndef STDIO_H__

#define STDIO_H__

cs



stdio.h가 선언되어있지 않으면 선언한다는 구문입니다.

그래서 stdio.h를 (1)에서 먼저 선언했기 때문에 전처리기 단에서 무시합니다.

컴파일러 또한 (1)만 검사를 합니다.




이상으로 전처리문에 대한 포스팅을 마칩니다! 글 잘 보셨다면 아래 하트 꾹 눌러주세요!

블로그 이미지

덕배님

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

,

유니티 / C#에서 게임 오브젝트에 접근하는 방법.



1. 부모 오브젝트로 자식 오브젝트 접근 하기


public Transform ParentObj; // 부모 오브젝트.


public Transform[] ChildrenObj; //자식 오브젝트 배열.


ChildrenObj = ParentObj.gameObject.GetComponentsInChildren<Transform>();

//ParentObj의 Transform Components가 있는 자식들을 가져옴.




2. 오브젝트 태그로 접근하기


GameObject A = GameObject.FindWithTag("태그 이름"); // 태그 이름으로 검색하여 가장 처음에 나타난 오브젝트를 GameObject로 가져옴.


GameObject A = GameObject.FindGameObjectsWithTag("태그 이름") // 태그 이름으로 검색하여 나타난 오브젝트 여러개를 배열로 가져옴.




3. 오브젝트 이름으로 접근하기


GameObject A = GameObject.Find("오브젝트 이름");




4. 오브젝트 형으로 접근하기


GameObject A = GameObject.FindObjectOfType(typeof(타입));

//게임내에서 오브젝트 타입을 검색하여 해당 오브젝트를 가져온다.

오브젝트가 없을 경우 null을 반환하고, Debug로 에러 메세지를 띄운다.

블로그 이미지

덕배님

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

,

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# 정보를 공유합니다

,