Code Block References


Eng & Kor

CAssetManager

template <typename T>
T* PlacementNew(void*& memoryBlock)
{
    T* manager  = new (memoryBlock) T;
    memoryBlock = (char*)memoryBlock + sizeof(T);
    
    return manager;
}

CAssetManager::CAssetManager(void* memoryBlock)
{
    mTextureManager   = PlacementNew<CTextureManager>(memoryBlock);
    mSpriteManager    = PlacementNew<CSpriteManager>(memoryBlock);
    mAnimationManager = PlacementNew<CAnimationManager>(memoryBlock);
    mUIManager        = PlacementNew<CUIManager>(memoryBlock);
    mFontManager      = PlacementNew<CFontManager>(memoryBlock);
    mSoundManager     = PlacementNew<CSoundManager>(memoryBlock);
}

CAssetManager* CAssetManager::GetInst()
{
    if (!mInst)
    {
        const size_t totalSize = sizeof(CAssetManager)
            + sizeof(CTextureManager)   + sizeof(CSpriteManager)
            + sizeof(CAnimationManager) + sizeof(CUIManager)
            + sizeof(CFontManager)      + sizeof(CSoundManager);
		
        void* memoryBlock = malloc(totalSize);
        mInst = new (memoryBlock) CAssetManager((char*)memoryBlock + sizeof(CAssetManager));
    }
    return mInst;
}


CSceneManager

void CSceneManager::ChangeRequest(ETransition transition, ESceneState state, void* payload = nullptr)
{
	mPending.transition   = transition;
	mPending.pendingState = state;
	mPending.payload      = payload;
}

void CSceneManager::Update(float deltaTime)
{
	if (mPending.transition != ETransition::NONE)
		ChangeApply();
	
	mScenes.back()->Update(deltaTime);
}

void CSceneManager::ChangeApply()
{
	switch (mPending.transition)
	{
	case ETransition::PUSH:
		PushScene();
		break;
	case ETransition::POP:
		PopScene();
		break;
	case ETransition::SWAP:
		SwapScene();
		break;
	case ETransition::CLEAR:
		ClearScenes();
		break;
	case ETransition::CLEAR_THEN_PUSH:
		ClearThenPushScene();
		break;
	default:
		break;
	}
	
	mPending.transition   = ETransition::NONE;
	mPending.pendingState = ESceneState::NONE;
	mPending.payload      = nullptr;
}

void CSceneManager::PushScene()
{
	CScene* newScene = GetSceneFromState(mPending.pendingState);
	newScene->LoadResources();
	
	mScenes.push_back(newScene);
	mScenes.back()->Enter(mPending.payload);
}

void CSceneManager::PopScene()
{
	assert(!mScenes.empty());
	
	CScene* oldScene = mScenes.back();
	if (oldScene->Exit())
	{
		oldScene->UnloadResources();
		SAFE_DELETE(oldScene);
		mScenes.pop_back();
	}
}

void CSceneManager::SwapScene()
{
	CScene* newScene = GetSceneFromState(mPending.pendingState);
	newScene->LoadResources();
	
	PopScene();
	
	mScenes.push_back(newScene);
	mScenes.back()->Enter(mPending.payload);
}

void CSceneManager::ClearScenes()
{
	while (!mScenes.empty())
		PopScene();
}

void CSceneManager::ClearThenPushScene()
{
	CScene* newScene = GetSceneFromState(mPending.pendingState);
	newScene->LoadResources();
	
	ClearScenes();
	
	mScenes.push_back(newScene);
	mScenes.back()->Enter(mPending.payload);
}

void CSceneManager::SwapScene()
{
	CScene* newScene = GetSceneFromState(mPending.pendingState);
	newScene->LoadResources();
	
	CScene* oldScene = mScenes.back();
	if (oldScene->Exit())
	{
		oldScene->UnloadResources();
		SAFE_DELETE(oldScene);
		mScenes.pop_back();
	}
	
	mScenes.push_back(newScene);
	mScenes.back()->Enter(mPending.payload);
}

class CScene abstract
{
	friend class CSceneManager;
	
protected:
	CScene();
	virtual ~CScene();
	
protected:
	std::vector<std::shared_ptr<class CTexture>> mTextures;
    std::vector<std::shared_ptr<class CFont>> mFonts;
    std::vector<std::shared_ptr<class CSFX>>  mSFXs;
    std::vector<std::shared_ptr<class CBGM>>  mBGMs;
    
protected:
	virtual void LoadResources() = 0;
	
    void UnloadResources()
    {
	    mTextures.clear();
	    mFonts.clear();
	    mSFXs.clear();
	    mBGMs.clear();
    }
}

class CTextureManager
{
private:
	std::unordered_map<std::string, std::weak_ptr<CTexture>> mTextures;
	
public:
	std::shared_ptr<CTexture> LoadTexture(...);
}
//---------------------------------------------------------------//
class CFontManager
{
private:
	std::unordered_map<std::string, std::weak_ptr<CFont>> mFonts;
	
public:
	std::shared_ptr<CFont> LoadFont(...);
}
//---------------------------------------------------------------//
class CSoundManager
{
private:
	std::unordered_map<std::string, std::weak_ptr<CSFX>> mSFXs;
	std::unordered_map<std::string, std::weak_ptr<CBGM>> mBGMs;
	
public:
	std::shared_ptr<CSFX> LoadSFX(...);
	std::shared_ptr<CBGM> LoadBGM(...);
}


CScene

class CScene abstract
{
	friend class CSceneManager;
	
protected:
	CScene();
	virtual ~CScene();
	
protected:
    std::vector<CLayer*> mLayers;
	
protected:
	virtual bool Enter(void* payload = nullptr) = 0;
	virtual bool Exit()  = 0;
	
    virtual void Update(float deltaTime);
    virtual void LateUpdate(float deltaTime);
    virtual void Render(SDL_Renderer* renderer);
	
public:
    template <typename T, int initialCapacity = 50>
    T* InstantiateObject(const std::string& name, ELayer::Type type);
};

CScene::CScene()
{
    mLayers.resize(ELayer::Type::MAX);
    for (int i = 0; i < ELayer::Type::MAX; i++)
    {
        mLayers[i] = CMemoryPoolManager::GetInst()->Allocate<CLayer>();
    }
}

