明论  

 

 

 

1,让我们先从主界面学起,TASpring主界面生成。

当进入TASpring我们可以看到如下主界面,

 


在探究主界面的生成的过程中,我们可以观察到该项目图形界面(UI)生成的基本架构。

我们可以通过代码查在PreGame.Cpp中找到如下方法:

void CPreGame::ShowMapList()

{

      CglList* list = SAFE_NEW CglList("Select map", SelectMap, 2);

      std::vector<std::string> found = filesystem.FindFiles("maps/","{*.sm3,*.smf}");

      std::vector<std::string> arFound = archiveScanner->GetMaps();

      if (found.begin() == found.end() && arFound.begin() == arFound.end()) {

           handleerror(0, "Couldn't find any map files", "PreGame error", 0);

           return;

      }

 

      std::set<std::string> mapSet; // use a set to sort them

      for (std::vector<std::string>::iterator it = found.begin(); it != found.end(); it++) {

           std::string fn(filesystem.GetFilename(*it));

           mapSet.insert(fn.c_str());

      }

      for (std::vector<std::string>::iterator it = arFound.begin(); it != arFound.end(); it++) {

           mapSet.insert((*it).c_str());

      }

 

      list->AddItem("Random map", "Random map"); // always first

      for (std::set<std::string>::iterator sit = mapSet.begin(); sit != mapSet.end(); ++sit) {

           list->AddItem(sit->c_str(), sit->c_str());

      }

      showList = list;

}

请注意看这句 CglList* list = SAFE_NEW CglList("Select map", SelectMap, 2);

CglList是Rendering"GL中的一个类,我们可以看到在Rendering"GL将CgList封装成一个控件。

以下是CgList定义的头文件。

class CglList : public CInputReceiver

{

public:

 

      // CInputReceiver implementation

      bool KeyPressed(unsigned short k, bool isRepeat);

      bool MousePress(int x, int y, int button);

      void MouseMove(int x, int y, int dx,int dy, int button);

      void MouseRelease(int x, int y, int button);

      bool IsAbove(int x, int y);

      void Draw();

      std::string GetTooltip(int x,int y) { return tooltip; }

 

      // CglList functions

      void Select();

      void AddItem(const char* name,const char* description);

      CglList(const char* name, ListSelectCallback callback, int id = 0);

      virtual ~CglList();

      int place;

      std::vector<std::string> items;

      std::string name;

 

      std::string lastChoosen;

      ListSelectCallback callback;

      // when attempting to cancel (by pressing escape, clicking outside a button)

      // place is set to cancelPlace (if it's positive) and Select is called.

      int cancelPlace;

      std::string tooltip;

 

private:

      bool Filter(bool reset);

      void UpOne();

      void DownOne();

      void UpPage();

      void DownPage();

      bool MouseUpdate(int x, int y);

 

      // GUI

      ContainerBox box;

      bool activeMousePress;

 

      // used to save default to configHandler

      int id;

 

      // for filtering

      std::string query;

      std::vector<std::string>* filteredItems;

      std::vector<std::string> temp1;

      std::vector<std::string> temp2;

};

我们几乎可以看到如同Winfrom控件一样完善的定义,属性,图形生成,用户事件。我们先考察他的图形生成。

以下是Cglist中的Draw();方法。

void CglList::Draw()

