アットウィキロゴ
2010/5/16のペリク鯖バックアップ
掲示板 掲示板 ページ検索 ページ検索 メニュー メニュー

2010/5/16のペリク鯖バックアップ

チュートリアル - XML,DLL,Pythonの連携

最終更新:

civilization

- view
だれでも歓迎! 編集

MOD/作成情報/チュートリアル - XML,DLL,Pythonの連携

概要

#ref error :ご指定のファイルが見つかりません。ファイル名を確認して、再度指定してください。 (girl.jpg)

ユニットに性別を加えるMODの作成を通して、主にXML,DLL,Python間の連携について学びます。
Civilization Bts 3.13 日本語版を使用。

完成MODダウンロード

#ref error :画像を取得できませんでした。しばらく時間を置いてから再度お試しください。

DLLのソースコードも同封

仕様の決定

ユニットの属性に性別を導入します。前人未到の18禁MOD開発への足掛かりであり妄想は広がりますが、初期段階としてまずは極力簡素な仕様を決めましょう。

  • 性別は男または女
  • どちらになるかはユニット作成時にランダムで決まる
  • ユニットの種類によって性別の有無がある - 戦士は性別有、カタパルトは性別無し
  • ユニット選択時の情報画面名前欄に性別が表示される

これを作ります。

MODフォルダ作成

MOD名はSexにします。My Documents\My Games\Beyond the Sword(J)\MODS\Sex フォルダを作成。

XMLに要素を加える

ユニットが性別をもつかどうかの情報をXMLに加えます。ユニットの種類ごとの情報は C:\Program Files\CYBERFRONT\Sid Meier's Civilization 4(J)\Beyond the Sword(J)\Assets\XML\Units\CIV4UnitInfos.xml に書かれていることはご存知でしょう。このXMLにおもむろに要素を加える、とその前に同じフォルダにある CIV4UnitSchema.xml を編集する必要があります。Schema(スキーマ)とはXMLの構造のことです。どの要素にどの要素が含まれているのか、例えば、UnitInfosタグにはUnitInfoが複数含まれていて……、という情報がこのファイルに書かれています。では MODS\Sex\Assets\XML\Units に CIV4UnitSchema.xml をコピーして、エディタで開いてください。"UnitInfo" で検索すると以下のような行が見つかります。

  • MODS\Sex\Assets\XML\Units\CIV4UnitSchema.xml
    
    ... 前略 ...
    	<ElementType name="UnitInfo" content="eltOnly">
    		<element type="Class"/>
    		<element type="Type"/>
    		<element type="UniqueNames"/>
    		<element type="Special"/>
    ... 中略 ...
    		<element type="iLeaderExperience"/>
    		<element type="iOrderPriority" minOccurs="0"/>
    	</ElementType>
    ... 中略 ...
    	<ElementType name="Class" content="textOnly"/>
    	<ElementType name="UniqueName" content="textOnly"/>
    	<ElementType name="UniqueNames" content="eltOnly">
    		<element type="UniqueName" minOccurs="0" maxOccurs="*"/>
    	</ElementType>
    	<ElementType name="Special" content="textOnly"/>
    ... 後略 ...
    
    
    これが表しているものはなんとなくわかりますね。UnitInfo定義の末尾に要素bHasSexを加えます。
  • MODS\Sex\Assets\XML\Units\CIV4UnitSchema.xml
    
    ...
    		<element type="iLeaderExperience"/>
    		<element type="iOrderPriority" minOccurs="0"/>
    		<!-- Sex MOD begin -->
    		<element type="bHasSex"/>
    		<!-- Sex MOD end -->
    	</ElementType>
    	<!-- Sex MOD begin -->
    	<ElementType name="bHasSex" content="textOnly" dt:type="boolean"/>
    	<!-- Sex MOD end -->
    ...
    

Civ4のコードの変数名にはハンガリアン記法が使われているので従っておきましょう。今回は性別を持つかどうかの真偽値なので boolean の b を頭に付けています。
続いて MODS\Sex\Assets\XML\Units に CIV4UnitInfos.xml をコピーして編集。各 UnitInfo に bHasSex 要素を加えます。

  • MODS\Sex\Assets\XML\Units\CIV4UnitInfos.xml
    
    ...
    <Civ4UnitInfos xmlns="x-schema:CIV4UnitSchema.xml">
    	<UnitInfos>
    		<UnitInfo>
    			<Class>UNITCLASS_LION</Class>
    			<Type>UNIT_LION</Type>
    ...
    			<LeaderPromotion>NONE</LeaderPromotion>
    			<iLeaderExperience>0</iLeaderExperience>
    			<bHasSex>1</bHasSex>
    		</UnitInfo>
    		<UnitInfo>
    			<Class>UNITCLASS_BEAR</Class>
    			<Type>UNIT_BEAR</Type>
    ...
    

ここでゲームを起動してみましょう。ゲームには何の変化もありませんが、XMLの文法エラーがないことが確認できます。

DLLの編集

