6 캐릭터 생성, 저장, 조회 구현하기

지난번 연재 6-1과 이어집니다.

6.1 캐릭터 생성 구현하기

6.1.3 데이터베이스관리 Class 생성하기

sqlite3라이브러리를 추가했으니 데이터베이스를 관리할 클래스를 생성합니다.

Figure 6‑12 클래스 마법사

DatabaseManager라는 클래스를 생성하였습니다. 생성 위치는 Classes 폴더로 변경해야 합니다. 연재물 [5.3]을 참고하세요.

 

Figure 6‑13 클래스 생성

DatabaseManager.h 와 DatabaseManager.cpp 파일이 생성되었습니다.

 

DatabaseManager.h파일과 DatabaseManager.cpp 파일을 아래와 같이 수정합니다.

----------DatabaseManager.h----------

#include "sqlite3.h"

#include "cocos2d.h"

 

//USING_NS_CC;와 같습니다.

using namespace cocos2d;

 

class DatabaseManager

{

private:

    bool openDB();

    void closeDB();

    sqlite3 *_sqlite;

 

public:

    DatabaseManager();

    ~DatabaseManager();

 

    //에러메시지를 담을 변수

    char *_errorMSG;

    //결과의 상태를 담을 변수

    int _result;

 

    static DatabaseManager *getInstance();

    void createDB();

};

 

----------DatabaseManager.cpp----------

#include "DatabaseManager.h"

#include <string>

 

#define DB_FILE_NAME "db.sqlite"

 

//DatabaseManager 객체

static DatabaseManager *_sharedSqlite = NULL;

 

//생성자

DatabaseManager::DatabaseManager()

{

}

 

//소멸자

DatabaseManager::~DatabaseManager()

{

    //_sharedSqlite가 있으면

    if (_sharedSqlite != NULL){

        _sharedSqlite->closeDB();

        _sharedSqlite = NULL;

    }

}

 

//DatabaseManager 객체를 반환한다.

DatabaseManager* DatabaseManager::getInstance(){

    //_sharedSqlite가 없으면

    if (!_sharedSqlite){

        _sharedSqlite = new DatabaseManager;

        _sharedSqlite->openDB();

    }

 

    return _sharedSqlite;

}

 

//데이터베이스를 오픈한다.

bool DatabaseManager::openDB(){

    //데이터베이스의 path를 가져온다.

    std::string path = FileUtils::getInstance()->getWritablePath() + DB_FILE_NAME;

 

    //데이터베이스의 path를 출력한다.

    log("path : %s", path.c_str());

 

    //데이터베이스를 open한다.

    _result = sqlite3_open(path.c_str(), &_sqlite);

 

    if (_result != SQLITE_OK){

        log("failed to create db");

        return false;

    }

 

    return true;

}

 

//데이터베이스를 닫는다.

void DatabaseManager::closeDB(){

    sqlite3_close(_sqlite);

}

 

//테이블 생성

void DatabaseManager::createDB(){

    std::string query = "create table if not exists TB_FACE(";

    query += "NO integer primary key autoincrement";

    query += ")";

 

    _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

    if (_result == SQLITE_OK){

        log("createDB() SUCCESS");

    }

    else

        log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

}

 

데이터베이스에 테이블을 추가하는 메서드를 생성했습니다.

데이터베이스 파일을 저장할 경로를 지정하고 해당 경로의 파일을 Open합니다. createDB()는 create 쿼리를 실행하고 실행이 실패하면 에러코드와 에러메시지를 출력하는 코드를 작성하였습니다.

 

그럼 createDB()는 CharacterScene에서 호출해보도록 하겠습니다.

----------CharacterScene.cpp----------

#include "CharacterScene.h"

#include "DatabaseManager.h"

 

USING_NS_CC;

 

…생략…

 

// on "init" you need to initialize your instance

bool CharacterScene::init()

{

    //////////////////////////////

    // 1. super init first

    if (!Layer::init())

    {

        return false;

    }

 

    //code here

 

    DatabaseManager::getInstance()->createDB();

 

    /*****Device의 크기를 가져옵니다.*****/

    //Director를 가져옵니다.

    auto director = Director::getInstance();

    //OpenGLView를 가져옵니다.

    auto glView = director->getOpenGLView();

    //OpenGLView에서 DesignResolutionSize를 가져옵니다.

    auto winSize = glView->getDesignResolutionSize();

    /*****Device의 크기를 가져옵니다.*****/

 

#include “DatabaseManager.h”와 DatabaseManager::getInstance()->createDB();를 추가했습니다.

DatabaseManager::getInstance()->createDB();는 DatabaseManager의 객체를 getInstance()로 가져와 createDB()를 실행시킨 것 입니다.

 

디버거를 실행하고 CharacterScene을 호출해보고 로그를 확인해보도록 합니다.

 

Figure 6‑14 데이터베이스 생성 로그

 

로그를 보면 db.sqlite파일이 있는 경로와 createDB() SUCCESS라는 메시지가 나왔습니다.

path의 경로로 이동해보고 실제 데이터베이스 파일이 있는지 확인해보도록 합니다.

 

  

Figure 6‑15 데이터베이스 파일

db.sqlite 파일이 있습니다. 이 파일에 데이터베이스가 구축되는 것 입니다.

win32에서 실행하면 이곳에 데이터베이스 파일이 생성됩니다. 각 플랫폼마다 데이터베이스가 저장되는 위치가 다릅니다.

 

이번엔 insert와 select를 해보도록 하겠습니다.   

DatabaseManager.h파일과 DatabaseManager.cpp 파일을 아래와 같이 수정합니다.

----------DatabaseManager.h----------

 

...생략...

 

    static DatabaseManager *getInstance();

    void createDB();

    void insertDB();

    void selectDB();

};

 

----------DatabaseManager.cpp----------

 

…생략…

 

//테이블 생성

void DatabaseManager::createDB(){

    std::string query = "create table if not exists TB_FACE(";

    query += "NO integer primary key autoincrement";

    query += ")";

 

    _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

    if (_result == SQLITE_OK){

        log("createDB() SUCCESS");

    }

    else

        log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

}

 