CScene::~CScene()
{
    for (CLayer* layer : mLayers)
    {
        CMemoryPoolManager::GetInst()->DeallocateButKeepPool<CLayer>(layer);
    }
}

void CScene::Update(float deltaTime)
{
    for (CLayer* layer : mLayers)
        layer->Update(deltaTime);
    
    if (mSceneCollision)
        mSceneCollision->Update(deltaTime);
    
    if (mCamera)
        mCamera->Update(deltaTime);
    
    if (mSceneUI)
        mSceneUI->Update(deltaTime);
}

void CScene::LateUpdate(float deltaTime)
{
    for (CLayer* layer : mLayers)
        layer->LateUpdate(deltaTime);
    
    if (mSceneCollision)
	    mSceneCollision->LateUpdate(deltaTime);
	
    if (mSceneUI)
        mSceneUI->LateUpdate(deltaTime);
}

void CScene::Render(SDL_Renderer* renderer)
{
    for (CLayer* layer : mLayers)
        layer->Render(renderer);
	
    if (mSceneUI)
        mSceneUI->Render(renderer);
}

template <typename T, int initialCapacity = 50>
T* InstantiateObject(const std::string& name, ELayer::Type type)
{
	if (!CMemoryPoolManager::GetInst()->HasPool<T>())
		CMemoryPoolManager::GetInst()->CreatePool<T>(initialCapacity);
	
	T* obj = CMemoryPoolManager::GetInst()->Allocate<T>();
	obj->SetName(name);
	obj->mScene = this;
	obj->mLayer = mLayers[type];
	
	if (!obj->Init())
	{
		CMemoryPoolManager::GetInst()->Deallocate<T>(obj);
		return nullptr;
	}
	
	mLayers[type]->AddObject(obj);
	return obj;
}


CLayer

class CLayer
{
	friend class CScene;
	
public:
	CLayer();
	~CLayer();
	
private:
    std::vector<class CObject*> mObjects;
	ESort::Type mSort = ESort::Y;
	
protected:
	void Update(float deltaTime);
	void LateUpdate(float deltaTime);
	void Render(SDL_Renderer* renderer);
	
public:
	void AddObject(CObject* obj) { mObjects.emplace_back(obj); }
	
private:
	static bool SortY(CObject* objA, CObject* objB);
};

CLayer::~CLayer()
{
    for (CObject* obj : mObjects)
    {
        obj->Release();
    }
}

void CLayer::Update(float deltaTime)
{
	for (int i = 0; i < mObjects.size(); i++)
	{
		CObject* obj = mObjects[i];
 
		if (!obj->GetActive())
		{
			obj->Destroy();
 
			continue;
		}
		else if (!obj->GetEnable())
		{
			continue;
		}
		obj->Update(deltaTime);
	}
}

void CLayer::LateUpdate(float deltaTime)
{
    for (size_t i = mObjects.size(); i > 0; i--)
    {
        CObject* obj = mObjects[i - 1];
		
        if (!obj->GetActive())
        {
            std::swap(mObjects[i - 1], mObjects.back());
            mObjects.pop_back();
            
            obj->Release();
            continue;
        }
        else if (!obj->GetEnable())
        {
            continue;
        }
        obj->LateUpdate(deltaTime);
    }
}

void CLayer::Render(SDL_Renderer* renderer)
{
    if (mSort == ESort::Type::Y)
	    std::sort(mObjects.begin(), mObjects.end(), SortY);
	    
    for (CObject* obj : mObjects)
    {
        if (!obj->GetActive() || !obj->GetEnable())
        {
            continue;
        }
        obj->Render(renderer);
    }
}


CObject

class CObject abstract : public CEntityBase
{
	friend class CScene;
	friend class CLayer;
	
protected:
	CObject();
	virtual ~CObject();
	
protected:
	CScene* mScene;
	CLayer* mLayer;
	
	CComponent* mRootComponent;
	
protected:
	virtual bool Init();
	virtual void Update(float deltaTime);
	virtual void LateUpdate(float deltaTime);
	virtual void Render(SDL_Renderer* renderer);
	
	virtual void Release() = 0;
	
public:
	CTransform* GetTransform() const { return mRootComponent->GetTransform(); }
	CComponent* GetComponent(const std::string& name = "")
	{
		if (name.empty())
			return mRootComponent;
			
		size_t hashID = std::hash<std::string>()(name);
		return mRootComponent->FindComponent(hashID);
	}
	template <typename T>
	T* GetComponent() const { return mRootComponent->FindComponent<T>(); }
	
	template <typename T, int initialCapacity = 10>
	T* AllocateComponent(const std::string& name);
};

CObject::CObject() :
	mScene(nullptr),
	mLayer(nullptr)
{
	mRootComponent = new CComponent;
	
	mRootComponent->SetName("RootComponent");
	mRootComponent->mObject = this;
}

CObject::~CObject()
{
	SAFE_DELETE(mRootComponent);
}

bool CObject::Init()
{
	return mRootComponent->Init();
}

 
void CObject::Update(float deltaTime)
{
	mRootComponent->Update(deltaTime);
}

void CObject::LateUpdate(float deltaTime)
{
	mRootComponent->LateUpdate(deltaTime);
}

void CObject::Render(SDL_Renderer* renderer)
{
	mRootComponent->Render(renderer);
}

template <typename T, int initialCapacity = 10>
T* AllocateComponent(const std::string& name)
{
	if (!CMemoryPoolManager::GetInst()->HasPool<T>())
		CMemoryPoolManager::GetInst()->CreatePool<T>(initialCapacity);
	
	T* component = CMemoryPoolManager::GetInst()->Allocate<T>();
	component->SetName(name);
	
	return component;
}


CComponent

class CComponent : public CEntityBase
{
	friend class CObject;
	
protected:
	CComponent();
	virtual ~CComponent();
	
protected:
	size_t mTypeID = -1;
	
	CObject*    mObject    = nullptr;
	CTransform* mTransform = nullptr;
	
	CComponent* mParent = nullptr;
	std::vector<CComponent*> mChilds;
	
protected:
	virtual bool Init();
	virtual void Update(float deltaTime);
	virtual void LateUpdate(float deltaTime);
	virtual void Render(SDL_Renderer* renderer);
	