ユニットオブジェクトに性別属性を追加するためにDLLのC++コードを編集します。DLL作成環境のセットアップはMOD/作成情報/CvGameCoreDLL.dllの作り方を参照。今の段階では元のコードのコピペレベルの事しかしないので、XML編集に比べて格別難解ということはありません。

XML → DLL

ロードしたCIV4UnitInfos.xmlのbHasSexの値をC++のコードから参照できるようにします。ユニット情報はCvInfo.h/.cppファイル中のCvUnitInfoに保存されるのでこれをいじります。他のXMLの変数がやっていることを真似すればOKです。Read/Writeの順番を一致させるよう気をつけましょう

  • CvInfo.h
    
    public:
    	DllExport bool hasSex() const;					// Exposed to Python
    protected:
    	bool m_bHasSex;
    
  • CvInfo.cpp
    
    CvUnitInfo::CvUnitInfo() :
    m_iAIWeight(0),
    ...
    m_bHasSex(false)
    {
    }
    
    ...
    bool CvUnitInfo::hasSex() const
    {
    	return m_bHasSex;
    }
    ...
    void CvUnitInfo::read(FDataStreamBase* stream)
    {
    	CvHotkeyInfo::read(stream);
    
    	uint uiFlag=0;
    	stream->Read(&uiFlag);	// flags for expansion
    
    	stream->Read(&m_iAIWeight);
    	stream->Read(&m_iProductionCost);
    ...
    	stream->Read(&m_bHasSex);
    ...
    }
    ...
    void CvUnitInfo::write(FDataStreamBase* stream)
    {
    	CvHotkeyInfo::write(stream);
    
    	uint uiFlag=0;
    	stream->Write(uiFlag);		// flag for expansion
    
    	stream->Write(m_iAIWeight);
    	stream->Write(m_iProductionCost);
    ...
    	stream->Write(m_bHasSex);
    ...
    }
    ...
    bool CvUnitInfo::read(CvXMLLoadUtility* pXML)
    {
    ...
    	pXML->GetChildXmlValByName(&m_bHasSex, "bHasSex");
    ...
    
    

CvUnitクラス拡張

続いて、ユニットクラスに性別フィールドを加えます。CvEnum.hで性別型定義を、CvUnitに性別フィールド・アクセサ・初期化コードを普通に書きます。

  • CvEnum.h
    
    ...
    enum DllExport SexTypes		// Exposed to Python
    {
    	NO_SEX = -1,
    
    	SEX_MALE,
    	SEX_FEMALE 
    };
    ...
    
  • CvUnit.h
    
    ...
    public:
    	DllExport bool hasSex() const;									// Exposed to Python
    	DllExport SexTypes getSex() const;								// Exposed to Python
    	DllExport void setSex(SexTypes eSex);							// Exposed to Python
    protected:
    	SexTypes m_eSexType;
    	void setRandomSex();
    ...
    
    
  • CvUnit.cpp
    
    ...
    void CvUnit::reset(int iID, UnitTypes eUnit, PlayerTypes eOwner, bool bConstructorCall)
    {
    ...
    	setRandomSex();
    ...
    }
    ...
    bool CvUnit::hasSex() const
    {
    	return m_eSexType != NO_SEX;
    }
    
    SexTypes CvUnit::getSex() const
    {
    	return m_eSexType;
    }
    
    void CvUnit::setSex(SexTypes eSex)
    {
    	m_eSexType = eSex;
    }
    
    void CvUnit::setRandomSex()
    {
    	if (m_eUnitType == NO_UNIT || !m_pUnitInfo->hasSex())
    		m_eSexType = NO_SEX;
    	else
    		m_eSexType = GC.getGameINLINE().getSorenRandNum(2, "Sex selection") == 0 ? SEX_MALE : SEX_FEMALE;
    }
    ...
    

最後の行について。CvGame::getSorenRandNum(int iNum, const char* pszLog) は0以上iNum未満のランダムなintを返します。第2引数にはロギングのためランダム数の用途を書きます。

DLL → Python

これでユニットに性別が付くようになりました。次はPythonからユニットオブジェクトの性別にアクセスできるようにします。ここまで編集したC++のクラス名はCvから始まっていましたが、これをCyに変えたものがPythonからDLLにアクセスするためのラッパークラスです。CyUnit に性別のアクセサを加えます。

  • CyUnit.h
    
    public:
    ...
    	bool hasSex();
    	int /* SexTypes */ CyUnit::getSex();
    	void setSex(int /* SexTypes */ eSex);
    ...
    
    
  • CyUnit.cpp
    
    bool CyUnit::hasSex()
    {
    	return m_pUnit ? m_pUnit->hasSex() : false;
    }
    int /* SexTypes */ CyUnit::getSex()
    {
    	return m_pUnit ? (int)m_pUnit->getSex() : (int)NO_SEX;
    }
    void CyUnit::setSex(int /* SexTypes */ eSex)
    {
    	if (m_pUnit)
    		m_pUnit->setSex((SexTypes) eSex);
    }
    
    
    enumはintに変換しないといけないので注意しましょう。
    実はこのままではまだPythonからメソッドにアクセスできません。Cy*Interface.cpp で明示的にメソッドをエクスポートする必要があります。
  • CyUnitInterface.cpp
    
    ...
    void CyUnitPythonInterface1(python::class_<CyUnit>& x)
    {
    	OutputDebugString("Python Extension Module - CyUnitPythonInterface1\n");
    
    	x
    		.def("isNone", &CyUnit::isNone, "bool () - Is this a valid unit instance?")
    		.def("convert", &CyUnit::convert, "void (CyUnit* pUnit)")
    ...
    		.def("hasSex", &CyUnit::hasSex, "bool ()")
    		.def("getSex", &CyUnit::getSex, "SexTypes ()")
    		.def("hasSex", &CyUnit::setSex, "void (SexTypes)")
    ...
    
    
  • CyEnumsInterface.cpp
    
    ...
    	python::enum_<SexTypes>("SexTypes")
    		.value("NO_SEX", NO_SEX)
    		.value("SEX_MALE", SEX_MALE)
    		.value("SEX_FEMALE", SEX_FEMALE)
    		;
    ...
    
    
    
  • CyInfoInterface1.cpp
    
    ...
    	python::class_<CvUnitInfo, python::bases<CvInfoBase, CvScalableInfo> >("CvUnitInfo")
    
    		.def("getAIWeight", &CvUnitInfo::getAIWeight, "int ()")
    		.def("getProductionCost", &CvUnitInfo::getProductionCost, "int ()")
    		.def("getHurryCostModifier", &CvUnitInfo::getHurryCostModifier, "int ()")
    ...
    		.def("hasSex", &CvUnitInfo::hasSex, "bool ()")
    ...
    
    
    
    第3引数の文字列はメソッドの説明文です。もっときちんと書くとより良し。
    これでめでたくPythonからユニットの性別を参照できるようになりました。

ゲームのインターフェースに表示

メイン画面の左下のユニット表示欄をカスタマイズするため、C:\Program Files\CYBERFRONT\Sid Meier's Civilization 4(J)\Beyond the Sword(J)\Assets\Python\Screens\CvMainInterface.py を置き換えます。MODS\Assets\Python\Screens にコピーして編集。

  • MODS\Sex\Assets\Python\Screens\CvMainInterface.py
    
    ...
    				# >>> Sex MOD
    				##if (pHeadSelectedUnit.getHotKeyNumber() == -1):
    				##	szBuffer = localText.getText("INTERFACE_PANE_UNIT_NAME", (pHeadSelectedUnit.getName(), ))
    				##else:
    				##	szBuffer = localText.getText("INTERFACE_PANE_UNIT_NAME_HOT_KEY", (pHeadSelectedUnit.getHotKeyNumber(), pHeadSelectedUnit.getName()))
    				szName = pHeadSelectedUnit.getName()
    				if (pHeadSelectedUnit.hasSex()):
    					if pHeadSelectedUnit.getSex() == SexTypes.SEX_MALE:
    						szName += localText.getText("TXT_KEY_SEX_MALE", ())
    					elif pHeadSelectedUnit.getSex() == SexTypes.SEX_FEMALE:
    						szName += localText.getText("TXT_KEY_SEX_FEMALE", ())
    				if (pHeadSelectedUnit.getHotKeyNumber() == -1):
    					szBuffer = localText.getText("INTERFACE_PANE_UNIT_NAME", (szName, ))
    				else:
    					szBuffer = localText.getText("INTERFACE_PANE_UNIT_NAME_HOT_KEY", (pHeadSelectedUnit.getHotKeyNumber(), szName))
    				# <<< Sex MOD
    ...
    
    
  • MODS\Sex\Assets\XML\Text\CIV4GameText_Sex.xml
    
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <Civ4GameText xmlns="http://www.firaxis.com">
    	<TEXT>
    		<Tag>TXT_KEY_SEX_MALE</Tag>
    		<English>m</English>
    		<French>m</French>
    		<German>m</German>
    		<Italian>m</Italian>
    		<Spanish>m</Spanish>
    		<Japanese>&#9794;</Japanese>
    	</TEXT>
    	<TEXT>
    		<Tag>TXT_KEY_SEX_FEMALE</Tag>
    		<English>f</English>
    		<French>f</French>
    		<German>f</German>
    		<Italian>f</Italian>
    		<Spanish>f</Spanish>
    		<Japanese>&#9792;</Japanese>
    	</TEXT>
    </Civ4GameText>
    

これで完成です。ではゲームを起動してMODをロードしましょう。どこでつまづいたかを調べるのは結構大変なので、実際の開発の際は少しの修正ごとにこまめに確認することをお勧めします。

#ref error :ご指定のファイルが見つかりません。ファイル名を確認して、再度指定してください。 (chu.jpg)

ジャガ男さんとジャガ子さんが麦畑

以上です。お疲れ様でした。

タグ:

+ タグ編集
  • タグ:
記事メニュー
最近更新されたスレッド
ウィキ募集バナー