3D游戏编程与设计_hw4
1. 基本操作演练
-
下载 Fantasy Skybox FREE, 构建自己的游戏场景
-
写一个简单的总结,总结游戏对象的使用
首先创建一个Material,然后在shader选项选择Skybox/6 Sided,会出现六个插入Texture的框框,在Fantasy Skybox FREE中选择一个自己喜欢的图片放入,完成后挂载到Main Camera中,运行游戏。
效果图:
游戏对象的使用总结:
以我的理解,游戏对象可以分为辅助对象和实体对象。辅助对象如摄像头、光线和空游戏对象等,在游戏运行时看不到具体的对象,但却对游戏运行的效果和功能的实现起着不可或缺的作用:光线可以增加场景亮度;空游戏对象可以用来挂载不需要游戏对象作为载体的脚本。而实体对象则是游戏运行时可以看见甚至操作的具体对象。
2. 编程实践
牧师与魔鬼 动作分离版
【2019开始的新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束
完整代码见我的Gitee
改进方案
所谓动作分离,即设置一个动作管理类来管理所有游戏对象的动作,而不用每个游戏对象调用自己的动作函数(即对游戏对象统一管理)。因此这次改进我们要设置一个管理类,同时管理船、牧师、恶魔的移动,而不为他们各自设计运动函数。
对象类
游戏的对象有:牧师、恶魔、船、岸、水。其中牧师和恶魔的行为类似因此有共同的父类RoleModel。每个Role和船创建时都为其添加一个Click类,使其在鼠标按下时可以进行对应动作。以RoleModel的构造函数为例:
public RoleModel(string role_name)
{
if (role_name == "priest")
{ Debug.Log("Priest created!");
role = Object.Instantiate(Resources.Load("Priest", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
role_sign = 0;
}
else
{
Debug.Log("Devil created!");
role = Object.Instantiate(Resources.Load("Devil", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
role_sign = 1;
}
click = role.AddComponent(typeof(Click)) as Click;
Debug.Log("click created!");
play_ani = role.AddComponent(typeof(PlayAnimation)) as PlayAnimation;
click.SetRole(this);
}
岸的对象设计关键在于Role站立的位置,还有其是出发岸还是结束岸。其构造代码如下:
public LandModel(string land_mark)
{
positions = new Vector3[] {new Vector3(5,1,0), new Vector3(6,1,0), new Vector3(7,1,0),
new Vector3(8,1,0), new Vector3(9,1,0), new Vector3(10,1,0)};
if (land_mark == "start")
{
land = Object.Instantiate(Resources.Load("Land", typeof(GameObject)), new Vector3(8, -1.5f, 0f), Quaternion.identity) as GameObject;
land_sign = 1;
}
else
{
land = Object.Instantiate(Resources.Load("Land", typeof(GameObject)), new Vector3(-8, -1.5f, 0f), Quaternion.identity) as GameObject;
land_sign = -1;
}
}
Click类为玩家和游戏对象提供交互接口:
public class Click : MonoBehaviour
{
IUserAction action;
RoleModel role = null;
BoatModel boat = null;
public void SetRole(RoleModel role)
{
this.role = role;
}
public void SetBoat(BoatModel boat)
{
this.boat = boat;
}
void Start()
{Debug.Log("Start!");
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
}
void OnMouseDown()
{
if (boat == null && role == null) return;
if (boat != null)
action.MoveBoat();
else if(role != null)
action.MoveRole(role);
}
}
动作类
首先设计一个SSAction类作为所有动作的基类,其子类是具体动作的实现。SSAction包括动作的对象、是否正在进行动作等成员变量。具体声明如下:
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;
protected SSAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
以移动动作的类为例,类所需要的成员变量有移动对象,移动速度还有移动的起始位置。因为游戏是点击对象后移动,因此不需要声明起始位置。代码如下:
public class SSMoveToAction : SSAction //移动
{
public Vector3 target; //移动到的目的地
public float speed; //移动的速度
private SSMoveToAction() { }
public static SSMoveToAction GetSSAction(Vector3 target, float speed)
{
SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();//让unity自己创建一个MoveToAction实例,并自己回收
action.target = target;
action.speed = speed;
return action;
}
public override void Update()
{
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
if (this.transform.position == target)
{
this.destroy = true;
this.callback.SSActionEvent(this); //告诉动作管理或动作组合这个动作已完成
}
}
public override void Start()
{
//移动动作建立时候不做任何事情
}
}
为了让游戏场景更真实,我们引入一个动作序列类SequenceAction,让角色上船和下船的动作分解为两步。SequenceAction继承了SSAction和ISSActionCallback(用于告知动作完成情况的类),代码如下:
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence; //动作的列表
public int repeat = -1; //-1就是无限循环做组合中的动作
public int start = 0; //当前做的动作的索引
public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence)
{
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();//让unity自己创建一个SequenceAction实例
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
public override void Update()
{
if (sequence.Count == 0) return;
if (start < sequence.Count)
{
sequence[start].Update(); //一个组合中的一个动作执行完后会调用接口,所以这里看似没有start++实则是在回调接口函数中实现
}
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null)
{
source.destroy = false; //先保留这个动作,如果是无限循环动作组合之后还需要使用
this.start++;
if (this.start >= sequence.Count)
{
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0)
{
this.destroy = true; //整个组合动作就删除
this.callback.SSActionEvent(this); //告诉组合动作的管理对象组合做完了
}
}
}
public override void Start()
{
foreach (SSAction action in sequence)
{
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this; //组合动作的每个小的动作的回调是这个组合动作
action.Start();
}
}
void OnDestroy()
{
//如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
}
}
总的动作管理类为SSActionManager,游戏开始时让场景控制器令自己为动作管理器,可调用的函数有移动船和移动角色。
public class MySceneActionManager : SSActionManager //本游戏管理器
{
private SSMoveToAction moveBoatToEndOrStart; //移动船到结束岸,移动船到开始岸
private SequenceAction moveRoleToLandorBoat; //移动角色到陆地,移动角色到船上
public Controllor sceneController;
protected new void Start()
{
sceneController = (Controllor)SSDirector.GetInstance().CurrentScenceController;
sceneController.actionManager = this;
}
public void moveBoat(GameObject boat, Vector3 target, float speed)
{
moveBoatToEndOrStart = SSMoveToAction.GetSSAction(target, speed);
this.RunAction(boat, moveBoatToEndOrStart, this);
}
public void moveRole(GameObject role, Vector3 middle_pos, Vector3 end_pos, float speed)
{ Debug.Log("moverole");
SSAction action1 = SSMoveToAction.GetSSAction(middle_pos, speed);
SSAction action2 = SSMoveToAction.GetSSAction(end_pos, speed);
moveRoleToLandorBoat = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
this.RunAction(role, moveRoleToLandorBoat, this);
}
}
裁判类
裁判类用于判断游戏是否结束,胜利还是失败。
public class Judge : MonoBehaviour
{
public LandModel start_land;
public LandModel end_land;
public BoatModel boat;
public Judge(LandModel _fromLand, LandModel _toLand, BoatModel _boat)
{
start_land = _fromLand;
end_land = _toLand;
boat = _boat;
}
public int isOver()
{
int start_priest = (start_land.GetRoleNum())[0];
int start_devil = (start_land.GetRoleNum())[1];
int end_priest = (end_land.GetRoleNum())[0];
int end_devil = (end_land.GetRoleNum())[1];
if (end_priest + end_devil == 6) //获胜
return 2;
int[] boat_role_num = boat.GetRoleNumber();
if (boat.GetBoatSign() == 1) //在开始岸和船上的角色
{
start_priest += boat_role_num[0];
start_devil += boat_role_num[1];
}
else //在结束岸和船上的角色
{
end_priest += boat_role_num[0];
end_devil += boat_role_num[1];
}
if (start_priest > 0 && start_priest < start_devil) //失败
{
return 1;
}
if (end_priest > 0 && end_priest < end_devil) //失败
{
return 1;
}
return 0; //未完成
}
}
场景控制器类
场景控制器需要加载游戏对象、通知裁判类判断游戏胜负,并且每次需要游戏对象进行动作时,只要通知动作管理器执行相关动作即可。
其包含的成员变量:
public LandModel start_land; //开始陆地
public LandModel end_land; //结束陆地
public BoatModel boat; //船
private RoleModel[] roles; //角色
UserGUI user_gui; //胜负标志
Judge judge; //裁判
public MySceneActionManager actionManager; //动作管理
以移动船为例,代码如下:
public void MoveBoat() //移动船
{
if (boat.IsEmpty() || user_gui.sign != 0) return;
actionManager.moveBoat(boat.getGameObject(),boat.BoatMoveToPosition(),boat.move_speed); //动作分离版本改变
user_gui.sign = judge.isOver();
if (user_gui.sign == 1)
{
for (int i = 0; i < 3; i++)
{
roles[i].PlayGameOver();
roles[i + 3].PlayGameOver();
}
}
}