	virtual void Release();
	
public:
	CObject* GetObject() const { return mObject; }
	CTransform* GetTransform() const { return mTransform; }
	
	void AddChild(CComponent* child);
	bool DeleteChild(CComponent* child);
	
private:
	CComponent* FindRootComponent();
	CComponent* FindComponent(size_t id);
	
	template <typename T>
	T* FindComponent();
};

CComponent::CComponent()
{
	mTransform = CMemoryPoolManager::GetInst()->Allocate<CTransform>();
}

CComponent::~CComponent()
{
	for (CComponent* child : mChilds)
	{
		child->Release();
	}
	CMemoryPoolManager::GetInst()->DeallocateButKeepPool<CTransform>(mTransform);
}

bool CComponent::Init()
{
	for (int i = 0; i < mChilds.size(); i++)
	{
		CComponent* child = mChilds[i];
 
		if (!child->Init())
			return false;
	}
	return true;
}

void CComponent::Update(float deltaTime)
{
	for (int i = 0; i < mChilds.size(); i++)
	{
		CComponent* child = mChilds[i];
 
		if (!child->GetActive())
		{
			child->Destroy();
 
			continue;
		}
		else if (!child->GetEnable())
		{
			continue;
		}
		child->Update(deltaTime);
	}
}

void CComponent::LateUpdate(float deltaTime)
{
	for (size_t i = mChilds.size(); i > 0; i--)
	{
		CComponent* child = mChilds[i - 1];
		
		if (!child->GetActive())
		{
			std::swap(mChilds[i - 1], mChilds.back());
			mChilds.pop_back();
			
			std::vector<CTransform*> transChilds = mTransform->GetChilds();
			
			std::swap(mTransform->GetChilds()[i - 1], mTransform->GetChilds().back());
			mTransform->GetChilds().pop_back();
			
			child->Release();
			continue;
		}
		else if (!child->GetEnable())
		{
			continue;
		}
		child->LateUpdate(deltaTime);
	}
}

void CComponent::Render(SDL_Renderer* renderer)
{
	for (CComponent* child : mChilds)
	{
		if (!child->GetActive() || !child->GetEnable())
			continue;
		
		child->Render(renderer);
	}
}


CAnimation

class CAnimation
{
    friend class CAnimationManager;
    friend class CSpriteComponent;
    friend class CVFXComponent;
    
public:
    CAnimation();
    ~CAnimation();
    
protected:
    std::unordered_map<EAnimationState, std::shared_ptr<FAnimationData>> mAnimationStates;
    EAnimationState mCurrentState;
    
    // for EAnimationType::MOVE
    CTransform* mTransform;
    FVector2D mPrevPos;
    
    // for EAnimationType::MOVE & EAnimationType::TIME
    float mFrameInterval;
    int   mCurrIdx;
    bool  mLooped;
    
private:
    void Update(float deltaTime);
    void Release();
    
    CAnimation* Clone() const;
    
public:
    const SDL_Rect& GetFrame();
    
    bool IsPlayedOnce() const;
	void ResetPlayedOnce();
    
    EAnimationState GetState() const;
    void SetState(EAnimationState state);
    void AddState(EAnimationState state, std::shared_ptr<FAnimationData> data);
};

enum class EAnimationState : unsigned char
{
	NONE,
	
	IDLE,
	WALK,
	JUMP,
	
	VFX
};

struct FAnimationData
{
	EAnimationType type = EAnimationType::NONE;
	
	bool  isLoop = 0;
	float intervalPerFrame = 0;
	
	std::vector<SDL_Rect> frames;
};

void CAnimation::Update(float deltaTime)
{
	FAnimationData* aniData = mAnimationStates[mCurrentState].get();
 
	switch (aniData->type)
	{
		case EAnimationType::NONE:
			break;
 
		case EAnimationType::MOVE:
		{
			const FVector2D& currentPos = mTransform->GetWorldPos();
 
			FVector2D posDelta = currentPos - mPrevPos;
 
			mFrameInterval += posDelta.Length();
 
			float frameTransitionDistance = aniData->intervalPerFrame / aniData->frames.size();
 
			if (mFrameInterval >= frameTransitionDistance)
			{
				mCurrIdx = (mCurrIdx + 1) % aniData->frames.size();
 
				mFrameInterval -= frameTransitionDistance;
			}
			mPrevPos = currentPos;
		}
		break;
 
		case EAnimationType::TIME:
		{
			if (mPlayedOnce)
				return;
 
			mFrameInterval += deltaTime;
			if (mFrameInterval >= aniData->intervalPerFrame)
			{
				if (aniData->isLoop)
				{
					mCurrIdx = (mCurrIdx + 1) % aniData->frames.size();
				}
				else
				{
					mPlayedOnce = (mCurrIdx >= aniData->frames.size() - 1) ? true : false;
					if (!mPlayedOnce)
						mCurrIdx++;
				}
				mFrameInterval = 0.0f;
			}
		}
		break;
	}
}

void SetState(EAnimationState state)
{
	if (mCurrentState != state)
	{
		mCurrentState = state;
		mFrameInterval = 0.0f;
		mCurrIdx = 0;
	}
}

CAnimation* CAnimation::Clone() const
{
	CAnimation* clone = CMemoryPoolManager::GetInst()->Allocate<CAnimation>();
	*clone = *this;
	
	return clone;
}


CSpriteComponent

class CSpriteComponent : public CComponent
{
public:
	CSpriteComponent();
	virtual ~CSpriteComponent();
	
private:
	std::shared_ptr<CTexture> mTexture;
	CAnimation* mAnimation;
	SDL_Rect mFrame;
	
	SDL_RendererFlip mFlip;
	
private:
	virtual void Update(float deltaTime)        final;
	virtual void Render(SDL_Renderer* renderer) final;
	virtual void Release()                      final;
	
public:
	std::shared_ptr<CTexture> GetTexture() const { return mTexture; }
	CAnimation* GetAnimation() const { return mAnimation; }
	
	void SetTexture(const std::string& key);
	void SetAnimation(const std::string& key);
	void SetFrame(const std::string& key);
	
