游戏的MVC框架
前言
最近看看公司的代码,了解到了公司的游戏UI基本上都是利用MVC框架去实现的,所以想写一篇关于MVC框架的博客。公司用的是lua去实现,我就直接用C#在Unity中实现一个排行榜系统.
GitHub项目:
一、MVC框架的概念
1.1 什么是MVC框架
MVC框架是一种设计模式,在游戏中一般用于UI模块,它将UI模块分为三个部分:模型(Model也就是数据),视图(View也就是UI界面),控制器(Controller).模型用于负责管理数据与逻辑,视图负责用户的界面,控制器负责处理用户输入和协调模型和视图 .
1.2 MVC框架的优点
可以将应用程序的数据,视图和控制器分离,使代码更加清晰和易维护.也可以方便扩展模型和视图的功能,在项目中能够修改数据结构或者UI界面,而不影响其他部分
1.3 MVC框架流程:
- Ctrl: Controller
- M: Model
- V: View
- 接收数据 : 服务器发送数据在C中接收数据
- 更新数据 : Ctrl更新M中的数据
- 获得数据:V从Ctrl中获得M数据并更新V
- 发送数据: V用户交互通过Ctrl数据发送给服务器
如下图所示:
二、MVC框架项目(实现排行榜)
2.1 基本思路
利用MVC框架简单的实现以下排行榜,我会利用Xml文件序列化与反序列化去进行一个本地化存储.一共分了四个代码去实现.
- XmlUtil:Xml序列化与反序列化
- RankData:排行榜的数据与逻辑
- UI_Rank:排行榜的界面
- RankCtrl:用于负责处理排行榜输入和协调数据和界面
2.2 相关代码
2.2.1 序列化相关代码(XmlUtil)
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
using UnityEngine;
using System.IO;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;
public class XmlUtil
{
//序列化:将数据写入Xml文件中
public static void SerializeXml<T>(string path, T data) where T: class
{
if (path == null || data == null) return;
if (File.Exists(path)) File.Delete(path);
//XmlSerializer:用于xml序列化与反序列的类
XmlSerializer serializer = new XmlSerializer(typeof(T));
//FileStream:表示文件流的类,它继承自Stream类,可以对文件进行字节级的读写操作。
FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
//StreamWriter:表示文本写入器的类,将字符或字符串编码为字节并写入到一个Stream中
using (StreamWriter sw = new StreamWriter(fileStream, System.Text.Encoding.UTF8))
{
serializer.Serialize(sw, data);//保存数据
sw.Close();
fileStream.Close();
}
}
//反序列化,读取数据
public static T DeserializeXml<T>(string path) where T : class
{
T data = null;
try
{
if (path == null) return null;
if (!File.Exists(path))
{
XmlDocument xml = new XmlDocument();
//创建根节点
XmlElement root = xml.CreateElement("objects");
//将根节点添加到xml文档中
xml.AppendChild(root);
xml.Save(path);
return null;
}
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
XmlSerializer serializer = new XmlSerializer(typeof(T));
data = serializer.Deserialize(fs) as T;
}
catch
{
Debug.LogError("读取文件失败!");
}
return data;
}
//深拷贝
public static T DeepCopy<T>(T obj)
{
object retval;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
retval = bf.Deserialize(ms);
ms.Close();
}
return (T)retval;
}
}
2.2.2 排行榜数据相关代码(RankData)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System;
using System.Xml.Serialization;
using System.IO;
using System.Linq;
[Serializable]
public class RankData
{
private string path = Application.streamingAssetsPath + "/rank.xml";
private RankMembers rankMemers;
private List<Member> members;
public Member[] Members
{
get
{
if (rankMemers != null && rankMemers.members != null && rankMemers.members.Length > 0) return rankMemers.members;
return new Member[1] { new Member(1, 0) };
}
set { }
}
//初始化数据
public void InitData()
{
members = new List<Member>();
rankMemers = GetRankXml();
if(rankMemers != null && rankMemers.members != null && rankMemers.members.Length > 0)
{
foreach (var item in rankMemers.members)
{
members.Add(item);
}
}
}
//获得本地化数据
public RankMembers GetRankXml()
{
RankMembers rankmemers = new RankMembers();
rankmemers = XmlUtil.DeserializeXml<RankMembers>(path);
return rankmemers;
}
//刷新当前排行榜数据
public void RefreshRankMemeber(Member member)
{
if(members.Count < 10)
{
members.Add(member);
rankMemers.members = members.ToArray();
SortRank(rankMemers, members);
}
else
{
var lastIndex = members.Count - 1;
var lastmember = members[lastIndex];
if (lastmember.score <= member.score)
{
lastmember.score = member.score;
member = null;
rankMemers.members = members.ToArray();
SortRank(rankMemers, members);
}
}
}
//对排行榜数据进行排序
public void SortRank(RankMembers rankmemers, List<Member> members)
{
Array.Sort(rankmemers.members, (a, b) => { return b.score - a.score; });
members.Sort((a, b) => { return b.score - a.score; });
if(rankmemers.members.Length > 0)
{
for (int i = 0; i < rankmemers.members.Length; i++)
{
var item = rankmemers.members[i];
item.id = i + 1;
members[i].id = i + 1;
}
}
}
public void OnDestory()
{
SaveRankData();
members.Clear();
members = null;
rankMemers = null;
}
//保存数据
public void SaveRankData()
{
if (path == null)
{
Debug.LogError("RankXml的路径为空!");
return;
}
if (!File.Exists(path)) File.Create(path);
try
{
XmlUtil.SerializeXml(path, rankMemers);
}
catch
{
Debug.Log("Xml文件保存失败!");
}
}
}
//需要序列化的类
[Serializable]
[XmlRoot("Members")]
public class RankMembers
{
public Member[] members;
}
[Serializable]
public class Member
{
[XmlAttribute("id")]
public int id;
[XmlAttribute("score")]
public int score;
public Member() { }
public Member(int _id, int _score)
{
id = _id;
score = _score;
}
}
2.2.3 排行榜界面(UI_Rank)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UI_Rank : MonoBehaviour
{
private Text[] texts;
private Transform obj;
private Button closeBtn;
private Button addBtn;
private RankData rankData;
private InputField inputField;
private Action<Member> addAction;
private void Start()
{
OnInit();
}
//获得RankCtlr里的rankData数据,以及初始化UI
private void OnInit()
{
rankData = RankCtrl.Instance.RankData;
InitUI();
}
//初始化UI
private void InitUI()
{
obj = transform.Find("Scroll View/Viewport/Content");
inputField = transform.Find("InputField").GetComponent<InputField>();
addBtn = transform.Find("AddBtn").GetComponent<Button>();
closeBtn = transform.Find("CloseBtn").GetComponent<Button>();
texts = new Text[10];
int num = obj.childCount;
for (int i = 0; i < num; i++)
{
texts[i] = obj.GetChild(i).gameObject.GetComponent<Text>();
}
closeBtn.onClick.AddListener(CloseSelf);
addBtn.onClick.AddListener(AddMember);
RefreshUI();
}
//率刷新UI界面
private void RefreshUI()
{
if (rankData != null)
{
if(rankData.Members.Length == 0)
{
for (int i = 0; i < 10; i++)
{
texts[i].text = string.Format("第{0}名: {1}分", i + 1, 0);
}
}
else if(rankData.Members.Length < 10)
{
for (int i = 0; i < rankData.Members.Length; i++)
{
var item = rankData.Members[i];
texts[i].text = string.Format("第{0}名: {1}分", item.id, item.score);
}
for (int i = rankData.Members.Length; i < 10; i++)
{
texts[i].text = string.Format("第{0}名: {1}分", i + 1, 0);
}
}
else
{
for (int i = 0; i < rankData.Members.Length; i++)
{
var item = rankData.Members[i];
texts[i].text = string.Format("第{0}名: {1}分", item.id, item.score);
}
}
}
}
//点击按钮,添加排行榜成员,并刷新UI
private void AddMember()
{
var numTxt = inputField.text;
if (int.TryParse(numTxt, out var num))
{
Member member = new Member();
member.score = num;
addAction.Invoke(member);
RefreshUI();
}
}
//回调函数
public void SetHandler(Action<Member> action)
{
addAction = action;
}
//关闭UI
private void CloseSelf()
{
this.gameObject.SetActive(false);
}
private void OnDestroy()
{
texts = null;
addAction = null;
}
}
2.2.4 控制器(RankCtrl)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
public class RankCtrl : MonoBehaviour
{
private RankData rankData;
private GameObject rankPanelObj;
private UI_Rank rankPanel;
public RankData RankData => rankData;
private void Awake()
{
OnInit();
}
//初始化
private void OnInit()
{
rankData = new RankData();
rankData.InitData();
rankPanelObj = GameObject.Find("UI_Rank");
rankPanel = rankPanelObj.GetComponent<UI_Rank>();
rankPanel.SetHandler(AddRankMember);
}
//单例模式
private static RankCtrl instance;
public static RankCtrl Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<RankCtrl>();
if(instance == null)
{
GameObject rankCtrlObj = new GameObject("RankCtrl");
instance = rankCtrlObj.AddComponent<RankCtrl>();
}
DontDestroyOnLoad(instance.gameObject);
}
return instance;
}
set { }
}
//添加数据
public void AddRankMember(Member member)
{
rankData.RefreshRankMemeber(member);
}
public void OnDestroy()
{
rankData.OnDestory();
rankData = null;
rankPanelObj = null;
rankPanel = null;
}
}
三、对MVC框架的想法
在网上搜索了很多博客和资料,也拿这些资料与公司的MVC框架做比较,最后发现,MVC只是一种设计模式,它的体现是将数据和UI分离开来,提高代码的可扩展、可维护性等.一千个读者里面有一千个哈姆雷特,在游戏中不同的人实现MVC框架的方式不同,最后实现的都是由自己做决定,只要其主要思想不变就行.