一、基本操作演练
1.下载Fantasy Skybox FREE, 构建自己的游戏场景
使用Asset Store 下载Fantasy Skybox Free ,在Unity 中 菜单->Window->Package Manager,下载资源包,导入到项目中。
效果
加入自己绘制的地形:
2. 游戏对象使用的简单总结:
GameObject 可以通过unity 自带的基础资源如Cube,Sphere等几何体创建;GameObject 有Transform,纹理材质等多个属性可以调整来呈现不同效果,能给GameObject添加Component,例如音频,脚本等,丰富游戏场景和对象动作等效果;GameObject的实例化能够在图形界面完成或者用脚本代码完成;
二、编程实践(牧师与魔鬼 动作分离版):
演示视频
https://www.bilibili.com/video/BV18G4y1Z7Bz/
牧师与魔鬼 动作分离版
【2019新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束
这次作业的就是在上次的基础上进行改进,主要改进的思路就是:设计一个抽象类作为游戏动作的基类;设计一个动作管理器类管理一组游戏动作的实现类;通过回调,实现动作完成时的通知。
我们先来看UML图(盗用的)
解释一下创建的几个类:
动作基类:public class SSAction : ScriptableObject{}
简单动作实现类: public class SSMoveToAction : SSAction {}
组合动作实现类: public class SequenceAction: SSAction, ISSActionCallback{}
动作管理基类:public class SSActionManager: MonoBehaviour, ISSActionCallback{}
以及,我们还要建立一个动作事件接口:public interface ISSActionCallback{}
动作管理
定义动作基类:使用 virtual 申明虚方法,通过重写实现多态。这样继承者就明确使用Start 和 Update 编程游戏对象行为,利用接口实现消息通知,避免与动作管理者直接依赖。
简单动作实现:让 Unity 创建动作类,确保内存正确回收。使用多态,C++ 语言必申明重写,Java则默认重写。
组合动作实现:创建一个动作顺序执行序列,-1 表示无限循环,start 开始动作。然后执行当前动作。收到当前动作执行完成,推下一个动作,如果完成一次循环,减次数。如完成,通知该动作的管理者。
动作接口定义:定义了事件处理接口,所有事件管理者都必须实现这个接口,来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。
动作管理基类:创建 MonoBehaiviour 管理一个动作集合,动作做完自动回收动作。该类演示了复杂集合对象的使用。提供了运行一个新动作的方法。该方法把游戏对象与动作绑定,并绑定该动作事件的消息接收者。执行改动作的 Start 方法。
动作分离版的不同- 新建裁判类,相当于将牧师与魔鬼中判断游戏结束 胜负状态的逻辑函数分离出来单独成一个类,用SSDirect实例化场景控制器载入Start函数,之后在FirstController 主控制器中调用Check的CheckGame判断游戏状态即可。
Check.cs 裁判类
using UnityEngine;
namespace CheckApplication{
public class Check : MonoBehaviour
{
public FirstController sceneController;
void Start()
{
sceneController = (FirstController)SSDirect.getInstance().CurrentSceneController;
sceneController.gameStatusManager=this;
}
public int CheckGame(){
//0-游戏1继续;1-游戏成功;-1-游戏失败
int[] boatRole=sceneController.boat.getRoleNum();
int[] startRole=sceneController.startLand.getRoleNum();
int[] endRole=sceneController.endLand.getRoleNum();
if(endRole[0]+endRole[1]==6) return 1;
if(sceneController.boat.getBoatSign()==1){
startRole[0]+=boatRole[0];
startRole[1]+=boatRole[1];
}else{
endRole[0]+=boatRole[0];
endRole[1]+=boatRole[1];
}
if((endRole[0]>0 && endRole[1]>endRole[0]) || (startRole[0]>0 && startRole[1]>startRole[0]))
return -1;
return 0;
}
}
}
其余代码逻辑与牧师与魔鬼基本差不多,资源此处我也使用作业三的预制体,详情参考之前的博客
https://blog.csdn.net/ex__plorer/article/details/127464505
``
其余类参考代码如下:
ActionController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using InterfaceApplication;
//基类
public class SSAction: ScriptableObject{
public bool enable=true; //是否存在
public bool destory=false; //是否删除
public GameObject gameobject; //动作对象
public Transform transform; //动作对象的transform
public ISSActionCallback callback; //回调函数
/*防止用户自己new对象*/
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) {
//unity创建
SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();
action.target = _target;
action.speed = _speed;
return action;
}
public override void Start() {
//重写
}
public override void Update() {
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
//完成
if (this.transform.position == target) {
this.destory = true;
this.callback.SSActionEvent(this);
}
}
}
//动作组合
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) {
//unity创建
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
action.sequence = sequence;
action.repeat = repeat;
action.start = start;
return action;
}
public override void Start() {
//执行每个动作
foreach (SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this; //每一个动作的回调函数为该动作组合
action.Start();
}
}
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.destory = false; //可能会无限循环所以先不删除
this.start++; //下一个动作
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0) { //动作组合结束
this.destory = true;
this.callback.SSActionEvent(this);
}
}
}
}
//动作管理基类
public class SSActionManager : MonoBehaviour, ISSActionCallback {
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //动作字典
private List<SSAction> waitingAdd = new List<SSAction>(); //等待执行
private List<int> waitingDelete = new List<int>(); //等待删除
protected void Update() {
foreach (SSAction ac in waitingAdd) {
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
//对于字典中每一个pair,看是执行还是删除
foreach (KeyValuePair<int, SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destory) {
waitingDelete.Add(ac.GetInstanceID());
}else if (ac.enable) {
ac.Update();
}
}
//删除所有已完成的动作并清空待删除列表
foreach (int key in waitingDelete) {
SSAction ac = actions[key];
actions.Remove(key);
Object.Destroy(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) {
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
public void SSActionEvent(
SSAction source, SSActionEventType events = SSActionEventType.competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
}
}
//动作管理类
public class ActionManager : SSActionManager {
public FirstController sceneController;
private SequenceAction boatMove;
private SequenceAction roleMove;
protected void Start() {
sceneController = (FirstController)SSDirect.getInstance().CurrentSceneController;
sceneController.actionManager = this;
}
protected new void Update() {
base.Update();
}
public void moveBoat(GameObject boat, Vector3 endPos, float speed) {
SSAction action1 = SSMoveToAction.GetSSAction(endPos, speed);
boatMove = SequenceAction.GetSSAcition(0, 0, new List<SSAction> { action1 });
this.RunAction(boat, boatMove, this);
}
public void moveRole(GameObject role, Vector3 middlePos, Vector3 endPos, float speed) {
//两段移动
SSAction action1 = SSMoveToAction.GetSSAction(middlePos, speed);
SSAction action2 = SSMoveToAction.GetSSAction(endPos, speed);
//两个动作
roleMove = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
this.RunAction(role, roleMove, this);
}
}
点击事件 ClickeController.cs
using UnityEngine;
using ControllerApplication;
using InterfaceApplication;
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(){
action=SSDirect.getInstance().CurrentSceneController as IUserAction;
}
void OnMouseDown(){
if(boat==null && role==null) return;
if(boat!=null) action.moveBoat();
else if(role!=null) action.moveRole(role);
}
}
FirstController .cs 载入资源等的基本控制器
using UnityEngine.SceneManagement;
using UnityEngine;
using ControllerApplication;
using InterfaceApplication;
using CheckApplication;
public class FirstController : MonoBehaviour,ISceneController,IUserAction{
public LandModel startLand;
public LandModel endLand;
public Water water;
public BoatModel boat;
public RoleModel[] roles;
public UserGUI GUI;
public Check gameStatusManager;
public ActionManager actionManager;
void Start(){
SSDirect director=SSDirect.getInstance();
director.CurrentSceneController=this;
GUI=gameObject.AddComponent<UserGUI>() as UserGUI;
actionManager=gameObject.AddComponent<ActionManager>() as ActionManager;
gameStatusManager=gameObject.AddComponent<Check>() as Check;
LoadResoureces();
}
public void LoadResoureces(){
water=new Water();//构建水
startLand=new LandModel("start");//构建两岸
endLand=new LandModel("end");
boat=new BoatModel();//构建船
roles=new RoleModel[6];//构建角色
for(int i=0;i<3;i++) {
RoleModel role=new RoleModel("priest", startLand.getEmptyPos());
role.setName("priest"+i);
startLand.addRole(role);
roles[i]=role;
}
for(int i=0;i<3;i++) {
RoleModel role=new RoleModel("devil", startLand.getEmptyPos());
role.setName("devil"+i);
startLand.addRole(role);
roles[i+3]=role;
}
}
public void moveBoat(){
if (boat.empty() || GUI.sign!=0) return;
Vector3 endPos;
boat.changeBoatSign();
if(boat.getBoatSign()==-1) endPos=new Vector3(-5,0.5f,0);
else endPos=new Vector3(5,0.5f,0);
actionManager.moveBoat(boat.getBoat(),endPos,boat.boatSpeed);
GUI.sign=gameStatusManager.CheckGame();
}
public void moveRole(RoleModel role){
if(GUI.sign!=0) return;
Vector3 middlePos,endPos;
if(role.getOnBoat()==1) {
boat.deleteRole(role.getName());
role.setOnBoat(0);
role.getRole().transform.parent=null;
if(boat.getBoatSign()==1) {
endPos=startLand.getEmptyPos();
role.setLandSign(1);
startLand.addRole(role);
}
else {
endPos=endLand.getEmptyPos();
role.setLandSign(-1);
endLand.addRole(role);
}
middlePos=new Vector3(role.getRole().transform.position.x,endPos.y,endPos.z);
actionManager.moveRole(role.getRole(),middlePos,endPos,role.roleSpeed);
}else{
if(role.getLandSign()==1) {
if(boat.getEmptyNumber()==-1 || role.getLandSign()!=boat.getBoatSign()) return;
startLand.deleteRole(role.getName());
}else{
if(boat.getEmptyNumber() == -1 || role.getLandSign()!=boat.getBoatSign()) return;
endLand.deleteRole(role.getName());
}
endPos=boat.getEmptyPos();
middlePos=new Vector3(endPos.x,role.getRole().transform.position.y,endPos.z);
actionManager.moveRole(role.getRole(),middlePos,endPos,role.roleSpeed);
role.getRole().transform.parent=boat.getBoat().transform;
role.setOnBoat(1);
boat.addRole(role);
}
GUI.sign=gameStatusManager.CheckGame();
}
public void reStart(){
SceneManager.LoadScene(0);
}
}
``场景接口类 Interface.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ControllerApplication;
namespace InterfaceApplication{
//场景接口
public interface ISceneController{
void LoadResoureces();
}
//用户动作接口
public interface IUserAction{
void moveBoat();
void moveRole(RoleModel role);
void reStart();
}
public enum SSActionEventType: int{started,competeted}
public interface ISSActionCallback{
void SSActionEvent(
SSAction source,SSActionEventType events=SSActionEventType.competeted,
int intParam=0,string strParam=null,Object ObjectParam=null
);
}
ModelController.cs
using UnityEngine;
namespace ControllerApplication{
public class Water{
GameObject water;
public Water(){
water=Object.Instantiate(Resources.Load<GameObject>("Prefabs/Water"),
Vector3.zero, Quaternion.identity) as GameObject;
water.name="water";
}
}
public class LandModel{
GameObject land;
int landSign;
Vector3[] positions;
RoleModel[] roles = new RoleModel[6];
public LandModel(string str){
positions=new Vector3[] {
new Vector3(6.35F,2F,0), new Vector3(7.35f,2F,0), new Vector3(8.35f,2F,0),
new Vector3(9.35f,2F,0), new Vector3(10.35f,2F,0), new Vector3(11.35f,2F,0)
};
if (str=="start") landSign=1;
else landSign=-1;
land=Object.Instantiate(Resources.Load<GameObject>("Prefabs/Land"),
new Vector3(9*landSign,1,0), Quaternion.identity) as GameObject;
land.name =str+"Land";
}
//一系列用于处理角色上岸和上船的成员函数
public int getLandSign(){
return landSign;
}
public int getEmptyNumber(){
for(int i=0;i<6;i++){
if(roles[i]==null)
return i;
}
return -1;
}
public Vector3 getEmptyPos(){
Vector3 res=positions[getEmptyNumber()];
res.x=res.x*landSign;
return res;
}
public int[] getRoleNum(){
int[] res={0,0};
for(int i=0;i<6;i++){
if(roles[i]!=null){
if(roles[i].getSign()==0) res[0]++;
else res[1]++;
}
}
return res;
}
public void addRole(RoleModel role){
roles[getEmptyNumber()]=role;
}
public RoleModel deleteRole(string name){
for(int i=0;i<6;i++){
if(roles[i]!=null && roles[i].getName()==name){
RoleModel res=roles[i];
roles[i]=null;
return res;
}
}
return null;
}
}
public class BoatModel{
GameObject boat;
int boatSign;
Vector3[] startPos,endPos;
RoleModel[] roles=new RoleModel[2];
Click click;
public float boatSpeed=40f;
public BoatModel(){
boat=Object.Instantiate(Resources.Load<GameObject>("Prefabs/Boat"),
new Vector3(5,0.5f,0), Quaternion.identity) as GameObject;
boatSign=1;
startPos=new Vector3[]{ new Vector3(5.5f,1,0), new Vector3(4.5f,1,0) };
endPos = new Vector3[]{ new Vector3(-4.5f,1,0), new Vector3(-5.5f,1,0) };
click=boat.AddComponent(typeof(Click)) as Click;
click.setBoat(this);
boat.name="boat";
}
//一系列用于处理角色上岸和上船以及船移动的成员函数
public void changeBoatSign(){
boatSign*=-1;
}
public int getBoatSign(){
return boatSign;
}
public int getEmptyNumber(){
for(int i=0;i<2;i++){
if(roles[i]==null)
return i;
}
return -1;
}
public Vector3 getEmptyPos(){
Vector3 res;
if(boatSign==1) res=startPos[getEmptyNumber()];
else res=endPos[getEmptyNumber()];
return res;
}
public int[] getRoleNum(){
int[] res={0,0};
for(int i=0;i<2;i++){
if(roles[i]!=null){
if(roles[i].getSign()==0) res[0]++;
else res[1]++;
}
}
return res;
}
public void addRole(RoleModel role){
roles[getEmptyNumber()]=role;
}
public RoleModel deleteRole(string name){
for(int i=0;i<2;i++){
if(roles[i]!=null && roles[i].getName()==name){
RoleModel res=roles[i];
roles[i]=null;
return res;
}
}
return null;
}
public GameObject getBoat(){
return boat;
}
public bool empty(){
for(int i=0;i<2;i++){
if(roles[i]!=null) return false;
}
return true;
}
}
public class RoleModel{
GameObject role;
int roleSign;
int onBoat;
int landSign;
Click click;
public float roleSpeed=20f;
public RoleModel(string name,Vector3 pos){
if(name=="priest") {
role=Object.Instantiate(Resources.Load<GameObject>("Prefabs/Priest"),
pos, Quaternion.Euler(0,-90,0)) as GameObject;
roleSign=0;
}else{
role=Object.Instantiate(Resources.Load<GameObject>("Prefabs/Devil"),
pos, Quaternion.Euler(0,-90,0)) as GameObject;
roleSign=1;
}
landSign=1;
click=role.AddComponent(typeof(Click)) as Click;
click.setRole(this);
}
public string getName(){
return role.name;
}
public int getLandSign(){
return landSign;
}
public void setLandSign(int land){
landSign=land;
}
public int getSign(){
return roleSign;
}
public int getOnBoat(){
return onBoat;
}
public void setOnBoat(int a){
onBoat=a;
}
public void setName(string name){
role.name=name;
}
public void setPosition(Vector3 pos) {
role.transform.position = pos;
}
public GameObject getRole(){
return role;
}
}
}
``导演类SSDirect.cs
using UnityEngine;
using ControllerApplication;
using InterfaceApplication;
public class SSDirect : System.Object{
private static SSDirect instance;
public ISceneController CurrentSceneController{ get; set;}
public static SSDirect getInstance(){
if(instance==null) instance=new SSDirect();
return instance;
}
}
用户界面 UserGUI.cs
using UnityEngine;
using InterfaceApplication;
public class UserGUI : MonoBehaviour
{
public IUserAction action;
public int sign=0;
void Start() {
action = SSDirect.getInstance().CurrentSceneController as IUserAction;
}
void OnGUI() {
GUIStyle text_style;
GUIStyle button_style;
text_style = new GUIStyle() {
fontSize = 30
};
button_style = new GUIStyle("button") {
fontSize = 15
};
if(sign==-1) {
GUI.Label(new Rect(Screen.width/2-90,Screen.height/2-120,100,50),"Gameover!",text_style);
}
else if(sign==1) {
GUI.Label(new Rect(Screen.width/2-80,Screen.height/2-120,100,50),"You Win!",text_style);
}
if (GUI.Button(new Rect(Screen.width/2-50,Screen.height/2-200,100,50),"Restart",button_style)) {
action.reStart();
sign = 0;
}
}
}