兵营系统及兵营信息显示

思考并回答以下问题:

本章涵盖:

  • 兵营系统
  • 兵营系统的组成
  • 初始兵营系统
  • 兵营信息的显示流程

兵营系统

Unity3D的界面设计-组合模式中介绍Unity3D用户界面(User Interface)的设计方式时,提到一个“兵营界面”,兵营界面在《P级阵地》中是用来显示玩家当前用鼠标单击而选中的兵营信息,如图1所示。

图1 兵营界面

界面上显示出当前用鼠标单击而选中的兵营基本信息,包含名称、等级、武器等级等,另外还有4个功能按钮,提供给玩家作为对兵营下达命令的界面。而这些从界面下达的命令将会使用命令模式(Command),让玩家的操作与游戏的功能产生连接并执行。这一部分将在兵营训练单位-命令模式中详细说明,在此之前,我们先来说明《P级阵地》中“兵营系统”的运行方式。

兵营系统的组成

比起“角色系统”,《P级阵地》中的“兵营系统”的设计较为简单,它主要是由兵营系统(CampSystem)、兵营(ICamp)和兵营界面(CampInfoUI)组成,如图2所示。

图2 兵营系统的组成

  • ICamp(兵营接口):定义兵营的操作接口和信息的提供。
  • SoliderCamp(兵营):实现兵营接口,并记录当前等级、武器等级等信息。
  • CampSystem(兵营系统):初始化和管理游戏中玩家的所有兵营,并成为PBaseDefenseGame的游戏子系统之一,方便与其他系统进行沟通和获取信息。
  • CampInfoUI(兵营界面):负责显示玩家用鼠标单击而选中到的兵营之相关信息。

“兵营”负责作战单位的训练,而按照游戏的需求设置,每个兵营只能训练一种玩家角色,而且各兵营所需要的训练时间及费用都不相同。不同兵营间的差异设置,可以区别出其中的不同,并且利用相关属性来调整游戏平衡。

在兵营(ICamp)类中,除了基本的3D模型成像和2D图像显示外,也包含了其他差异属性:

