【游戏面包屑】功能:排序(过滤器)、查找(最相似)、记忆回退Esc
作者:互联网
通过过滤器模式实现功能:根据不同选择对数据进行针对性排序。
思路
1.获得玩家的排序选择(如类型、数量、价格等,可使用简单的按钮button组合或者下拉框等方式)。
2.使用过滤器模式进行设计。
3.根据玩家的选择,选取不同的过滤器进行数据过滤、排序并返回。
过滤器模式
帮助开发者用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
1.创建一个类
2.创建一个接口-接口实现方法:过滤方法(参数为该类的列表,返回也为该类的列表)
3.创建该接口的实体类来过滤对象类的列表
通过在实体类中的过滤方法进行过滤(实体类的过滤方法中新建列表,然后遍历传入的参数列表,过滤符合条件的加入到新建列表中。最后返回这个新建列表)
过滤器的核心有两点:1.通过接口将其实体类的过滤方法联系起来。2.针对具体过滤条件进行编程。
应用场景:需要对一系列的对象进行不同条件筛选的场景。
如排序、筛选符合条件的对象集等。
相关部分代码:
本例在排序部分使用了快排。
接口的实体类
public interface IGoodsCriteria
{
/// <summary>
/// 排序标准
/// </summary>
/// <param name="listGoods">排序的列表</param>
/// <param name="order">>=0表示顺序,0<表示逆序</param>
/// <returns></returns>
List<BaseGoods> sortCriteria(List<BaseGoods> listGoods,int order = 1);
}
public class SortGoodsAmount : IGoodsCriteria
{
public List<BaseGoods> sortCriteria(List<BaseGoods> listGoods,int order =1)
{
List<BaseGoods> amountGoods = new List<BaseGoods>();
//快排·从小到大
ToolMgr.Instance.QuickSortList(listGoods, GlobalPropertyMgr.SortGoodsType.Amount, 0, listGoods.Count - 1);
//顺序
if (order >= 0)
{
//将排序结果顺序加入
for (int i = 0; i < listGoods.Count; i++)
{
amountGoods.Add(listGoods[i]);
}
}
//逆序
else
{
//将排序结果逆序加入
for (int i = listGoods.Count - 1; i >= 0; i--)
{
amountGoods.Add(listGoods[i]);
}
}
return amountGoods;
}
}
public class SortGoodsQuality : IGoodsCriteria
{
public List<BaseGoods> sortCriteria(List<BaseGoods> listGoods, int order = 1)
{
List<BaseGoods> qualityGoods = new List<BaseGoods>();
//快排·从小到大
ToolMgr.Instance.QuickSortList(listGoods, GlobalPropertyMgr.SortGoodsType.Quality, 0, listGoods.Count - 1);
//顺序
if (order >= 0)
{
//将排序结果顺序加入
for (int i = 0; i < listGoods.Count; i++)
{
qualityGoods.Add(listGoods[i]);
}
}
//逆序
else
{
//将排序结果逆序加入
for (int i = listGoods.Count-1; i >=0; i--)
{
qualityGoods.Add(listGoods[i]);
}
}
return qualityGoods;
}
}
流程代码:
private void OnClickDPOption(int index)
{
//根据下拉选项框进行排序方式的选择
sortType = (SortGoodsType)dpSortOrder.value;
}
//修改排序次序
private void OnClickChangeOrder()
{
//修改排序顺序
intSortOrder *= -1;
//重置dicBagItem
//点击事件
foreach (BagItem item in dicBagItem.Values)
{
item.ClickBtn.onClick.RemoveListener(delegate { OnClickBagItem(item.Go); });
}
//字典数据清除
dicBagItem.Clear();
//每次点击事件时根据点击的选项生成,默认生成第一个选项(“全部”)
//清除原有的选项(优化:修改原有的选项,多余的删除,不足的新建)
//清除所有选项
for (int i = 0; i < goBagContent.transform.childCount; i++)
{
Destroy(goBagContent.transform.GetChild(i).gameObject);
}
//按照该排序方式进行排序更新
OnSort();
AddBagItemBySortList();
}
//排序
private void OnSort()
{
//根据选择的排序方式进行过滤器排序
//默认为品质从高到低排序。采用快排算法
switch (sortType)
{
case SortGoodsType.Quality:
listBagItem = listSortQuality.sortCriteria(listBagItem, intSortOrder);
break;
case SortGoodsType.Amount:
listBagItem = listSortAmount.sortCriteria(listBagItem, intSortOrder);
break;
default:
Debug.LogError("Wrong sortType:"+ sortType);
break;
}
}//OnClickSort_end
/// <summary>
/// 根据listBagItem(排序列表)进行重新生成物品选项集
/// </summary>
private void AddBagItemBySortList()
{
foreach (BaseGoods bagItem in listBagItem)
{
//根据需要,初始化BagItem选项
BagItem newBagItem = new BagItem();
//Go,new预设体并设置父节点。需要调整SceneMgr
newBagItem.Go = GameObject.Instantiate(SceneMgr.Instance.UIGoBagItem, goBagContent.transform);
//根据具体的预设体,调整BagItem类与以下数据获取的语句
newBagItem.Name = newBagItem.Go.GetChildControl<Text>("Name");
newBagItem.Icon = newBagItem.Go.GetChildControl<Image>("Icon");
newBagItem.ClickBtn = newBagItem.Go.GetComponent<Button>();
newBagItem.Amount = newBagItem.Go.GetChildControl<Text>("Amount");
newBagItem.Quality = newBagItem.Go.GetChildControl<Text>("Quality");
//修改Text文本
if (String.IsNullOrEmpty(bagItem.Name))
{
Debug.LogError("item.refObjs.Name:" + bagItem.Name);
return;
}
newBagItem.Name.text = bagItem.Name;
//如果数量<=1,则不显示数量
if (bagItem.Amount<=1)
{
newBagItem.Amount.gameObject.SetActive(false);
}
else
{
newBagItem.Amount.text = bagItem.Amount.ToString();
}
newBagItem.Quality.text = bagItem.ValueType;
//更新图标icon
if (String.IsNullOrEmpty(bagItem.IconPath))
{
Debug.LogError("item.refObjs.Name:" + bagItem.IconPath);
return;
}
newBagItem.Icon.sprite = Resources.Load<Sprite>(bagItem.IconPath);
//添加点击事件
newBagItem.ClickBtn.onClick.AddListener(delegate { OnClickBagItem(newBagItem.Go); });
newBagItem.Id = bagItem.Id;
dicBagItem.Add(bagItem.Id, newBagItem);
}
}
快速排序算法
private int Paritition<T>(List<T> list, SortGoodsType sortKind, int low, int high)
{
//判定是否为BaseGoods
var tryList = list as List<BaseGoods>;
if (tryList != null)
{
BaseGoods pivotObj = tryList[low];
//自定义数据类型,前置操作符要比后置操作符性能好点
switch (sortKind)
{
case SortGoodsType.Quality:
//品质:int(数值越大,品质越好)
while (low < high)
{
//从高位一直往后寻找第一个比pivot小或等于的数
while (low < high && pivotObj.Quality <= tryList[high].Quality)
{
--high;
}
//将array[low]即pivot,赋给这个比pivot小的数
list[low] = list[high];
//从低位一直往前到当前high的位置寻找第一个比pivot大或等于的数
while (low < high && pivotObj.Quality >= tryList[low].Quality)
{
++low;
}
//将array[high]赋值给array[low]。
tryList[high] = tryList[low];
}
//将保存的pivot(初始值)赋值给low,完成交换
tryList[low] = pivotObj;
//将当前的位置返回
return low;
case SortGoodsType.Amount:
//数量:int
while (low < high)
{
//从高位一直往后寻找第一个比pivot小或等于的数
while (low < high && pivotObj.Amount <= tryList[high].Amount)
{
--high;
}
//将array[low]即pivot,赋给这个比pivot小的数
list[low] = list[high];
//从低位一直往前到当前high的位置寻找第一个比pivot大或等于的数
while (low < high && pivotObj.Amount >= tryList[low].Amount)
{
++low;
}
//将array[high]赋值给array[low]。
tryList[high] = tryList[low];
}
//将保存的pivot(初始值)赋值给low,完成交换
tryList[low] = pivotObj;
//将当前的位置返回
return low;
default:
break;
}
}
Debug.LogError("No find List<obj> or wrong sortKind!");
return low;
}
/// <summary>
/// List快排
/// 备注:与其做成泛型增加复杂度(N*M),不如拆解为各自类的List的方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="sortType">枚举:排序类型</param>
/// <param name="low">起始点</param>
/// <param name="high">终止点</param>
public void QuickSortList<T>(List<T> list,SortGoodsType sortType, int low, int high)
{
//判定是否为BaseGoods
var tryList = list as List<BaseGoods>;
if (tryList != null)
{
if (low < high)
{
int pivot = Paritition(list, sortType, low, high);
QuickSortList(list, sortType, low, pivot - 1);
QuickSortList(list, sortType, pivot + 1, high);
}
}
//根据需要添加判定是否为其他类List
//...
}
实现效果:
查找(通过最相似字符串匹配对目标字符串相似数据查找)
1.获取玩家需要查找的数据(如使用UI获取玩家输入,并对数据进行整理)
2.对数据集遍历寻找玩家需要的数据(简单的if语句进行匹配即可)。对于如名字等这类字符串使用简单的最相似字符串匹配算法进行匹配检测。
3.将符合查找条件的数据整合并返回。
最相似字符串匹配算法:
设置一个匹配度(int,0~1)。制定匹配规则(大小写错误惩罚、字符缺失惩罚、字符匹配加权等)。将字符串从头到尾遍历匹配字符,每找到一个符合匹配规则的字符,根据匹配规则对匹配度进行累加,并开始下一个字符匹配。直到到达字符串尾部。最终得到一个0~1的匹配度,0表示毫无相关,1表示一模一样。匹配度越接近1,表示2个字符串越匹配。
代码:
//BagPanel
//查找
private void OnClickSearch()
{
//关闭详细信息UI
curGoods = null;
//打开条件查找窗口SearchPanel
//位置为具体信息UI的地方出现,方便玩家查找。
WindowMgr.Instance.OpenWindow<SearchPanel>(true);
//窗口的确定/查找按钮使用消息播报传回
Send.SendMsg(SendType.SearchSendInfo, this.name);
RefreshUI();
}
private void EventSearchGoods(object[] _objs)
{
if (_objs.Length < 5)
{
Debug.LogError("Wrong args");
return;
}
string name = (string)_objs[0];
int mainType = (int)_objs[1];
int kind = (int)_objs[2];
int quality = (int)_objs[3];
int priceBegin = (int)_objs[4];
int priceEnd = (int)_objs[5];
ClearDicBagItem();
//将性能消耗最多的名字匹配放在最里层
foreach (BaseGoods bagItem in PlayerMgr.Instance.ItemView.Bag.DetailData.Values)
{
//寻找匹配MainType的背包数据或curMainType为"全部"
if (bagItem.MainType == mainType || mainType == GlobalPropertyMgr.MAINTYPE_ALL)
{
//寻找匹配Kind的背包数据或curKind为"全部"
if (bagItem.Kind == kind || kind == GlobalPropertyMgr.KIND_ALL)
{
if (bagItem.Quality==quality || quality==GlobalPropertyMgr.QUALITY_ALL)
{
if (bagItem.PriceSpace <= priceEnd && bagItem.PriceSpace>=priceBegin)
{
//如果名字有输入,则进行匹配
if (!string.IsNullOrEmpty(name))
{
//名字匹配·最相似字符串算法:6-55,写在GameTool中,传入string与string,返回相似匹配的数值
//待优化:将匹配度按高到底再排序一次
float similar = GameToolMgr.MaxSimilarMatchString(name,bagItem.Name);
if (similar >= GlobalPropertyMgr.FLO_SIMILAR_NAME)
{
listBagItem.Add(bagItem);
}
}
else
{
listBagItem.Add(bagItem);
}
}
}//if_Quality
}//if_Kind
}//if_MainType
}//foreach_Bag.DetailData_end
//根据当前排序选项进行list排序
OnSort();
//根据listBagItem进行生成UI物品选项
AddBagItemBySortList();
}
//SearchPanel
private void OnClickSearch()
{
//将搜集到的查找信息反馈给对应的Panel
string name = inputName.text;
int mainType = dpMainType.value;
int kind = dpKind.value;
int quality = dpQuality.value;
int priceBegin = int.Parse(inputPriceBegin.text);
int priceEnd = int.Parse(inputPriceEnd.text);
switch (sendName)
{
case "Win_BagPanel":
Send.SendMsg(SendType.SearchBagGoods,name,mainType,kind,quality,priceBegin,priceEnd);
break;
case "Win_TradePanel":
Send.SendMsg(SendType.SearchTradeGoods, name, mainType, kind, quality, priceBegin, priceEnd);
break;
default:
Debug.LogError("Wrong Name Switch:"+sendName);
break;
}
}
//GameToolMgr
/// <summary>
/// 最相似字符串匹配
/// 说明:该算法不是最优化算法,最佳性能不是本算法的首要考虑方向。游戏正常运行是不会调用该算法。
/// 本算法匹配规则未实现:位置错误惩罚
/// 后续游戏需要则根据具体需求优化该算法的新版本或使用经典字符串匹配算法技术
/// 优化方向:创建索引的哈希表、动态规划思想、额外的存储等。
/// </summary>
/// <param name="str1"></param>
/// <param name="str2"></param>
/// <returns>匹配度</returns>
public static float MaxSimilarMatchString(string str1, string str2)
{
//匹配度
//每找到一个匹配字符,则给matchVal增加比重数值
float matchVal = 0;
//左右字符串的长度
int leftSize = str1.Length;
int rightSize = str2.Length;
//取大的字符串的长度
int largerSize = leftSize > rightSize ? leftSize : rightSize;
//起始
int left = 0;
int right = 0;
//记录匹配的字符数量
int count = 0;
while (left != leftSize && right != rightSize)
{
//最简单的左右匹配检测
if (str1[left] == str2[right])
{
//增加权值
matchVal += 1.0f / largerSize;
++count;
//若字符串未到其尾部,则+1
if (left != leftSize)
{
++left;
}
if (right != rightSize)
{
++right;
}
}
//大小写无关的匹配检测
else if (char.ToLower(str1[left]) == char.ToLower(str2[right]))
{
//增加权值,需乘以 大小写敏感惩罚系数
matchVal += 1.0f / largerSize * CaseSensitive;
++count;
//若字符串未到其尾部,则+1
if (left != leftSize)
{
++left;
}
if (right != rightSize)
{
++right;
}
}
else
{
//核心:在限定的距离内(bestCount)遍历寻找目标
int lsBest = leftSize;
int rsBest = rightSize;
int totalCount = 0;
//StrBestCount为默认遍历长度,数值越小效率越高(如果字符串过长,而该值过小,可能导致与预期有相差)
//如"超级无敌霹雷火云邪神求和信"与"求和信"。按理“求和信”应该有匹配度,如果StrBestCount设置为5,其访问不到,导致最终匹配值为0
//如果StrBestCount设置为15,就可以访问到,最终得到匹配值为:0.2307692
int bestCount = StrBestCount;
int leftCount = 0;
int rightCount = 0;
//在外层循环遍历整个左字符串,并确保不会超过最佳数量(bestCount)
for (int ls = left; ls != (leftSize) && (leftCount + rightCount) < bestCount; ++ls)
{
for (int rs = right; rs != (rightSize) && (leftCount + rightCount) < bestCount; ++rs)
{
//大小写无关的匹配检测
if (char.ToLower(str1[ls]) == char.ToLower(str2[rs]))
{
//如果当前遍历总数量(长度)>=最佳数量,则跳过并停止循环(局部剪枝优化)
totalCount = ls + rs;
if (totalCount < bestCount)
{
//更新最佳数量
bestCount = totalCount;
lsBest = ls;
rsBest = rs;
}
}
++rightCount;
}
++leftCount;
rightCount = 0;
}
//从匹配的左右位置开始下一个比较。在下一轮的比较中进行matchVal值的计算
left = lsBest;
right = rsBest;
}
}
//匹配数量缺失惩罚
//matchVal需要乘以 其匹配字符数量占最长字符串长度的比重。
matchVal *= count * 1.0f / largerSize;
//避免浮点错误:如果需要返回这个matchVal
if (matchVal > 0.99f)
{
matchVal = 1.0f;
}
else if (matchVal < 0.01f)
{
matchVal = 0.0f;
}
//打印相关信息
Debug.Log(str1 + "|" + str2 + "|匹配度:" + matchVal);
return matchVal;
}
实现效果:
记忆回退Esc
创建一个栈。当手动打开一个窗口时入栈这个窗口,关闭这个窗口时出栈一个窗口进行关闭。Esc则是一个手动出栈窗口,当栈的数量>0时关闭最近一个窗口。但数量为0时,打开Esc窗口。
代码:
//EscPanel
/// <summary>
/// 当玩家点击Esc按键时,出现或隐藏EscUI
/// 如果是在最外层,则显示或隐藏Esc。负责回退上一层。类似原神打开其他窗口,按Esc回退上一层
/// </summary>
/// <param name="_objs"></param>
private void EventEscBtn(object[] _objs) {
//如果Esc栈中没有保存的窗口,则显示/隐藏Esc窗口
//需要设计一个关闭CD
//...
if (WindowMgr.Instance.StackWindow.Count<=0)
{
if (goEscContent.activeSelf)
{
goEscContent.SetActive(false);
}
else
{
goEscContent.SetActive(true);
}
}
else
{
//移除栈顶,并关闭该窗口
string closeWin = WindowMgr.Instance.StackWindow.Peek();
WindowMgr.Instance.CloseWindow(closeWin,true);
}
}
/// <summary>
/// 窗口管理
/// </summary>
public class WindowMgr : Singleton<WindowMgr> {
public Dictionary<string, BaseWindow> allList = new Dictionary<string, BaseWindow>();
public List<string> openList = new List<string>();
public Stack<string> StackWindow = new Stack<string>(); //窗口栈(用于玩家返回上一级使用)
public void OpenWindow<T>(bool addStack=false) where T : BaseWindow {
string winName = typeof(T).Name;
BaseWindow window = GetWindow(winName);
if (window == null) {
Debug.LogError("open window fail window is null " + winName);
return;
}
if (window.hasOpen)
return;
if (window.windowInfo.group != 0) {
CloseGroupWindow(window.windowInfo.group);
}
if (addStack)
{
StackWindow.Push(winName);
}
openList.Add(winName);
window.transform.SetAsLastSibling();
window.DoOpen();
}
public void CloseWindow<T>(bool popStack = false) {
string winName = typeof(T).Name;
CloseWindow(winName,popStack);
}
/// <summary>
/// 关闭窗口
/// </summary>
/// <param name="winName">需要关闭的窗口名</param>
/// <param name="isPop">是否需要出栈</param>
public void CloseWindow(string winName,bool isPop = false) {
BaseWindow window = GetWindow(winName);
if (window == null)
{
Debug.LogError("open window fail window is null " + winName);
return;
}
if (!window.hasOpen)
return;
//如果需要出栈,且出栈的窗口名与需要关闭的窗口名一致
if (isPop && winName.CompareTo(StackWindow.Peek())==0)
{
//出栈
StackWindow.Pop();
openList.Remove(winName);
window.DoClose();
}
else
{
//只关闭窗口
openList.Remove(winName);
window.DoClose();
}
}
}
演示效果
标签:排序,匹配,int,List,Esc,low,newBagItem,回退,面包屑 来源: https://blog.csdn.net/Evil_Carl/article/details/122015141