20.点缀作战地图——享元模式
周一的早上,精力充沛(这是因为周日没有加班),随时准备迎接新的挑战。
挑战马上就到。
为了展现战争的巨大破坏力量,老板决定在地图上添加大量的残垣断壁单位,这样可以让战场景象更真实。听说你创建了游戏中的各种单位,这个工作只好又交给你了。
第一个方案现在,你首先在EUnit枚举中添加了新的成员:
''游戏单位标识枚举
Public Enum EUnit
...
Debris = 5004 '残骸
End Enum
(项目:FlyweightPatternDemo 文件:Enums.vb)
接下来着手继承CUnit单位;创建CDebris类,就像这样:
Public Class CDebris
Inherits CUnit
Public Sub New()
myBehavior = New CNoBehavior
myWeapon = New CNoWeapon
mySpeed = 0
UnitId = EUnit.Debris
End Sub
End Class
(项目:FlyweightPatternDemo 文件:CDebris.vb)
最后,在CBuildUnitCreator类中添加这个单位的创建代码就好了:
''建筑物单位创建者
Public Class CBuildUnitCreator
Inherits CUnitCreator
Public Overrides Function CreateUnit(ByVal unitType As EUnit) As IUnit
Select Case unitType
...
Case EUnit.Debris
Return New CDebris
...
End Select
End Function
End Class
(项目:FlyweightPatternDemo 文件:Creator.vb)
一些都是模式化的,修改起来非常简单,又是不到10分钟,你就将代码提交了,看来这次不会有什么问题了,地图团队的那帮人一定得请你吃午饭。
但是(我也不喜欢但是),项目经理很快又过来了。
“关于新的单位‘残垣断壁’们,有一些问题。”
l 你只设计了一种残骸的枚举成员,也就是说只能有一种残垣断壁,这不可能,战场上应该有各式各样的建筑物和其它类型的残骸,比如汽车、飞机、工厂等等。
l 作战单位已经创建了太多的对象,而这些残骸又不能动,又不能作为其他单位的掩体;它们只是显示在那儿就行了,不用浪费这么多资源去创建对象。
l 这个功能是地图模块的,而不是作战单位模块;你应该把显示这些单位的代码放到CMapManager类中;比如,在CMapManager类中创建一个ShowDebris()的方法,让它们随机分布就行了。
l 还有一点,作战单位如果觉得这些东西挡路,可以把它们轰掉。
问题还真多!只能重新设计了。“设计模式!赐予我力量吧!”
好吧,我们使用享元模式(Flyweight Patterns)。
使用享元模式享元模式(Flyweight Pattern),又称为蝇量模式,它的定义是:运用共享技术有效地支持大量“轻量级”的对象;使用享元模式时,我们只创建一个实例来管理大量的虚拟对象,比如本例中的那些残垣断壁。
地图组的CMapManager类代码下载过来了,你不能修改任何原有代码,只能添加Debris相关的代码。在这期间,CMapManager类将被代码管理系统锁定,所以你得抓紧时间,不然,地图团队的那帮人就有理由去喝茶了。
我们先来解决第一个问题,多种Debris,现在我们创建一个EDebris枚举,用于定义多种类型的残骸类型,它的定义如下:
Public Enum EDebris
HouseA = 9001
HouseB = 9002
CarA = 9003
CarB = 9004
End Enum
(项目:FlyweightPatternDemo 文件:Enums.vb)
第二个问题,虽然这些Debris单位不能有什么动作,但它们还是应该有一些信息,比如位置、类型标识、名称,以及“生命值”等;不创建类,我们可以创建一个结构来表示它们,定义如下:
Public Structure SDebris
Public X As Integer
Public Y As Integer
Public Name As String
Public UnitId As EDebris
Public Life As Integer
'测试用,显示信息
Public Sub ShowInfo()
Console.WriteLine("{0} 类型:{1} 位置:({2}, {3}) 生命值:{4}", _
Name, UnitId, X, Y, Life)
End Sub
End Structure
(项目:FlyweightPatternDemo 文件:Structures.vb)
好吧,现在我们打开CMapManager类,添加显示这些Debris单位的代码:
Public Class CMapManager
Private r As New Random '随机数产生器
Const ScreenWidth As Integer = 800 '屏幕宽度(像素)
Const ScreenHeight As Integer = 600 '屏幕高度(像素)
Const DebrisNumber As Integer = 6 '多少个Debris单位
Const DebrisTypeMin As Integer = 9001 'Debris单位类型
Const DebrisTypeMax As Integer = 9004 + 1
Protected arrDebris(DebrisNumber - 1) As SDebris '创建的Debris单位
Public Sub ShowDebris()
Dim i, iMax As Integer
iMax = DebrisNumber - 1
For i = 0 To iMax
With arrDebris(i)
.X = r.Next(0, ScreenWidth)
.Y = r.Next(0, ScreenHeight)
.UnitId = r.Next(DebrisTypeMin, DebrisTypeMax)
.Name = "Debris_" & i.ToString
.Life = r.Next(10, 50)
'测试,显示信息
.ShowInfo()
End With
Next
End Sub
End Class
(项目:FlyweightPatternDemo 文件:CMapManager.vb)
在这个示例中,我们没有为Debris单位创建实例,只是借用CMapManager类进行统一的管理,这些Debris单位的信息保存在arrDebris数组中;现在,我们使用如下代码进行测试:
Module Module1
Sub Main()
'显示一张作战地图
Dim map As New CMapManager
map.ShowDebris()
Console.ReadLine()
End Sub
End Module
(项目:FlyweightPatternDemo 文件:Module1.vb)
本例运行结果如下图:
好了,任务完成了,我们在地图中随机分布了6(DebrisNumber)个Debris单位,并没有创建新的Debris单位对象;它们的数据存在于一个地图类(CMapManager)的实例中,因为它们本来就是地图场景的一部分。
最后一个问题,如果Debris单位的生命值被轰成了0,在下一个游戏循环中不再显示它就是了。
小结这就是享元模式(Flyweight Pattern),我们使用一个实例管理多个虚拟对象;有效地减少了对象数量,节省运行时的系统资源开销。此模式在使用相同或相似的方法操作多个对象时非常有效,但对于逻辑较复杂的对象,如游戏中的作战单位,就不太适合。
出自:http://www.caohuayu.com/books/B0003/B0003.aspx