版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/darkrabbit/article/details/87918459
第十一章 地图动作与地图事件(Map Action and Map Event)
我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制。
我们这一章来完成地图上的操作,地图的操作将全部由MapAction
控制。
文章目录
八 地图事件(Map Event)
地图事件是我们地图中所发生的事情,包括:
-
触发条件;
-
触发结果。
它也比较常见,例如:
条件 | 结果 | FE4中实例 |
---|---|---|
地图开始 | 创建各种地图对象 | 战斗开始时的剧情,然后创建地图对象 |
角色到达某个地点 | 获取物品 | 第一章时斧骑士到达海边获取二回攻击斧 |
某回合开始 | 创建各种地图对象 | 某些剧情需要 |
对应角色对话 | 获取物品、提升属性等 | 某些章节对话 |
等等诸如此类的事件。
这一节,我们挑选一些典型的事件来说明。
1 地图事件类(Class MapEvent)
我们的地图事件必须包含:
-
入口条件(主条件);
-
一些子条件;
-
触发结果。
当然,还有一些帮助类的属性:
-
唯一标识(
id
); -
是否只触发一次;
-
是否触发过。
创建类:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace DR.Book.SRPG_Dev.ScriptManagement
{
[Serializable]
public class MapEvent
{
[XmlAttribute]
public int id;
/// <summary>
/// 是否只能触发一次
/// </summary>
[XmlAttribute]
public bool onlyonce;
/// <summary>
/// 进入事件条件的类型
/// </summary>
[XmlIgnore]
public MapEventConditionType entryConditionType;
/// <summary>
/// 进入事件条件
/// </summary>
[XmlChoiceIdentifier("entryConditionType")]
[XmlElement("NoneCondition", typeof(Condition)),
XmlElement("TurnCondition", typeof(TurnCondition)),
XmlElement("PositionCondition", typeof(PositionCondition)),
XmlElement("RoleCondition", typeof(RoleCondition), IsNullable = true),
XmlElement("RoleDeadCondition", typeof(RoleDeadCondition)),
XmlElement("RoleTalkCondition", typeof(RoleTalkCondition)),
XmlElement("RoleCombatTalkCondition", typeof(RoleCombatTalkCondition)),
XmlElement("PropertyCondition", typeof(PropertyCondition), IsNullable = true),
XmlElement("ItemCondition", typeof(ItemCondition), IsNullable = true)]
public Condition entryCondition;
/// <summary>
/// 额外的事件条件
/// </summary>
[XmlArray]
[XmlArrayItem(typeof(TurnCondition)),
XmlArrayItem(typeof(PositionCondition)),
XmlArrayItem(typeof(RoleCondition)),
XmlArrayItem(typeof(RoleDeadCondition)),
XmlArrayItem(typeof(PropertyCondition)),
XmlArrayItem(typeof(ItemCondition))]
public List<Condition> conditions = new List<Condition>();
/// <summary>
/// 事件结果
/// </summary>
[XmlArray]
[XmlArrayItem(typeof(ScenarioResult)),
XmlArrayItem(typeof(CreateObjectResult)),
XmlArrayItem(typeof(PositionResult)),
XmlArrayItem(typeof(PropertyResult)),
XmlArrayItem(typeof(ItemResult)),
XmlArrayItem(typeof(WinResult)),
XmlArrayItem(typeof(LoseResult))]
public List<Result> triggers = new List<Result>();
/// <summary>
/// 是否已经触发过
/// </summary>
[XmlIgnore]
public bool isTriggered { get; private set; }
public MapEvent()
{
isTriggered = false;
}
// TODO 方法
}
}
在触发事件之前,我们应该判断每一个条件。
/// <summary>
/// 是否满足单个事件条件
/// </summary>
/// <param name="condition"></param>
/// <param name="action"></param>
/// <returns></returns>
protected bool CanConditionTrigger(Condition condition, MapAction action)
{
if (condition == null)
{
return true;
}
return condition.GetResult(action);
}
/// <summary>
/// 是否满足所有事件条件
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public virtual bool CanTrigger(MapAction action)
{
if (!CanConditionTrigger(entryCondition, action))
{
return false;
}
if (conditions != null && conditions.Count != 0)
{
for (int i = 0; i < conditions.Count; i++)
{
if (!CanConditionTrigger(conditions[i], action))
{
return false;
}
}
}
return true;
}
最后,触发我们的事件。
要注意, 触发时,我们规定每帧执行一个结果,而结果可能包含剧情,这时应该等待剧情运行结束后,再执行下面的结果。
/// <summary>
/// 触发事件
/// </summary>
/// <param name="action"></param>
/// <param name="onTriggered"></param>
/// <param name="onError"></param>
/// <returns></returns>
public virtual 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 (onlyonce)
{
isTriggered = true;
}
if (onTriggered != null)
{
onTriggered(this);
}
}
2 地图事件集合(Map Event Collection)
我们建立一个集合,将所有的MapEvent
都存储到这里面,且它还负责执行事件。
创建类:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace DR.Book.SRPG_Dev.ScriptManagement
{
/// <summary>
/// 事件集合
/// </summary>
public class MapEventCollection
{
#region Fields
/// <summary>
/// 所有事件
/// </summary>
protected readonly Dictionary<int, MapEvent> m_Events = new Dictionary<int, MapEvent>();
/// <summary>
/// 触发过的事件
/// </summary>
protected readonly Dictionary<int, MapEvent> m_TriggeredEvents = new Dictionary<int, MapEvent>();
/// <summary>
/// 进入地图事件
/// </summary>
public readonly List<MapEvent> startEvents = new List<MapEvent>();
/// <summary>
/// 每回合开始事件
/// </summary>
public readonly List<MapEvent> turnEvents = new List<MapEvent>();
/// <summary>
/// 角色死亡事件
/// </summary>
public readonly Dictionary<int, MapEvent> deadEvents = new Dictionary<int, MapEvent>();
/// <summary>
/// 对话事件
/// </summary>
public readonly Dictionary<Vector2Int, MapEvent> roleTalkEvents = new Dictionary<Vector2Int, MapEvent>();
/// <summary>
/// 战斗对话事件
/// </summary>
public readonly Dictionary<Vector2Int, MapEvent> roleCombatTalkEvents = new Dictionary<Vector2Int, MapEvent>();
/// <summary>
/// 移动到坐标事件
/// </summary>
public readonly Dictionary<Vector3Int, MapEvent> posEvents = new Dictionary<Vector3Int, MapEvent>();
#endregion
// TODO
}
}
2.1 添加(Add)
我们在添加事件时,要注意它们的唯一标识。
/// <summary>
/// 添加事件
/// </summary>
/// <param name="me"></param>
/// <returns></returns>
public bool Add(MapEvent me)
{
if (m_Events.ContainsKey(me.id))
{
return false;
}
m_Events.Add(me.id, me);
switch (me.entryConditionType)
{
case MapEventConditionType.NoneCondition:
startEvents.Add(me);
break;
case MapEventConditionType.TurnCondition:
turnEvents.Add(me);
break;
case MapEventConditionType.PositionCondition:
MapEvent.PositionCondition pc = me.entryCondition as MapEvent.PositionCondition;
posEvents.Add(new Vector3Int(pc.x, pc.y, 0), me);
break;
case MapEventConditionType.RoleDeadCondition:
MapEvent.RoleDeadCondition rdc = me.entryCondition as MapEvent.RoleDeadCondition;
deadEvents.Add(rdc.characterId, me);
break;
case MapEventConditionType.RoleTalkCondition:
MapEvent.RoleTalkCondition rtc = me.entryCondition as MapEvent.RoleTalkCondition;
if (rtc.characterId == rtc.targetId)
{
return false;
}
roleTalkEvents.Add(new Vector2Int(rtc.characterId, rtc.targetId), me);
break;
case MapEventConditionType.RoleCombatTalkCondition:
MapEvent.RoleCombatTalkCondition rctc = me.entryCondition as MapEvent.RoleCombatTalkCondition;
if (rctc.characterId == rctc.targetId)
{
return false;
}
roleCombatTalkEvents.Add(new Vector2Int(rctc.characterId, rctc.targetId), me);
break;
default:
return false;
}
return true;
}
2.2 触发事件(Trigger Events)
在触发事件后,我们要将触发过的事件加入到m_TriggeredEvents
中。而可以重复触发的事件则不动。
为每一个入口条件创建触发方法:
public virtual IEnumerator TriggerStartEvents(MapAction action, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
int i = 0;
while (i < startEvents.Count)
{
MapEvent me = startEvents[i];
yield return me.Trigger(action, onTriggered, onError);
if (me.isTriggered)
{
startEvents.RemoveAt(i);
m_TriggeredEvents.Add(me.id, me);
}
else
{
i++;
}
}
}
public virtual IEnumerator TriggerTurnEvents(MapAction action, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
int i = 0;
while (i < turnEvents.Count)
{
MapEvent me = turnEvents[i];
yield return me.Trigger(action, onTriggered, onError);
if (me.isTriggered)
{
turnEvents.RemoveAt(i);
m_TriggeredEvents.Add(me.id, me);
}
else
{
i++;
}
}
}
public virtual IEnumerator TriggerDeadEvents(MapAction action, int id, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
MapEvent me;
if (!deadEvents.TryGetValue(id, out me))
{
yield break;
}
yield return me.Trigger(action, onTriggered, onError);
if (me.isTriggered)
{
deadEvents.Remove(id);
m_TriggeredEvents.Add(me.id, me);
}
}
public virtual IEnumerator TriggerRoleTalkEvents(MapAction action, int id, int target, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
Vector2Int key = new Vector2Int(id, target);
MapEvent me;
if (!roleTalkEvents.TryGetValue(key, out me))
{
yield break;
}
yield return me.Trigger(action, onTriggered, onError);
if (me.isTriggered)
{
roleTalkEvents.Remove(key);
m_TriggeredEvents.Add(me.id, me);
}
}
public virtual IEnumerator TriggerRoleCombatTalkEvents(MapAction action, int id1, int id2, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
Vector2Int key = new Vector2Int(id1, id2);
MapEvent me;
if (!roleCombatTalkEvents.TryGetValue(key, out me))
{
key = new Vector2Int(id2, id1);
if (!roleCombatTalkEvents.TryGetValue(key, out me))
{
yield break;
}
}
yield return me.Trigger(action, onTriggered, onError);
if (me.isTriggered)
{
roleCombatTalkEvents.Remove(key);
m_TriggeredEvents.Add(me.id, me);
}
}
public virtual IEnumerator TriggerPositionEvents(MapAction action, Vector3Int position, Action<MapEvent> onTriggered = null, Action<string> onError = null)
{
MapEvent me;
if (!posEvents.TryGetValue(position, out me))
{
yield break;
}
yield return me.Trigger(action, onTriggered, onError);
if (me.isTriggered)
{
posEvents.Remove(position);
m_TriggeredEvents.Add(me.id, me);
}
}
2.3 清空事件(Clear Events)
/// <summary>
/// 删除所有事件
/// </summary>
public void Clear()
{
m_Events.Clear();
m_TriggeredEvents.Clear();
startEvents.Clear();
turnEvents.Clear();
deadEvents.Clear();
posEvents.Clear();
roleTalkEvents.Clear();
roleCombatTalkEvents.Clear();
posEvents.Clear();
}
3 读取事件(Load Map Events)
我们在MapEventInfo
中,将事件添加进去:
[XmlArray, XmlArrayItem]
public MapEvent[] events;
然后在MapAction
中,填充我们的LoadMapEvent
:
private bool LoadMapEvent(MapEventInfo info)
{
// 地图中的事件
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;
}
}
}
// TODO 触发开始事件
return true;
}