개요
이미 수정한 구조를 구버전으로 적는 것 만큼 괴로운 일이 없습니다.
제가 지금 그러고 있거든요.
솔직히 의미 없는 짓 같아서 그냥 쓰겠습니다.
뭘?
개요에서 말은 그렇게 했지만 실제로 아군 공격을 끝내고 Entity 구조를 수정했습니다.
그래서 뭘 수정하냐?
지금 Enemy와 Ally를 봤을때 공통적으로 무조건 update에서 돌아가야 하는 함수가 있죠.
일정 간격을 두어 실행해야 하는 tryMove와 tryAttack이 있죠.
이것들을 Entity 클래스에서 update함수를 만들어 묶어주고
둘 다 타이머를 사용하기 때문에 타이머 또한 Entity에서 묶어주면 좋겠죠.
그러기 위해선 모든 Entity를 담는 EntityVector가 따로 있으면 좋겠죠.
지금의 EntityManager는 Entity를 따로 담아주지 않고 Enemy와 Ally만 따로 담아주는 구조를 사용합니다.
한번 고쳐보죠.
Entity
Entity 먼저 해볼까요?
거창한건 없습니다. 그냥 정의만 해주면 되죠.
//Entity.h
protected:
clock_t _timer;
public:
virtual void update();
update가 추상 함수가 아니라 가상 함수인 이유는 기본적으로 모든 Entity들은 _timer를 계속해서 측정해야하기 때문입니다.
void Entity::update()
{
_timer = clock();
}
그리고 Ally와 Enemy 모두 tryMove와 tryAttack 부분을 Entity에게서 상속받은 update함수 내로 옮겨줍니다.
void Ally::update()
{
Entity::update();
if (_timer - _lastAttackTime > _attackTime)
{
attack();
_lastAttackTime = _timer;
}
}
void Enemy::update()
{
Entity::update();
tryMove();
if (_isHit)
{
if (_timer - _lastHitTime > _hitEffectTime)
{
_color = _originColor;
GET_SINGLETON(MapManager)->getCell(_currentPos)->charColor = _color;
_isHit = false;
}
}
}
저 _isHit은 맞는 효과를 구현한 흔적입니다.
별로 맘에 들진 않아요.
EntityManager
EntityManager는 모든 Entity의 스폰, 디스폰을 관리합니다.
그러니 모든 Entity를 기본적으로 갖고있어야겠죠.
근데 전 아군과 적만 갖고 있게 했습니다.
사실 얘네가 Entity의 전부긴한데 Entity로만 묶인 vector가 필요하다는 말이죠.
//EntityManager.h
vector<Entity*> _entityVec;
그리고 소환할때 같이 넣어주면 끝이죠.
Ally* EntityManager::spawnEntity(ALLY_TYPE type, const Vector2& pos)
{
auto it = _allyMap.find(type);
if (it != _allyMap.end())
{
Ally* ally = it->second();
ally->setPos(pos);
_allyVec.push_back(ally);
_entityVec.push_back(ally);
return ally;
}
return nullptr;
}
지워줄때도 같이 뺴주면 됩니다.
void EntityManager::despawnEntity(Ally* ally)
{
auto it = find(_allyVec.begin(), _allyVec.end(), ally);
if (it != _allyVec.end())
{
GET_SINGLETON(MapManager)->deregisterEntityInCell(ally, ally->getPos());
_allyVec.erase(it);
_entityVec.erase(find(_entityVec.begin(), _entityVec.end(), (Entity*)ally));
delete ally;
}
}
간단하죠? Enemy도 대충 같은 방식으로 해놨습니다.
이제 이 _entityVec을 가져와서 update를 호출해주면 끝입니다.
void InGameState::entityUpdate()
{
for (auto& entity : GET_SINGLETON(EntityManager)->getEntities())
{
entity->update();
}
}
근데 문제가 하나 발생합니다.
만약 아군이 적을 쏴죽였을때 게임이 터져요.
명확히 말하자면 _entityVec에 먼저 들어간 아군이 그 뒤에 들어간 적을 쏴죽여서 delete 시킬 경우 터집니다.
그 이유는 간단합니다,
아군이 적을 쏴죽이고 vector에서 적을 지웠지만 for문 안에선 아직 반영되지 않았고 delete된 객체를 참조하게 되는것입니다.
그러니 delete를 해주기 전에 vector에서 미리 지우고, 그 뒤에 delete를 해주면 되겠죠.
지워야할 Entity를 저장하는 vector를 만들어주고...
vector<Entity*> _lazyDeleteVec;
디스폰 할때 delete를 바로 하는게 아닌 _lazyDeleteVec에 넣어주고,
_enemyVec.erase(it);
_entityVec.erase(find(_entityVec.begin(), _entityVec.end(), (Entity*)enemy));
_lazyDeleteVec.push_back(enemy);
하나씩 돌면서 지워주면 됩니다.
void EntityManager::deleteEntity()
{
for (auto& entity : _lazyDeleteVec)
{
auto it = find(_lazyDeleteVec.begin(), _lazyDeleteVec.end(), entity);
if (it != _lazyDeleteVec.end())
{
_lazyDeleteVec.erase(it);
entity = nullptr;
delete entity;
}
}
}
혹시 모를 상황을 대비해 이미 지워진 entity라고 명시해주기 위해 nullptr을 할당해주고 지워줍니다.
그리고 update가 모두 끝난 뒤에 delete를 하도록 해줍니다.
for (auto& entity : GET_SINGLETON(EntityManager)->getEntities())
{
entity->update();
}
GET_SINGLETON(EntityManager)->deleteEntity();
하나만 더
Entity를 모두 되었지만 이제 Cell을 수정하겠습니다.
문제점은 Cell이 본인 위치에 있는 Entity는 가지고 있지만 이 Entity를 Ally나 Enemy로 타입 캐스트하기 어렵습니다.
왜냐하면 이 Cell에 아군과 적이 동시에 있을 수도 있기 때문이죠.
물론 그런 경우는 존재하지 않습니다만, 그렇다고 해서 예외처리를 느슨하게 할 순 없는법이죠.
원래 추가하려고 했던 덫같은 경우도 동시에 같은 칸에 존재할 수 있는 다른 타입의 Entity입니다.
아무튼 아군과 적이 동시에 있다면 그 type을 하나하나 비교해서 내가 원하는 타입의 Entity만 가져와서 캐스트 해야하는데 이러면 너무 번거롭죠.
그렇기 때문에 템플릿을 사용하여 해보겠습니다.
Cell
//Cell.h
template<typename T>
vector<T*> getEntities(ENTITY_TYPE type);
C#과 똑같습니다. 꺽쇠안에 대충 C#처럼 T 적어주면 됩니다.
물론 굳이 T일 필욘 없어요.
차이점은 위에 템플릿을 사용하겠다고 명시를 해줘야합니다.
그리고 cpp파일로 분리할 수 없어요. 같은 헤더파일 내에서 정의하고 본문을 작성해야 합니다.
//Cell.h
template<typename T>
std::vector<T*> Cell::getEntities(ENTITY_TYPE type) {
std::vector<T*> entities;
for (Entity* i : entityVec) {
if (type == i->getType()) {
T* specificEntity = dynamic_cast<T*>(i);
entities.push_back(specificEntity);
}
}
return entities;
}
그냥 T를 기반으로 타입캐스트를 해주는 그런 느낌입니다.
마치는 말
글 쓰는 속도가 빨라진 이유는 그냥 게임이 재미 없어서 그냥 유튜브에서 노래나 듣다가 너무 비생산적이라서 습관적으로 글적는거라 빠를 수밖에 없습니다.
하지만 분량이 적은건 어쩔 수 없네요...
'개발 > Default Defense' 카테고리의 다른 글
C++ 콘솔에서 Bad Apple!! 출력하기 | Default Defense # 9 (1) | 2024.07.08 |
---|---|
Default Defense | 다섯번째 프로젝트 # 8 : Wave, InGameState (1) | 2024.07.04 |
Default Defense | 다섯번째 프로젝트 # 6 : Ally Attack (0) | 2024.06.29 |
Default Defense | 다섯번째 프로젝트 # 5 : Enemy Movement (1) | 2024.06.21 |
Default Defense | 다섯번째 프로젝트 # 4 : Entity (2) | 2024.06.15 |