1,让我们先从主界面学起,TASpring主界面生成。
当进入TASpring我们可以看到如下主界面,
我们可以通过代码查在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的逻辑,让他像冰封王座一样的炫目。

浙公网安备 33010602011771号