	void SetFlip(SDL_RendererFlip flip) { mFlip = flip; }
 
private:
	const SDL_Rect& GetFrame() const;
	SDL_Rect GetDest() const;
	
	bool IsVisibleToCamera() const;
	SDL_Rect GetCameraSpaceRect() const;
};

void CSpriteComponent::Update(float deltaTime)
{
	CComponent::Update(deltaTime);
	
	if (mAnimation)
		mAnimation->Update(deltaTime);
}

void CSpriteComponent::Render(SDL_Renderer* renderer)
{
	if (mTexture && IsVisibleToCamera())
	{
		const SDL_Rect& frame = GetFrame();
		const SDL_Rect  dest  = GetCameraSpaceRect();
		
		SDL_RenderCopyEx(renderer, mTexture->GetTexture(), &frame, &dest, 0.0, nullptr, mFlip);
	}
	CComponent::Render(renderer);
}

void CSpriteComponent::SetTexture(const std::string& key)
{
	mTexture = CAssetManager::GetInst()->GetTextureManager()->GetTexture(key);
}

void CSpriteComponent::SetAnimation(const std::string& key)
{
	CAnimation* base = CAssetManager::GetInst()->GetAnimationManager()->GetAnimation(key);
	
	if (base)
	{
		mAnimation = base->Clone();
		mAnimation->mTransform = mTransform;
	}
}

void CSpriteComponent::SetFrame(const std::string& key)
{
	const SDL_Rect* const framePtr = CAssetManager::GetInst()->GetSpriteManager()->GetSpriteFrame(key);
	
	mFrame = *framePtr;
}


CVFXComponent

class CVFXComponent : public CComponent
{
public:
	CVFXComponent();
	virtual ~CVFXComponent();
	
private:
	std::shared_ptr<CTexture> mTexture;
	CAnimation* mAnimation;
	
	bool mPlayVFX;
	
private:
	virtual void Update(float deltaTime)        final;
	virtual void Render(SDL_Renderer* renderer) final;
	virtual void Release()                      final;
	
public:
	std::shared_ptr<CTexture> GetTexture() const { return mTexture; }
	CAnimation* GetAnimation() const { return mAnimation; }
	
	void SetTexture(const std::string& key);
	void SetAnimation(const std::string& key);
	
	void PlayVFX(const FVector2D& pos);
	
private:
	SDL_Rect GetDest() const;
	
	bool IsVisibleToCamera() const;
	SDL_Rect GetCameraSpaceRect() const;
};

void CVFXComponent::Update(float deltaTime)
{
	CComponent::Update(deltaTime);
	
	if (!mPlayVFX || !mAnimation)
		return;
	
	mAnimation->Update(deltaTime);
	
	if (mAnimation->IsPlayedOnce())
	{
		mPlayVFX = false;
		mAnimation->ResetPlayedOnce();
	}
}

void CVFXComponent::Render(SDL_Renderer* renderer)
{
	if (mPlayVFX && mTexture && IsVisibleToCamera())
	{
		const SDL_Rect& frame = mAnimation->GetFrame();
		const SDL_Rect  dest  = GetCameraSpaceRect();
		
		SDL_RenderCopy(renderer, mTexture->GetTexture(), &frame, &dest);
	}
	CComponent::Render(renderer);
}

void CVFXComponent::SetTexture(const std::string& key)
{
	mTexture = CAssetManager::GetInst()->GetTextureManager()->GetTexture(key);
}

void CVFXComponent::SetAnimation(const std::string& key)
{
	CAnimation* base = CAssetManager::GetInst()->GetAnimationManager()->GetAnimation(key);
	
	if (base)
	{
		mAnimation = base->Clone();
		mAnimation->mTransform = mTransform;
	}
}

void CVFXComponent::PlayVFX(const FVector2D& pos)
{
	if (mPlayVFX || !mAnimation)
		return;
	
	mPlayVFX = true;
	
	mTransform->SetWorldPos(pos);
	mAnimation->mCurrIdx = 0;
	mAnimation->mFrameInterval = 0.0f;
}


CInputUtils

enum class EKeyAction : unsigned char
{
	PRESS,
	HOLD,
	RELEASE,
	MAX
};

struct FKeyState
{
	bool Press   = false;
	bool Hold    = false;
	bool Release = false;
};

struct FBindFunction
{
	void* obj = nullptr;
	std::function<void()> func;
};

struct FBinder
{
	std::vector<std::pair<SDL_Scancode, EKeyAction>> Keys;
	std::vector<std::pair<Uint8, EKeyAction>> Mouses;
	
	std::vector<FBindFunction*> Functions;
};


CInputManager

class CInputManager
{
	friend class CEngine;
	
private:
	CInputManager();
	~CInputManager();
	
private:
	static CInputManager* mInst;
	
	// for keyboard
	std::unordered_map<SDL_Scancode, FKeyState> mKeys;
	
	// for mouse
	std::unordered_map<Uint8, FKeyState> mMouses;
	FVector2D mMousePos = FVector2D::ZERO;
	
private:
	bool Init();
	bool RegisterKey(SDL_Scancode keyCode);
	bool RegisterMouse(Uint8 button);
	
	void Update();
	void UpdateInputState();
	void HandleInputState(bool& press, bool& hold, bool& release, bool isPressed);
	
public:
	bool GetKeyState(SDL_Scancode keyCode, EKeyAction action);
	bool GetMouseButtonState(Uint8 button, EKeyAction action);
	const FVector2D& GetMousePos() const { return mMousePos; }
	
public:
	static CInputManager* GetInst()
	{
		if (!mInst)
			mInst = new CInputManager;
		return mInst;
	}
	
	static void DestroyInst()
	{
		SAFE_DELETE(mInst);
	}
};

bool CInputManager::RegisterKey(SDL_Scancode keyCode)
{
	if (mKeys.find(keyCode) != mKeys.end())
		return false;
	
	FKeyState state;
	mKeys[keyCode] = state;
	
	return true;
}

bool CInputManager::RegisterMouse(Uint8 button)
{
	if (mMouses.find(button) != mMouses.end())
		return false;
	
	FKeyState state;
	mMouses[button] = state;
	
	return true;
}

