基于MVC设计模式,在游戏客户端代码开发过程中,易于跨引擎(引擎兼容)和测试的代码设计方法
一、前言
本文讲的设计方法,不涉及算法、优化、接口讲解等技术介绍。
游戏开发的一些问题。
伪代码,有一点代码基础的问基本看得懂。
该设计方法由师兄教授,在项目使用过之后,感觉确实不错,特地提取一个方法论出来以记录。
二、MVC简介
在游戏开发中,目前用到架构主要分为MVC和ESC架构(这部分如有异议欢迎指正,有其他架构也希望能提出,博主也可以学习)。
在一个功能复杂的模块中,通常会有很多的UI,MVC将控制和View分离可以的看清整个功能的结构,而且在扩展和代码复用方面有很大的益处(同一个控制类中,方法可以复用;以及添加一个界面或功能加文件就行了)
1 class Model():
2 def __init__(self):
3 self.data = {}
4
5 class Ctrl():
6 def __init__(self):
7 self.model = Model()
8 self.view = View()
9
10 class View():
11 def __init__(self):
12 LoadUIFile(sUrl)
其他就不做太详细的介绍了,这里起个抛转引玉的作用,想深入了解的可以自行搜索相关内容。
本文主要用到的是MVC中的控制类
三、服务类抽取
这个是本文的重点,目的是将客户端具体实现逻辑和提供服务的引擎接口/通信协议分离。
1、为什么要将提供非客户端数据的接口/通信协议(主要是获得服务端数据)封装,这点的作用主要表现在下一块,本块不讲
2、为什么将引擎提供的方法分离(主要是引擎提供的数据和方法),这是本块的重点。
首先,假设我们做一个pc游戏,我们逻辑正常怎么写?
class Ctrl():
def __init__(self):
self.model = Model()
self.view = View()
def Working(self):
DoPCwork()
DoNextWork()
如上所示,DoPCwork应该改成Dowork,因为我们如果只是简单制作一个游戏的话,不会考虑跨平台的问题。但是如果你是一个专业的游戏开发者,或者想要把游戏做大的话,就需要考虑这些了。
这个时候,如果我们需要兼容安卓平台,或者IOS,那应该怎么做?
def Working(self):
# 我随便搜到的cocos的接口
if cc.sys.isMobile:
DoMobilework()
else:
DoPCwork()
DoNextWork()
耦合了,所以你必须去修改控制逻辑,那怎么确保你现在的控制逻辑是正确的?需要通过测试。当然这个代码只改了一个if,测试起来方便的很,只测一个条件就够了。但是如果其他地方有细微的小改动呢?为了确保质量,必须全部测一遍!
控制逻辑是代码的核心,必须保证它的正确性。但是我只是做个兼容,本身逻辑没怎么变,居然就要直接对控制逻辑动手脚是种很危险的行为。因此,我们需要把引擎提供的数据和方法抽取出来。
class ServiceBase():
@classmethod
def Dowork(cls):
pass
class PCService(ServiceBase):
@classmethod
def Dowork(cls):
DoPCwork()
class MobileService(ServiceBase):
@classmethod
def Dowork(cls):
DoMobilework()
def GetService():
# 这里用到了python的特性
# 效果等于返回一个实例
if cc.sys.isMobile:
return MobileService
else:
return PCService
class Ctrl():
def __init__(self):
self.model = Model()
self.view = View()
def Working(self):
GetService().Dowork()
DoNextWork()
这里用到了设计模式的核心思想——面向接口编程。继承实现具体方法,接口选择用哪种去实现。好处其一,就是易扩展,也是设计模式经常考虑的问题之一,我再换个平台(比如Mac端)再写一个方法继承即可。其二就是,无论你怎么扩展,你的核心逻辑不会变,测试成功一次之后,你的这个逻辑就不会错了,错也一定是引擎相关的问题。
总结:抽出引擎提供的服务,可增加工程的扩展性,以及发生错误时能更快速准确的定位问题。
四、逻辑类测试
非客户端提供的数据服务,用人话说就是引擎提供的数据以及服务端提供的数据。
举个例子
class Ctrl():
def __init__(self):
self.model = Model()
self.view = View()
def Working(self):
Socket.GetServerTime()
DoNextWork()
又只能去改控制逻辑(捂脸笑哭.jpg)。因为你非客户端数据服务和逻辑又耦合了。那么,把它抽出来!
class ServiceBase():
# 服务类基础,这里其实并不需要
@classmethod
def GetServerTime(cls):
pass
class Service(ServiceBase):
# 提供具体服务
@classmethod
def GetServerTime(cls):
return Socket.GetServerTime()
class TestService(Service):
@classmethod
def GetServerTime(cls):
return "2019/11/11 11:11"
test = True
def GetService():
# 这里用到了python的特性
# 效果等于返回一个实例
global test
if test:
return TestService
else:
return Service
class Ctrl():
def __init__(self):
self.model = Model()
self.view = View()
def Working(self):
GetService().GetServerTime()
DoNextWork()
通过继承 and 重写去伪造客户端本身所不能提供的逻辑,可以在不修改控制逻辑的情况下,完成测试。如上面代码样例,测试环境和正式环境只改一个字段就可以了。
五、总结
总结:这个设计方法的是将非客户端的数据服务,以及引擎提供的服务,进行提取,然后通过OOP继承and重写的特性去做逻辑测试和兼容。目的是避免测试和兼容过程中,对控制逻辑作修改,保证安全。
但是这个设计方法有个问题,就是如果不是和数据相关的引擎方法,即使抽取了,测试方法不变,因为他依赖图形界面,造成了代码的冗余。当然你这里可以说“我不提取也可以啊”,这句话没问题,是可以的,但是如果数据服务和非数据服务同时存在的同时,只抽取数据服务影响代码的一致性,抽取非数据服务又会造成代码的冗余,这一部分如何去择一,就要看具体需求了。
设计模式or方法终究是一种思想,是一种对某种特殊情况的巧妙的思想,但是绝不会适用于任何情况。
这个设计方法经过一段时间的使用之后,我觉得是个非常不错的设计方法。
这种“玩法很简单,但是就是能让你眼前一亮”才是小游戏的乐趣。