Cocos2d-x 로 만든 게임에서 아이템 개수가 화면과 실제가 달랐다. 화면에는 3개인데 쓰면 2개만 쓰였다.
Table of contents
Open Table of contents
두 곳에 있었다
Player 쪽에 실제 개수가 있고 HUDLayer 쪽에 그리는 개수가 따로 있었다. 아이템을 먹으면 둘 다 고쳐야 한다.
player->addPotion(1);
hud->setPotion(hud->getPotion() + 1);
두 줄을 같이 써야 하니 한쪽만 쓰는 자리가 생긴다. 실제로 보상으로 받는 경로에서 화면 쪽을 안 고치고 있었다.
화면이 읽어 가게 했다
값을 두 곳에 두는 대신 HUD 가 플레이어 쪽에서 읽어 가게 했다. scheduleUpdate() 를 켜면 update(ccTime dt) 가 매 프레임 불린다. 거기서 읽되 바뀌었을 때만 다시 그린다.
int n = m_player->getPotionCount();
if (n != m_lastDrawn) {
m_label->setString(...);
m_lastDrawn = n;
}
마지막으로 그린 값을 들고 있지만 그건 다시 그릴지 정하는 용도일 뿐이다. 값 자체는 한 곳에만 있다.
바뀔 때만 그리는 데는 다른 이유도 있었다. CCLabelTTF::setString 을 열어 보니 부를 때마다 CCTexture2D 를 새로 만들어 글자를 그린다. 매 프레임 부르면 초당 예순 장을 만드는 셈이다.
자주 바뀌는 숫자는 CCLabelBMFont 로 바꿨다. 이쪽은 아틀라스에서 글자 스프라이트를 꺼내 쓰기만 한다. 텍스처를 새로 만들지 않는다.
바뀔 때 알려 주는 방식도 있었다. Player 에 콜백을 걸어 두고 값이 바뀔 때 부르는 것이다. 읽어 가는 쪽은 매 프레임 확인하는 비용이 든다. 대신 알려 주는 것을 빠뜨릴 자리가 없다. 알려 주는 쪽은 확인이 없는 대신 한 군데를 빠뜨리면 화면이 안 바뀐다. 화면 요소가 몇 개 안 돼서 빠뜨릴 자리가 없는 쪽으로 했다.
저장과 서버 값
앱을 껐다 켜니 개수가 또 달랐다. 개수를 더하는 함수가 메모리만 고치고 CCUserDefault 쓰기는 다른 자리에서 따로 하고 있었다. 값을 바꾸는 자리에서 같이 저장하게 했다. 따로 두면 또 한쪽만 하게 된다.
그러니 한 프레임에 여러 번 바뀔 때 저장도 여러 번 일어났다. CCUserDefault 는 값을 넣을 때마다 UserDefault.xml 을 다시 쓴다. 바뀌었다는 표시만 해 두고 프레임 끝에 한 번 쓰게 했다. 값은 여전히 한 곳에 있다.
서버에서 받은 값과도 갈렸다. HTTP 응답이 늦게 오면 그 사이에 먹은 아이템이 사라진다. 받은 값은 요청을 보낸 시점의 값이기 때문이다. 어느 시점의 값인지를 같이 받아서 그 뒤에 바뀐 것이 있으면 안 덮게 했다.
무엇이 원본인지 적었다
같은 값이 여러 자리에 보이는 것 자체는 없앨 수 없었다. HUDLayer 에도 있고 UserDefault.xml 에도 있고 MySQL 에도 있다.
무엇이 원본인지를 코드에 적었다.
/* 아이템 개수의 원본은 여기다.
화면은 읽어 가고, 저장은 이 값을 따라간다.
다른 곳에서 따로 세지 않는다. */
int m_potionCount;
적어 두니 화면 쪽에 값을 하나 더 두려는 생각을 안 하게 됐다.
정리
- 같은 값을 두 곳에 두면 한쪽만 고치는 자리가 반드시 생긴다
- 화면은 값을 갖지 말고 읽어 간다
- 매 프레임 읽되 바뀌었을 때만 다시 그린다
CCLabelTTF::setString은 부를 때마다 텍스처를 새로 만든다- 자주 바뀌는 숫자는
CCLabelBMFont로 둔다. 아틀라스에서 꺼내 쓰기만 한다 - 읽어 가기와 알려 주기 중 빠뜨릴 자리가 없는 쪽이 안전하다
- 값을 바꾸는 자리에서 저장까지 한다. 따로 두면 또 한쪽만 하게 된다
- 저장이 잦으면 표시만 해 두고 프레임 끝에 한 번 쓴다
- 서버 응답은 보낸 시점의 값이라 그 뒤 변경을 덮을 수 있다
- 어느 시점의 값인지 같이 받아야 덮어도 되는지 갈린다
- 무엇이 원본인지 코드에 적으면 다른 곳에 값을 더할 생각을 안 한다