void CInputManager::UpdateInputState()
{
	// FOR KEYBOARD //
	{
		const Uint8* keyboardState = SDL_GetKeyboardState(nullptr);
		
		std::unordered_map<SDL_Scancode, FKeyState>::iterator iter = mKeys.begin();
		std::unordered_map<SDL_Scancode, FKeyState>::iterator iterEnd = mKeys.end();
		
		for (; iter != iterEnd; iter++)
		{
			bool isPressed = keyboardState[iter->first];
			FKeyState& key = iter->second;
			
			HandleInputState(key.Press, key.Hold, key.Release, isPressed);
		}
	}
	
	// FOR MOUSE //
	{
		int mouseX, mouseY;
		Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
		
		mMousePos = { (float)mouseX, (float)mouseY };
		
		std::unordered_map<Uint8, FKeyState>::iterator iter = mMouses.begin();
		std::unordered_map<Uint8, FKeyState>::iterator iterEnd = mMouses.end();
		
		for (; iter != iterEnd; iter++)
		{
			bool isPressed = mouseState & SDL_BUTTON(iter->first);
			FKeyState& mouse = iter->second;
			
			HandleInputState(mouse.Press, mouse.Hold, mouse.Release, isPressed);
		}
	}
}

void CInputManager::HandleInputState(bool& press, bool& hold, bool& release, bool isPressed)
{
	if (isPressed)
	{
		if (!hold)
		{
			press = true;
			hold  = true;
		}
		else
			press = false;
	}
	else
	{
		if (hold)
		{
			press   = false;
			hold    = false;
			release = true;
		}
		else if (release)
			release = false;
	}
}

bool CInputManager::GetKeyState(SDL_Scancode keyCode, EKeyAction action)
{
	switch (action)
	{
	case EKeyAction::PRESS:
		return mKeys[keyCode].Press;
	case EKeyAction::HOLD:
		return mKeys[keyCode].Hold;
	case EKeyAction::RELEASE:
		return mKeys[keyCode].Release;
	default:
		return false;
	}
}

bool CInputManager::GetMouseButtonState(Uint8 button, EKeyAction action)
{
	switch (action)
	{
		case EKeyAction::PRESS:
			return mMouses[button].Press;
		case EKeyAction::HOLD:
			return mMouses[button].Hold;
		case EKeyAction::RELEASE:
			return mMouses[button].Release;
		default:
			return false;
	}
}


CInputComponent

class CInputComponent : public CComponent
{
public:
	CInputComponent();
	virtual ~CInputComponent();
	
private:
	std::unordered_map<std::string, FBinder*> mBinders;
	
private:
	virtual void Update(float deltaTime) final;
	virtual void Release() final;
	
public:
	template <typename T>
	void AddFuncToBinder(const std::string& key, T* obj, void(T::* func)());
	void AddFuncToBinder(const std::string& key, void* obj, const std::function<void()>& func);
	
	void DeleteFuncFromBinder(const std::string& key, void* obj);
	
	void AddInputToBinder(const std::string& key, SDL_Scancode keyCode, EKeyAction action);
	void AddInputToBinder(const std::string& key, Uint8 button, EKeyAction action);
};

void CInputComponent::Update(float deltaTime)
{
	CComponent::Update(deltaTime);
	
	std::unordered_map<std::string, FBinder*>::iterator iter = mBinders.begin();
	std::unordered_map<std::string, FBinder*>::iterator iterEnd = mBinders.end();
	
	for (; iter != iterEnd; iter++)
	{
		FBinder* binder = iter->second;
		if (binder->Keys.empty() && binder->Mouses.empty())
			continue;
		
		bool match = true;
		
		// KEYBOARD //
		for (const auto& binderKey : binder->Keys)
		{
			const SDL_Scancode& key = binderKey.first;
			const EKeyAction& action = binderKey.second;
			
			if (!CInputManager::GetInst()->GetKeyState(key, action))
			{
				match = false;
				break;
			}
		}
		
		if (!match)
			continue;
		
		// MOUSE //
		for (const auto& binderMouse : binder->Mouses)
		{
			const Uint8& mouse = binderMouse.first;
			const EKeyAction& action = binderMouse.second;
			
			if (!CInputManager::GetInst()->GetMouseButtonState(mouse, action))
			{
				match = false;
				break;
			}
		}
		
		if (match)
		{
			for (FBindFunction* bindFunc : binder->Functions)
				bindFunc->func();
		}
	}
}

// Bind functions (lambda, std::function)
void CInputComponent::AddFunctionToBinder(const std::string& key, void* obj, const std::function<void()>& func)
{
	FBinder* binder = mBinders[key];
	
	if (!binder)
	{
		binder = CMemoryPoolManager::GetInst()->Allocate<FBinder>();
		mBinders[key] = binder;
	}
	
	FBindFunction* binderFunc = CMemoryPoolManager::GetInst()->Allocate<FBindFunction>();
	
	binderFunc->obj = obj;
	binderFunc->func = func;
	
	binder->Functions.emplace_back(binderFunc);
}
 
// Bind functions (member)
template <typename T>
void CInputComponent::AddFunctionToBinder<T>(const std::string& key, T* obj, void(T::* func)())
{
	AddFunctionToBinder(key, obj, std::bind(func, obj));
}

void CInputComponent::DeleteFunctionFromBinder(const std::string& key, void* obj)
{
	FBinder* binder = mBinders[key];
	
	if (!binder)
		return;
	
	std::vector<FBindFunction*>& functions = binder->Functions;
	
	for (size_t i = functions.size(); i > 0; i--)
	{
		FBindFunction* bindFunc = functions[i - 1];
		
		if (bindFunc->obj == obj)
		{
			std::swap(functions[i - 1], functions.back());
			functions.pop_back();
			
			CMemoryPoolManager::GetInst()->DeallocateButKeepPool<FBindFunction>(bindFunc);
		}
	}
}

// keyboard
void AddInputToBinder(const std::string& key, SDL_Scancode keyCode, EKeyAction action)
{
	FBinder* binder = mBinders[key];
	
	if (!binder)
		return;
	
	binder->Keys.emplace_back(std::make_pair(keyCode, action));
}
 
// mouse
void AddInputToBinder(const std::string& key, Uint8 button, EKeyAction action)
{
	FBinder* binder = mBinders[key];
	
	if (!binder)
		return;
	
	binder->Mouses.emplace_back(std::make_pair(button, action));
}


