C++에서 보통은 헤더파일에는 외부 클래스를 전방선언만 해두고, .cpp파일에서 구현부를 가져다 쓰는 것이 일반적이다.(순환참조를 막기 위해)


헌데 템플릿 멤버함수를 선언하려니, 템플릿 함수는 구현부와 선언부를 헤더와 cpp파일에 분리할 수 없다.(어쩌고 저쩌고 사정으로 인해.)

따라서 전방선언해둔 클래스의 기능을 실제로 호출하는 코드를 헤더에 정의하게 되는데, 비주얼스튜디오는 알아서 잘 컴파일해주지만, 몇몇 컴파일러에서는 깐깐하게도 "정의는 없고 선언만 있는 클래스인데 멤버함수를 호출하셨네요! 컴파일 못해요!" 해버린다.

어떻게 해결해야 할까..


#pragma once

class UnitState;

//Unit 자식 클래스를 위한 CREATE_FUNC
#define CREATE_FUNC_UNIT(__TYPE__) \
static __TYPE__* create(PLAYER_KIND ownerPlayer) \
{ \
    __TYPE__ *pRet = new(std::nothrow) __TYPE__(); \
    if (pRet && pRet->init(ownerPlayer)) \
    { \
        pRet->autorelease(); \
        pRet->scheduleUpdate();\
        return pRet; \
    } \
    else \
    { \
        delete pRet; \
        pRet = nullptr; \
        return nullptr; \
    } \
}

class Unit :
    public Sprite
{
public:
    bool init(PLAYER_KIND playerKind);
    CREATE_FUNC_UNIT(Unit)
    virtual void        update(float delta);
    template<typename T_STATE>
    void                changeState()
    {
        if (_state)
        {
            this->getState()->EndState(this);
            this->removeComponent(_state);
        }
        auto state = T_STATE::create();
        this->setState(state);
        this->getState()->StartState(this);//문제가 되는 부분. 선언만 해놓은 클래스(UnitState)의 구현부를 가져다 쓰고 있다.
        this->addComponent(state);
    }

    CC_SYNTHESIZE(UnitState*, _state, State);
    CC_SYNTHESIZE(std::string, _unitName, UnitName);
    CC_SYNTHESIZE(float, _attackRange, AttackRange);
    CC_SYNTHESIZE(float, _attackSpeed, AttackSpeed);
    CC_SYNTHESIZE(float, _moveSpeed, MoveSpeed);
    CC_SYNTHESIZE(Unit*, _attackTarget, AttackTarget);
    CC_SYNTHESIZE_READONLY(PLAYER_KIND, _ownerPlayer, OwnerPlayer);
    Unit*               scanNearestTarget();
private:
};

ㄴ 문제의 코드.


공부좀 해보고 해결하면 다시 정리해서 올려야겠다.


* 일단은 문제의 UnitState.h에서 Unit.h를 include하지 않아도 되게 코드를 수정해서 해결하였다.

Posted by RPG만들기XP
,

cocos2d-x 프레임워크 최상단에는 Ref 클래스가 존재한다.

Ref 클래스는 모든 오브젝트들의 최상위 부모 클래스로, cocos2d-x의 autorelease pool에 의해 관리된다.

Node 클래스는 화면상에 표시될 수 있는 클래스로, Ref 클래스를 상속받으며 Tree 구조를 이루며 서로 참조한다.

(Sprite 클래스도 Node 클래스의 자식이다.)


그런데, 지금까지 기능 모듈을 개발할 때 addChild등을 이용하기 위해 Node 클래스를 상속받아 구현했었는데, 알고 보니 cocos2d-x에도 Component 클래스라는 것이 있었다!!


언리얼엔진4에서는 Node에 대응하는 것이 Actor인데, Actor에 ActorComponent라는 기능 모듈을 붙였다 떼었다 하는 방식으로 모듈형 프로그래밍을 할 수 있었다.

cocos2d-x에서는 안되는줄 알았는데 Component클래스라는 것이 있다는 걸 알고 지금까지 Node를 상속받아서 구현했던 기능 모듈들을 Component로 뜯어고쳤다.


사용법은 크게 달라질 것이 없는데, 모듈을 붙이고자 하는 클래스에 AddChild() 대신 AddComponent()하면 끝(;;).


class UnitState : public Component
{
public:
    virtual void StartState(Unit* unit) = 0;
    virtual void RunState(Unit* unit) = 0;
    virtual void EndState(Unit* unit) = 0;
};

