為何要客製化?對企劃來說:
- 因為修改、測試、調整GUI處理流程進度會卡在程式人員(因為串接流程或顯示動態遊戲數據資料是透過程式接起來的)。
- 新增功能的時候,往往只是處理流程的不同,卻往往要重新設計對應的邏輯系統。
為何要客製化?對程式來說:
- 要有超多份的Layout或GUI Widget處理邏輯程式碼,比方一堆*LayoutModifier.cpp及一堆以下的Code:
...
{
strIndexName = UTILITY::ValueToString((int)(szItemIndex+1));
CManufactureInfo& REFManufactureInfo = m_vecManufactureInfo[szItemIndex];
//判斷一堆Widget Name,然後再填入正確的動態遊戲數據資料,好醜的Code...
REFManufactureInfo.m_cpInterface->GetQuilityIcon(strIconFileName);
pImageBox = static_cast
(REFGUIManager.GetWidget(m_strDestLayoutName,
"Building_Manufacture_BuildItemQualityImageBox_"+strIndexName));
assert(pImageBox);
pImageBox->setImageTexture(strIconFileName);
pImageBox = static_cast
(REFGUIManager.GetWidget(m_strDestLayoutName,
"Building_Manufacture_BuildItemImageBox_"+strIndexName));
assert(pImageBox);
pImageBox->setImageTexture(REFManufactureInfo.m_cpInterface->GetBaseIcon());
REFManufactureInfo.m_cpInterface->GetClassIcon(strIconFileName);
pImageBox = static_cast
(REFGUIManager.GetWidget(m_strDestLayoutName,
"Building_Manufacture_BuildItemClassImageBox_"+strIndexName));
assert(pImageBox);
pImageBox->setImageTexture(strIconFileName);
REFManufactureInfo.m_cpInterface->GetPartyFlagIcon(strIconFileName);
pImageBox = static_cast
(REFGUIManager.GetWidget(m_strDestLayoutName,
"Building_Manufacture_BuildItemPartTypeImageBox_"+strIndexName));
assert(pImageBox);
if(!strIconFileName.empty())
pImageBox->setImageTexture(strIconFileName);
else
pImageBox->setVisible(false);
pTextBox = static_cast
(REFGUIManager.GetWidget(m_strDestLayoutName,
"Building_Manufacture_BuildItemCountTextBox_"+strIndexName));
assert(pTextBox);
REFManufactureInfo.m_uiHaveCount =
CaluculateHaveCount(REFManufactureInfo.m_iID);
}
- 企劃每次一調整Widget的名稱,就會人仰馬翻,得在以上這種很醜的Code把相關對應的名稱修掉。
- 一堆流程要串接,一堆函式綁定要處理:
if(pButton->getName() == "BuyItem")
pButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemManager::OnBuyItem);
else if(pButton->getName() == "SellItem")
else if(pButton->getName() == "SellItem")
pButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemManager::OnSellItem);
else if(pButton->getName() == "SelectItem")
else if(pButton->getName() == "SelectItem")
pButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemManager::OnSelectItem);
...
...
- 其實只是一些功能或流程不同的結合,還是得寫兩份,比方以下程式碼:
{
...
}
{
...
}
{
int ItemID = ConvertNameToID(pSender->getName());
ProcessSelectItem(ItemID);
}
{
//真是無聊呀
int ItemID = ConvertNameToID(pSender->getName());
ProcessSelectItem(ItemID);
ProcessBuyItem(ItemID);
}
{
//真是無聊呀
int ItemID = ConvertNameToID(pSender->getName());
ProcessSelectItem(ItemID);
OnSellItem(ItemID);
}
//===================================================================
有沒有解決方法?其實真正要處理的問題是以下三大類:
GUI Widget初始值的綁定:
請參考以下MyGUI Layout檔的片斷:
property key="TextAlign" value="Right VCenter"...
}
{
...
}
{
int ItemID = ConvertNameToID(pSender->getName());
ProcessSelectItem(ItemID);
}
{
//真是無聊呀
int ItemID = ConvertNameToID(pSender->getName());
ProcessSelectItem(ItemID);
ProcessBuyItem(ItemID);
}
{
//真是無聊呀
int ItemID = ConvertNameToID(pSender->getName());
ProcessSelectItem(ItemID);
OnSellItem(ItemID);
}
//===================================================================
有沒有解決方法?其實真正要處理的問題是以下三大類:
- 如何取得GUI Widget的初始值?
- 當遊戲進行時,如何動態通知對應的GUI Widget要刷新對應的數值?
- 當GUI Widget觸發事件的時候(比方Click Button),要如何對應所要執行的程式邏輯功能(Delegate Function)
以上三件事情用LUA+LUABind+MyGUI就可以做到。
GUI Widget初始值的綁定:
請參考以下MyGUI Layout檔的片斷:
property key="FontHeight" value="24"
property key="TextColour" value="1 1 1"
//這是重點,使用了UserString的機制達到屬性值的綁定處理,Property代表是一個需要做屬性綁定的Widget,而Money則是屬性名稱
userstring key="Property" value="Money"
請參考以下程式碼片斷:(下面架構出來後,之後只要增新的對應處理函式及屬性名稱即可,比方"Reward"=>GetReward())
//Widget初始值設定
void CGUIManager::IntializeWidgetProperty(const std::string& strTypeName, MyGUI::Widget* pWidget, const std::string& strKey, const std::string& strKeyValue)
{
if(!m_pGUIManagementInterface)
return;
if(strKey != "Property")
return;
std::string strOutValue;
//這裡使用了Interface的架構來取得一個屬性值,降低相依性
if(m_pGUIManagementInterface->GetGUIPropertyValue(strKeyValue, strOutValue))
UpdatePropertyValue(strTypeName, pWidget, strOutValue);
//m_mapUserNameMapping是用來做名稱索引用,之後再解釋
m_mapUserNameMapping[strKeyValue].push_back(CMappingInformation(m_strInLoadingLayoutName, pWidget));
}
//m_pGUIManagementInterface的實作
bool CGameAPP::GetGUIPropertyValue(strPropertyName, strOutValue)
{
return m_GUIPropertyProcessor.RetrievePropertyValue(strPropertyName, strOutValue);
}
//m_GUIPropertyProcessor相關函式實作
CGUIPropertyProcessor::CGUIPropertyProcessor(void)
{
m_mapProcessManualTradeFuncs["Money"] = &COnePieceGUIPropertyProcessor::GetMoney;
m_mapProcessManualTradeFuncs["Diamond"] = &COnePieceGUIPropertyProcessor::GetDiamond;
}
bool CGUIPropertyProcessor::RetrievePropertyValue(const std::string& strPropertyName, std::string& strOutValue)
{
std::map
if(it == m_mapProcessManualTradeFuncs.end())
return false;
(this->*(it->second))(strOutValue);
return true;
}
void COnePieceGUIPropertyProcessor::GetMoney(std::string& OutstrValue)
{
const CPlayerInfo& REFPlayerInfo = CPlayerInfoManager::GetSingleton().GetPlayerInfo();
UTILITY::ValueToString(REFPlayerInfo.m_iHoldGolds);
OutstrValue = UTILITY::ValueToString(REFPlayerInfo.m_iHoldGolds);
}
void COnePieceGUIPropertyProcessor::GetDiamond(std::string& OutstrValue)
{
const CPlayerInfo& REFPlayerInfo = CPlayerInfoManager::GetSingleton().GetPlayerInfo();
UTILITY::ValueToString(REFPlayerInfo.m_iHoldGolds);
OutstrValue = UTILITY::ValueToString(REFPlayerInfo.m_iHoldDiamonds);
}
/補上UpdatePropertyValue的實作,目前只處理字串顯示的部份及更換貼圖的部份
void CGUIManager::UpdatePropertyValue(const std::string& strTypeName, MyGUI::Widget* pWidget, const std::string& strValue)
{
if((strTypeName == "TextBox") || (strTypeName == "Button"))
static_cast
else if(strTypeName == "ImageBox")
static_cast
}
//===================================================================
動態刷新GUI Widget的屬性值
這邊就要提一下m_mapUserNameMapping了,看一下這個東西的定義:
class CMappingInformation
{
public:
std::string m_strReferenceLayoutName; //所對應的Layout檔名稱,用來處理移除Layout時,所對應的MappingInformation也要移除之用
MyGUI::Widget* m_pTargetWidget; //Property所對應的Widget
//std::string m_strValue;
CMappingInformation(void){}
CMappingInformation(const std::string& strReferenceLayoutName, MyGUI::Widget* pTargetWidget):m_strReferenceLayoutName(strReferenceLayoutName),
m_pTargetWidget(pTargetWidget){}
virtual ~CMappingInformation(void){}
};
//Second是一個vector,也就是能處理超過一個以上的Widget顯示相關屬性(比方在不同的Widget顯示玩家所擁有的金幣數量)
std::map
void CGUIManager::IntializeWidgetProperty(const std::string& strTypeName, MyGUI::Widget* pWidget, const std::string& strKey, const std::string& strKeyValue)
{
if(!m_pGUIManagementInterface)
return;
if(strKey != "Property")
return;
std::string strOutValue;
//這裡使用了Interface的架構來取得一個屬性值,降低相依性
if(m_pGUIManagementInterface->GetGUIPropertyValue(strKeyValue, strOutValue))
UpdatePropertyValue(strTypeName, pWidget, strOutValue);
//m_mapUserNameMapping是用來做名稱索引用,在這裡做綁定
m_mapUserNameMapping[strKeyValue].push_back(CMappingInformation(m_strInLoadingLayoutName, pWidget));
}
//在這裡做屬性值的設定,UpdatePropertyValue之前有提過
bool CGUIManager::SetWidgetProperty(const std::string& strPropertyName, const std::string& strValue)
{
std::map
if(PropertyIt == m_mapUserNameMapping.end())
return false;
std::vector
while(MappingInfoIt != PropertyIt->second.end())
{
UpdatePropertyValue((*MappingInfoIt).m_pTargetWidget->getTypeName(), (*MappingInfoIt).m_pTargetWidget, strValue);
++MappingInfoIt;
}
return true;
}
//之後程式就可以很大方的直接刷新對應Widget的屬性值
CGUIManager::GetSingleton().SetWidgetProperty("Money", UTILITY::ValueToString(iHoldGolds));
CGUIManager::GetSingleton().SetWidgetProperty("Diamond", UTILITY::ValueToString(iHoldDiamonds));
//===================================================================
Script函式綁定(這裡使用了萬用的Command架構來做綁定,減少LUABind函式綁定所消耗的記憶體及複雜的函式綁定實作)
這裡才開始用到了LUA及LUABind,請參考以下Layou檔的內容:
Widget type="Button" skin="Button" position_real="0 0 0.205742 1" name="ShowPlayerInfoDetailButton"
Property key="ImageTexture" value="Mondy.png"
//Script表示是一段LUA的程式碼,GameCommandProcessor是透過LUABind Binding的一個Global變數,可以當Singleton使用
//,主要是用來分派命令用的一個處理器,ShowPlayerDetailInfo是一個CommandName,之後的參數用空白隔開
UserString key="Script" value="GameCommandProcessor:OnReceiveCommandString("ShowPlayerDetailInfo 1")"
請參考以下的程式碼:
...
//這裡將GUIManager裡的LUAState綁定了GameCommandProcessor及它的OnReceiveCommandString函式
luabind::module(pLuaState)[
luabind::class_
.def("OnReceiveCommandString", &CGameCommandProcessor::OnReceiveCommandString)
];
luabind::globals(pLuaState)["GameCommandProcessor"] = this;
void CGUIManager::OnAddUserString(MyGUI::Widget* pSender, const std::string& strKey, const std::string& strKeyValue)
{
...
//在讀取UserString時,如果是個Script,則將其Click事件對應到CGUIManager::OnScripButtionClick
if(strKey == "Script")
{
//eventMouseButtonReleased
pButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CGUIManager::OnScripButtionClick);
}
...
}
//執行Script的地方
void CGUIManager::OnScripButtionClick(MyGUI::Widget* pSender)
{
assert(m_pLUAContex);
if(strScriptString.empty())
return;
m_pLUAContex->ResetFromString(strScriptString, false);
}
//當Script執行完後,這裡會被喚起
void CGameCommandProcessor::OnReceiveCommandString(const std::string& strCommand)
{
if(strCommand.empty())
return;
std::vector
UTILITY::StringSplit(strCommand, vecArguments, " ");
if(!vecArguments.size())
return;
std::vector
std::string strCommandName = (*ArugmentIt);
vecArguments.erase(ArugmentIt);
std::map
if(ProcessIt != m_mapProcessCommandFunc.end())
(this->*((*ProcessIt).second))(vecArguments);
else if(m_pInterface)
m_pInterface->OnReceiveUnDefineCommand(strCommandName, vecArguments);
}
//之後只要做命令綁定,不用做Widget綁定,而且CommandProcessor的觸發也不一定要從GUI的Script來,遊戲的CommandLine Edit也可以下Command,
//所以做個CP值高,又不用認識WidgetName,摸蛤兼洗褲…
void CGameCommandProcessor::RebornClientActor(const std::vector
{
std::string ExtendModelPath;
if(m_pInterface)
m_pInterface->GetExtendModelPath(ExtendModelPath);
if(!ExtendModelPath.empty())
ExtendModelPath += "/";
CTransformData TranformData;
TranformData.m_LocationData.m_Position.ReadFromString(vecArguments[2]);
TranformData.m_ScalerData.m_ScaleFactor = CVector2(1.2f, 1.2f);
CEffectManager::GetSingleton().CreateEffectPackage(vecArguments[1], TranformData.m_LocationData.m_Position, CVector2::UNIT_SCALE, 0.0f);
std::string strDestModelPathName = ExtendModelPath+vecArguments[0];
CClientActorData ClientActorData(CModelData(TranformData, strDestModelPathName, "Idle", UTILITY::StringToBool(vecArguments[3]))
, "Idle", ENEMY_GROUP_TYPE_NPC, 10, 10);
CModelManager::GetSingleton().CreateModel
}
//===================================================================
後記:目前已經有將此開發方法應用在公司Cocos2d專案上(筆者之前已經有將此開發方法應
用在自己的Ogre3d Base的Game Engine Project上),目前成效良好,當企劃有要調整流程或動
態資料對應的時候,完全不用透過程式,除非有新的Widget實作需求、屬性或Command(重
點是新的積木需求,而不是改改流程或名字),才需要程式下去進行。如果企劃初期不熟,
程式自己在Layout加Command或對應屬性也不錯,因為至少不用 重新編譯程式碼,對手遊或
IOS更新資料即可執行而不用發佈新的版本也有幫助。
沒有留言:
張貼留言