CCollisionManager

class CCollisionManager
{
private:
	CCollisionManager();
	~CCollisionManager();
	
private:
	static CCollisionManager* mInst;
	
	std::unordered_map<std::string, FCollisionProfile*>	mProfileMap;
	
public:
	bool Init();
	
	bool CreateProfile(const std::string& name, 
		ECollision::Channel myChannel, ECollision::Interaction interaction);
	
	bool SetCollisionInteraction(const std::string& name,
		ECollision::Channel otherChannel, ECollision::Interaction interaction);
	
	FCollisionProfile* FindProfile(const std::string& name);
	
public:
	bool AABBCollision(CBoxCollider* collider1, CBoxCollider* collider2);
	bool CircleCircleCollision(CCircleCollider* collider1, CCircleCollider* collider2);
	bool AABBCircleCollision(CBoxCollider* collider1, CCircleCollider* collider2);
	bool AABBPointCollision(CBoxCollider* collider, const FVector2D& point);
	bool CirclePointCollision(CCircleCollider* collider, const FVector2D& point);
	
	bool AABBPointCollision(const SDL_Rect& rect, const FVector2D& point);
	
public:
	static CCollisionManager* GetInst();
	static void DestroyInst();
};

bool CCollisionManager::AABBCollision(CBoxCollider* collider1, CBoxCollider* collider2)
{
	const SDL_FRect& box1 = collider1->GetRect();
	const SDL_FRect& box2 = collider2->GetRect();
	
	if (box1.x + box1.w < box2.x ||
		box1.x > box2.x + box2.w ||
		box1.y + box1.h < box2.y ||
		box1.y > box2.y + box2.h)
	{
		return false;
	}
	
	FVector2D hitPoint;
	hitPoint.x = (std::max(box1.x, box2.x) + std::min(box1.x + box1.w, box2.x + box2.w)) * 0.5f;
	hitPoint.y = (std::max(box1.y, box2.y) + std::min(box1.y + box1.h, box2.y + box2.h)) * 0.5f;
	
	collider1->mHitPoint = hitPoint;
	collider2->mHitPoint = hitPoint;
	
	return true;
}

bool CCollisionManager::CircleCircleCollision(CCircleCollider* collider1, CCircleCollider* collider2)
{
	const FCircle& circle1 = collider1->GetCircle();
	const FCircle& circle2 = collider2->GetCircle();
	
	float distance  = circle1.center.Distance(circle2.center);
	float sumRadius = circle1.radius + circle2.radius;
	
	if (distance > sumRadius)
	{
		return false;
	}
	
	FVector2D hitPoint = (circle1.center + circle2.center) * 0.5f;
	
	collider1->mHitPoint = hitPoint;
	collider2->mHitPoint = hitPoint;
	
	return true;
}

bool CCollisionManager::AABBCircleCollision(CBoxCollider* collider1, CCircleCollider* collider2)
{
	const SDL_FRect& box  = collider1->GetRect();
	const FCircle& circle = collider2->GetCircle();
	
	FVector2D closestPointOnBox = circle.center.Clamp(box.x, box.x + box.w, box.y + box.h, box.y);
	
	float distance = circle.center.Distance(closestPointOnBox);
	
	if (circle.radius < distance)
	{
		return false;
	}
	
	FVector2D hitPoint = closestPointOnBox;
	
	collider1->mHitPoint = hitPoint;
	collider2->mHitPoint = hitPoint;
	
	return true;
}


CSceneCollision

class CSceneCollision
{
	friend class CScene;
	
public:
	CSceneCollision() = delete;
	CSceneCollision(CCamera* camera);
	~CSceneCollision();
	
private:
	CQuadTree* mQuadTree;
	std::unordered_map<FColliderPair, EPair::Status> mPairs;
	
public:
	void Update(float deltaTime);
	void LateUpdate(float deltaTime);
	
public:
	void AddCollider(CCollider* collider);
	void HandleCollision(CCollider* collider1, CCollider* collider2);
	
private:
	void CleanPairs();
};

void CSceneCollision::HandleCollision(CCollider* collider1, CCollider* collider2)
{
	FColliderPair  pair = { collider1, collider2 };
	EPair::Status& status = mPairs[pair];
	
	if (status == EPair::DNE)
		status = EPair::NOT_COLLIDED;
	
	if (collider1->Intersect(collider2))
	{
		if (status == EPair::NOT_COLLIDED)
		{
			collider1->OnCollisionEnter(collider2);
			collider2->OnCollisionEnter(collider1);
			
			status = EPair::COLLIDED;
		}
		else
		{
			collider1->OnCollisionStay(collider2);
			collider2->OnCollisionStay(collider1);
			
			CPhysicsManager::GetInst()->ResolveOverlapIfPushable(collider1, collider2);
		}
	}
	else
	{
		if (status == EPair::COLLIDED)
		{
			collider1->OnCollisionExit(collider2);
			collider2->OnCollisionExit(collider1);
		}
		mPairs.erase(pair);
	}
}

void CSceneCollision::CleanPairs()
{
	std::unordered_map<FColliderPair, EPair::Status>::iterator iter = mPairs.begin();
	std::unordered_map<FColliderPair, EPair::Status>::iterator iterEnd = mPairs.end();
	
	while (iter != iterEnd)
	{
		CCollider* collider1 = iter->first.collider1;
		CCollider* collider2 = iter->first.collider2;
		const EPair::Status& status = iter->second;
		
		if (!collider1->GetActive() || !collider2->GetActive() || !collider1->Intersect(collider2))
		{
			if (status == EPair::COLLIDED)
			{
				collider1->OnCollisionExit(collider2);
				collider2->OnCollisionExit(collider1);
			}
			iter = mPairs.erase(iter);
			continue;
		}
		iter++;
	}
}


CQuadTree

class CQuadTree
{
	friend class CSceneCollision;
	
private:
	CQuadTree() = delete;
	CQuadTree(CCamera* camera);
	~CQuadTree();
	
private:
	CQTNode* mRoot = nullptr;
	std::vector<CCollider*> mColliders;
	
public:
	void Update(float deltaTime);
	void LateUpdate(float deltaTime);
	
public:
	void AddCollider(CCollider* collider);
	
private:
	void UpdateBoundary();
};

