1、基本操作演练【建议做】
- 下载 Fantasy Skybox FREE, 构建自己的游戏场景
- 写一个简单的总结,总结游戏对象的使用
构建自己的游戏场景:
游戏对象的使用:
在3DUnity 中,游戏对象通过手动添加到场景中,然后可以配置好它的大小旋转角度等属性,通过添加Material 等组件来增加游戏对象的属性。之后可以编写脚本,然后绑定到该有对象上,使其完成一定的动作或事件的处理。
之后可以在将制作好的对象存为预制,这样有助于处理多个相同的对象。在脚本中通过实例化预制好的对象,可以快捷方便的的生成多个相似的对象,同时可以很方便的同时配置多个对象。
同时多个对象可以通过组合模式,把多个对象组合成一个对象,形成树状结构。同时当组成复合对象的多个简单对象实现了相同的接口时,就可以把复合对象当做一个简单对象来处理了。
当多个对象可能会执行相同或者类似的动作时,可以把动作分离出来单独做一个类。然后编写调用动作的接口,然后让对象来调用这些动作,这样就可以不用为每个对象编写动作代码了。
2、编程实践
- 牧师与魔鬼 动作分离版
- 【2019新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束
在动作分离版,把牧师和魔鬼的归为一种游戏对象,因为他们的动作一致。之后牧师或魔鬼(统称为物体)的移动关系到,岸,船两种对象,所以要实现岸和船的控制器。物体离岸上船离船上岸的动作,需要得到岸和船的空位,以及船的位置。所以物体的实现代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interfaces;
public class FirstController : MonoBehaviour, ISceneController, UserAction
{
InteracteGUI UserGUI;
public CoastController fromCoast;
public CoastController toCoast;
public BoatController boat;
private GameObjects[] GameObjects;
public Referee referee_=new Referee();
private FirstSceneActionManager FCActionManager;
void Start()
{
FCActionManager = GetComponent<FirstSceneActionManager>();
}
void Awake()
{
SSDirector director = SSDirector.getInstance();
director.currentScenceController = this;
UserGUI = gameObject.AddComponent<InteracteGUI>() as InteracteGUI;
GameObjects = new GameObjects[6];
LoadResources();
}
public void LoadResources()
{
fromCoast = new CoastController("from");
toCoast = new CoastController("to");
boat = new BoatController();
GameObject water = Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), new Vector3(0, 0.5F, 0), Quaternion.identity, null) as GameObject;
water.name = "water";
for (int i = 0; i < 3; i++)
{
GameObjects s = new GameObjects("priest");
s.setName("priest" + i);
s.setPosition(fromCoast.getEmptyPosition());
s.getOnCoast(fromCoast);
fromCoast.getOnCoast(s);
GameObjects[i] = s;
}
for (int i = 0; i < 3; i++)
{
GameObjects s = new GameObjects("devil");
s.setName("devil" + i);
s.setPosition(fromCoast.getEmptyPosition());
s.getOnCoast(fromCoast);
fromCoast.getOnCoast(s);
GameObjects[i + 3] = s;
}
}
public void ObjectIsClicked(GameObjects Objects)//物体的点击事件
{
if (FCActionManager.Complete == SSActionEventType.Started) return;
if (Objects.isOnBoat())//物体在船上时
{
CoastController whichCoast;
if (boat.get_State() == -1)//得到船的位置
{ // to->-1; from->1
whichCoast = toCoast;
}
else
{
whichCoast = fromCoast;
}
boat.GetOffBoat(Objects.getName());//下船,
FCActionManager.GameObjectsMove(Objects,whichCoast.getEmptyPosition());//移动
Objects.getOnCoast(whichCoast);//决定上哪个岸,就去拿到岸的控制器
whichCoast.getOnCoast(Objects);//上岸
}
else//在岸上
{
Debug.Log("On Coast!");
CoastController whichCoast = Objects.getCoastController(); // 得到岸的控制器
if (boat.getEmptyIndex() == -1)
{
return;
}
if (whichCoast.get_State() != boat.get_State()) // 船不在岸边
return;
whichCoast.getOffCoast(Objects.getName());//离岸
FCActionManager.GameObjectsMove(Objects, boat.getEmptyPosition());//移动
Objects.getOnBoat(boat);//拿到穿的控制权
boat.GetOnBoat(Objects);//上船
}
//UserGUI.SetState = Check();
}
public void MoveBoat()//船的点击事件
{ UserGUI.SetState = referee_.Check(fromCoast, toCoast, boat);
if (FCActionManager.Complete == SSActionEventType.Started || boat.isEmpty()) return;
FCActionManager.BoatMove(boat);
}
public void Restart()
{
fromCoast.reset();
toCoast.reset();
foreach (GameObjects gameobject in GameObjects)
{
gameobject.reset();
}
boat.reset();
}
}
之后就要去实现岸的控制器,在逻辑上要记录物体的是否在岸上,在岸上的第几个位置,通过一个数组来记录。这样就可以得到有几个物体在岸上,以及得到一个空位可以使物体上岸。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoastController
{
readonly GameObject coast;
readonly Vector3 from_pos = new Vector3(9, 1, 0);
readonly Vector3 to_pos = new Vector3(-9, 1, 0);
readonly Vector3[] positions;
readonly int State; // to->-1, from->1
GameObjects[] passengerPlaner;
public CoastController(string _State)
{
positions = new Vector3[] {new Vector3(6.5F,2.25F,0), new Vector3(7.5F,2.25F,0), new Vector3(8.5F,2.25F,0),
new Vector3(9.5F,2.25F,0), new Vector3(10.5F,2.25F,0), new Vector3(11.5F,2.25F,0)};
passengerPlaner = new GameObjects[6];
if (_State == "from")
{
coast = Object.Instantiate(Resources.Load("Perfabs/Ston", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;
coast.name = "from";
State = 1;
}
else
{
coast = Object.Instantiate(Resources.Load("Perfabs/Ston", typeof(GameObject)), to_pos, Quaternion.identity, null) as GameObject;
coast.name = "to";
State = -1;
}
}
public int getEmptyIndex()
{
for (int i = 0; i < passengerPlaner.Length; i++)
{
if (passengerPlaner[i] == null)
{
return i;
}
}
return -1;
}
public Vector3 getEmptyPosition()
{
Vector3 pos = positions[getEmptyIndex()];
pos.x *= State;
return pos;
}
public void getOnCoast(GameObjects ObjectControl)
{
int index = getEmptyIndex();
passengerPlaner[index] = ObjectControl;
}
public GameObjects getOffCoast(string passenger_name)
{ // 0->priest, 1->devil
for (int i = 0; i < passengerPlaner.Length; i++)
{
if (passengerPlaner[i] != null && passengerPlaner[i].getName() == passenger_name)
{
GameObjects charactorCtrl = passengerPlaner[i];
passengerPlaner[i] = null;
return charactorCtrl;
}
}
Debug.Log("cant find passenger on coast: " + passenger_name);
return null;
}
public int get_State()
{
return State;
}
public int[] GetobjectsNumber()
{
int[] count = { 0, 0 };
for (int i = 0; i < passengerPlaner.Length; i++)
{
if (passengerPlaner[i] == null)
continue;
if (passengerPlaner[i].getType() == 0)
{ // 0->priest, 1->devil
count[0]++;
}
else
{
count[1]++;
}
}
return count;
}
public void reset()
{
passengerPlaner = new GameObjects[6];
}
}
同样的实现船的控制器,同样在逻辑上采用一个数组来记录船上是否有空位,是否有物体在船上等等,实现代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoatController
{
readonly GameObject boat;
readonly Vector3 fromPosition = new Vector3(5, 1, 0);
readonly Vector3 toPosition = new Vector3(-5, 1, 0);
readonly Vector3[] from_positions;
readonly Vector3[] to_positions;
int State; // to->-1; from->1
GameObjects[] passenger = new GameObjects[2];
int Speed = 10;
int MovingState = -1; // Move = 1;Not Move = -1;
public BoatController()
{
State = 1;
MovingState = -1;
from_positions = new Vector3[] { new Vector3(4.5F, 1.5F, 0), new Vector3(5.5F, 1.5F, 0) };
to_positions = new Vector3[] { new Vector3(-5.5F, 1.5F, 0), new Vector3(-4.5F, 1.5F, 0) };
boat = Object.Instantiate(Resources.Load("Perfabs/Boat", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;
boat.name = "boat";
boat.AddComponent(typeof(ClickGUI));
}
public int getEmptyIndex()
{
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] == null)
{
return i;
}
}
return -1;
}
public bool isEmpty()
{
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] != null)
{
return false;
}
}
return true;
}
public Vector3 getEmptyPosition()
{
Vector3 pos;
int emptyIndex = getEmptyIndex();
if (State == -1)
{
pos = to_positions[emptyIndex];
}
else
{
pos = from_positions[emptyIndex];
}
return pos;
}
public void GetOnBoat(GameObjects ObjectControl)
{
int index = getEmptyIndex();
passenger[index] = ObjectControl;
}
public GameObjects GetOffBoat(string passenger_name)
{
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] != null && passenger[i].getName() == passenger_name)
{
GameObjects charactorCtrl = passenger[i];
passenger[i] = null;
return charactorCtrl;
}
}
return null;
}
public GameObject GetGameObject()
{
return boat;
}
public void ChangeState()
{
State = -State;
}
public int get_State()
{ // to->-1; from->1
return State;
}
public int[] GetobjectsNumber()
{
int[] count = { 0, 0 };// [0]->priest, [1]->devil
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] == null)
continue;
if (passenger[i].getType() == 0)
{
count[0]++;
}
else
{
count[1]++;
}
}
return count;
}
public Vector3 GetDestination()
{
if (State == 1) return toPosition;
else return fromPosition;
}
public int GetMoveSpeed()
{
return Speed;
}
public void reset()
{
State = 1;
boat.transform.position = fromPosition;
passenger = new GameObjects[2];
MovingState = -1;
}
public int GetMovingState()
{
return MovingState;
}
public void ChangeMovingstate()
{
MovingState = -MovingState;
}
}
当控制器要做的事都定义实现好了,之后,要实现一个场景的控制器来控制所有的控制器,加载场景资源,并实例化控制器,来实现控制器的调用逻辑,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interfaces;
public class FirstController : MonoBehaviour, ISceneController, UserAction
{
InteracteGUI UserGUI;
public CoastController fromCoast;
public CoastController toCoast;
public BoatController boat;
private GameObjects[] GameObjects;
public Referee referee_=new Referee();
private FirstSceneActionManager FCActionManager;
void Start()
{
FCActionManager = GetComponent<FirstSceneActionManager>();
}
void Awake()
{
SSDirector director = SSDirector.getInstance();
director.currentScenceController = this;
UserGUI = gameObject.AddComponent<InteracteGUI>() as InteracteGUI;
GameObjects = new GameObjects[6];
LoadResources();
}
public void LoadResources()
{
fromCoast = new CoastController("from");
toCoast = new CoastController("to");
boat = new BoatController();
GameObject water = Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), new Vector3(0, 0.5F, 0), Quaternion.identity, null) as GameObject;
water.name = "water";
for (int i = 0; i < 3; i++)
{
GameObjects s = new GameObjects("priest");
s.setName("priest" + i);
s.setPosition(fromCoast.getEmptyPosition());
s.getOnCoast(fromCoast);
fromCoast.getOnCoast(s);
GameObjects[i] = s;
}
for (int i = 0; i < 3; i++)
{
GameObjects s = new GameObjects("devil");
s.setName("devil" + i);
s.setPosition(fromCoast.getEmptyPosition());
s.getOnCoast(fromCoast);
fromCoast.getOnCoast(s);
GameObjects[i + 3] = s;
}
}
public void ObjectIsClicked(GameObjects Objects)//物体的点击事件
{
if (FCActionManager.Complete == SSActionEventType.Started) return;
if (Objects.isOnBoat())//物体在船上时
{
CoastController whichCoast;
if (boat.get_State() == -1)//得到船的位置
{ // to->-1; from->1
whichCoast = toCoast;
}
else
{
whichCoast = fromCoast;
}
boat.GetOffBoat(Objects.getName());//下船,
FCActionManager.GameObjectsMove(Objects,whichCoast.getEmptyPosition());//移动
Objects.getOnCoast(whichCoast);//决定上哪个岸,就去拿到岸的控制器
whichCoast.getOnCoast(Objects);//上岸
}
else//在岸上
{
Debug.Log("On Coast!");
CoastController whichCoast = Objects.getCoastController(); // 得到岸的控制器
if (boat.getEmptyIndex() == -1)
{
return;
}
if (whichCoast.get_State() != boat.get_State()) // 船不在岸边
return;
whichCoast.getOffCoast(Objects.getName());//离岸
FCActionManager.GameObjectsMove(Objects, boat.getEmptyPosition());//移动
Objects.getOnBoat(boat);//拿到穿的控制权
boat.GetOnBoat(Objects);//上船
}
//UserGUI.SetState = Check();
}
public void MoveBoat()//船的点击事件
{ UserGUI.SetState = referee_.Check(fromCoast, toCoast, boat);
if (FCActionManager.Complete == SSActionEventType.Started || boat.isEmpty()) return;
FCActionManager.BoatMove(boat);
}
public void Restart()
{
fromCoast.reset();
toCoast.reset();
foreach (GameObjects gameobject in GameObjects)
{
gameobject.reset();
}
boat.reset();
}
}
这样,整个游戏的大致的逻辑便实现了,下面就是为物体和船添加动作了,船的动作简单,只有一种直线的移动方式,但是物体上船时的动作和上岸时的动作是要拐弯的,不能直线移动过去,所以每次移动要分成两次完成,而且上船和上岸的顺序是相反的,所以采用一个队列来把分成两次的动作按照应该的顺序存储起来,然后依次执行对应的动作即可,为节约空间资源,每个动作都是SSAction类的一个实例,执行完动作后要把实例销毁。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interfaces;
public class SSAction : ScriptableObject {
public bool enable = true;
public bool destroy = false;
public GameObject gameObject;
public Transform transform;
public SSActionCallback CallBack;
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
public class CCMoveToAction : SSAction//Object移动的动作,从当前位置移动到目标位置
{
public Vector3 target;
public float speed;
private CCMoveToAction() { }//
public static CCMoveToAction getAction(Vector3 target, float speed)
{
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
public override void Update()//位置移动
{
this.transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
if (transform.position == target)
{
destroy = true;
CallBack.SSActionCallback(this);
}
}
public override void Start()
{
}
}
public class CCSequenceAction : SSAction, SSActionCallback//对象被点击一次,会分成一次或两次动作来完成,记录动作的顺序
{
public List<SSAction> sequence;
public int repeat = 1; // 1->only do it for once, -1->repeat forever
public int currentActionIndex = 0;
public static CCSequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence)
{
CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
action.sequence = sequence;
action.repeat = repeat;
action.currentActionIndex = currentActionIndex;
return action;
}
public override void Update()
{
if (sequence.Count == 0) return;//动作队列为空
if (currentActionIndex < sequence.Count)
{
sequence[currentActionIndex].Update();//执行动作队列里的动作
}
}
public void SSActionCallback(SSAction source)//执行完队列尾的动作后,开始执行队列头的动作
{
source.destroy = false;
this.currentActionIndex++;
if (this.currentActionIndex >= sequence.Count)
{
this.currentActionIndex = 0;
if (repeat > 0) repeat--;
if (repeat == 0)
{
this.destroy = true;
this.CallBack.SSActionCallback(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()
{
foreach (SSAction action in sequence)//销毁实例
{
DestroyObject(action);
}
}
}
public class SSActionManager : MonoBehaviour
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingToAdd = new List<SSAction>();
private List<int> watingToDelete = new List<int>();
protected void Update()
{
foreach (SSAction ac in waitingToAdd)//在待加队列要加入字典的动作,加入进去
{
actions[ac.GetInstanceID()] = ac;
}
waitingToAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)//字典里要删除的动作加入到待删队列
{
SSAction ac = kv.Value;
if (ac.destroy)
{
watingToDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}
foreach (int key in watingToDelete)//删除待删队列里的实例,释放空间
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
watingToDelete.Clear();
}
public void addAction(GameObject gameObject, SSAction action, SSActionCallback ICallBack)//把动作加入到待加队列中区
{
action.gameObject = gameObject;
action.transform = gameObject.transform;
action.CallBack = ICallBack;
waitingToAdd.Add(action);
action.Start();
}
}
之后在为动作添加一个管理类,来决定什么时候以什么顺序来执行动作。代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interfaces;
public class FirstSceneActionManager : SSActionManager, SSActionCallback//船和牧师魔鬼的动作执行函数
{
public SSActionEventType Complete = SSActionEventType.Completed;
public void BoatMove(BoatController Boat)
{
Complete = SSActionEventType.Started;
CCMoveToAction action = CCMoveToAction.getAction(Boat.GetDestination(), Boat.GetMoveSpeed());
addAction(Boat.GetGameObject(), action, this);
Boat.ChangeState();
}
public void GameObjectsMove(GameObjects GameObject, Vector3 Destination)
{
Complete = SSActionEventType.Started;
Vector3 CurrentPos = GameObject.GetPosition();
Vector3 MiddlePos = CurrentPos;//移动时的需要转弯的位置
if (Destination.y > CurrentPos.y)
{
MiddlePos.y = Destination.y;
}
else
{
MiddlePos.x = Destination.x;
}
SSAction action1 = CCMoveToAction.getAction(MiddlePos, GameObject.GetMoveSpeed());
SSAction action2 = CCMoveToAction.getAction(Destination, GameObject.GetMoveSpeed());
SSAction seqAction = CCSequenceAction.getAction(1, 0, new List<SSAction> { action1, action2 });//加入到动作序列里去,并执行动作
this.addAction(GameObject.GetGameobject(), seqAction, this);
}
public void SSActionCallback(SSAction source)
{
Complete = SSActionEventType.Completed;
}
}
最后使用了一个裁判类来决定当前游戏的状态,当每次船移动时,都调用该类的函数来判断游戏当前的状态,代码如下:
using UnityEngine;
using System.Collections;
public class Referee
{
// Use this for initialization
public int Check(CoastController fromCoast,CoastController toCoast,BoatController boat){
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;
int[] fromCount = fromCoast.GetobjectsNumber();
from_priest += fromCount[0];
from_devil += fromCount[1];
int[] toCount = toCoast.GetobjectsNumber();
to_priest += toCount[0];
to_devil += toCount[1];
if (to_priest +to_devil == 6) // win
return 2;
int[] boatCount = boat.GetobjectsNumber();
if (from_priest < from_devil && from_priest > 0)
{ // lose
return 1;
}
return 0;
}
}
剩余的就是用户交互的 GUI 的配置了,配置完成后,便完成了该游戏。
github 地址:
https://github.com/xutaiqi/3DUnity_HW3.git