double이 더 정밀하다. 다이렉트에서는 float를 많이 사용하기 때문에 float을 사용할 것이다. 언리얼은 double을 사용했다.
float를 표현하는 비트에는 지수부와 가수부가 있다. 그 사이에 소수점이 있는데 소수점의 위치가 고정되어 있지 않다.
부동 소수점(떠돌이 소수점) : 소수점의 위치를 고정하지 않는 방식이다.
float은 0을 제외하고 절대로 정확할 수 없다. 정확한 값을 비교해야 하는 연산에서는 사용하지 않는 것이 좋다.
float Value = 1.1f;
if(Value == 1.1f){ // 안될 수 있음.
}
float Value = 1.0f;
Value += 1.1f; // Value가 2.1이 아님
대부분의 게임 연산에서 코드에서의 연산은 실수를 동반한 연산을 통해서 현실에 근사하게 연산하고
모니터에 출력할 때는 모니터는 정확하게 정수로 이루어진 공간이기 때문에 정수로 변환해서 출력하게 된다.
어떤 입력 키에 대한 Down, Press, Up, Free의 상태와 상태를 확인할 수 있는 함수(IsDown 등), 상태를 바꿀 수 있는 함수(KeyCheck)를 가질 수 있도록 EngineInput을 만들었다.
#pragma once
// 키보드를 제어해주는 건 OS일것이기 때문에
// 입력에 대한 함수도 당연히 OS가 우리에게 제공해야 합니다.
#include <Windows.h>
#include <map>
#include <EngineBase\EngineDebug.h>
// AllStateClass
// 설명 :
class EngineInput
{
friend class InputInitCreator;
private:
class /*EngineInput::*/EngineKey
{
friend EngineInput;
public:
bool Down = false; // 누른 순간
bool Press = false; // 계속 누르면
bool Up = false; // 떼어진 순간
bool Free = true; // 누르지 않으면
float PressTime = 0.0f;
int Key = -1; // VK_LBUTTON
void KeyCheck();
EngineKey()
{
}
EngineKey(int _Key)
: Key(_Key)
{
}
};
public:
// constrcuter destructer
EngineInput();
~EngineInput();
// delete Function
EngineInput(const EngineInput& _Other) = delete;
EngineInput(EngineInput&& _Other) noexcept = delete;
EngineInput& operator=(const EngineInput& _Other) = delete;
EngineInput& operator=(EngineInput&& _Other) noexcept = delete;
static bool IsDown(int _Key)
{
if (false == AllKeys.contains(_Key))
{
MsgBoxAssert("입력설정이 존재하지 않는 키 입니다");
}
return AllKeys[_Key].Down;
}
static bool IsPress(int _Key)
{
if (false == AllKeys.contains(_Key))
{
MsgBoxAssert("입력설정이 존재하지 않는 키 입니다");
}
return AllKeys[_Key].Press;
}
static bool IsUp(int _Key)
{
if (false == AllKeys.contains(_Key))
{
MsgBoxAssert("입력설정이 존재하지 않는 키 입니다");
}
return AllKeys[_Key].Up;
}
static bool IsFree(int _Key)
{
if (false == AllKeys.contains(_Key))
{
MsgBoxAssert("입력설정이 존재하지 않는 키 입니다");
}
return AllKeys[_Key].Free;
}
static void KeyCheckTick(float _DeltaTime);
protected:
// 'A' 상태가 어때?
static std::map<int, EngineKey> AllKeys;
int Value;
private:
static void InputInit();
};
#include "EngineInput.h"
std::map<int, EngineInput::EngineKey> EngineInput::AllKeys;
void EngineInput::EngineKey::KeyCheck()
{
// 이 키가 눌렸다는 거죠?
// if (0 != GetAsyncKeyState('A'))
// A키가 눌렸다면
if (0 != GetAsyncKeyState(Key))
{
if (true == Free)
{
// 이전까지 이 키는 눌리고 있지 않았다
Down = true;
Press = true;
Up = false;
Free = false;
}
else if(true == Down)
{
// 이전까지 이 키는 눌리고 있었다.
Down = false;
Press = true;
Up = false;
Free = false;
}
}
else
{
if (true == Press)
{
// 이전까지 이 키는 눌리고 있었다.
Down = false;
Press = false;
Up = true;
Free = false;
}
else if(true == Up)
{
// 이전까지 이 키는 안눌리고 있었고 앞으로도 안눌릴거다.
Down = false;
Press = false;
Up = false;
Free = true;
}
}
}
EngineInput::EngineInput()
{
}
EngineInput::~EngineInput()
{
}
void EngineInput::InputInit()
{
AllKeys[VK_LBUTTON] = EngineKey(VK_LBUTTON);
AllKeys[VK_RBUTTON] = EngineKey(VK_RBUTTON);
AllKeys[VK_CANCEL] = EngineKey(VK_CANCEL);
AllKeys[VK_MBUTTON] = EngineKey(VK_MBUTTON);
AllKeys[VK_BACK] = EngineKey(VK_BACK);
AllKeys[VK_TAB] = EngineKey(VK_TAB);
AllKeys[VK_CLEAR] = EngineKey(VK_CLEAR);
AllKeys[VK_RETURN] = EngineKey(VK_RETURN);
AllKeys[VK_SHIFT] = EngineKey(VK_SHIFT);
AllKeys[VK_LSHIFT] = EngineKey(VK_LSHIFT);
AllKeys[VK_CONTROL] = EngineKey(VK_CONTROL);
AllKeys[VK_MENU] = EngineKey(VK_MENU);
AllKeys[VK_PAUSE] = EngineKey(VK_PAUSE);
AllKeys[VK_CAPITAL] = EngineKey(VK_CAPITAL);
AllKeys[VK_KANA] = EngineKey(VK_KANA);
AllKeys[VK_HANGEUL] = EngineKey(VK_HANGEUL);
AllKeys[VK_HANGUL] = EngineKey(VK_HANGUL);
AllKeys[VK_IME_ON] = EngineKey(VK_IME_ON);
AllKeys[VK_JUNJA] = EngineKey(VK_JUNJA);
AllKeys[VK_FINAL] = EngineKey(VK_FINAL);
AllKeys[VK_HANJA] = EngineKey(VK_HANJA);
AllKeys[VK_KANJI] = EngineKey(VK_KANJI);
AllKeys[VK_IME_OFF] = EngineKey(VK_IME_OFF);
AllKeys[VK_ESCAPE] = EngineKey(VK_ESCAPE);
AllKeys[VK_CONVERT] = EngineKey(VK_CONVERT);
AllKeys[VK_NONCONVERT] = EngineKey(VK_NONCONVERT);
AllKeys[VK_ACCEPT] = EngineKey(VK_ACCEPT);
AllKeys[VK_MODECHANGE] = EngineKey(VK_MODECHANGE);
AllKeys[VK_SPACE] = EngineKey(VK_SPACE);
AllKeys[VK_PRIOR] = EngineKey(VK_PRIOR);
AllKeys[VK_NEXT] = EngineKey(VK_NEXT);
AllKeys[VK_END] = EngineKey(VK_END);
AllKeys[VK_HOME] = EngineKey(VK_HOME);
AllKeys[VK_LEFT] = EngineKey(VK_LEFT);
AllKeys[VK_UP] = EngineKey(VK_UP);
AllKeys[VK_RIGHT] = EngineKey(VK_RIGHT);
AllKeys[VK_DOWN] = EngineKey(VK_DOWN);
AllKeys[VK_SELECT] = EngineKey(VK_SELECT);
AllKeys[VK_PRINT] = EngineKey(VK_PRINT);
AllKeys[VK_EXECUTE] = EngineKey(VK_EXECUTE);
AllKeys[VK_SNAPSHOT] = EngineKey(VK_SNAPSHOT);
AllKeys[VK_INSERT] = EngineKey(VK_INSERT);
AllKeys[VK_DELETE] = EngineKey(VK_DELETE);
AllKeys[VK_HELP] = EngineKey(VK_HELP);
AllKeys[VK_LWIN] = EngineKey(VK_LWIN);
AllKeys[VK_RWIN] = EngineKey(VK_RWIN);
AllKeys[VK_APPS] = EngineKey(VK_APPS);
AllKeys[VK_SLEEP] = EngineKey(VK_SLEEP);
AllKeys[VK_NUMPAD0] = EngineKey(VK_NUMPAD0);
AllKeys[VK_NUMPAD1] = EngineKey(VK_NUMPAD1);
AllKeys[VK_NUMPAD2] = EngineKey(VK_NUMPAD2);
AllKeys[VK_NUMPAD3] = EngineKey(VK_NUMPAD3);
AllKeys[VK_NUMPAD4] = EngineKey(VK_NUMPAD4);
AllKeys[VK_NUMPAD5] = EngineKey(VK_NUMPAD5);
AllKeys[VK_NUMPAD6] = EngineKey(VK_NUMPAD6);
AllKeys[VK_NUMPAD7] = EngineKey(VK_NUMPAD7);
AllKeys[VK_NUMPAD8] = EngineKey(VK_NUMPAD8);
AllKeys[VK_NUMPAD9] = EngineKey(VK_NUMPAD9);
AllKeys[VK_MULTIPLY] = EngineKey(VK_MULTIPLY);
AllKeys[VK_ADD] = EngineKey(VK_ADD);
AllKeys[VK_SEPARATOR] = EngineKey(VK_SEPARATOR);
AllKeys[VK_SUBTRACT] = EngineKey(VK_SUBTRACT);
AllKeys[VK_DECIMAL] = EngineKey(VK_DECIMAL);
AllKeys[VK_DIVIDE] = EngineKey(VK_DIVIDE);
AllKeys[VK_F1] = EngineKey(VK_F1);
AllKeys[VK_F2] = EngineKey(VK_F2);
AllKeys[VK_F3] = EngineKey(VK_F3);
AllKeys[VK_F4] = EngineKey(VK_F4);
AllKeys[VK_F5] = EngineKey(VK_F5);
AllKeys[VK_F6] = EngineKey(VK_F6);
AllKeys[VK_F7] = EngineKey(VK_F7);
AllKeys[VK_F8] = EngineKey(VK_F8);
AllKeys[VK_F9] = EngineKey(VK_F9);
AllKeys[VK_F10] = EngineKey(VK_F10);
AllKeys[VK_F11] = EngineKey(VK_F11);
AllKeys[VK_F12] = EngineKey(VK_F12);
AllKeys[VK_F13] = EngineKey(VK_F13);
AllKeys[VK_F14] = EngineKey(VK_F14);
AllKeys[VK_F15] = EngineKey(VK_F15);
AllKeys[VK_F16] = EngineKey(VK_F16);
AllKeys[VK_F17] = EngineKey(VK_F17);
AllKeys[VK_F18] = EngineKey(VK_F18);
AllKeys[VK_F19] = EngineKey(VK_F19);
AllKeys[VK_F20] = EngineKey(VK_F20);
AllKeys[VK_F21] = EngineKey(VK_F21);
AllKeys[VK_F22] = EngineKey(VK_F22);
AllKeys[VK_F23] = EngineKey(VK_F23);
AllKeys[VK_F24] = EngineKey(VK_F24);
AllKeys['-'] = EngineKey(VK_OEM_MINUS);
AllKeys['+'] = EngineKey(VK_OEM_PLUS);
AllKeys[VK_OEM_4] = EngineKey(VK_OEM_4);
AllKeys[VK_OEM_6] = EngineKey(VK_OEM_6);
for (int i = 'A'; i <= 'Z'; i++)
{
AllKeys[i] = EngineKey(i);
}
for (int i = '0'; i <= '9'; i++)
{
AllKeys[i] = EngineKey(i);
}
}
void EngineInput::KeyCheckTick(float _DeltaTime)
{
for (std::pair<const int, EngineKey>& Key : AllKeys)
{
EngineKey& CurKey = Key.second;
CurKey.KeyCheck();
}
}
class InputInitCreator
{
public:
InputInitCreator()
{
EngineInput::InputInit();
}
};
InputInitCreator CreateValue = InputInitCreator();
EngineInput::InputInit()에서는 AllKeys에 들어갈 키들을 만든다, 즉 게임에 쓰일 키들을 생성한다. InputInitCreator라는 클래스를 만들고 생성자에 EngineInput::InputInit()을 넣어주었고, 전역 객체를 만들어줌으로써 생성자를 실행시켰다.
→ 한마디로 EngineInput::InputInit()을 일부러 실행시켜줄 필요 없이 실행시키기 위한 클래스라는 말이다.
_DeltaTime
: 이전 프레임에서 지금 실행된 프레임까지의 사이 시간이다.
그 DeltaTime을 구하는 방법 -> EngineTime(Level0)
#include <chrono>
: 최신 시간 재는 std 헤더.
CPU 내부에 하드웨어적으로 시간을 카운트하는 부분이 존재한다. 초 단위가 아니고 그냥 똑딱똑딱(?) 카운트한다. 근데 그게 초당 오차가 거의 없을 정도로 일정하게 센다.
컴퓨터켰다 0
1초에 100만
2초에 200만
3초에 300만
4초에 400만 1
5초에 500만
...
이런 느낌
그리고 윈도우는 지금까지 얼마나 셌는지 알려주는 함수를 제공한다. 초당 얼마를 셀 수 있는지를 알려주는 함수도 제공한다. : 각각 QueryPerformanceCount
, QueryPerformanceFrequency
int Count = 초당 얼마나 셀 수 있는지
int PrevTime = 얼마나 셌어?
~~~ 한 프레임 지나감 ~~~
int CurTime = 얼마나 셌어?
float DeltaTime = (CurTime - PrevTime) / Count; // 한 프레임당 지나는 시간을 알 수 있다.
이 방식을 활용해서 EngineTime에서 기능을 만든다. QueryPerformanceCount
, QueryPerformanceFrequency
를 활용해서 만들면 된다.
그리고 DeltaTime을 이용해서 레벨과 액터의 Tick에서 활용하여
플레이어가 1초에 100만큼 이동하는 등의 일정한 이동을 만들어낼 수 있다. (1프레임당 1씩 움직인다면 컴퓨터 성능에 따라 게임 속 능력치가 달라짐)