CQuadTree::CQuadTree(CCamera* camera) :
	mRoot(nullptr)
{
	int totalNodes = (int)((pow(4, MAX_SPLIT + 1) - 1) / 3);
	CMemoryPoolManager::GetInst()->CreatePool<CQTNode>(totalNodes);
	
	if (!mRoot)
		mRoot = CMemoryPoolManager::GetInst()->Allocate<CQTNode>();
	
	mRoot->mCamera = camera;
}

void CQuadTree::Update(float deltaTime)
{
	UpdateBoundary();
	
	for (size_t i = mColliders.size(); i > 0; i--)
	{
		CCollider* collider = mColliders[i - 1];
		
		if (!collider->GetActive())
		{
			std::swap(mColliders[i - 1], mColliders.back());
			mColliders.pop_back();
			
			continue;
		}
		else if (!collider->GetEnable())
		{
			continue;
		}
		mRoot->AddCollider(collider);
	}
	mRoot->Update(deltaTime);
}


CQTNode

class CQTNode
{
	friend class CQuadTree;
	
public:
	CQTNode();
	~CQTNode();
	
private:
	CCamera* mCamera;
	
	CQTNode* mParent;
	CQTNode* mChilds[4];
	std::vector<CCollider*> mColliders;
	
	SDL_FRect mBoundary;
	
	int mSplitLevel = 0;
	
public:
	void Update(float deltaTime);
	void Render(SDL_Renderer* renderer);
	
public:
	bool HasChild();
	bool ShouldSplit();
	void Split();
	
	bool IsWithinNode(CCollider* collider);
	void AddCollider(CCollider* collider);
	
	void MoveCollidersToChildren();
	
	void Clear();
};

void CQTNode::Update(float deltaTime)
{
	if (HasChild())
	{
		for (int i = 0; i < 4; i++)
		{
			mChilds[i]->Update(deltaTime);
		}
	}
	else
	{
		size_t size = mColliders.size();
		if (size >= 2)
		{
			CSceneCollision* sceneCollision = CSceneManager::GetInst()->GetCurrentScene()->GetCollision();
			
			for (size_t i = 0; i < size; i++)
			{
				for (size_t j = i + 1; j < size; j++)
				{
					FCollisionProfile* profile1 = mColliders[i]->GetProfile();
					FCollisionProfile* profile2 = mColliders[j]->GetProfile();
					
					if (profile1->collisionResponses[profile2->channel] == ECollision::Interaction::IGNORE ||
						profile2->collisionResponses[profile1->channel] == ECollision::Interaction::IGNORE)
					{
						continue;
					}
					sceneCollision->HandleCollision(mColliders[i], mColliders[j]);
				}
			}
		}
	}
}

void CQTNode::AddCollider(CCollider* collider)
{
	if (!IsWithinNode(collider))
		return;
		
	if (HasChild())
	{
		for (int i = 0; i < 4; i++)
		{
			if (mChilds[i]->IsWithinNode(collider))
			{
				mChilds[i]->AddCollider(collider);
			}
		}
	}
	else
	{
		mColliders.emplace_back(collider);
		
		if (ShouldSplit())
		{
			Split();
			
			MoveCollidersToChildren();
		}
	}
}


CPhysicsManager

class CPhysicsManager
{
	friend class CScene;
	
private:
	CPhysicsManager();
	~CPhysicsManager();
	
private:
	static CPhysicsManager* mInst;
	
	const float CONST_MinMtvLSq = 0.5f;
	
public:
	void ResolveOverlapIfPushable(CCollider* collider1, CCollider* collider2);
	
private:
	void ResolveOverlap(CCollider* collider1, CCollider* collider2, bool pushObj1, bool pushObj2);
	
	void ResolveAABBOverlap(CBoxCollider* collider1, CBoxCollider* collider2, bool pushObj1, bool pushObj2);
	void ResolveCircleCircleOverlap(CCircleCollider* collider1, CCircleCollider* collider2, bool pushObj1, bool pushObj2);
	void ResolveAABBCircleOverlap(CBoxCollider* collider1, CCircleCollider* collider2, bool pushObj1, bool pushObj2);
	
	void MoveBy(CCollider* collider, const FVector2D& mtv);
	
public:
	static CPhysicsManager* GetInst();
	static void DestroyInst();
};

void CPhysicsManager::ResolveOverlapIfPushable(CCollider* collider1, CCollider* collider2)
{
	FCollisionProfile* profile1 = collider1->GetProfile();
	FCollisionProfile* profile2 = collider2->GetProfile();
	
	ECollision::Interaction response1to2 = profile1->collisionResponses[profile2->channel];
	ECollision::Interaction response2to1 = profile2->collisionResponses[profile1->channel];
	
	CRigidbody* rb1 = nullptr;
	CRigidbody* rb2 = nullptr;
	
	if (response1to2 == ECollision::Interaction::BLOCK)
		rb1 = collider1->GetObject()->GetComponent<CRigidbody>();
	
	if (response2to1 == ECollision::Interaction::BLOCK)
		rb2 = collider2->GetObject()->GetComponent<CRigidbody>();
	
	bool pushObj1 = rb1 != nullptr && rb1->GetType() == ERigidbodyType::DYNAMIC;
	bool pushObj2 = rb2 != nullptr && rb2->GetType() == ERigidbodyType::DYNAMIC;
	
	if (!pushObj1 && !pushObj2)
		return;
	
	ResolveOverlap(collider1, collider2, pushObj1, pushObj2);
}


CRigidbody

class CRigidbody : public CComponent
{
public:
	CRigidbody();
	virtual ~CRigidbody();
	
private:
	ERigidbodyType mType;
	
	float mMass;
	float mGravityScale;
	
	FVector2D mVelocity;
	FVector2D mAcceleration;
	FVector2D mAccumulatedForce;
	
private:
	virtual void Update(float deltaTime) final;
	virtual void Release() final;
	
public:
	void AddForce(const FVector2D& force);
	void AddImpulse(const FVector2D& impulse);
	
	const FVector2D& GetVelocity() const { return mVelocity; }
	ERigidbodyType GetType() const { return mType; }
	float GetMass() const { return mMass; }
	