{

      // dont call Mouse[XY] if we're being used in CPreGame (when gu or mouse is still NULL)

      float mx = (gu && mouse) ? MouseX(mouse->lastx) : 0;

      float my = (gu && mouse) ? MouseY(mouse->lasty) : 0;

 

      glDisable(GL_TEXTURE_2D);

      glDisable(GL_ALPHA_TEST);

      glEnable(GL_BLEND);

      glLoadIdentity();

      glColor4f(0.2f,0.2f,0.2f,guiAlpha);

      DrawBox(box);

 

      glColor4f(1,1,0.4f,0.8f);

      glLoadIdentity();

      glTranslatef(box.x1 + 0.01f, box.y2 - 0.05f, 0.0f);

      glScalef(0.035f*0.7f,0.05f*0.7f,0.1f);

      font->glPrint(name.c_str());

      int nCurIndex = 0; // The item we're on

      int nDrawOffset = 0; // The offset to the first draw item

      ContainerBox b = box;

 

      b.x1 += 0.01f;

      b.x2 -= 0.01f;

 

      // Get list started up here

      std::vector<std::string>::iterator ii = filteredItems->begin();

      // Skip to current selection - 3; ie: scroll

      while ((nCurIndex + 7) <= place && nCurIndex+13 <= filteredItems->size()) { ii++; nCurIndex++; }

 

      for (/*ii = items.begin()*/; ii != filteredItems->end() && nDrawOffset < 12; ii++)

      {

           glLoadIdentity();

 

           b.y2 = box.y2 - 0.06f - (nDrawOffset * 0.06f);

           b.y1 = b.y2 - 0.05f;

 

           // TODO lots of this should really be merged with GuiHandler.cpp

           // (as in, factor out the common code...)

 

           glColor4f(1,1,1,0.1f);

           DrawBox(b, GL_LINE_LOOP);

 

           if (nCurIndex == place) {

                 glBlendFunc(GL_ONE, GL_ONE); // additive blending

                 glColor4f(0.2f,0,0,1);

                 DrawBox(b);

                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

                 glColor4f(1,0,0,0.5f);

                 glLineWidth(1.49f);

                 DrawBox(b, GL_LINE_LOOP);

                 glLineWidth(1.0f);

           } else if (InBox(mx, my, b)) {

                 glBlendFunc(GL_ONE, GL_ONE); // additive blending

                 glColor4f(0,0,0.2f,1);

                 DrawBox(b);

                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

                 glColor4f(1,1,1,0.5f);

                 glLineWidth(1.49f);

                 DrawBox(b, GL_LINE_LOOP);

                 glLineWidth(1.0f);

           }

 

           const float dShadow = 0.002f;

           const float xStart = box.x1 + 0.02f;

           const float yStart = box.y2 - 0.11f - (nDrawOffset * 0.06f);

 

           glColor4f(0.0f, 0.0f, 0.0f, 0.8f);

           glTranslatef(xStart + dShadow, yStart - dShadow, 0.0f);

           glScalef(0.035f, 0.05f, 0.1f);

           font->glPrint(ii->c_str());

 

           glLoadIdentity();

           glColor4f(1,1,1,0.8f);

           glTranslatef(xStart, yStart, 0.0f);

           glScalef(0.035f,0.05f,0.1f);

           font->glPrint(ii->c_str());

 

           // Up our index's

           nCurIndex++; nDrawOffset++;

      }

      /**************

      * End insert *

      **************/

      glLoadIdentity();

}

至此我们以找到了界面生成的原理,从文件中读出列表再,调用OPGL的API生成界面。我们继续查看他的事件,就是当用户点击按钮是,由什么代码作出处理。

我们可以看到,PreGame.cpp中有如下方法

void CPreGame::SelectMap(std::string s)

{

      if (s == "Random map") {

           s = pregame->showList->items[1 + gu->usRandInt() % (pregame->showList->items.size() - 1)];

      }

      stupidGlobalMapname = pregame->mapName = s;

      delete pregame->showList;

      pregame->showList = 0;

      logOutput << "Map: " << s.c_str() << ""n";

      if (net)

           // inform CNetProtocol about our map (for demo writing)

           net->SendMapName(archiveScanner->GetMapChecksum(pregame->mapName), pregame->mapName);

}

将Map的名称传入函数之中作相应处理,那么这个方法在什么时候调用的,让我们回到,之前的这句CgList* list的代码,

CglList* list = SAFE_NEW CglList("Select map", SelectMap, 2);

他将这个方法的函数指针传入Cglist的定义之中,让我们来查看Cglist的构造函数:

CglList::CglList(const char *name, ListSelectCallback callback, int id) :

           place(0),

           name(name),

           callback(callback),

           cancelPlace(-1),

           tooltip("No tooltip defined"),

           activeMousePress(false),

           id(id),

           filteredItems(&items)