기존에 Node를 상속받아서 만들었던 State클래스를 Component로 바꿔주었다.


void Unit::changeState(UnitState* state)
{
    if (_state)
    {
        this->getState()->EndState(this);
        this->removeComponent(_state);
    }
    this->setState(state);
    this->getState()->StartState(this);
    this->addComponent(state);
}

이렇게 붙여서 reference count를 자동으로 관리할 수 있다.

Posted by RPG만들기XP
,
void Unit::changeState
class Unit :
	public Sprite
{
public:
	bool			init();
	CREATE_FUNC(Unit);
	virtual void		update(float delta);

	void				changeState(UnitState* state);

	CC_SYNTHESIZE(UnitState*, _state, State);
private:
};

유닛 구조를 대충 잡고

class Swordman :
    public Unit
{
public:
    bool    init();
    CREATE_FUNC(Swordman);
};

유닛 종류를 하나 만들어 주었다.

bool Swordman::init()
{
    if (!Unit::init())
        return false;
    _attackSpeed = 1.f;
    _moveSpeed = 30.0;
    
    this->setUnitName("swordman");
    this->initWithFile("SpriteSource/swordman/swordman_walk_1.png");
    AnimationManager::getInstance()->addAnimation(_unitName, "walk", 0.1f,{
        "SpriteSource/swordman/swordman_walk_1.png",
        "SpriteSource/swordman/swordman_walk_2.png",
        "SpriteSource/swordman/swordman_walk_3.png",
        "SpriteSource/swordman/swordman_walk_4.png",
        "SpriteSource/swordman/swordman_walk_5.png",
        "SpriteSource/swordman/swordman_walk_6.png"});
    this->changeState(UnitState_Walk::create());
    return true;
}

추가한 유닛 종류에 대해 셋팅도 해주고
(AnimationManager 클래스도 구현했는데, addAnimation클래스는 이미 있는지 검사해서 없으면 검사하는 클래스. 게임 시작할 때 한 번만 호출되게 바꿀 예정)


class UnitState : public Node
{
public:
    virtual void StartState(Unit* unit) = 0;
    virtual void RunState(Unit* unit) = 0;
    virtual void EndState(Unit* unit) = 0;
};

상태에 대한 인터페이스도 정해주었다.

class UnitState_Walk : public UnitState
{
public:
    CREATE_FUNC(UnitState_Walk);
    bool init() { return true; }
    void StartState(Unit* unit);
    void RunState(Unit* unit);
    void EndState(Unit* unit);
};

void UnitState_Walk::StartState(Unit* unit)
{
    auto animation = AnimationManager::getInstance()->getAnimation(unit->getUnitName(), "walk");
    auto animate = Animate::create(animation);
    unit->runAction(RepeatForever::create(animate));

    auto moveAction = MoveBy::create(1.f, Vec2(-unit->getMoveSpeed()*3, 0));
    unit->runAction(RepeatForever::create(moveAction));
}

void UnitState_Walk::RunState(Unit* unit)
{
    if (unit->getPositionX() < 10)
    {
        unit->removeFromParentAndCleanup(true);
    }
}

void UnitState_Walk::EndState(Unit* unit)
{

}

state 인터페이스를 상속받아 걷는 상태를 구현하였다. 


bool BattleScene::onSprTouchBegan(Touch* touch, Event* event)
{
    auto target = event->getCurrentTarget();
    Point pos = target->convertToNodeSpace(touch->getLocation());
    Rect rect = Rect(0, 100, target->getContentSize().width, 320);
    if (rect.containsPoint(pos))
    {
        auto swordman = Swordman::create();
        swordman->setPosition(pos);
        this->addChild(swordman,2);
        swordman->scheduleUpdate();
    }
    return false;
}

그 다음 클릭할 때마다 생성되게 해놓으면

autorelease pool도 잘 작동하고 있는 것을 볼 수 있다.

처음에는 std::unordered_map으로 짰다가 바로 버그를 여러개 만들었는데, cocos2d::Map으로 바꿔서 간단하게 해결하였다.

역시 엔진에서 정해놓은 대로 쓰는 것이 맘 편한 듯..


상태 머신을 처음으로 구현해본데다, 그냥 느낌 가는대로 만들어서 이렇게 만들어도 되는 것인지 잘 모르겠다.

기능 하나씩 추가하다 보면 분명 문제가 생기지 않을까?

그러면 그때 또 해결하는 것이 공부가 되겠지~

Posted by RPG만들기XP
,