//board.h#ifndef __BOARD_H__#define __BOARD_H__#include "SexyAppFramework/Widget.h"#include "SexyAppFramework/ButtonListener.h" // 本例子我们将学习几个新的部件: 输入框. 单选框. 列表框. 所以要用到这些部件的事件监听器类.#include "SexyAppFramework/EditListener.h"#include "SexyAppFramework/CheckboxListener.h"#include "SexyAppFramework/ListListener.h" class Graphics; class GameApp;// 声明几个将要使用的部件类class ButtonWidget;class EditWidget;class Checkbox;class ListWidget;class ScrollbarWidget; class WidgetManager; class Board : public Widget, public ButtonListener, public EditListener, public CheckboxListener, public ListListener{ GameApp* mApp; ButtonWidget* mButton1; ButtonWidget* mButton2; EditWidget* mEditWidget; Checkbox* mCheckboxWidget; ListWidget* mListWidget; ScrollbarWidget* mScrollbarWidget; SexyString mText; //两个运动图片的X坐标. float mMotionX; float mUpdateFMotionX; public: Board(GameApp* theApp); virtual ~Board(); ////////////////////////////////////////////////////////////////////////// // 从输入框事件监听器的函数 // 在编辑框输入时按下 回车 键会自动调用该消息. void EditWidgetText(int theId, const std::string& theString); ////////////////////////////////////////////////////////////////////////// // 从编辑框事件监听器继承的函数 // 当有任何 字母 被输入时会自动调用该函数. // 返回值为 true 表示输入的字符被加入到原来的字符串. 返回false表示不加入. bool AllowChar(int theId, char theChar); ////////////////////////////////////////////////////////////////////////// // 从单选框事件监听器继承的函数 // 当选择/取消事件发生时会自动调用该函数. void CheckboxChecked(int theId, bool checked) {;} ////////////////////////////////////////////////////////////////////////// // 从列表框事件监听器继承的函数. // 当点击列表框中的子项时会自动调用该函数. // 参数为 列表框ID, 选择第几个子项, 鼠标的哪个键在点击 void ListClicked(int theId, int theIdx, int theClickCount); virtual void Draw(Graphics* g); virtual void Update(); ////////////////////////////////////////////////////////////////////////// // Function: UpdateF // Parameters: // theFrac - The number of updates this time slice represents. // // Returns: none // // Purpose: // There has been a fundamental temporal aliasing issue in the previous // demos because games run at a 100 Hz Update rate while the user's monitor // is refreshing at some other rate, generally between 60 and 85 Hz. The fixed // 100 Hz Update rate is convenient because it allows game logic to proceed // independantly from the refresh rate, but in some cases it's worth the extra // trouble of updating at a variable rate in order to provide smoother animation, // as in the case of a scrolling background, a shark with words written on it, // or an Arkanoid ball. // // To illustrate the aliasing problem, imagine a ball that is supposed to move // 200 pixels per second, running on a 75 Hz monitor. The update rate of the // game is 100 Hz, so that means that we will add 2 pixels to the ball position // every update, and there will be 1.33 updates per monitor refresh (on average). // That means that that 2 out of every 3 monitor refreshes will show the ball // moving 2 pixels, and and the third will show it moving 4 pixels. That isn't // smooth motion. The correct solution would be for the ball to move 2.67 // pixels every monitor refresh. But how do we do that? // // To support smooth motion, we use UpdateF. Widget::UpdateF is similar to // Widget::Update, but Widget::UpdateF gets a float passed into it that // represents how many Update's this time slice represents. In the 75 Hz // example, UpdateF would always be called with 1.33. Update has certainly // not been made obsolete, however, and you can choose which // parts of your game logic should be in Update and which should be in // UpdateF. To facilitate cooperation and good behavior between the two // update methods, there are some rules they follow: Updating always occurs // in blocks, with one or two Update calls followed immediately with an // UpdateF call. This means that the application will never get the chance // to draw or process input between an Update and a Draw without calling // UpdateF in the middle. Therefore, you can assume that focus won't be // lost, nor will input change between an Update and an UpdateF, and you'll // know that you'll have a chance to finalize your state in UpdateF so things // can be left dangling (whatever that means for your app) after Update. // You are also guaranteed that the value passed in to UpdateF will be between // 1.67 (for a 60 Hz monitor) and 1.0 (for a 100 Hz monitor). Even if the // monitor is 60 Hz but the computer is only fast enough to draw at 30 FPS // you will get two Update blocks in a row before the draw, so it will still // appear to your app as if you are updating at 60 Hz. // // IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT // // In order to fully use this, you need to set up a few things. // Set GameApp::mVSyncUpdates to true, override UpdateF(float theFrac), // and move some code from Update that used to look like // this: "mPos += 1.5;", changing it to "mPos += 1.5 * theFrac;". // Check out the C++ code for an example of motion using both Update and // UpdateF. // // IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT // // Because UpdateF is called a variable number of times per second, // you do NOT want to put game logic in it that needs to remain framerate // independant. Use UpdateF ONLY for movement related operations, and not // for your main game code. // // If you really want to avoid shearing in windowed mode, you can // set GameApp::mWaitForVSync to true and set GameApp::mSoftVSyncWait // to false. NOTE: This winds up doing some busy waiting and consumes // more processor time. // IMPORTANT: YOU MUST ALSO DELETE THE FOLLOWING REGISTRY KEY: // Whereever your registry settings are stored // (HKEY_LOCAL_MACHINE\SOFTWARE\SexyAppFramework\Demo4 for this case), // you must delete the key "WaitForVSync". This is VERY important, and it // won't work otherwise. ////////////////////////////////////////////////////////////////////////// virtual void UpdateF(float theFrac); virtual void ButtonDepress(int theId); virtual void AddedToManager(WidgetManager* theWidgetManager); virtual void RemovedFromManager(WidgetManager* theWidgetManager); }; #endif // __BOARD_H__//gameapp.h#ifndef __GAMEAPP_H__#define __GAMEAPP_H__ #include "SexyAppFramework/SexyAppBase.h" class Board; // 为游戏增加前导屏幕而定义的类 class TitleScreen;class GameApp : public SexyAppBase{ Board* mBoard; TitleScreen* mTitleScreen; public: GameApp(); virtual ~GameApp(); virtual void Init(); virtual void LoadingThreadProc(); virtual void LoadingThreadCompleted(); ////////////////////////////////////////////////////////////////////////// // 在前导屏幕结束时被 TitleScreen 类调用. 然后撤销掉前导屏幕部件. 并加载Board部件. void TitleScreenIsFinished(); ////////////////////////////////////////////////////////////////////////// // 检查主函数的参数 virtual void HandleCmdLineParam(const std::string& theParamName, const std::string& theParamValue);}; #endif // __GAMEAPP_H__//res.h#ifndef __Res_H__#define __Res_H__namespace Sexy{ class ResourceManager; class Image; class Font; Image* LoadImageById(ResourceManager *theManager, int theId); bool ExtractResourcesByName(ResourceManager *theManager, const char *theName); // Game Resources bool ExtractGameResources(ResourceManager *theMgr); extern Image* IMAGE_BG0; extern Image* IMAGE_BG1; extern Image* IMAGE_BG2; extern Image* IMAGE_BUTTON_DOWN; extern Image* IMAGE_BUTTON_NORMAL; extern Image* IMAGE_BUTTON_OVER; extern Image* IMAGE_CHECKBOX; extern Image* IMAGE_DIALOG_BOX; extern Image* IMAGE_DIALOG_BUTTON; extern Image* IMAGE_ROBOTROBOT; extern Image* IMAGE_SLIDER_THUMB; extern Image* IMAGE_SLIDER_TRACK; extern int SOUND_MUTATOR; extern int SOUND_TIMER; // Hungarr Resources bool ExtractHungarrResources(ResourceManager *theMgr); extern Image* IMAGE_ATOMIC_EXPLOSION; extern Image* IMAGE_BOMB_RADIAL_DEATH; extern Image* IMAGE_HUNGARR_BEAM_DOWN; extern Image* IMAGE_HUNGARR_BEAM_LEFT; extern Image* IMAGE_HUNGARR_BEAM_RIGHT; extern Image* IMAGE_HUNGARR_BEAM_UP; extern Image* IMAGE_HUNGARR_HORIZ; extern Image* IMAGE_HUNGARR_SMALL; extern Image* IMAGE_HUNGARR_VERT; extern Image* IMAGE_PARTICLE_LIGHTNING; extern Image* IMAGE_PLANETS; extern Image* IMAGE_SPARK; extern int SOUND_BEAM_HIT; extern int SOUND_BEAM_MOVING; extern int SOUND_BUTTON; extern int SOUND_EXPLOSION; extern int SOUND_GAME_OVER_CLICK; extern int SOUND_GAME_OVER_RESTART; extern int SOUND_GAME_OVER_STATS; extern int SOUND_GAME_OVER_TEXT; extern int SOUND_LEVEL_UP1; extern int SOUND_LEVEL_UP2; extern int SOUND_LEVEL_UP3; extern int SOUND_LEVEL_UP4; extern int SOUND_MAGZAP; extern int SOUND_PLANET; extern int SOUND_PLANET_HIT; extern int SOUND_REGION_FILLED; // Init Resources bool ExtractInitResources(ResourceManager *theMgr); extern Font* FONT_DEFAULT; extern Font* FONT_HUNGARR; extern Image* IMAGE_CUSTOM_DRAGGING; extern Image* IMAGE_CUSTOM_HAND; extern Image* IMAGE_CUSTOM_POINTER; extern Image* IMAGE_CUSTOM_TEXT; extern Image* IMAGE_HUNGARR_LOGO; // TitleScreen Resources bool ExtractTitleScreenResources(ResourceManager *theMgr); extern Image* IMAGE_LOADER_BAR; extern Image* IMAGE_LOADER_LOADINGTXT; extern int SOUND_CONTINUE; enum ResourceId { FONT_DEFAULT_ID, FONT_HUNGARR_ID, IMAGE_CUSTOM_POINTER_ID, IMAGE_CUSTOM_HAND_ID, IMAGE_CUSTOM_DRAGGING_ID, IMAGE_CUSTOM_TEXT_ID, IMAGE_HUNGARR_LOGO_ID, IMAGE_LOADER_BAR_ID, IMAGE_LOADER_LOADINGTXT_ID, SOUND_CONTINUE_ID, SOUND_MUTATOR_ID, SOUND_TIMER_ID, IMAGE_ROBOTROBOT_ID, IMAGE_CHECKBOX_ID, IMAGE_BG0_ID, IMAGE_BG1_ID, IMAGE_BG2_ID, IMAGE_BUTTON_DOWN_ID, IMAGE_BUTTON_OVER_ID, IMAGE_BUTTON_NORMAL_ID, IMAGE_DIALOG_BOX_ID, IMAGE_DIALOG_BUTTON_ID, IMAGE_SLIDER_TRACK_ID, IMAGE_SLIDER_THUMB_ID, IMAGE_HUNGARR_SMALL_ID, IMAGE_HUNGARR_BEAM_UP_ID, IMAGE_HUNGARR_BEAM_DOWN_ID, IMAGE_HUNGARR_BEAM_LEFT_ID, IMAGE_HUNGARR_BEAM_RIGHT_ID, IMAGE_HUNGARR_HORIZ_ID, IMAGE_HUNGARR_VERT_ID, IMAGE_ATOMIC_EXPLOSION_ID, IMAGE_BOMB_RADIAL_DEATH_ID, IMAGE_PLANETS_ID, IMAGE_SPARK_ID, IMAGE_PARTICLE_LIGHTNING_ID, SOUND_MAGZAP_ID, SOUND_BUTTON_ID, SOUND_PLANET_ID, SOUND_LEVEL_UP1_ID, SOUND_LEVEL_UP2_ID, SOUND_EXPLOSION_ID, SOUND_BEAM_HIT_ID, SOUND_PLANET_HIT_ID, SOUND_BEAM_MOVING_ID, SOUND_LEVEL_UP4_ID, SOUND_LEVEL_UP3_ID, SOUND_GAME_OVER_CLICK_ID, SOUND_GAME_OVER_STATS_ID, SOUND_GAME_OVER_RESTART_ID, SOUND_GAME_OVER_TEXT_ID, SOUND_REGION_FILLED_ID, RESOURCE_ID_MAX }; Image* GetImageById(int theId); Font* GetFontById(int theId); int GetSoundById(int theId); ResourceId GetIdByImage(Image *theImage); ResourceId GetIdByFont(Font *theFont); ResourceId GetIdBySound(int theSound); const char* GetStringIdById(int theId); ResourceId GetIdByStringId(const char *theStringId);} // namespace Sexy#endif//titleScreen.h#ifndef __TITLE_SCREEN_H__#define __TITLE_SCREEN_H__#include "SexyAppFramework/Widget.h"#include "SexyAppFramework/ButtonListener.h"namespace Sexy{class GameApp;class Graphics;class WidgetManager;// A new widget that we'll be learning about. It's explained in the .CPP code.class HyperlinkWidget;// 超连接类和按钮类都是用 ButtonListener 作为监听器.class TitleScreen : public Widget, public ButtonListener{ GameApp* mApp; HyperlinkWidget* mContinueLink; public: TitleScreen(GameApp* pApp){ mApp = pApp; mContinueLink = NULL; } virtual ~TitleScreen(){ delete mContinueLink;} void Init(); void AddedToManager(WidgetManager* theWidgetManager); void RemovedFromManager(WidgetManager* theWidgetManager); virtual void ButtonDepress(int theId); void Draw(Graphics* g); ////////////////////////////////////////////////////////////////////////// // 在 GameApp::LoadingThreadCompleted() 中手动调用. // 用来告诉该前导屏幕部件 资源已经加载完毕. void LoadingComplete();};}#endif //__TITLE_SCREEN_H__//board.cpp#include "Board.h"#include "GameApp.h"// Contains all the resources from the resources.xml file in our// properties directory. See that file for more information.#include "Res.h" #include "SexyAppFramework/Graphics.h"#include "SexyAppFramework/Color.h"#include "SexyAppFramework/Rect.h"#include "SexyAppFramework/ButtonWidget.h"#include "SexyAppFramework/WidgetManager.h"#include "SexyAppFramework/ImageFont.h" #include "SexyAppFramework/EditWidget.h"#include "SexyAppFramework/Checkbox.h"#include "SexyAppFramework/ListWidget.h"#include "SexyAppFramework/ScrollbarWidget.h"#include "SexyAppFramework/ScrollListener.h"// 处理声音#include "SexyAppFramework/SoundManager.h"#include "SexyAppFramework/SoundInstance.h"// 处理文件I/O#include "SexyAppFramework/Buffer.h" using namespace Sexy; Board::Board(GameApp* theApp){ mApp = theApp; mButton1 = NULL; mButton2 = NULL; mEditWidget = NULL; mCheckboxWidget = NULL; mListWidget = NULL; mScrollbarWidget = NULL; mMotionX = mUpdateFMotionX = 0;} Board::~Board(){ delete mButton1; delete mButton2; delete mEditWidget; delete mCheckboxWidget; delete mListWidget; delete mScrollbarWidget;} //为了说明平滑的运动(使用 UpdateF) 和 普通的运动(使用 Update). //这个例子中同时使用了2者. 可以对比它们的效果.//////////////////////////////////////////////////////////////////////////void Board::Update(){ Widget::Update(); // 普通的运动. 每次移动 5 象素. // 但因为游戏的帧频率和屏幕的刷新率不一样. 可能造成 这次移动了4象素. 下次又移动6象素. 移动效果不稳定. if ((mMotionX += 5.0f) >= mWidth) mMotionX = (float) -IMAGE_ROBOTROBOT->GetWidth(); MarkDirty();} //////////////////////////////////////////////////////////////////////////// 这个函数的详细说明在 Board.h 中.void Board::UpdateF(float theFrac){ // 参数是一个比例. 表示屏幕的实际刷新率 与 游戏100Hz 的帧频率 的比值. // 这样每次屏幕刷新时可以计算出 移动物体此时的坐标. if ((mUpdateFMotionX += 5.0f * theFrac) >= mWidth) mUpdateFMotionX = (float) -IMAGE_ROBOTROBOT->GetWidth(); // 不用再 MarkDirty (因为Update()已经做过了).}////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////void Board::Draw(Graphics* g){ g->SetColor(Color(0, 0, 0)); g->FillRect(0, 0, mWidth, mHeight); // Draw the image first using the standard method, with the coordinate // updated via Board::Update as an example of non-smooth motion. // Let's also print some text too. g->SetFont(FONT_DEFAULT); g->SetColor(Color(255, 255, 255)); g->DrawString(_S("Non smooth motion is jerky"), 10, 100); // What's this? A new DrawImage function? Yes. Believe it. It is true. // DrawImageF is just like it's best friend, DrawImage except that // it takes floating point values instead of integer ones. This is // slower than DrawImage, as the resulting image is anti-aliased to // give the illusion of moving at sub-pixel increments. A common // optimization technique at PopCap is to use DrawImageF // for motion when the user has a supported 3D card and DrawImage if // the user has to run in software mode. g->DrawImageF(IMAGE_ROBOTROBOT, mMotionX, 120.0f); // Now let's draw the image but using the smooth motion amounts: g->DrawString(_S("Smooth motion is silky smoothness"), 10, 200); g->DrawImageF(IMAGE_ROBOTROBOT, mUpdateFMotionX, 220.0f); // Let's draw the currently selected list item: g->DrawString(mText, mListWidget->mX, mListWidget->mY + mListWidget->mHeight + 20);} ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////void Board::AddedToManager(WidgetManager* theWidgetManager){ Widget::AddedToManager(theWidgetManager); // 两个播放声音的按钮 mButton1 = new ButtonWidget(1, this); mButton1->Resize(5, 5, 100, 50); mButton1->SetFont(FONT_DEFAULT); mButton1->mLabel = _S("Sound Left"); theWidgetManager->AddWidget(mButton1); mButton2 = new ButtonWidget(2, this); mButton2->Resize(106, 5, 100, 50); mButton2->SetFont(FONT_DEFAULT); mButton2->mLabel = _S("Sound Right"); theWidgetManager->AddWidget(mButton2); // 创建输入框部件. mEditWidget = new EditWidget(1, this); mEditWidget->SetFont(FONT_DEFAULT); //必须设置输入框的字体. 否则无法输入. mEditWidget->mMaxChars = 15; // 最多字符数 // 如果是输入密码. 可以设置 mEditWidget->mPasswordChar 成员 mEditWidget->Resize(10, 300, 100, 15); theWidgetManager->AddWidget(mEditWidget); // 创建单选框. // 单选框没有缺省的绘制函数. 所以它和其他的部件不同. // 我们必须在创建时指定两幅图片表示它的选择和不选. 也可以把这两个图像放入一个图片文件. 如下例: mCheckboxWidget = new Checkbox(IMAGE_CHECKBOX, IMAGE_CHECKBOX, 1, this); // 然后必须告诉单选框. 在这唯一的一副图片中. 哪部分是"选择"用的. 哪部分是"不选"用的. int checkWidth = IMAGE_CHECKBOX->GetWidth() / 2; mCheckboxWidget->mUncheckedRect = Rect(0, 0, checkWidth, IMAGE_CHECKBOX->GetHeight()); mCheckboxWidget->mCheckedRect = Rect(checkWidth, 0, checkWidth, IMAGE_CHECKBOX->GetHeight()); mCheckboxWidget->Resize(200, 300, checkWidth, IMAGE_CHECKBOX->GetHeight()); theWidgetManager->AddWidget(mCheckboxWidget); // 创建列表框 // 参数为 ID, 字体, 监听器 mListWidget = new ListWidget(1, FONT_DEFAULT, this); // 是否显示边框 mListWidget->mDrawOutline = true; // 设置列表框的颜色. 先用一个颜色数组准备一些颜色. 再用 SetColors() 设置之. int listWidgetColors[][3] = { {255, 255, 255}, //背景色 {255, 0, 0}, //边框色 {0, 0, 0}, //文本色(未选择时) {0, 0, 255}, //文本色(鼠标经过) {128, 128, 128}, //条目被选择后的颜色 {190, 0, 80} //文本色(被选择后). }; mListWidget->SetColors(listWidgetColors, 6); // 为上一步的列表框创建一个滚动条 mScrollbarWidget = new ScrollbarWidget(1, mListWidget); mListWidget->mScrollbar = mScrollbarWidget; // 要在滚动条设置之后. 列表框再 Resize(). mListWidget->Resize(300, 300, 100, 100); // 将滚动条移动到 列表框右边. mScrollbarWidget->ResizeScrollbar(mListWidget->mX + mListWidget->mWidth, mListWidget->mY, 25, // an arbitrary width for the bar itself mListWidget->mHeight); // 隐藏滚动条. 直到列表框已满. mScrollbarWidget->SetInvisIfNoScroll(true); theWidgetManager->AddWidget(mListWidget); theWidgetManager->AddWidget(mScrollbarWidget); // If you check out Board::EditWidgetText you'll see that we write out the // contents of the list box to disk every time something is added to it, or // it is cleared. For files that were created from a buffer, we can read // the data back into a buffer for ease of use. // // IMPORTANT: You can NOT load files into a buffer if they were not // created from a buffer in the first place. Thus, loading a random // text or binary file is not a good idea, since the buffer will // have no way to identify the datatypes beyond just a standard byte. // Plase read Board::EditWidgetText for an explanation of writing // with buffers. // // Let's load in the contents of that file, if it exists, // and repopulate the list box with it. The first step is to create // a Buffer object. The Buffer object will contain all the data // in the file: Buffer buffer; // Check if the file exists: if (mApp->FileExists("list_items.dat")) { // Read in all the data from the file. It will be stored in a format // unique to the Buffer object: if (!mApp->ReadBufferFromFile("list_items.dat", &buffer)) { // error, the file was corrupted or some other error occurred mApp->Popup("Could not read contents of list_items.txt"); } // In Board::EditWidgetText we wrote out all the strings // via buffer.WriteString. The order in which data is written // to the buffer is the order in which it is read. Thus, if // you wrote out 2 integers, a string, and 10 booleans, when // reading from the buffer you'd first ask for the 2 integers, // then the string, then the 10 booleans. This is important: // the order DOES matter. If you asked to read a string when // a long was actually the next element in the file, you'd // get an error. Buffers are very useful for userprofiles or // data files like that where you can guarantee an explicit // file format. while (!buffer.AtEnd()) mListWidget->AddLine(StringToSexyStringFast(buffer.ReadString()), true); } // If you read Board::ListClicked, you'll see that we // wrote the last selected item from the list box to the registry. // Let's read that value in, if it exists, and set the mText // variable to it so that it displays on screen. We use the // RegsitryRead... functions to grab values from the regsitry. // The values are assumed to be in the registry location that you // set by setting the mRegKey variable in GameApp's constructor. // The functions return false if there was an error reading the // key or the key doesn't exist: mApp->RegistryReadString("ListItem", &SexyStringToStringFast(mText));}//////////////////////////////////////////////////////////////////////////void Board::RemovedFromManager(WidgetManager* theWidgetManager){ Widget::RemovedFromManager(theWidgetManager); theWidgetManager->RemoveWidget(mButton1); theWidgetManager->RemoveWidget(mButton2); theWidgetManager->RemoveWidget(mEditWidget); theWidgetManager->RemoveWidget(mCheckboxWidget); theWidgetManager->RemoveWidget(mListWidget); theWidgetManager->RemoveWidget(mScrollbarWidget);}//////////////////////////////////////////////////////////////////////////void Board::ButtonDepress(int theId){ if (theId == 1) { // Our "left" button was clicked. Let's play a sound // in the left speaker with a slight pitch shift. // In order to play a pitch shifted sample, we have to do more // than just say "PlaySample." We have to get a pointer to the sound // instance that represents our sound effect. We do that by asking the // app's sound manager to return us a sound instance, and we tell it // the ID of the sound file that we want. Let's do that now, // using the sound "SOUND_MUTATOR" which we set up in properties/resources.xml: SoundInstance* sample = mApp->mSoundManager->GetSoundInstance(SOUND_MUTATOR); // It's good to make sure the sample isn't NULL. It would be NULL if you // specified an invalid sound id. if (sample != NULL) { //Now we actually adjust the pitch. Specify the number of //steps to raise (positive) or lower (negative) the original sound by. //We'll just arbitrarily choose 13. sample->AdjustPitch(13); //Let's make it play on the left speaker only. We set a panning value //in decibels, which for DirectX range from -10000 to +10000, where //-10000 is fully left and +10000 is fully right: sample->SetPan(-10000); // Now we have the sample play. This is again slightly different than // our PlaySample from previous demos. The first parameter indicates // whether or not we want the sample to loop (in this case, no), and // the second indicates whether the memory taken up by this special // sound instance pointer should be reclaimed when the sample is done // playing. If true, then the pointer will be invalid once its // done playing, sine the memory will be reclaimed. If false, the // memory won't be reclaimed and you'll have to do it yourself. sample->Play(false, true); } } else if (theId == 2) { // Let's do the same as we did for the left button, except make it // play on the right speaker and pitch shift it down SoundInstance* sample = mApp->mSoundManager->GetSoundInstance(SOUND_MUTATOR); if (sample != NULL) { sample->AdjustPitch(-5); sample->SetPan(10000); sample->Play(false, true); } }}//////////////////////////////////////////////////////////////////////////// 当在输入框输入 回车 时. 会调用该函数.void Board::EditWidgetText(int theId, const std::string& theString){ if (theString.length() > 0) { // 当输入框字符串为 "clear" 时. 清空列表框. 否则将输入的字符串加入列表框. if (StringToUpper(theString) == "CLEAR") mListWidget->RemoveAll(); else mListWidget->AddLine(StringToSexyStringFast(theString), true); mEditWidget->SetText(_S("")); // 将列表框中的所有字符串写入 buffer. // 再将buffer写入文件. Buffer buffer; for (unsigned int i = 0; i < mListWidget->mLines.size(); i++) buffer.WriteString(SexyStringToStringFast(mListWidget->mLines.at(i))); mApp->WriteBufferToFile("list_items.dat", &buffer); }}////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////bool Board::AllowChar(int theId, char theChar){ // As an example of denying input, let's prevent the user // from typing in the following: :-+.@#$%^&*() switch (theChar) { case ':': case '-': case '+': case '.': case '@': case '#': case '$': case '%': case '^': case '&': case '*': case '(': case ')': return false; default: return true; }}////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////void Board::ListClicked(int theId, int theIdx, int theClickCount){ if (theId == 1) { // Actually select the index. This is done to allow us to // block the selection, if we chose to. mListWidget->SetSelect(theIdx); // And update the text that's displaying on screen... // The strings are stored in the list widget's mLines variable // and the one clicked can be indexed via theIdx. mText = mListWidget->mLines[theIdx]; // As an example of writing to the registry, let's write this value // and later on load it in when we restart the app. You'll notice // a bunch of RegistryWrite... functions in SexyAppBase. Each one // of these takes as first argument the name of the value to // add to the registry. The second argument is the actual data // to store in that value. The value is saved under the registry // location that you set in GameApp's constructor when you // set the mRegKey variable. The function returns false // if there was an error, such as a lack of permission: if (!mApp->RegistryWriteString("ListItem", SexyStringToStringFast(mText))) mApp->Popup("Couldn't save \"ListItem\" to registry"); }}//gameapp.cpp#include "GameApp.h"#include "TitleScreen.h"#include "Board.h"#include "SexyAppFramework/WidgetManager.h"// 这个例子将使用资源管理器类#include "SexyAppFramework/ResourceManager.h"// 声音#include "SexyAppFramework/BassMusicInterface.h" // 从 resources.xml 文件加载资源#include "Res.h" using namespace Sexy; //////////////////////////////////////////////////////////////////////////GameApp::GameApp(){ mProdName = "Demo 4"; mProductVersion = "1.0"; mTitle = StringToSexyStringFast("SexyAppFramework: " + mProdName + " - " + mProductVersion); mWidth = 800; mHeight = 600; mAutoEnable3D = true; mBoard = NULL; mTitleScreen = NULL; // See Board::UpdateF for a very lengthy explanation of this and smooth motion mVSyncUpdates = true;} //////////////////////////////////////////////////////////////////////////GameApp::~GameApp(){ if (mBoard != NULL) mWidgetManager->RemoveWidget(mBoard); delete mBoard; //如果在前导屏幕结束前关闭游戏. 需要手动将前导部件remove if (mTitleScreen != NULL) mWidgetManager->RemoveWidget(mTitleScreen); delete mTitleScreen; // 总是应该释放所有加载的资源组. 因为对已经释放的资源组再释放一遍也没事. mResourceManager->DeleteResources("Init"); mResourceManager->DeleteResources("TitleScreen"); mResourceManager->DeleteResources("Game"); } //////////////////////////////////////////////////////////////////////////void GameApp::Init(){ SexyAppBase::Init(); // 首先. 告诉资源管理器去读取所有的组. // 读取的资源清单默认在"properties/resources.xml". // 但这个操作并不加载任何资源. 它只是解析xml中的数据. LoadResourceManifest(); // 接着. 通过资源管理器真正的加载资源. // 这只要调用 mResourceManager->LoadResources() 并指定组名. 这里组名是"Init". if (!mResourceManager->LoadResources("Init")) { mLoadingFailed = true; // 将显示错误信息. 来报告在加载线程中发生了什么错误 ShowResourceError(true); return; } // 转换加载的资源. // 例如把加载的声音转换为wav文件. 对images做palletizes等. if (!ExtractInitResources(mResourceManager)) { mLoadingFailed = true; ShowResourceError(true); return; } // We also need to load our title screen graphics in, since you can't // display the title screen without any graphics. For an explanation of why // we placed this in a separate group from Init, see properties/resources.xml. // This code works exactly like the above did for the Init group. // 加载 TitleScreen 组中的资源. if (!mResourceManager->LoadResources("TitleScreen")) { mLoadingFailed = true; ShowResourceError(true); return; } if (!ExtractTitleScreenResources(mResourceManager)) { mLoadingFailed = true; ShowResourceError(true); return; } // 创建前导屏幕部件. 并将其加入部件管理器 mTitleScreen = new TitleScreen(this); mTitleScreen->Resize(0, 0, mWidth, mHeight); mTitleScreen->Init(); mWidgetManager->AddWidget(mTitleScreen); // 载入声音. // 所有与声音有关的东西. 包括载入. 播放. 停止. 音量等等.. // 我们都用 APP 的 mMusicInterface 成员搞定. mMusicInterface->LoadMusic(0, "music/music.mo3"); mMusicInterface->LoadMusic(1, "music/music.mo3"); // 播放声音用 MusicInterface::PlayMusic(id, offset, no_loop); // 要播放时有淡出淡入效果用 MusicInterface::FadeIn(id, offset, speed, no_loop); // 参数为: // id . 音频ID 加载时指定. // offset. 不知何物. // speed. 淡出淡入的速度. 貌似在 0.001左右时有效. // no_loop. 是否"不循环". 注意: false 表示循环. mMusicInterface->FadeIn(0, 0, 0.002, false); // 这时我们要知道有多少资源需要加载. 因为前导屏幕的进度条将使用它. // SexyAppBase::mNumLoadingThreadTasks 成员用来保存该值. 所以下边设置它. // //通过资源管理器的GetNumResources(组名)函数可以得到改组的资源数. mNumLoadingThreadTasks = mResourceManager->GetNumResources("Game"); } //////////////////////////////////////////////////////////////////////////void GameApp::LoadingThreadProc(){ // 这次我们不再手工加载资源. 而是交给 resource manager 处理. // 给出组名调用 StartLoadResources() 开始加载. 把该组做为当前组. mResourceManager->StartLoadResources("Game"); // 加载每个个体资源. // LoadNextResource() 加载当前组中的下一个资源. 若没有下一个. 返回false. while (mResourceManager->LoadNextResource()) { // App基类的又一个成员. 表示目前加载了多少个资源. 它也是用来在前导屏幕的进度条中使用. // 我们每加载一个资源就手动修改该值. mCompletedLoadingThreadTasks++; // 如果资源加载器在加载资源时失败. 会设置下边的值. 我们应该检查它. if (mShutdown) return; // 使前导屏幕变脏 . 导致进度条被重绘. // 把它放在这里而不是 TitleScreen::Update() 中. 可以节约cpu时间. mTitleScreen->MarkDirty(); } // 和在Init()中做的一样. 加载资源后我们要将其转换. ExtractGameResources(). // 同时也检查错误. if (mResourceManager->HadError() || !ExtractGameResources(mResourceManager)) { ShowResourceError(false); mLoadingFailed = true; return; } }////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////void GameApp::LoadingThreadCompleted(){ SexyAppBase::LoadingThreadCompleted(); if (mLoadingFailed) return; // 告诉前导屏幕. 资源已经加载完毕. mTitleScreen->LoadingComplete(); //然后前导屏幕会显示出它的超连接. mTitleScreen->MarkDirty();} //////////////////////////////////////////////////////////////////////////// 在 TitleScreen::ButtonDepress() 函数中(即点击超连接后) 调用了该函数. void GameApp::TitleScreenIsFinished(){ mTitleScreen = NULL; mBoard = new Board(this); // 撤销掉前导屏幕部件. 同时创建 Board 部件. // 前导屏幕使用的资源这时可以释放掉了. mResourceManager->DeleteResources("TitleScreen"); //释放一组资源. mBoard->Resize(0, 0, mWidth, mHeight); mWidgetManager->AddWidget(mBoard); // 停止前导屏幕时的声音. 并开始播放下一个声音. // 和 FadeIn() 类似. 这里是"淡出". mMusicInterface->FadeOut(0, true, 0.004); mMusicInterface->FadeIn(1, 9, 0.002, false); // We'll cover changing the music and sound volumes in a later demo.}//////////////////////////////////////////////////////////////////////////void GameApp::HandleCmdLineParam(const std::string& theParamName, const std::string& theParamValue){ //在这个函数中可以检查主函数启动时的 参数 OutputDebugString(StrFormat("theParamName = \"%s\", theParamValue = \"%s\"", theParamName.c_str(), theParamValue.c_str()).c_str());}//res.cpp#include "Res.h"#include "SexyAppFramework/ResourceManager.h"using namespace Sexy;#pragma warning(disable:4311 4312)static bool gNeedRecalcVariableToIdMap = false;bool Sexy::ExtractResourcesByName(ResourceManager *theManager, const char *theName){ if (strcmp(theName,"Game")==0) return ExtractGameResources(theManager); if (strcmp(theName,"Hungarr")==0) return ExtractHungarrResources(theManager); if (strcmp(theName,"Init")==0) return ExtractInitResources(theManager); if (strcmp(theName,"TitleScreen")==0) return ExtractTitleScreenResources(theManager); return false;}// 把一个字符串ID映射到对应的整数ID.Sexy::ResourceId Sexy::GetIdByStringId(const char *theStringId){ typedef std::map<std::string,int> MyMap; static MyMap aMap; if(aMap.empty()) { for(int i=0; i<RESOURCE_ID_MAX; i++) aMap[GetStringIdById(i)] = i; } MyMap::iterator anItr = aMap.find(theStringId); if (anItr == aMap.end()) return RESOURCE_ID_MAX; else return (ResourceId) anItr->second;}// Game ResourcesImage* Sexy::IMAGE_BG0;Image* Sexy::IMAGE_BG1;Image* Sexy::IMAGE_BG2;Image* Sexy::IMAGE_BUTTON_DOWN;Image* Sexy::IMAGE_BUTTON_NORMAL;Image* Sexy::IMAGE_BUTTON_OVER;Image* Sexy::IMAGE_CHECKBOX;Image* Sexy::IMAGE_DIALOG_BOX;Image* Sexy::IMAGE_DIALOG_BUTTON;Image* Sexy::IMAGE_ROBOTROBOT;Image* Sexy::IMAGE_SLIDER_THUMB;Image* Sexy::IMAGE_SLIDER_TRACK;int Sexy::SOUND_MUTATOR;int Sexy::SOUND_TIMER;bool Sexy::ExtractGameResources(ResourceManager *theManager){ gNeedRecalcVariableToIdMap = true; ResourceManager &aMgr = *theManager; try { IMAGE_BG0 = aMgr.GetImageThrow("IMAGE_BG0"); IMAGE_BG1 = aMgr.GetImageThrow("IMAGE_BG1"); IMAGE_BG2 = aMgr.GetImageThrow("IMAGE_BG2"); IMAGE_BUTTON_DOWN = aMgr.GetImageThrow("IMAGE_BUTTON_DOWN"); IMAGE_BUTTON_NORMAL = aMgr.GetImageThrow("IMAGE_BUTTON_NORMAL"); IMAGE_BUTTON_OVER = aMgr.GetImageThrow("IMAGE_BUTTON_OVER"); IMAGE_CHECKBOX = aMgr.GetImageThrow("IMAGE_CHECKBOX"); IMAGE_DIALOG_BOX = aMgr.GetImageThrow("IMAGE_DIALOG_BOX"); IMAGE_DIALOG_BUTTON = aMgr.GetImageThrow("IMAGE_DIALOG_BUTTON"); IMAGE_ROBOTROBOT = aMgr.GetImageThrow("IMAGE_ROBOTROBOT"); IMAGE_SLIDER_THUMB = aMgr.GetImageThrow("IMAGE_SLIDER_THUMB"); IMAGE_SLIDER_TRACK = aMgr.GetImageThrow("IMAGE_SLIDER_TRACK"); SOUND_MUTATOR = aMgr.GetSoundThrow("SOUND_MUTATOR"); SOUND_TIMER = aMgr.GetSoundThrow("SOUND_TIMER"); } catch(ResourceManagerException&) { return false; } return true;}// Hungarr ResourcesImage* Sexy::IMAGE_ATOMIC_EXPLOSION;Image* Sexy::IMAGE_BOMB_RADIAL_DEATH;Image* Sexy::IMAGE_HUNGARR_BEAM_DOWN;Image* Sexy::IMAGE_HUNGARR_BEAM_LEFT;Image* Sexy::IMAGE_HUNGARR_BEAM_RIGHT;Image* Sexy::IMAGE_HUNGARR_BEAM_UP;Image* Sexy::IMAGE_HUNGARR_HORIZ;Image* Sexy::IMAGE_HUNGARR_SMALL;Image* Sexy::IMAGE_HUNGARR_VERT;Image* Sexy::IMAGE_PARTICLE_LIGHTNING;Image* Sexy::IMAGE_PLANETS;Image* Sexy::IMAGE_SPARK;int Sexy::SOUND_BEAM_HIT;int Sexy::SOUND_BEAM_MOVING;int Sexy::SOUND_BUTTON;int Sexy::SOUND_EXPLOSION;int Sexy::SOUND_GAME_OVER_CLICK;int Sexy::SOUND_GAME_OVER_RESTART;int Sexy::SOUND_GAME_OVER_STATS;int Sexy::SOUND_GAME_OVER_TEXT;int Sexy::SOUND_LEVEL_UP1;int Sexy::SOUND_LEVEL_UP2;int Sexy::SOUND_LEVEL_UP3;int Sexy::SOUND_LEVEL_UP4;int Sexy::SOUND_MAGZAP;int Sexy::SOUND_PLANET;int Sexy::SOUND_PLANET_HIT;int Sexy::SOUND_REGION_FILLED;bool Sexy::ExtractHungarrResources(ResourceManager *theManager){ gNeedRecalcVariableToIdMap = true; ResourceManager &aMgr = *theManager; try { IMAGE_ATOMIC_EXPLOSION = aMgr.GetImageThrow("IMAGE_ATOMIC_EXPLOSION"); IMAGE_BOMB_RADIAL_DEATH = aMgr.GetImageThrow("IMAGE_BOMB_RADIAL_DEATH"); IMAGE_HUNGARR_BEAM_DOWN = aMgr.GetImageThrow("IMAGE_HUNGARR_BEAM_DOWN"); IMAGE_HUNGARR_BEAM_LEFT = aMgr.GetImageThrow("IMAGE_HUNGARR_BEAM_LEFT"); IMAGE_HUNGARR_BEAM_RIGHT = aMgr.GetImageThrow("IMAGE_HUNGARR_BEAM_RIGHT"); IMAGE_HUNGARR_BEAM_UP = aMgr.GetImageThrow("IMAGE_HUNGARR_BEAM_UP"); IMAGE_HUNGARR_HORIZ = aMgr.GetImageThrow("IMAGE_HUNGARR_HORIZ"); IMAGE_HUNGARR_SMALL = aMgr.GetImageThrow("IMAGE_HUNGARR_SMALL"); IMAGE_HUNGARR_VERT = aMgr.GetImageThrow("IMAGE_HUNGARR_VERT"); IMAGE_PARTICLE_LIGHTNING = aMgr.GetImageThrow("IMAGE_PARTICLE_LIGHTNING"); IMAGE_PLANETS = aMgr.GetImageThrow("IMAGE_PLANETS"); IMAGE_SPARK = aMgr.GetImageThrow("IMAGE_SPARK"); SOUND_BEAM_HIT = aMgr.GetSoundThrow("SOUND_BEAM_HIT"); SOUND_BEAM_MOVING = aMgr.GetSoundThrow("SOUND_BEAM_MOVING"); SOUND_BUTTON = aMgr.GetSoundThrow("SOUND_BUTTON"); SOUND_EXPLOSION = aMgr.GetSoundThrow("SOUND_EXPLOSION"); SOUND_GAME_OVER_CLICK = aMgr.GetSoundThrow("SOUND_GAME_OVER_CLICK"); SOUND_GAME_OVER_RESTART = aMgr.GetSoundThrow("SOUND_GAME_OVER_RESTART"); SOUND_GAME_OVER_STATS = aMgr.GetSoundThrow("SOUND_GAME_OVER_STATS"); SOUND_GAME_OVER_TEXT = aMgr.GetSoundThrow("SOUND_GAME_OVER_TEXT"); SOUND_LEVEL_UP1 = aMgr.GetSoundThrow("SOUND_LEVEL_UP1"); SOUND_LEVEL_UP2 = aMgr.GetSoundThrow("SOUND_LEVEL_UP2"); SOUND_LEVEL_UP3 = aMgr.GetSoundThrow("SOUND_LEVEL_UP3"); SOUND_LEVEL_UP4 = aMgr.GetSoundThrow("SOUND_LEVEL_UP4"); SOUND_MAGZAP = aMgr.GetSoundThrow("SOUND_MAGZAP"); SOUND_PLANET = aMgr.GetSoundThrow("SOUND_PLANET"); SOUND_PLANET_HIT = aMgr.GetSoundThrow("SOUND_PLANET_HIT"); SOUND_REGION_FILLED = aMgr.GetSoundThrow("SOUND_REGION_FILLED"); } catch(ResourceManagerException&) { return false; } return true;}// Init ResourcesFont* Sexy::FONT_DEFAULT;Font* Sexy::FONT_HUNGARR;Image* Sexy::IMAGE_CUSTOM_DRAGGING;Image* Sexy::IMAGE_CUSTOM_HAND;Image* Sexy::IMAGE_CUSTOM_POINTER;Image* Sexy::IMAGE_CUSTOM_TEXT;Image* Sexy::IMAGE_HUNGARR_LOGO;bool Sexy::ExtractInitResources(ResourceManager *theManager){ gNeedRecalcVariableToIdMap = true; ResourceManager &aMgr = *theManager; try { FONT_DEFAULT = aMgr.GetFontThrow("FONT_DEFAULT"); FONT_HUNGARR = aMgr.GetFontThrow("FONT_HUNGARR"); IMAGE_CUSTOM_DRAGGING = aMgr.GetImageThrow("IMAGE_CUSTOM_DRAGGING"); IMAGE_CUSTOM_HAND = aMgr.GetImageThrow("IMAGE_CUSTOM_HAND"); IMAGE_CUSTOM_POINTER = aMgr.GetImageThrow("IMAGE_CUSTOM_POINTER"); IMAGE_CUSTOM_TEXT = aMgr.GetImageThrow("IMAGE_CUSTOM_TEXT"); IMAGE_HUNGARR_LOGO = aMgr.GetImageThrow("IMAGE_HUNGARR_LOGO"); } catch(ResourceManagerException&) { return false; } return true;}// TitleScreen ResourcesImage* Sexy::IMAGE_LOADER_BAR;Image* Sexy::IMAGE_LOADER_LOADINGTXT;int Sexy::SOUND_CONTINUE;bool Sexy::ExtractTitleScreenResources(ResourceManager *theManager){ gNeedRecalcVariableToIdMap = true; ResourceManager &aMgr = *theManager; try { IMAGE_LOADER_BAR = aMgr.GetImageThrow("IMAGE_LOADER_BAR"); IMAGE_LOADER_LOADINGTXT = aMgr.GetImageThrow("IMAGE_LOADER_LOADINGTXT"); SOUND_CONTINUE = aMgr.GetSoundThrow("SOUND_CONTINUE"); } catch(ResourceManagerException&) { return false; } return true;}static void* gResources[] ={ &FONT_DEFAULT, &FONT_HUNGARR, &IMAGE_CUSTOM_POINTER, &IMAGE_CUSTOM_HAND, &IMAGE_CUSTOM_DRAGGING, &IMAGE_CUSTOM_TEXT, &IMAGE_HUNGARR_LOGO, &IMAGE_LOADER_BAR, &IMAGE_LOADER_LOADINGTXT, &SOUND_CONTINUE, &SOUND_MUTATOR, &SOUND_TIMER, &IMAGE_ROBOTROBOT, &IMAGE_CHECKBOX, &IMAGE_BG0, &IMAGE_BG1, &IMAGE_BG2, &IMAGE_BUTTON_DOWN, &IMAGE_BUTTON_OVER, &IMAGE_BUTTON_NORMAL, &IMAGE_DIALOG_BOX, &IMAGE_DIALOG_BUTTON, &IMAGE_SLIDER_TRACK, &IMAGE_SLIDER_THUMB, &IMAGE_HUNGARR_SMALL, &IMAGE_HUNGARR_BEAM_UP, &IMAGE_HUNGARR_BEAM_DOWN, &IMAGE_HUNGARR_BEAM_LEFT, &IMAGE_HUNGARR_BEAM_RIGHT, &IMAGE_HUNGARR_HORIZ, &IMAGE_HUNGARR_VERT, &IMAGE_ATOMIC_EXPLOSION, &IMAGE_BOMB_RADIAL_DEATH, &IMAGE_PLANETS, &IMAGE_SPARK, &IMAGE_PARTICLE_LIGHTNING, &SOUND_MAGZAP, &SOUND_BUTTON, &SOUND_PLANET, &SOUND_LEVEL_UP1, &SOUND_LEVEL_UP2, &SOUND_EXPLOSION, &SOUND_BEAM_HIT, &SOUND_PLANET_HIT, &SOUND_BEAM_MOVING, &SOUND_LEVEL_UP4, &SOUND_LEVEL_UP3, &SOUND_GAME_OVER_CLICK, &SOUND_GAME_OVER_STATS, &SOUND_GAME_OVER_RESTART, &SOUND_GAME_OVER_TEXT, &SOUND_REGION_FILLED, NULL};Image* Sexy::LoadImageById(ResourceManager *theManager, int theId){ return (*((Image**)gResources[theId]) = theManager->LoadImage(GetStringIdById(theId)));}Image* Sexy::GetImageById(int theId){ return *(Image**)gResources[theId];}Font* Sexy::GetFontById(int theId){ return *(Font**)gResources[theId];}int Sexy::GetSoundById(int theId){ return *(int*)gResources[theId];}static Sexy::ResourceId GetIdByVariable(const void *theVariable){ typedef std::map<int,int> MyMap; static MyMap aMap; if(gNeedRecalcVariableToIdMap) { gNeedRecalcVariableToIdMap = false; aMap.clear(); for(int i=0; i<RESOURCE_ID_MAX; i++) aMap[*(int*)gResources[i]] = i; } MyMap::iterator anItr = aMap.find((int)theVariable); if (anItr == aMap.end()) return RESOURCE_ID_MAX; else return (ResourceId) anItr->second;}Sexy::ResourceId Sexy::GetIdByImage(Image *theImage){ return GetIdByVariable(theImage);}Sexy::ResourceId Sexy::GetIdByFont(Font *theFont){ return GetIdByVariable(theFont);}Sexy::ResourceId Sexy::GetIdBySound(int theSound){ return GetIdByVariable((void*)theSound);}const char* Sexy::GetStringIdById(int theId){ switch(theId) { case FONT_DEFAULT_ID: return "FONT_DEFAULT"; case FONT_HUNGARR_ID: return "FONT_HUNGARR"; case IMAGE_CUSTOM_POINTER_ID: return "IMAGE_CUSTOM_POINTER"; case IMAGE_CUSTOM_HAND_ID: return "IMAGE_CUSTOM_HAND"; case IMAGE_CUSTOM_DRAGGING_ID: return "IMAGE_CUSTOM_DRAGGING"; case IMAGE_CUSTOM_TEXT_ID: return "IMAGE_CUSTOM_TEXT"; case IMAGE_HUNGARR_LOGO_ID: return "IMAGE_HUNGARR_LOGO"; case IMAGE_LOADER_BAR_ID: return "IMAGE_LOADER_BAR"; case IMAGE_LOADER_LOADINGTXT_ID: return "IMAGE_LOADER_LOADINGTXT"; case SOUND_CONTINUE_ID: return "SOUND_CONTINUE"; case SOUND_MUTATOR_ID: return "SOUND_MUTATOR"; case SOUND_TIMER_ID: return "SOUND_TIMER"; case IMAGE_ROBOTROBOT_ID: return "IMAGE_ROBOTROBOT"; case IMAGE_CHECKBOX_ID: return "IMAGE_CHECKBOX"; case IMAGE_BG0_ID: return "IMAGE_BG0"; case IMAGE_BG1_ID: return "IMAGE_BG1"; case IMAGE_BG2_ID: return "IMAGE_BG2"; case IMAGE_BUTTON_DOWN_ID: return "IMAGE_BUTTON_DOWN"; case IMAGE_BUTTON_OVER_ID: return "IMAGE_BUTTON_OVER"; case IMAGE_BUTTON_NORMAL_ID: return "IMAGE_BUTTON_NORMAL"; case IMAGE_DIALOG_BOX_ID: return "IMAGE_DIALOG_BOX"; case IMAGE_DIALOG_BUTTON_ID: return "IMAGE_DIALOG_BUTTON"; case IMAGE_SLIDER_TRACK_ID: return "IMAGE_SLIDER_TRACK"; case IMAGE_SLIDER_THUMB_ID: return "IMAGE_SLIDER_THUMB"; case IMAGE_HUNGARR_SMALL_ID: return "IMAGE_HUNGARR_SMALL"; case IMAGE_HUNGARR_BEAM_UP_ID: return "IMAGE_HUNGARR_BEAM_UP"; case IMAGE_HUNGARR_BEAM_DOWN_ID: return "IMAGE_HUNGARR_BEAM_DOWN"; case IMAGE_HUNGARR_BEAM_LEFT_ID: return "IMAGE_HUNGARR_BEAM_LEFT"; case IMAGE_HUNGARR_BEAM_RIGHT_ID: return "IMAGE_HUNGARR_BEAM_RIGHT"; case IMAGE_HUNGARR_HORIZ_ID: return "IMAGE_HUNGARR_HORIZ"; case IMAGE_HUNGARR_VERT_ID: return "IMAGE_HUNGARR_VERT"; case IMAGE_ATOMIC_EXPLOSION_ID: return "IMAGE_ATOMIC_EXPLOSION"; case IMAGE_BOMB_RADIAL_DEATH_ID: return "IMAGE_BOMB_RADIAL_DEATH"; case IMAGE_PLANETS_ID: return "IMAGE_PLANETS"; case IMAGE_SPARK_ID: return "IMAGE_SPARK"; case IMAGE_PARTICLE_LIGHTNING_ID: return "IMAGE_PARTICLE_LIGHTNING"; case SOUND_MAGZAP_ID: return "SOUND_MAGZAP"; case SOUND_BUTTON_ID: return "SOUND_BUTTON"; case SOUND_PLANET_ID: return "SOUND_PLANET"; case SOUND_LEVEL_UP1_ID: return "SOUND_LEVEL_UP1"; case SOUND_LEVEL_UP2_ID: return "SOUND_LEVEL_UP2"; case SOUND_EXPLOSION_ID: return "SOUND_EXPLOSION"; case SOUND_BEAM_HIT_ID: return "SOUND_BEAM_HIT"; case SOUND_PLANET_HIT_ID: return "SOUND_PLANET_HIT"; case SOUND_BEAM_MOVING_ID: return "SOUND_BEAM_MOVING"; case SOUND_LEVEL_UP4_ID: return "SOUND_LEVEL_UP4"; case SOUND_LEVEL_UP3_ID: return "SOUND_LEVEL_UP3"; case SOUND_GAME_OVER_CLICK_ID: return "SOUND_GAME_OVER_CLICK"; case SOUND_GAME_OVER_STATS_ID: return "SOUND_GAME_OVER_STATS"; case SOUND_GAME_OVER_RESTART_ID: return "SOUND_GAME_OVER_RESTART"; case SOUND_GAME_OVER_TEXT_ID: return "SOUND_GAME_OVER_TEXT"; case SOUND_REGION_FILLED_ID: return "SOUND_REGION_FILLED"; default: return ""; }} titleScreen.cpp#include "TitleScreen.h"#include "GameApp.h"// Contains all the resources from the resources.xml file in our// properties directory. See that file for more information.#include "Res.h" #include "SexyAppFramework/Font.h"#include "SexyAppFramework/Graphics.h"#include "SexyAppFramework/Image.h"#include "SexyAppFramework/WidgetManager.h"#include "SexyAppFramework/Rect.h" // 本例子使用一个新类 HyperlinkWidget(超连接部件) . 它看上去就像 www 的超连接.#include "SexyAppFramework/HyperlinkWidget.h"using namespace Sexy; //////////////////////////////////////////////////////////////////////////// 重写基类的 Init()// 这里只是 创建了一个超连接部件.void TitleScreen::Init(void){ // 超连接类和按钮类都是用 ButtonListener 作为监听器. // 它们的创建也类似. 构造函数的参数为 组件ID, 监听器对象 mContinueLink = new HyperlinkWidget(1, this); // 设置超连接部件的字体. // 这和第3个 demo 中给按钮设置字体是一样的. // 只是这里的字体是通过资源管理器加载的. 而字体对象的名字FONT_DEFAULT也是在xml文件中指定的. mContinueLink->SetFont(FONT_DEFAULT); // 设置超连接的文字 mContinueLink->mLabel = _S("CLICK TO CONTINUE"); // 和按钮一样. 我们可以设置超连接的缺省颜色和鼠标经过颜色. // 我们没有使用 SetColor() 函数. 而是直接设置 mColor 和 mOverColor 两个成员. mContinueLink->mColor = Color(255, 255, 255); //缺省色为白色 mContinueLink->mOverColor = Color(0, 255, 0); //鼠标经过颜色为绿色 mContinueLink->mUnderlineSize = 1; // 指定超连接下划线的宽度 } //////////////////////////////////////////////////////////////////////////// 将Init()中创建的超连接部件也放入WidgetManagervoid TitleScreen::AddedToManager(WidgetManager *theWidgetManager){ Widget::AddedToManager(theWidgetManager); // 在所有资源被加载完之前. 我们应该设置该超连接部件为不可见的. // 因为当用户点击该部件后就会进入游戏. 然而资源还没加载好... mContinueLink->SetVisible(false); // 而且即使隐藏起来也不行. 因为虽然看不见. 但技术上用户还是有可能点击到它. // 所以我们还要设置它为不可用的. mContinueLink->SetDisabled(true); // 计算并设置超连接部件的位置/大小. int labelWidth = FONT_DEFAULT->StringWidth(mContinueLink->mLabel); int labelHeight = FONT_DEFAULT->GetHeight(); mContinueLink->Resize( mWidth / 2 - labelWidth / 2, mHeight - labelHeight - 40, labelWidth, labelHeight+4); // 下边一行将 mDoFinger 成员设置为 ture. // 表示鼠标移动到该控件时. WidgetManager 自动将光标图像由箭头变为手形. // 不只是超连接部件. 所有的部件都有该功能. mContinueLink->mDoFinger = true; // 最后将该超连接部件也加入WidgetManager. theWidgetManager->AddWidget(mContinueLink);} void TitleScreen::RemovedFromManager(WidgetManager *theWidgetManager){ Widget::RemovedFromManager(theWidgetManager); theWidgetManager->RemoveWidget(mContinueLink);} //////////////////////////////////////////////////////////////////////////void TitleScreen::Draw(Graphics *g){ g->SetColor(Color::Black); //黑色(Black)和白色(White)是 Color 的两个静态成员. 方便使用. g->FillRect(0, 0, mWidth, mHeight); // 绘制 进度条(实际是缩放绘制一个图片) // 资源加载的百分比可以用 mApp->GetLoadingThreadProgress() 得到. 返回值是从0.0 - 1.0的小数. int loaderBarWidth = IMAGE_LOADER_BAR->GetWidth(); int drawWidth = (int)(mApp->GetLoadingThreadProgress() * loaderBarWidth); if (drawWidth > 0) { g->DrawImage(IMAGE_LOADER_BAR, mWidth / 2 - loaderBarWidth / 2, 400, Rect(0, 0, drawWidth, IMAGE_LOADER_BAR->GetHeight())); } // 在加载完成前. 显示 "loding.." 图片 if (mContinueLink->mVisible == false) g->DrawImage(IMAGE_LOADER_LOADINGTXT, mContinueLink->mX, mContinueLink->mY - 20); } ////////////////////////////////////////////////////////////////////////////这个函数将在 GameApp::LoadingThreadCompleted() 中手动调用.void TitleScreen::LoadingComplete(){ // 使超连接变为可用 mContinueLink->SetVisible(true); mContinueLink->SetDisabled(false);}////////////////////////////////////////////////////////////////////////////处理超连接事件void TitleScreen::ButtonDepress(int theId){ if (theId == 1) { // 点击超连接离开前导屏幕. // 这时应该从部件管理器中 remove 不再用到的东西. mApp->mWidgetManager->RemoveWidget(this); mApp->mWidgetManager->RemoveWidget(mContinueLink); // 然后这两个部件应该被delete. 但现在还在使用它们. // 所以用 SexyAppBase::SafeDeleteWidget() 来将这个动作放入消息队列以后执行. mApp->SafeDeleteWidget(this); mApp->SafeDeleteWidget(mContinueLink); mContinueLink = NULL; // 现在告诉游戏程序类. 应该开始board部件了. mApp->TitleScreenIsFinished(); }}