	void SetVelocity(const FVector2D& velocity)
	{
		mVelocity = velocity;
	}
	void SetType(ERigidbodyType type)
	{
		mType = type;
	}
	void SetMass(float mass)
	{ 
		mMass = mass;
	}
	
private:
	void ApplyGravity();
	void ApplyForces();
	void ApplyAcceleration(float deltaTime);
	void ApplyDrag(float deltaTime);
	void UpdateObjectPos(float deltaTime);
	void ClearForces();
};

void CRigidbody::Update(float deltaTime)
{
	CComponent::Update(deltaTime);
	
	if (mType == ERigidbodyType::STATIC || mMass <= 0.0f)
		return;
	
	ApplyGravity();
	
	ApplyForces();
	
	ApplyAcceleration(deltaTime);
	
	ApplyDrag(deltaTime);
	
	UpdateObjectPos(deltaTime);
	
	ClearForces();
}


CSceneUI

class CSceneUI
{
public:
	CSceneUI();
	virtual ~CSceneUI();
	
private:
	std::vector<CWidget*> mWidgets;
	
	CWidget* mCurrHovered = nullptr;
	CWidget* mHeldWidget  = nullptr;
	
public:
	virtual bool Init();
	virtual void Update(float deltaTime);
	virtual void LateUpdate(float deltaTime);
	virtual void Render(SDL_Renderer* renderer);
	
public:
	CWidget* FindWidget(size_t id);
	void BringWidgetToTop(CWidget* widget);
	CWidget* GetHoveredWidget() const { return mCurrHovered; }
	CWidget* GetHeldWidget() const { return mHeldWidget; }
	void SetHeldWidget(CWidget* heldWidget)
	{
		mHeldWidget = heldWidget;
	}
 
protected:
	void AddWidget(CWidget* widget);
	
private:
	void SetSceneUI(CWidget* widget);
	CWidget* FindHoveredWidget(const FVector2D& mousePos);
	CWidget* FindHoveredInTree(CWidget* widget, const FVector2D& mousePos);
	void UpdateInput();
};

CWidget* CSceneUI::FindHoveredWidget(const FVector2D& mousePos)
{
	for (size_t i = mWidgets.size(); i > 0; --i)
	{
		CWidget* hovered = FindHoveredInTree(mWidgets[i - 1], mousePos);
 
		if (hovered)
			return hovered;
	}
	return nullptr;
}

void CSceneUI::UpdateInput()
{
	const FVector2D& mousePos = CInputManager::GetInst()->GetMousePos();
	
	bool isPressed  = CInputManager::GetInst()->GetMouseButtonState(SDL_BUTTON_LEFT, EKeyAction::PRESS);
	bool isHeld     = CInputManager::GetInst()->GetMouseButtonState(SDL_BUTTON_LEFT, EKeyAction::HOLD);
	bool isReleased = CInputManager::GetInst()->GetMouseButtonState(SDL_BUTTON_LEFT, EKeyAction::RELEASE);
	
	if (mHeldWidget)
	{
		if (!CCollisionManager::GetInst()->AABBPointCollision(mHeldWidget->GetRect(), mousePos))
		{
			if (isHeld || isReleased)
			{
				mHeldWidget->HandleUnhovered(mousePos, isHeld, isReleased);
				return;
			}
		}
	}
	
	CWidget* newHovered = FindHoveredWidget(mousePos);
	{
		if (mCurrHovered != newHovered)
		{
			if (mCurrHovered)
				mCurrHovered->HandleUnhovered(mousePos, isHeld, isReleased);
			
			mCurrHovered = newHovered;
		}
		if (mCurrHovered)
			mCurrHovered->HandleHovered(mousePos, isPressed, isHeld, isReleased);
	}
}


CWidget

class CWidget abstract : public CWidgetBase
{
	friend class CSceneUI;
	friend class CWidgetComponent;
	
protected:
	CWidget();
	virtual ~CWidget();
	
protected:
	CSceneUI* mSceneUI = nullptr;
	
	CWidget* mParent = nullptr;
	std::vector<CWidget*> mChilds;
	
	bool mIsInteractable = false;
	bool mWidgetHovered  = false;
	bool mWidgetHeld     = false;
	
protected:
	virtual void Update(float deltaTime);
	virtual void LateUpdate(float deltaTime);
	virtual void Render(SDL_Renderer* renderer, const FVector2D& topLeft = FVector2D::ZERO);
	virtual void Release() = 0;
	
	virtual void HandleHovered(const FVector2D& mousePos, bool isPressed, bool isHeld, bool isReleased);
	virtual void HandleUnhovered(const FVector2D& mousePos, bool isHeld, bool isReleased);
	
public:	
	CWidget* FindRootWidget();
	CWidget* FindWidget(size_t id);
	
	void AddChild(CWidget* child);
	bool DeleteChild(CWidget* child);
	
	CSceneUI* GetOwnerSceneUI() const { return mSceneUI; }
};


CUserWidget

class CUserWidget abstract : public CWidget
{
public:
	CUserWidget();
	virtual ~CUserWidget();
	
private:
	bool mIsMovable = false;
	FVector2D mDragOffset = FVector2D::ZERO;
	
protected:
	virtual void Construct() = 0;
	virtual void Release()   = 0;
	
public:
	void SetInteractable(bool interactable)
	{
		mIsInteractable = interactable;
		mIsMovable &= mIsInteractable;
	}
	void SetMovable(bool movable)
	{
		mIsMovable = movable;
		mIsInteractable |= movable;
	}
	
protected:
	void HandleDragging(const FVector2D& mousePos, bool isPressed, bool isHeld, bool isReleased);
};

void CUserWidget::HandleDragging(const FVector2D& mousePos, bool isPressed, bool isHeld, bool isReleased)
{
    if (!mIsInteractable || !mIsMovable)
        return;
		
    if (isPressed)
    {
        mDragOffset = mousePos - GetTransform()->GetWorldPos();
        mSceneUI->BringWidgetToTop(this);
    }
    else if (isHeld && mDragOffset != FVector2D::ZERO)
    {
        FVector2D newPos = mousePos - mDragOffset;
        GetTransform()->SetWorldPos(newPos);
    }
    else if (isReleased)
        mDragOffset = FVector2D::ZERO;
}