{

      char buf[50];

      sprintf(buf, "LastListChoice%i", id);

      lastChoosen=configHandler.GetString(buf, "");

      box.x1 = 0.3f;

      box.y1 = 0.1f;

      box.x2 = 0.7f;

      box.y2 = 0.9f;

}

在构造函数中对CglList的属性callback进行赋值,这里的ListSelectCallback其实是前面定义的

typedef void (* ListSelectCallback) (std::string selected);其实是标准库字符串类型的指针。
void CglList::Select()

{

      if (!filteredItems->empty())

           callback((*filteredItems)[place]);

}

在CglList::KeyPressed中对Select注册,

bool CglList::KeyPressed(unsigned short k, bool isRepeat)

{

      if (k == SDLK_ESCAPE) {

           if (cancelPlace >= 0) {

                 place = cancelPlace;

                 Select();

                 return true;

           }

      } else if (k == SDLK_UP) {

           UpOne();

           return true;

      } else if (k == SDLK_DOWN) {

           DownOne();

           return true;

      } else if (k == SDLK_PAGEUP) {

           UpPage();

           return true;

      } else if (k == SDLK_PAGEDOWN) {

           DownPage();

           return true;

      } else if (k == SDLK_RETURN) {

           Select();

           return true;

      } else if (k == SDLK_BACKSPACE) {

           query = query.substr(0, query.length() - 1);

           return Filter(true);

      } else if ((k & ~0xFF) != 0) {

           // This prevents isalnum from asserting on msvc debug crt

           // We don't actually need to process the key tho;)

      } else if (isalnum(k)) {

           query += tolower(k);

           return Filter(false);

      }

      return false;

}

在CPreGame对Keypressed进行注册,当然在main.cpp中这个动作还会在更底层的SDL库进行注册,这里就不一一追溯了

int CPreGame::KeyPressed(unsigned short k,bool isRepeat)

{

      if (k == SDLK_ESCAPE){

           if(keys[SDLK_LSHIFT]){

                 logOutput.Print("User exited");

                 globalQuit=true;

           } else

                 logOutput.Print("Use shift-esc to quit");

      }

      if(showList){    //are we currently showing a list?

           showList->KeyPressed(k, isRepeat);

           return 0;

      }

 

      if (userWriting){

           keys[k] = true;

           if (k == SDLK_v && keys[SDLK_LCTRL]){

                 CClipboard clipboard;

                 const string text = clipboard.GetContents();

                 userInput.insert(writingPos, text);

                 writingPos += text.length();

                 return 0;

           }

           if(k == SDLK_BACKSPACE){

                 if (!userInput.empty() && (writingPos > 0)) {

                      userInput.erase(writingPos - 1, 1);

                      writingPos--;

                 }

                 return 0;

           }

           if(k == SDLK_DELETE){

                 if (!userInput.empty() && (writingPos < (int)userInput.size())) {

                      userInput.erase(writingPos, 1);

                 }

                 return 0;

           }

           else if(k==SDLK_LEFT) {

                 writingPos = max(0, min((int)userInput.length(), writingPos - 1));

           }

           else if(k==SDLK_RIGHT) {

                 writingPos = max(0, min((int)userInput.length(), writingPos + 1));

           }

           else if(k==SDLK_HOME) {

                 writingPos = 0;

           }

           else if(k==SDLK_END) {

                 writingPos = (int)userInput.length();

           }

           if(k == SDLK_RETURN){

                 userWriting=false;

                 return 0;

           }

           return 0;

      }

return 0;

}

至此,我们对开始游戏菜单操作就有了基本的了解了,其实其它的商业游戏也是差不多这样,我们很容易可以修改以下CglList::Draw()

的方法,让他加载质材,增加KeyPressed的逻辑,让他像冰封王座一样的炫目。

posted on 2008-08-05 14:40  konyel  阅读(869)  评论(0)    收藏  举报