第十一章 地图动作与地图事件(Map Action and Map Event)
我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制。
我们这一章来完成地图上的操作,地图的操作将全部由MapAction
控制。
十二 完善地图信息与测试(Perfect MapEventInfo and Testing)
我们来测试我们的成果。
1 完善地图信息(Perfect MapEventInfo)
首先,我们先来完善一下地图信息。
我们之前将所有的地图事件都放在了一起,在这里,我们将地图开始和地图结束的事件分开。
1.1 地图开始事件(Map Start Event)
这个事件是在读取地图时触发的。且它没有条件,是直接触发,且只触发一次。
我们在CanTrigger
中,让它直接返回true
,这样就直接屏蔽了条件。
我们单独建立一个类:
using System;
namespace DR.Book.SRPG_Dev.ScriptManagement
{
[Serializable]
public class MapEventNoCondition : MapEvent
{
public override bool CanTrigger(MapAction action)
{
if (!onlyonce)
{
onlyonce = true;
}
return true;
}
}
}
1.2 地图结束事件(Map End Event)
这个事件是在地图结束时触发的。无论它触发什么事件,在结尾都应该触发WinResult
或者是LoseResult
,同样它们只能触发一次。
所以这个事件,我们添加它们:
using System;
using System.Collections;
using System.Xml.Serialization;
namespace DR.Book.SRPG_Dev.ScriptManagement
{
[Serializable]
public class MapEventWinLose : MapEvent
{
[XmlElement("WinResult", typeof(WinResult))]
[XmlElement("LoseResult", typeof(LoseResult))]
public Result resultTrigger;
public override bool CanTrigger(MapAction action)
{
if (!onlyonce)
{
onlyonce = true;
}
return base.CanTrigger(action);
}
public override IEnumerator Trigger(MapAction action, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
// 触发过了
if (isTriggered)
{
yield break;
}
// 是否满足所有触发条件
if (!CanTrigger(action))
{
yield break;
}
// 触发事件
if (triggers != null && triggers.Count != 0)
{
int i = 0;
while (i < triggers.Count)
{
Result trigger = triggers[i++];
if (trigger == null)
{
continue;
}
if (!trigger.Trigger(action))
{
if (onError != null)
{
onError(string.Format("MapEvent {0} -> Event Trigger error.", id));
}
isTriggered = true;
yield break;
}
do
{
yield return null;
} while (action.status != ActionStatus.WaitInput);
}
}
if ((resultTrigger == null)
|| (resultTrigger.type != MapEventResultType.WinResult && resultTrigger.type != MapEventResultType.LoseResult)
|| (!resultTrigger.Trigger(action)))
{
if (onError != null)
{
onError(string.Format("MapEventWinLose {0} -> Event Trigger error.", id));
}
isTriggered = true;
action.MapEndCommand(-1);
yield break;
}
// 如果只能触发一次,则设置触发过事件了
if (onlyonce)
{
isTriggered = true;
}
if (onTriggered != null)
{
onTriggered(this);
}
}
}
}
1.3 胜利结果(Win Result)
我们结束地图的事件是可以有多个分支,
例如胜利条件有多种:
-
到达某地
-
杀死某个角色
失败条件类似,也可以有多种。
我们在这里规定失败时的参数就是-1,而胜利可以有多种状态。在剧本中,判断就可以有多种选择。
例如
battle Stage0Scene map0;
if Stage0Scene == 1 goto #Stage0WinPos;
if Stage0Scene == 2 goto #Stage0WinRole;
// other
goto #Stage0Lose;
所以我们的胜利结果需要一个参数:
[Serializable]
public class WinResult : Result
{
[XmlAttribute]
public int result;
public sealed override MapEventResultType type
{
get { return MapEventResultType.WinResult; }
}
public override bool Trigger(MapAction action)
{
action.MapEndCommand(result);
return true;
}
}
提示:加入参数后,可以不使用LoseResult
(它显得多余了),只使用WinResult
。
1.4 地图信息(Map Event Info)
类似于胜利结果,状态有多种,所以我们也可能有多个胜利结果。
using System;
using System.Xml.Serialization;
namespace DR.Book.SRPG_Dev.ScriptManagement
{
[Serializable]
public class MapEventInfo
{
// 省略其它参数
[XmlElement]
public MapEventNoCondition startEvent;
[XmlArray, XmlArrayItem]
public MapEvent[] events;
[XmlArray, XmlArrayItem]
public MapEventWinLose[] resultEvents;
}
}
不要忘记,在MapAction
中也要做相应的修改:
/// <summary>
/// 读取事件
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
private bool LoadMapEvent(MapEventInfo info)
{
// 地图加载事件
if (info.startEvent != null)
{
if (info.startEvent.entryConditionType != MapEventConditionType.NoneCondition)
{
info.startEvent.entryConditionType = MapEventConditionType.NoneCondition;
}
m_MapEvents.Add(info.startEvent);
}
// 地图中的事件
if (info.events != null)
{
for (int i = 0; i < info.events.Length; i++)
{
MapEvent me = info.events[i];
if (me == null)
{
continue;
}
if (!m_MapEvents.Add(me))
{
error = string.Format(
"MapAction -> Load map event failure. Entry type `{0}` is not supported."
+ " Or, id of map event is already exist.",
me.entryConditionType);
return false;
}
}
}
// 地图结束事件
if (info.resultEvents != null)
{
for (int i = 0; i < info.resultEvents.Length; i++)
{
MapEvent me = info.resultEvents[i];
if (me == null)
{
continue;
}
if (!m_MapEvents.Add(me))
{
error = string.Format(
"MapAction -> Load map event failure. Entry type `{0}` is not supported."
+ " Or, id of map event is already exist.",
me.entryConditionType);
return false;
}
}
}
// 执行地图加载事件
TriggerEvents(MapEventConditionType.NoneCondition, () =>
{
OnChangeTurn(AttitudeTowards.Player);
});
return true;
}
2 测试(Testing)
在测试阶段,我们可以多些一些地图的测试脚本进行测试。
2.1 地图信息与地图剧本(Map Event Info and Map Script)
最基本的脚本map0.xml
:
<?xml version="1.0" encoding="utf-8"?>
<MapEventInfo nextScene="" scenarioName="stage0">
<mouseCursor prefab="MouseCursor" x="5" y="5" />
<cursor>Cursor</cursor>
<startEvent id="0" onlyonce="true">
<triggers>
<CreateObjectResult>
<ClassUnique x="5" y="5" attitudeTowards="Player" id="0" />
<ClassUnique x="18" y="5" attitudeTowards="Enemy" id="1" />
<ClassFollowing x="19" y="5" attitudeTowards="Enemy" id="0" level="1" items="0" />
</CreateObjectResult>
<ScenarioResult flag="#Start" />
</triggers>
</startEvent>
<resultEvents>
<MapEventWinLose id="10" onlyonce="true">
<PositionCondition x="2" y="4" />
<triggers>
<ScenarioResult flag="#Win" />
</triggers>
<WinResult result="1" />
</MapEventWinLose>
<MapEventWinLose id="20" onlyonce="true">
<PositionCondition x="2" y="6" />
<LoseResult />
</MapEventWinLose>
</resultEvents>
</MapEventInfo>
-
地图开始事件:
-
创建3个角色(1个玩家,2个敌人)(2个独有角色,1个杂兵);
-
从剧本
stage0
的剧情标识符#Start
开始运行剧本;
-
-
地图事件:无;
-
地图结束事件:
-
到达坐标(2,4)从剧本
stage0
的剧情标识符#Win
开始运行剧本,之后战斗胜利; -
到达坐标(2,6)战斗失败。
-
这里只是举了个例子,你可以自己编写条件,并尝试更多的条件与结果。
地图剧本为stage0.txt
:
// stage0.txt
// 首关
#Start; // 剧本开始
text top
"战斗好激烈啊,我们行不行?";
text bottom
"没问题的,放心吧。";
clear text;
back;
#Win;
text top
"我们赢了";
clear text;
back;
2.2 测试剧情剧本(Test Script)
同样的,我们的测试剧本test.txt
也需要修改:
// 测试剧本
// test.txt
#Testing; // 测试剧本
#TestMenuIfGotoCmd;
clear text;
menu option
"测试var、calc和debug命令"
"测试text命令"
"测试battle命令与MapAction";
if option == 0 goto #TestVarCalcDebugCmd;
if option == 1 goto #TestTextCmd;
if option == 2 goto #TestMapAction;
goto #Testing;
// 省略中间剧本代码
#TestMapAction;
text global
"3:测试MapAction:"
"场景:Stage0Scene;"
"剧本:map0。";
battle Stage0Scene map0;
if Stage0Scene == 1 goto #Stage0Win;
goto #Stage0Lose;
#Stage0Win;
text global
"战斗胜利";
goto #TestMapActionEnd;
#Stage0Lose;
text global
"战斗失败";
goto #TestMapActionEnd;
#TestMapActionEnd;
text global
"测试MapAction结束";
goto #Testing;
2.3 测试结果(Testing Result)
我们直接在Unity中运行游戏,查看测试结果,如 图 11.12 所示。
- 图 11.12 Testing
2.4 测试完整代码(Testing Code)
修改测试代码(EditorTestGamePlot.cs
):
#region ---------- File Info ----------
/// **********************************************************************
/// Copyright (C) 2019 DarkRabbit(ZhangHan)
///
/// File Name: EditorTestGamePlot.cs
/// Author: DarkRabbit
/// Create Time: Sat, 12 Jan 2019 23:56:52 GMT
/// Modifier:
/// Module Description:
/// Version: V1.0.0
/// **********************************************************************
#endregion ---------- File Info ----------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace DR.Book.SRPG_Dev.ScriptManagement.Testing
{
using DR.Book.SRPG_Dev.Framework;
using DR.Book.SRPG_Dev.Models;
public class EditorTestGamePlot : MonoBehaviour
{
public const string testSceneName = "TestGamePlotScene";
public bool m_DebugInfo = true;
public string m_TestScript = "test";
public bool m_IsTxt = true;
#region Unity Callback
#if UNITY_EDITOR
private void Awake()
{
ConfigLoader.rootDirectory = Application.streamingAssetsPath + "/Config";
ConfigLoader.LoadConfig(typeof(TextInfoConfig));
}
private void Start()
{
GameDirector.instance.debugInfo = m_DebugInfo;
GameDirector.instance.firstScenario = m_TestScript;
GameDirector.instance.firstScenarioIsTxt = m_IsTxt;
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
SceneManager.LoadSceneAsync("InterludeScene", LoadSceneMode.Single);
}
private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.LogFormat("Load Scene: {0}.", scene.name);
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
if (!GameDirector.instance.LoadFirstScenario())
{
Debug.LogError("Load first scenario error.");
EditorApplication.isPlaying = false;
return;
}
GameDirector.instance.RunGameAction();
}
#endif
#endregion
}
}
至此,本章内容全部完成。