void DatabaseManager::insertDB(){

    std::string query = "insert into TB_FACE(NO) values (1), (2), (3)";

 

    _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

    if (_result == SQLITE_OK){

        log("insertDB() SUCCESS");

    }

    else

        log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

}

 

void DatabaseManager::selectDB(){

    std::string query = "select * from TB_FACE";

 

    sqlite3_stmt *stmt = NULL;

    _result = sqlite3_prepare_v2(_sqlite, query.c_str(), query.length(), &stmt, NULL);

 

    if (_result == SQLITE_OK){

        log("selectDB() SUCCESS");

        while (sqlite3_step(stmt) == SQLITE_ROW){

            int no = sqlite3_column_int(stmt, 0);

            log("no : %d", no);

        }

    }

    else{

        log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    sqlite3_finalize(stmt);

}

 

위와 같이 추가한 뒤 CharacterScene.cpp 파일에 createDB()를 호출한 부분을 아래와 같이 수정합니다.

 

----------CharacterScene.cpp----------

 

…생략…

 

// on "init" you need to initialize your instance

bool CharacterScene::init()

{

    //////////////////////////////

    // 1. super init first

    if (!Layer::init())

    {

        return false;

    }

 

    //code here

 

    DatabaseManager::getInstance()->createDB();

    DatabaseManager::getInstance()->insertDB();

    DatabaseManager::getInstance()->selectDB();

 

createDB()하단에 insertDB();와 selectDB();를 추가합니다.

 

그리고 디버거를 실행하고 로그를 살펴보도록 합니다.

Figure 6‑16 insert / select 로그

로그를 보면 createDB(), insertDB(), selectDB() 순으로 실행되었고 selectDB에서 NO를 출력한 부분이 확인되었습니다. Insert 쿼리로 1,2,3 이란 숫자를 데이터베이스의 TB_FACE 테이블에 NO라는 컬럼에 추가하였고 select 쿼리를 이용하여 TB_FACE 테이블의 NO를 가져왔습니다. TB_FACE에는 1, 2, 3이라는 3개의 로우가 입력되어 있기 때문에 select 메서드의 반복문을 통해서 1, 2, 3이 출력되었습니다.

 

그러면 지금상태 그대로 디버거를 다시 실행시키고 로그를 확인해보도록 합니다.

 

 

Figure 6‑17 insert 쿼리 에러 로그

아까와는 다르게 insertDB()에서 에러가 발생했습니다.

 

이유는 1, 2, 3을 NO에 추가했는데 NO는 Primary Key 속성이라 중복되는 값이 들어갈 수 없는 고유값입니다. 데이터베이스는 어플리케이션이 종료되도 사라지지 않습니다. 따라서 NO에 1, 2, 3이 존재하여 에러가 발생했고 에러메시지가 출력되었습니다. 이처럼 어플리케이션이 종료되어도 값을 저장할 기 위해 데이터베이스를 사용할 수 있습니다.

 

createDB()에서 에러가 발생하지 않은이유는 테이블 생성하는 쿼리에 테이블이 존재하지 않으면 이라는 조건이 들어가있기 때문입니다. create table if not exists TB_FACE 라는 쿼리는 TB_FACE라는 테이블이 존재하지 않으면 create쿼리를 실행 하라는 쿼리입니다.

 

데이터베이스 코딩도중 위와같이 이미 생성되어있는 DB때문에 의도치않은 에러가 발생할 수 있습니다. 예를 들면 테이블의 구조를 수정하도록 코드를 작성하였는데 기존에 테이블이 생성되어 있기 때문에 코드에 문제가 없어도 동작하지 않을 수 있습니다. 이럴땐 데이터베이스를 한번 삭제한 뒤 새롭게 DB를 만들어 테스트 하는 것이 편할 수 있습니다. 따라서 DB생성시 기존에 DB를 삭제하는 개발자 코드를 생성해보도록 하겠습니다.

 

개발자 코드 : 테스트를 쉽게 하기위해 개발자가 심어놓은 코드들로 값을 변경하여 원하는 상황을 쉽게 만들어 테스트할 수 있습니다.

 

먼저 DevConf.h 파일을 추가하겠습니다.

개발에 필요한 설정 파일을 모아놓아 나중에 개발을 위해 추가해놓은 코드들을 한번에 제어할 수 있는 헤더파일을 생성하겠습니다.

 

Figure 6‑18 파일 추가1

src 경로를 오른쪽 클릭하고 추가 – 새 항목을 선택합니다.

Figure 6‑19 파일 추가2

헤더파일 하나만 생성할 것이니 헤더 파일(.h)을 선택하고 Name을 DevConf라고 작성합니다. 위치를 Classes 폴더로 수정하기 위해 찾아보기를 선택합니다.

 

Figure 6‑20 파일 추가3

클레스를 생성한 것 과 동일하게 위치를 Classes폴더로 변경합니다.

Figure 6‑21 파일 추가4

위치를 확인하고 추가를 선택합니다.

 

Figure 6‑22 파일 추가5

DevConf.h파일이 정상적으로 추가되었습니다.

 

다음으로 DevConf.h파일을 아래와 같이 수정합니다.

----------DevConf.h----------

#define DB_INIT true

 

DB_INIT이 true라고 정의 해준 코드 한줄만 추가하였습니다.

 

그리고 DatabaseManager.cpp 에서 createDB()를 아래와 같이 수정합니다.

 

----------DatabaseManager.cpp----------

#include "DatabaseManager.h"

#include <string>

#include "DevConf.h"

 

#define DB_FILE_NAME "db.sqlite"

 

…생략…

 

//테이블 생성

void DatabaseManager::createDB(){

    if (DB_INIT){        //디비 초기화를 위한 개발자 변수값

        std::string path = FileUtils::getInstance()->getWritablePath() + DB_FILE_NAME;

        //데이터베이스의 파일이 열려있으면 삭제가 안되므로 데이터베이스를 닫아준다.

        closeDB();

        //db파일을 삭제

        remove(path.c_str());

        //데이터베이스를 닫았으므로 다시 열어준다.

        openDB();

    }

 

    std::string query = "create table if not exists TB_FACE(";

    query += "NO integer primary key autoincrement";

    query += ")";

 

    _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

    if (_result == SQLITE_OK){

        log("createDB() SUCCESS");

    }

    else

        log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

}

 

DB_INIT이 true이면 remove()를 실행하도록 하였습니다.

추후 필요에 따라 DB_INIT의 값을 변경해 테스트 해볼 수 있습니다.

 

디버거를 실행하여 에러가 나던 insertDB()에서 에러가 나는지 여러 번 재실행을 해보도록 합니다.

Figure 6‑23 로그

DB 파일을 삭제하고 다시 추가되었기 때문에 insertDB()에서 에러가 발생하지 않습니다.

실제 데이터를 저장했을 때에도 remove()가 실행되면 저장된 데이터가 삭제되므로 나중엔 꼭 DB_INIT을 false로 다시 정의해야 합니다.

 

create, insert, select의 사용법을 간단하게 익혔으니 실제 게임구현을 위해 테이블을 생성해보도록 하겠습니다.

 

먼저 DatabaseManager.cpp의 createDB()를 아래와 같이 수정합니다.

----------DatabaseManager.cpp----------

 

...생략…

 

//테이블 생성

void DatabaseManager::createDB(){

    if (DB_INIT){        //디비 초기화를 위한 개발자 변수값

        std::string path = FileUtils::getInstance()->getWritablePath() + DB_FILE_NAME;

        //데이터베이스의 파일이 열려있으면 삭제가 안되므로 데이터베이스를 닫아준다.

        closeDB();

        //db파일을 삭제

        remove(path.c_str());

        //데이터베이스를 닫았으므로 다시 열어준다.

        openDB();

    }

 

    for (int i = 0; i < 7; i++){

        std::string query;

        switch (i){

        case 0:

            //TB_FACE 테이블

            query = "create table if not exists TB_FACE(";

            break;

        case 1:

            //TB_HAIR1 테이블

            query = "create table if not exists TB_HAIR1(";

            break;

        case 2:

            //TB_HAIR2 테이블

            query = "create table if not exists TB_HAIR2(";

            break;

        case 3:

            //TB_EYE 테이블

            query = "create table if not exists TB_EYE(";

            break;

        case 4:

            //TB_MOUTH 테이블

            query = "create table if not exists TB_MOUTH(";

            break;

        case 5:

            //TB_ETC 테이블

            query = "create table if not exists TB_ETC(";

            break;

        case 6:

            //TB_BG 테이블

            query = "create table if not exists TB_BG(";

            break;

        }

 

        query += "NO integer primary key autoincrement, ";

 

        //이미지 파일명

        query += "IMG varChar(100), ";

 

        //해당 이미지의 x위치

        query += "X double default 0, ";

        //해당 이미지의 y위치

        query += "Y double default 0, ";

 

        //각 이미지마다 어울리는 색 4가지가 디폴트로 지정된다. 따라서 RGB값을 color에 따라 가지고있음

        query += "COLOR1_R interger default 0, ";

        query += "COLOR1_G interger default 0, ";

        query += "COLOR1_B interger default 0, ";

 

        query += "COLOR2_R interger default 0, ";

        query += "COLOR2_G interger default 0, ";

        query += "COLOR2_B interger default 0, ";

 

        query += "COLOR3_R interger default 0, ";

        query += "COLOR3_G interger default 0, ";

        query += "COLOR3_B interger default 0, ";

 

        query += "COLOR4_R interger default 0, ";

        query += "COLOR4_G interger default 0, ";

        query += "COLOR4_B interger default 0";

 

        query += ")";

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK){

            log("createDB() SUCCESS");

        }

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

}

 

7개의 테이블을 반복문인 for 구문과 swich ~ case 구문을 이용하여 생성하였습니다.

각 테이블의 구조는 같으며 NO(번호)와 IMG(파일명), X, Y(좌표), 색상값 4개를 가지고 있습니다.

 

7개의 테이블이 생성되었고, 테이블에 들어갈 값들을 insert 해보도록 하겠습니다.

DatabaseManager.cpp 파일의 insertDB()메서드를 아래와 같이 수정합니다.

 

----------DatabaseManager.cpp----------

 

...생략…

 

void DatabaseManager::insertDB(){

    {

        //얼굴 10개 row 추가

        std::string query = "insert into TB_FACE(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'face1.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(2, 'face2.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(3, 'face3.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(4, 'face4.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(5, 'face5.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(6, 'face6.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(7, 'face7.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(8, 'face8.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(9, 'face9.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219), ";

        query += "(10, 'face10.png', 156, 173, 254, 225, 194, 237, 188, 137, 184, 137, 105, 253, 242, 219)";

        //번호와 파일명을 제외한 모든 속성값 동일

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    {

        //hair1 10개 row 추가

        std::string query = "insert into TB_HAIR1(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'hair_1.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(2, 'hair_2.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(3, 'hair_3.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(4, 'hair_4.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(5, 'hair_5.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(6, 'hair_6.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(7, 'hair_7.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(8, 'hair_8.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(9, 'hair_9.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(10, 'hair_10.png', 156, 151.5, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34)";

        //번호와 파일명을 제외한 모든 속성값 동일

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    {

        //hair2 7개 row 추가

        std::string query = "insert into TB_HAIR2(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'hair_11.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(2, 'hair_12.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(3, 'hair_13.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(4, 'hair_14.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(5, 'hair_15.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(6, 'hair_16.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34), ";

        query += "(7, 'hair_17.png', 156, 157, 98, 59, 22, 170, 24, 13, 236, 183, 81, 62, 45, 34)";

        //번호와 파일명을 제외한 모든 속성값 동일

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    {

        //eye 10개 row 추가

        std::string query = "insert into TB_EYE(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'eye_1.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(2, 'eye_2.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(3, 'eye_3.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(4, 'eye_4.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(5, 'eye_5.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(6, 'eye_6.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(7, 'eye_7.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(8, 'eye_8.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(9, 'eye_9.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(10, 'eye_10.png', 156, 166.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)";

        //번호와 파일명을 제외한 모든 속성값 동일

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    {

        //mouth 10개 row 추가

        std::string query = "insert into TB_MOUTH(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'm_1.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(2, 'm_2.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(3, 'm_3.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(4, 'm_4.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(5, 'm_5.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(6, 'm_6.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(7, 'm_7.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(8, 'm_8.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(9, 'm_9.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(10, 'm_10.png', 156, 101.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)";

        //번호와 파일명을 제외한 모든 속성값 동일

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    {

        //etc 10개 row 추가

        std::string query = "insert into TB_ETC(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'etc_1.png', 156, 259, 234, 104, 119, 126, 206, 244, 189, 243, 57, 146, 7, 131), ";

        query += "(2, 'etc_2.png', 198, 118, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(3, 'etc_3.png', 156.5, 156.5, 234, 104, 119, 126, 206, 244, 189, 243, 57, 146, 7, 131), ";

        query += "(4, 'etc_4.png', 156.5, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(5, 'etc_5.png', 156, 272.5, 234, 104, 119, 126, 206, 244, 189, 243, 57, 146, 7, 131), ";

        query += "(6, 'etc_6.png', 156, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(7, 'etc_7.png', 156, 270, 234, 104, 119, 126, 206, 244, 189, 243, 57, 146, 7, 131), ";

        query += "(8, 'etc_8.png', 156, 267, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(9, 'etc_9.png', 156, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), ";

        query += "(10, 'etc_10.png', 156, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)";

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

 

    {

        //bg 4개 row 추가

        std::string query = "insert into TB_BG(NO, IMG, X, Y, COLOR1_R, COLOR1_G, COLOR1_B, COLOR2_R, COLOR2_G, COLOR2_B, COLOR3_R, COLOR3_G, COLOR3_B, COLOR4_R, COLOR4_G, COLOR4_B) ";

        query += "values (1, 'bg_1.png', 156, 155.5, 250, 237, 125, 171, 242, 0, 0, 216, 255, 243, 97, 166), ";

        query += "(2, 'bg_2.png', 156, 155.5, 250, 237, 125, 171, 242, 0, 0, 216, 255, 243, 97, 166), ";

        query += "(3, 'bg_3.png', 156, 155.5, 250, 237, 125, 171, 242, 0, 0, 216, 255, 243, 97, 166), ";

        query += "(4, 'bg_4.png', 156, 155.5, 250, 237, 125, 171, 242, 0, 0, 216, 255, 243, 97, 166)";

        //번호와 파일명을 제외한 모든 속성값 동일

 

        _result = sqlite3_exec(_sqlite, query.c_str(), NULL, NULL, &_errorMSG);

 

        if (_result == SQLITE_OK)

            log("insertDB() SUCCESS");

        else

            log("ERROR CODE : %d, ERROR MSG : %s", _result, _errorMSG);

    }

}

 

뭔가 코드가 매우 길며 복잡합니다. 하지만 TB_ETC를 제외한 모든 테이블은 NO와 IMG 컬럼값만 다르며 다른 값들은 동일합니다. 기존의 insertDB()의 내용을 삭제하고 코드를 수정하도록 합니다. 데이터베이스의 insert 쿼리의 형식을 생각하면서 작성하도록 합니다. 이런식으로 컬럼을 추가한 이유는 이미지 하나하나당 속성을 변경할 수 있게 구현하기 위함입니다. 이미 생성된 데이터베이스를 추가하는 방법도 있습니다.

 

insertDB() 메서드까지 수정이 되었다면 CharacterScene.cpp에서 최초 실행시에만 insertDB()메서드를 호출하도록 수정하겠습니다. 개발자 코드인 DB_INIT의 값이 true일때도 호출되도록 하겠습니다.

   

CharacterScene.cpp에 추가했던 데이터베이스 호출 코드를 아래와 같이 수정합니다.

----------CharacterScene.cpp----------

#include "CharacterScene.h"

#include "DatabaseManager.h"

#include "DevConf.h"

 

USING_NS_CC;

 

…생략…

 

// on "init" you need to initialize your instance

bool CharacterScene::init()

{

    //////////////////////////////

    // 1. super init first

    if (!Layer::init())

    {

        return false;

    }

 

    //code here

 

    //UserDefault를 가져옵니다.

    auto UserDefault = UserDefault::getInstance();

    //UserDefault에서 isFirst의 값을 가져옵니다.

    bool isFirst = UserDefault->getBoolForKey("isFirst", true);

    if (isFirst || DB_INIT){

        DatabaseManager::getInstance()->createDB();

        DatabaseManager::getInstance()->insertDB();

 

        //isFirst의 값을 변경합니다.

        UserDefault->setBoolForKey("isFirst", false);

        //UserDefault의 값을 변경하였으면 flush()를 호출해야 적용됩니다.

        UserDefault->flush();

    }

 

    /*****Device의 크기를 가져옵니다.*****/

    //Director를 가져옵니다.

    auto director = Director::getInstance();

    //OpenGLView를 가져옵니다.

    auto glView = director->getOpenGLView();

    //OpenGLView에서 DesignResolutionSize를 가져옵니다.

    auto winSize = glView->getDesignResolutionSize();

    /*****Device의 크기를 가져옵니다.*****/

 

UserDefault에는 어플에 값을 저장할 수 있습니다. 이 값은 데이터베이스처럼 복잡한 값을 갖는 것이 아니라 하나의 키값에 따른 결과값을 저장 할 수 있습니다.

위 소스에는 isFirst 라는 키에 bool 형식의 값을 저장했습니다.

bool isFirst = UserDefault->getBoolForKey("isFirst", true);

를 보면 isFirst를 가져오는데 최초엔 isFirst라는 키에 값이 존재하지 않아 두번째 매개변수를 디폴트 값으로 가져옵니다. 따라서 true값을 가져옵니다. 두번째 매개변수를 입력하지 않을 경우 디폴트 값은 false입니다.

if(isFirst || DB_INIT)

을 살펴보면 isFirst가 true거나 DB_INIT이 true이면 if 구문으로 들어가도록 구현했습니다.

DB_INIT이라는 개발자 테스트 변수값이 true이면 db를 새로 생성하도록 해야하므로 or 구문으로 연결하였습니다.

그리고 조건에 만족하면

DatabaseManager::getInstance()->createDB();

DatabaseManager::getInstance()->insertDB();

createDB()와 insertDB();를 호출하고

UserDefault->setBoolForKey("isFirst", false);

UserDefault->flush();

isFirst를 false로 지정하고 flush()를 실행합니다. 키에 값을 입력하여도 flush()를 호출하지 않으면 적용되지 않습니다. 꼭 값을 수정했으면 flush()를 해주시기 바랍니다.

UserDefault에 저장된 값도 데이터베이스가 저장된 위치와 동일한 위치에 저장됩니다.

Figure 6‑24 UserDefault.xml

 

그럼 디버거를 실행하여 로그를 살펴보도록 하겠습니다.

디버거를 실행하고 CharacterScene을 호출하여 로그를 봅니다.

Figure 6‑25 create / insert 쿼리 로그

createDB() SUCCESS가 7번이 출력되고 insertDB() SUCCESS가 7번이 출력되었습니다.

 

그러면 이제 다음 실행부턴 createDB()와 insertDB()가 실행이 안되도록 DB_INIT을 false로 바꾸어 봅니다.

----------DevConf.h----------

#define DB_INIT false

 

DevConf.h파일을 Open 하여 DB_INIT을 false로 바꾸어줍니다.

 

그다음 디버거를 실행하여 CharacterScene을 실행하고 로그를 봅니다.

createDB()와 insertDB()가 실행되지 않은 것을 확인 할 수 있습니다.

isFirst 값이 false로 저장되어있기 때문입니다.

DB는 한번 생성된 값을 이용하는 것이므로 최초 실행시에만 실행하면 됩니다.

 

그러면 이제 생성된 데이터베이스를 가져올 selectDB()구문을 수정해보도록 하겠습니다.

selectDB() 메서드 하나에서 매개변수를 이용하여 한번에 처리할 수 있도록 구현하겠습니다.

먼저 database.h파일을 수정하도록 합니다.

----------DatabaseManager.h----------

#include "sqlite3.h"

#include "cocos2d.h"

 

//USING_NS_CC;와 같습니다.

using namespace cocos2d;

 

//std의 네임스페이스를 사용하겠다고 정의합니다.

using namespace std;

 

struct head

{

public:

    int no;

    char *image;

 

    //Point에는 x와 y값이 들어갑니다.

    Point position;

 

    //Color3B에는 r, g, b값이 들어갑니다.

    Color3B color1;

    Color3B color2;

    Color3B color3;

    Color3B color4;

};

 

class DatabaseManager

{

private:

    bool openDB();

    void closeDB();

    sqlite3 *_sqlite;

 

public:

    DatabaseManager();

    ~DatabaseManager();

 

    //에러메시지를 담을 변수

    char *_errorMSG;

    //결과의 상태를 담을 변수

    int _result;

 

    static DatabaseManager *getInstance();

    void createDB();

    void insertDB();

    void selectDB();

 

    list<head*> selectDB(string table, int no);

};

 

std의 네임스페이스 지정을 생략하기 위해 using namespace std라고 네임스페이스를 지정합니다.

네임스페이스를 지정하지않으면 list와 string을 std::list와 std::string이라고 네임스페이스를 지정해서 호출해야 합니다.

 

head 라는 구조체를 선언하여 값을 받을수 있도록 수정합니다. selectDB에서 head 구조체 형식으로 값을 가져오도록 할 것 입니다.

 

selectDB()는 head라는 구조체의 리스트를 리턴값으로 넘기며 매개변수로 테이블 이름과 가져올 row 번호를 받도록 합니다. row번호가 0번이면 해당 테이블 모든 row를 0보다 작은 값이면 랜덤하게 하나의 row만 가져올 수 있도록 구현하겠습니다. random기능을 구현하기 위해서 매개변수를 이렇게 받도록 합니다.

 

정리하면

NO가 0보다 크면 테이블에서 해당 번호의 row를 가져오도록,

NO가 0이면 테이블의 모든 row를 가져오도록,

NO가 0보다 작으면 테이블의 row중 하나를 랜덤하게 가져오도록 구현하겠습니다.

 

DatabaseManager.h파일의 selectDB()를 수정했으면 DatabaseManager.cpp의 selectDB()를 수정하겠습니다.

 

----------DatabaseManager.cpp----------

 

...생략…

 

list<head*> DatabaseManager::selectDB(string table, int no){

    string query;

 

    if (no > 0){

        //no가 0보다 크면 해당번호를 가져온다.

        char temp[10];

        //temp에 no를 담는다.

        sprintf(temp, "%d", no);

        query = "select * from " + table + " where NO = " + temp;

    }

    else if (no < 0){

        //no가 0보다 작은값이 들어오면 랜덤하게 하나만 출력한다.(랜덤을 위한 기능)

        query = "select * from " + table + " order by random() limit 1";

    }

    else{

        //0이 들어오면 전체를 가져온다.

        query = "select * from " + table;

    }

 

    sqlite3_stmt *stmt = NULL;

    //stmt에 결과를 담는다.

    _result = sqlite3_prepare_v2(_sqlite, query.c_str(), query.length(), &stmt, NULL);

 

    list<head *> headList;

 

    if (_result == SQLITE_OK){

        log("selectDB() SUCCESS");

        while (sqlite3_step(stmt) == SQLITE_ROW){

            head *pHead = new head;

            //sqlite3_column_xxx는 첫번째 매개변수로 sqlite3_stmt를 받고 두번째 매개변수의 컬럼번호의 값을 가져온다.

            pHead->no = sqlite3_column_int(stmt, 0);

            pHead->image = strdup((char *)sqlite3_column_text(stmt, 1));

            pHead->position = Point(sqlite3_column_double(stmt, 2), sqlite3_column_double(stmt, 3));

            pHead->color1 = Color3B(sqlite3_column_int(stmt, 4), sqlite3_column_int(stmt, 5), sqlite3_column_int(stmt, 6));

            pHead->color2 = Color3B(sqlite3_column_int(stmt, 7), sqlite3_column_int(stmt, 8), sqlite3_column_int(stmt, 9));

            pHead->color3 = Color3B(sqlite3_column_int(stmt, 10), sqlite3_column_int(stmt, 11), sqlite3_column_int(stmt, 12));

            pHead->color4 = Color3B(sqlite3_column_int(stmt, 13), sqlite3_column_int(stmt, 14), sqlite3_column_int(stmt, 15));

 

            headList.push_back(pHead);

        }

    }

    else

        log("ERROR CODE : %d", _result);

 

    //sqlite3_stmt를 해제한다.

    sqlite3_finalize(stmt);

 

    return headList;

}

 

값을 저장할 headList 라는 list<head*> 형식의 변수를 생성하였고 no에 따라 쿼리를 다르게 호출하도록 하였습니다.

while문을 이용해 여러 개의 row가 있을경우 headList에 추가하여 값을 return 해주었습니다.

하나의 row가 있을경우 하나의 값만 추가되여 return 될 것입니다.   

 

selectDB()에 대한 테스트는 다음장에서 기능을 구현하면서 해보도록 하겠습니다.

 

여기서 알고 넘어가야 할 부분은 sprintf() 라는 메서드입니다.

sprintf()는 여러가지로 쓸 수 있는데요. 첫번째 매개변수에는 담을 포인터 변수, 두번째 매개변수에는 형식, 세번째 매개변수 부턴 형식에 맞는 값을 넣어주면 됩니다.

두번째 매개변수의 경우 로그에서 사용한 것처럼 값을 형식에 맞게 지정할 수 있습니다.

예를 들어 sprintf(temp, “%s”, “cocos2d-x”) 라고 지정하면 temp의 주소값에는 “cocos2d-x”라는 값이 들어갑니다. sprintf(temp, “%d”, 21)은 temp의 주소값에 “21” 이란 값이 들어갑니다. 조합해서 사용할 수도 있습니다. sprintf(temp, “%s : %d”, “x”, 21)은 temp의 주소값에 “x : 21” 이란 값이 들어갑니다. 이런식으로 문자열과 숫자들의 값을 받아 하나의 char변수에 담아 사용할 수 있습니다.

형식은 로그에서 사용하는것과 동일한 형식입니다.

 

이렇게 데이터베이스를 이용하여 create와 insert, select에 대해 구현해보았습니다.

sqlite는 서버나 다른용도의 데이터베이스와는 다르게 모바일에 맞게 축소된 데이터베이스이므로 동작하지 않는 기능이나 명령어가 있을 수 있으므로 확인하고 사용하는 것이 좋습니다.

 

6.1.4 캐릭터 생성 View Layer 구현하기

데이터베이스에 맞춰 화면에 이미지를 출력해보도록 하겠습니다.

 

먼저 CharacterScene.h파일에 setImage()메서드와 Sprite들을 선언해줍니다.

----------CharacterScene.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class CharacterScene : public cocos2d::Layer

{

public:

    //생성자

    CharacterScene();

    //소멸자

    ~CharacterScene();

 

    // there's no 'id' in cpp, so we recommend returning the class instance pointer

    static cocos2d::Scene* createScene();

 

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone

    virtual bool init();

 

    // implement the "static create()" method manually

    CREATE_FUNC(CharacterScene);

 

    Sprite *_characterBg;

 

    void onClickMenu(Ref *object);

    void onClickHome(Ref *object);

    void onClickRandom(Ref *object);

    void onClickSave(Ref *object);

    void onClickGallery(Ref *object);

 

    void setImage(std::string tableName, int rowNo);

 

    Sprite *_face;

    Sprite *_hair1;

    Sprite *_hair2;

    Sprite *_eye;

    Sprite *_mouth;

    Sprite *_etc;

    Sprite *_bgStyle;

};

 

스프라이트 객체를 생성자에서 초기화하기위해 클레스명과 동일한 메서드를 만들어줍니다.

이렇게 클레스명과 동일한 이름을 갖고있는 메서드를 생성자라고 합니다. 생성자의 앞에 ~가 있는 메서드는 소멸자라고 합니다. 생성자는 클레스가 생성될때 호출되며 소멸자는 클레스가 해제될때 호출됩니다.

setImage() 메서드는 테이블이름과 가져올 row번호를 매개변수로 받아 처리하도록 할 것입니다.

 

다음으로 CharacterScene.cpp 파일을 수정하도록 합니다.

----------CharacterScene.cpp----------

 

…생략…

 

CharacterScene::CharacterScene(){

    //객체를 초기화

    _face = NULL;

    _hair1 = NULL;

    _hair2 = NULL;

    _eye = NULL;

    _mouth = NULL;

    _etc = NULL;

    _bgStyle = NULL;

}

 

CharacterScene::~CharacterScene(){

 

}

 

// on "init" you need to initialize your instance

bool CharacterScene::init()

{

    //////////////////////////////

    // 1. super init first

    if (!Layer::init())

    {

        return false;

    }

 

    //code here

 

…생략…

 

 

    auto menu2 = Menu::create(menu2Home, menu2Random, menu2Save, menu2Gallery, NULL);

    menu2->setPosition(Point::ZERO);

    this->addChild(menu2);

 

    setImage("TB_FACE", -1);

    setImage("TB_HAIR1", -1);

    setImage("TB_HAIR2", -1);

    setImage("TB_EYE", -1);

    setImage("TB_MOUTH", -1);

    setImage("TB_ETC", -1);

    setImage("TB_BG", -1);

 

    return true;

}

 

…생략…

 

void CharacterScene::setImage(std::string tableName, int rowNo){

    //매개변수에 맞춰 headList를 가져온다.

    auto headList = DatabaseManager::getInstance()->selectDB(tableName, rowNo);

 

    //첫번째 구조체 가져오기, setImage()를 이용하면 하나의 row만 반환됩니다.

    auto head = headList.front();

 

    //zOrder 값

    int zOrder = 0;

 

    if (tableName == "TB_FACE"){

        //_face가 NULL이 아니면

        if (_face != NULL){

            //부모로부터 자기를 없앰

            _face->removeFromParentAndCleanup(true);

        }

 

        zOrder = 2;

 

        //데이터베이스에서 이미지의 파일명과 해당 파일의 좌표를 가져와 위치시킨다.

        _face = Sprite::create(head->image);

        _face->setPosition(head->position);

        _characterBg->addChild(_face, zOrder);

    }

    else if (tableName == "TB_HAIR1"){

        if (_hair1 != NULL){

            _hair1->removeFromParentAndCleanup(true);

        }

 

        zOrder = 4;

        _hair1 = Sprite::create(head->image);

        _hair1->setPosition(head->position);

        _characterBg->addChild(_hair1, zOrder);

    }

    else if (tableName == "TB_HAIR2"){

        if (_hair2 != NULL){

            _hair2->removeFromParentAndCleanup(true);

        }

 

        zOrder = 1;

        _hair2 = Sprite::create(head->image);

        _hair2->setPosition(head->position);

        _characterBg->addChild(_hair2, zOrder);

    }

    else if (tableName == "TB_EYE"){

        if (_eye != NULL){

            _eye->removeFromParentAndCleanup(true);

        }

 

        zOrder = 3;

        _eye = Sprite::create(head->image);

        _eye->setPosition(head->position);

        _characterBg->addChild(_eye, zOrder);

    }

    else if (tableName == "TB_MOUTH"){

        if (_mouth != NULL){

            _mouth->removeFromParentAndCleanup(true);

        }

 

        zOrder = 3;

        _mouth = Sprite::create(head->image);

        _mouth->setPosition(head->position);

        _characterBg->addChild(_mouth, zOrder);

    }

    else if (tableName == "TB_ETC"){

        if (_etc != NULL){

            _etc->removeFromParentAndCleanup(true);

        }

 

        zOrder = 5;

        _etc = Sprite::create(head->image);

        _etc->setPosition(head->position);

        _characterBg->addChild(_etc, zOrder);

    }

    else if (tableName == "TB_BG"){

        if (_bgStyle != NULL){

            _bgStyle->removeFromParentAndCleanup(true);

        }

 

        zOrder = 0;

        _bgStyle = Sprite::create(head->image);

        _bgStyle->setPosition(head->position);

        _characterBg->addChild(_bgStyle, zOrder);

    }

}

 

CharacterScene.cpp의 setImage()를 살펴보면 headList를 데이터베이스에서 가져와 테이블 이름에 맞춰 Sprite를 제어합니다.

각 Sprite마다 zOrder를 다르게 구현해야 하므로 zOrder변수를 이용하여 제어해줍니다.

그리고 해당 객체가 이미 존재하면 화면에서 지워주는 로직이 들어있습니다. 이로직은 추후 해당 이미지를 수정해야할 경우 지워버리고 다시 생성해버리는 로직으로 동작합니다.

 

init() 하단에 setImage를 각 이미지별로 테이블명과 가져올 rowNo를 지정해 호출합니다.

rowNo에 -1을 넣은 이유는 -1이 들어오면 랜덤하게 하나 가져오도록 selectDB()를 구현하였으므로 -1을 입력해줍니다. 원하는 번호를 지정해주면 해당 번호의 row를 가져오도록 되어있습니다.

 

디버거를 실행하여 확인해보도록 합니다.

    

Figure 6‑26 실행화면

 

실행할때마다 다른 이미지를 랜덤하게 가져오는 것을 알 수 있습니다.

[6.1.3]에서 생성한 selectDB()가 정상적으로 동작하는 것을 확인할 수 있습니다.

 

하지만 아직 색상이 지정되지 않았으므로 이번엔 색상을 추가하도록 해보겠습니다.

 

먼저 색상 수정이 가능한지 불가능한지 체크할 수 있는 변수를 하나 만들도록 하겠습니다.

head 구조체에 isColor 변수를 추가하도록 합니다.

----------DatabaseManager.h-----------

#include "sqlite3.h"

#include "cocos2d.h"

 

//USING_NS_CC;와 같습니다.

using namespace cocos2d;

 

//std의 네임스페이스를 사용하겠다고 정의합니다.

using namespace std;

 

struct head

{

public:

    int no;

    char *image;

 

    //Point에는 x와 y값이 들어갑니다.

    Point position;

 

    //Color3B에는 r, g, b값이 들어갑니다.

    Color3B color1;

    Color3B color2;

    Color3B color3;

    Color3B color4;

 

    //색상 변경 관련 체크 변수

    bool isColor;

};

변수를 추가했으니 관련 변수의 값을 체크할 로직을 추가하도록 합니다.

 

DatabaseManager.cpp의 selectDB() 부분을 수정하도록 합니다.

----------DatabaseManager.cpp----------

 

…생략…

 

list<head*> DatabaseManager::selectDB(string table, int no){

    string query;

 

    if (no > 0){

        //no가 0보다 크면 해당번호를 가져온다.

        char temp[10];

        //temp에 no를 담는다.

        sprintf(temp, "%d", no);

        query = "select * from " + table + " where NO = " + temp;

    }

    else if (no < 0){

        //no가 0보다 작은값이 들어오면 랜덤하게 하나만 출력한다.(랜덤을 위한 기능)

        query = "select * from " + table + " order by random() limit 1";

    }

    else{

        //0이 들어오면 전체를 가져온다.

        query = "select * from " + table;

    }

 

    sqlite3_stmt *stmt = NULL;

    //stmt에 결과를 담는다.

    _result = sqlite3_prepare_v2(_sqlite, query.c_str(), query.length(), &stmt, NULL);

 

    list<head *> headList;

 

    if (_result == SQLITE_OK){

        log("selectDB() SUCCESS");

        while (sqlite3_step(stmt) == SQLITE_ROW){

            head *pHead = new head;

            //sqlite3_column_xxx는 첫번째 매개변수로 sqlite3_stmt를 받고 두번째 매개변수의 컬럼번호의 값을 가져온다.

            pHead->no = sqlite3_column_int(stmt, 0);

            pHead->image = strdup((char *)sqlite3_column_text(stmt, 1));

            pHead->position = Point(sqlite3_column_double(stmt, 2), sqlite3_column_double(stmt, 3));

            pHead->color1 = Color3B(sqlite3_column_int(stmt, 4), sqlite3_column_int(stmt, 5), sqlite3_column_int(stmt, 6));

            pHead->color2 = Color3B(sqlite3_column_int(stmt, 7), sqlite3_column_int(stmt, 8), sqlite3_column_int(stmt, 9));

            pHead->color3 = Color3B(sqlite3_column_int(stmt, 10), sqlite3_column_int(stmt, 11), sqlite3_column_int(stmt, 12));

            pHead->color4 = Color3B(sqlite3_column_int(stmt, 13), sqlite3_column_int(stmt, 14), sqlite3_column_int(stmt, 15));

 

            //isColor가 true면 색상값을 변경할 수 있다.

            pHead->isColor = true;

 

            //eye는 색상변경 불가

            if (table == "TB_EYE")

                pHead->isColor = false;

            //mouth는 색상변경 불가

            else if (table == "TB_MOUTH")

                pHead->isColor = false;

            else if (table == "TB_ETC"){

                //ETC는 2, 4, 6, 8, 9, 10 번만 색상 변경불가

                if (pHead->no == 2 || pHead->no == 4 || pHead->no == 6 || pHead->no == 8 || pHead->no == 9 || pHead->no == 10)

                    pHead->isColor = false;

            }

 

            headList.push_back(pHead);

        }

    }

    else

        log("ERROR CODE : %d", _result);

 

    //sqlite3_stmt를 해제한다.

    sqlite3_finalize(stmt);

 

    return headList;

}

 

eye와 mouth는 색상변경이 불가능하고 etc는 2, 4, 6, 8, 9, 10번이 색상변경이 불가능 하므로 조건문을 추가하여 체크해주었습니다.

 

setImage()에 colorNo를 입력받을 수 있도록 매개변수를 추가해줍니다.

----------CharacterScene.h----------

#include "cocos2d.h"

 

USING_NS_CC;

 

class CharacterScene : public cocos2d::Layer

{

public:

    //생성자

    CharacterScene();

    //소멸자

    ~CharacterScene();

 

    // there's no 'id' in cpp, so we recommend returning the class instance pointer

    static cocos2d::Scene* createScene();

 

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone

    virtual bool init();

 

    // implement the "static create()" method manually

    CREATE_FUNC(CharacterScene);

 

    Sprite *_characterBg;

 

    void onClickMenu(Ref *object);

    void onClickHome(Ref *object);

    void onClickRandom(Ref *object);

    void onClickSave(Ref *object);

    void onClickGallery(Ref *object);

 

    void setImage(std::string tableName, int rowNo, int colorNo);

 

    Sprite *_face;

    Sprite *_hair1;

    Sprite *_hair2;

    Sprite *_eye;

    Sprite *_mouth;

    Sprite *_etc;

    Sprite *_bgStyle;

};

 

헤더파일을 수정하였으니 CharacterScene.cpp 파일의 setImage()도 수정하도록 합니다.

 

----------CharacterScene.cpp----------

 

…생략…

 

 

    auto menu2 = Menu::create(menu2Home, menu2Random, menu2Save, menu2Gallery, NULL);

    menu2->setPosition(Point::ZERO);

    this->addChild(menu2);

 

    setImage("TB_FACE", -1, -1);

    setImage("TB_HAIR1", -1, -1);

    setImage("TB_HAIR2", -1, -1);

    setImage("TB_EYE", -1, -1);

    setImage("TB_MOUTH", -1, -1);

    setImage("TB_ETC", -1, -1);

    setImage("TB_BG", -1, -1);

 

    return true;

}

 

…생략…

 

void CharacterScene::setImage(std::string tableName, int rowNo, int colorNo){

 

…생략…

 

    else if (tableName == "TB_BG"){

        if (_bgStyle != NULL){

            _bgStyle->removeFromParentAndCleanup(true);

        }

 

        zOrder = 0;

        _bgStyle = Sprite::create(head->image);

        _bgStyle->setPosition(head->position);

        _characterBg->addChild(_bgStyle, zOrder);

    }

 

    if (head->isColor){

        Color3B color;

 

        if (colorNo < 0){

            //srand로 rand의 기준되는 값을 변경하지 않으면 같은 패턴으로 반복된다.

            srand(time(NULL));

            //1~4사이의 값을 랜덤하게 발생시킴

            colorNo = rand() % 4 + 1;

        }

 

        switch (colorNo)

        {

        case 1:

            color = head->color1;

            break;

        case 2:

            color = head->color2;

            break;

        case 3:

            color = head->color3;

            break;

        case 4:

            color = head->color4;

            break;

        }

 

        if (tableName == "TB_FACE")

            _face->setColor(color);

        else if (tableName == "TB_HAIR1")

            _hair1->setColor(color);

        else if (tableName == "TB_HAIR2")

            _hair2->setColor(color);

        else if (tableName == "TB_EYE")

            _eye->setColor(color);

        else if (tableName == "TB_MOUTH")

            _mouth->setColor(color);

        else if (tableName == "TB_ETC")

            _etc->setColor(color);

        else if (tableName == "TB_BG")

            _bgStyle->setColor(color);

 

    }

}

 

colorNo도 0보다 작은 값이 들어오면 랜덤하게 선택하고 colorNo가 0보다 큰값이 들어오면 해당 색상번호를 가져오도록 수정하였습니다.

 

관련 색상을 수정할 수 있도록 코드를 수정하였습니다.

setColor() 메서드를 이용하면 해당 스프라이트의 색상을 변경할 수 있습니다. 색상을 변경할 수 있는 이미지들의 경우 색상을 변경할 수 있는 부분의 색상이 하얀색으로 되어있기 때문에 setColor()를 이용하여 색상을 수정하면 해당 색상이 하얀색과 합쳐집니다.

 

디버거를 실행하여 확인해보도록 합니다.

     

Figure 6‑27 실행화면

실행할 때 마다 다른 이미지가 생성되는 것을 볼 수 있습니다.

 

rand()을 사용할 때 주의 사항은 srand()를 이용하여 기준값을 변경하지 않으면 같은 패턴이 반복되기때문에 srand()를 이용해 기준되는 seed값을 변경해야 패턴이 반복되지 않습니다.

 

    

Figure 6‑28 srand()로 seed값을 변경하기 전

 

반복해서 실행해보아도 같은색의 얼굴색, 초록색 배경, 진한 갈색 앞머리, 연한 갈색 뒷머리만 들어가는 것을 볼 수 있습니다. rand()로 가져오는 값이 같기 때문에 패턴이 반복되기 때문입니다.

 

    

Figure 6‑29 srand()로 seed값을 변경한 후

srand()를 사용하고나면 rand()의 기준값이 바뀌어 패턴이 바뀌게 된다. 따라서 같은 패턴의 값이 나오지 않습니다.


Prev | Next