Listing1 兵营接口(ICamp.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
public abstract class ICamp 
{
protected GameObject m_GameObject = null;
protected string m_Name = "Null"; // 名称
protected string m_IconSpriteName = "";
protected ENUM_Soldier m_emSoldier = ENUM_Soldier.Null;

// 训练相关
protected float m_CommandTimer = 0; // 目前冷却剩余时间
protected float m_TrainCoolDown = 0; // 冷却时间

// 训练花费
protected ITrainCost m_TrainCost = null;

// 主游戏界面(必要时设置)
protected PBaseDefenseGame m_PBDGame = null;

public ICamp(GameObject GameObj, float TrainCoolDown,string Name,string IconSprite)
{
m_GameObject = GameObj;
m_TrainCoolDown = TrainCoolDown;
m_CommandTimer = m_TrainCoolDown;
m_Name = Name;
m_IconSpriteName = IconSprite;
m_TrainCost = new TrainCost();
}

// 设置主游戏界面
public void SetPBaseDefenseGame(PBaseDefenseGame PBDGame)
{
m_PBDGame = PBDGame;
}

public ENUM_Soldier GetSoldierType()
{
return m_emSoldier;
}

// 等级
public virtual int GetLevel()
{
return 0;
}

// 升级花费
public virtual int GetLevelUpCost()
{
return 0;
}

// 升级
public virtual void LevelUp()
{}

// 武器等级
public virtual ENUM_Weapon GetWeaponType()
{
return ENUM_Weapon.Null;
}

// 武器升级花费
public virtual int GetWeaponLevelUpCost()
{
return 0;
}

// 武器升级
public virtual void WeaponLevelUp()
{}

// 训练Timer
public float GetTrainTimer()
{
return m_CommandTimer;
}

// 名称
public string GetName()
{
return m_Name;
}

// Icon文件名
public string GetIconSpriteName()
{
return m_IconSpriteName;
}

// 设置是否显示
public void SetVisible(bool bValue)
{
m_GameObject.SetActive(bValue);
}

// 获取训练金额
public abstract int GetTrainCost();

// 训练
public abstract void Train();
}

兵营(ICamp)当前只有一个子类SoldierCamp:

Listing2 Soldier兵营(SoldierCamp.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class SoldierCamp : ICamp
{
const int MAX_LV = 3;
ENUM_Weapon m_emWeapon = ENUM_Weapon.Gun; // 武器等级
int m_Lv = 1; // 兵营等级
Vector3 m_Position; // 训练完成后的集合点

// 设置兵营产出的单位
public SoldierCamp(GameObject theGameObject,
ENUM_Soldier emSoldier,
string CampName,
string IconSprite ,
float TrainCoolDown,
Vector3 Position):base(theGameObject, TrainCoolDown,CampName,IconSprite )
{
m_emSoldier = emSoldier;
m_Position = Position;
}

// 等级
public override int GetLevel()
{
return m_Lv;
}

// 升级花费
public override int GetLevelUpCost()
{
if( m_Lv >= MAX_LV)
return 0;

return 100;
}

// 升级
public override void LevelUp()
{
m_Lv++;
m_Lv = Mathf.Min( m_Lv , MAX_LV);
}

// 武器等级
public override ENUM_Weapon GetWeaponType()
{
return m_emWeapon;
}

// 武器升级花费
public override int GetWeaponLevelUpCost()
{
if( (m_emWeapon + 1) >= ENUM_Weapon.Max )
return 0;
return 100;
}

// 武器升级
public override void WeaponLevelUp()
{
m_emWeapon++;

if( m_emWeapon >=ENUM_Weapon.Max)
m_emWeapon--;
}

// 获取训练金额
public override int GetTrainCost()
{
return m_TrainCost.GetTrainCost( m_emSoldier, m_Lv, m_emWeapon) ;
}
}

初始兵营系统

游戏画面中的兵营,是在Unity3D编辑模式下的战斗场景(Battle Scene),如图3所示。

图3 编辑模式下的战斗场景

在Unity3D这类“所见即所得”的游戏开发编辑器中,通过“场景设置”或所谓的“关卡设置”方式,让企划人员能方便地设计出想要呈现的游戏效果和关卡难度。因此,在游戏制作的分工上,程序人员可以提供方便的“属性系统工具/企划工具”,让企划人员设置属性、视觉呈现、关卡布置安排及画面设置等,程序人员也可以让企划人员直接使用Unity3D等工具来进行设置。

《P级阵地》兵营的3D视觉呈现,是在Unity3D编辑模式下,将设计好的兵营模型由企划人员放在场景上。等到游戏实际执行时,在程序代码中以“实时”的方式获取所需要的游戏对象(GameObject),并根据需求建立系统功能。所以,《P级阵地》中“兵营系统(CampSystem)”的设计方式,是在战斗场景(Battle Scene)加载完成后,实时找出场景上的兵营游戏对象,并用它们和程序代码中的兵营类(ICamp)进行连接。

在兵营系统(CampSystem)中包含了一个容器类,用来管理游戏运行时,产生的兵营类(ICamp)对象:

Listing3 兵营系统(CampSystem.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class CampSystem : IGameSystem
{
private Dictionary<ENUM_Soldier, ICamp> m_SoldierCamps = new Dictionary<ENUM_Soldier,ICamp>();

public CampSystem(PBaseDefenseGame PBDGame):base(PBDGame)
{
Initialize();
}

// 初始化兵营系统
public override void Initialize()
{
// 加入3个兵营
m_SoldierCamps.Add ( ENUM_Soldier.Rookie, SoldierCampFactory( ENUM_Soldier.Rookie ));
m_SoldierCamps.Add ( ENUM_Soldier.Sergeant, SoldierCampFactory( ENUM_Soldier.Sergeant ));
m_SoldierCamps.Add ( ENUM_Soldier.Captain, SoldierCampFactory( ENUM_Soldier.Captain ));
}

// 更新
public override void Update()
{}

// 获取场景中的兵营
private SoldierCamp SoldierCampFactory( ENUM_Soldier emSoldier )
{
string GameObjectName = "SoldierCamp_";
float CoolDown = 0;
string CampName = "";
string IconSprite = "";

switch( emSoldier )
{
case ENUM_Soldier.Rookie:
GameObjectName += "Rookie";
CoolDown = 3;
CampName = "菜鸟兵营";
IconSprite = "RookieCamp";
break;

case ENUM_Soldier.Sergeant:
GameObjectName += "Sergeant";
CoolDown = 4;
CampName = "中士兵营";
IconSprite = "SergeantCamp";
break;

case ENUM_Soldier.Captain:
GameObjectName += "Captain";
CoolDown = 5;
CampName = "上尉兵营";
IconSprite = "CaptainCamp";
break;

default:
Debug.Log("没有指定["+emSoldier+"]要获取的场景对象名称");
break;
}

// 获取对象
GameObject theGameObject = UnityTool.FindGameObject( GameObjectName );

// 获取集合点
Vector3 TrainPoint = GetTrainPoint( GameObjectName );

// 产生兵营
SoldierCamp NewCamp = new SoldierCamp(theGameObject,
emSoldier,
CampName,
IconSprite,
CoolDown,
TrainPoint);
NewCamp.SetPBaseDefenseGame( m_PBDGame );

// 设置兵营使用的Script
AddCampScript( theGameObject, NewCamp);

return NewCamp;
}

// 获取集合点
private Vector3 GetTrainPoint(string GameObjectName )
{
// 获取对象
GameObject theCamp = UnityTool.FindGameObject( GameObjectName );
// 获取集合点
GameObject theTrainPoint = UnityTool.FindChildGameObject( theCamp, "TrainPoint" );

theTrainPoint.SetActive(false);

return theTrainPoint.transform.position;
}

// 设置兵营使用的Script
private void AddCampScript(GameObject theGameObject,ICamp Camp)
{
// 加入Script
CampOnClick CampScript = theGameObject.AddComponent<CampOnClick>();
CampScript.theCamp = Camp;
}
...
}

在兵营系统中,使用泛型容器Dictionary作为管理兵营的地方,并在初始化方法Initialize中,将3个玩家阵营的兵营产生出来。在产生兵营方法(SoldierCampFactory)中,会直接在当前的场景中找出对应的兵营游戏对象和集合点的位置,然后利用这两个信息来产生SoldierCamp类对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取对象
GameObject theGameObject = UnityTool.FindGameObject( GameObjectName );

// 获取集合点
Vector3 TrainPoint = GetTrainPoint( GameObjectName );

// 产生兵营
SoldierCamp NewCamp = new SoldierCamp(theGameObject,
emSoldier,
CampName,
IconSprite,
CoolDown,
TrainPoint);
NewCamp.SetPBaseDefenseGame( m_PBDGame );

// 设置兵营使用的Script
AddCampScript( theGameObject, NewCamp);

在这一段程序代码的最后,会调用AddCampScript方法:

1
2
3
4
5
6
7
// 设置兵营使用的Script
private void AddCampScript(GameObject theGameObject, ICamp Camp)
{
// 加入Script
CampOnClick CampScript = theGameObject.AddComponent<CampOnClick>();
CampScript.theCamp = Camp;
}

这段类的私有成员方法,主要是将脚本类CampOnClick.cs加入到兵营的游戏对象中。该脚本类是用来侦测玩家的“鼠标单击屏幕画面”的操作,并通知PBaseDefenseGame显示被鼠标单击的兵营:

Listing4 兵营单击成功后通知显示(CampOnClick.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CampOnClick : MonoBehaviour 
{
public ICamp theCamp = null;

// Use this for initialization
void Start ()
{}

// Update is called once per frame
void Update ()
{}

public void OnClick()
{
// 显示兵营信息
PBaseDefenseGame.Instance.ShowCampInfo( theCamp );
}
}

脚本中的OnClick方法,会在PBaseDefenseGame的InputProcess方法中被调用,而InputProcess就是在Game Loop中扮演“判断用户输入”的角色,它会在Update方法中不断地被调用:

Listing5 判断兵营是否被鼠标单击(PBaseDefenseGame.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class PBaseDefenseGame
{
...
// 更新
public void Update()
{
// 玩家输入
InputProcess();
...
}

// 玩家输入
private void InputProcess()
{
// Mouse左键
if(Input.GetMouseButtonUp( 0 ) ==false)
return ;

// 由摄像机产生一条射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray);

// 遍历每一个被Hit到的GameObject
foreach (RaycastHit hit in hits)
{
// 是否有兵营点击
CampOnClick CampClickScript = hit.transform.gameObject.GetComponent<CampOnClick>();
if( CampClickScript!=null )
{
CampClickScript.OnClick();
return;
}
...
}
}
}

在每一次的输入判断中,确认玩家是否单击鼠标左键之后,就利用Unity3D的碰撞侦测方式,获取场景中被鼠标单击的游戏对象。如果该游戏对象包含了脚本组件CampOnClick类,那么被鼠标单击的对象就是兵营,即可调用下列的OnClick方法:

1
2
3
4
5
public void OnClick()
{
// 显示兵营信息
PBaseDefenseGame.Instance.ShowCampInfo( theCamp );
}

在CampOnClick类的OnClick中,会利用PBaseDefenseGame的外观界面方法ShowCampInfo:

1
2
3
4
5
6
7
8
9
10
11
public class PBaseDefenseGame
{
...
// 显示兵营信息
public void ShowCampInfo( ICamp Camp )
{
m_CampInfoUI.ShowInfo( Camp );
m_SoldierInfoUI.Hide();
}
...
}

在ShowCampInfo中,会再去调用兵营界面(CampInfoUI)显示当前鼠标单击的兵营。

回顾Unity3D的界面设计-组合模式提到的兵营界面(CampInfoUI)的显示信息(ShowInfo)方法,在该方法中,会将传入的兵营对象显示到界面上,如此就完成了兵营信息的显示功能:

Listing6 兵营界面(CampInfoUI.cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CampInfoUI : IUserInterface
{
...
// 显示信息
public void ShowInfo(ICamp Camp)
{
// Debug.Log("显示兵营信息");
Show ();
m_Camp = Camp;

// 名称
m_CampNameTxt.text = m_Camp.GetName();

// 训练花费
m_TrainCostText.text = string.Format("AP:{0}",m_Camp.GetTrainCost());
// 训练中信息
ShowOnTrainInfo();

// Icon
IAssetFactory Factory = PBDFactory.GetAssetFactory();
m_CampImage.sprite = Factory.LoadSprite( m_Camp.GetIconSpriteName());

// 升级功能
if( m_Camp.GetLevel() <= 0 )
EnableLevelInfo(false);
else
{
EnableLevelInfo(true);
m_CampLvTxt.text = string.Format("等级:" + m_Camp.GetLevel());
ShowWeaponLv(); // 显示武器等级
}
}
...
}

兵营信息的显示流程

图4显示了兵营系统的建立流程,以及判断玩家用鼠标单击兵营对象之后,将信息显示到兵营界面的整个流程。

图4 兵营信息的显示流程

0%