天空盒
通过组件中的skybox可以创建天空盒。老师在上课的时候提到过天空盒是一个球状贴图,通常用6面贴图来表示。但是我所使用的天空盒似乎只有一面,不知道是不是哪里弄错了。
游戏对象总结
在Unity中游戏对象十分有用,这是从很多方面来讲的。
从查找的角度来说,使用GameObject的Find方法可以很轻松地通过路径来获取其他对象。虽然对于active=false的对象无法使用,而且频繁使用会导致代码十分混乱,但还是值得考虑的方法。其他的像是FindWithTag等方法也可以。
第二个是从transform的角度上来讲,通过transform的position属性可以很轻松地对对象的位置进行修改,产生移动的效果。
第三是从结构的角度上来讲的,通过对象的transform.parent可以组织对象之间的上下关系,可以比较方便地进行各种操作,像是批量移动、失效等。
牧师与魔鬼动作分离版
参考代码:PPT上的代码
https://github.com/csr632/Priests-and-devils/tree/Improvement2
其实相比于上周而言,动作分离版的并不算太难。UML图我画的不好,就不画了。
我是通过逐步将上周代码中的动作进行分离最终完成的作业,所以可能有些地方会显得很奇怪,因为是逐步修改过来的。
* 所有的新代码放在Action.cs中。我将新定义的类分为两类,一类是动作类,包括了BaseAction(基本动作类)、LineAction(简单动作类)、SequenceAction(复杂动作类);另一类是管理类,包括BaseActionManager(基本管理类)、FirstSceneActionManager(特化管理类)
* BaseAction是动作类的基类,所有动作类都从它这里继承。它本身是从Unity的ScriptableObject中继承而来的,就我个人对老师PPT上代码的理解而言这是为了方便内存管理。LineAction实现的是单个直线型动作,而SequenceAction内置了一个列表来保存动作,实现的是复数个动作的集合。
* BaseActionManager实现的是范用的动作管理类的模板,而FirstSceneActionManager从上一个中继承而来,实现了很多与这个游戏相关的内容。
就抽象、封装等层次而言这份代码写的不是很好,有空会理一下UML图。
枚举类和接口类其实用的不是特别多,没什么提的必要:
public enum BaseActionEventType : int { Started, Completed }
public interface ActionCallback
{
void actionDone(BaseAction source);
}
下面是动作类部分,其中我到后期debug时才明白动作类中的actionDone调用的是管理器的actionDone,然而我已经写好了其他框架,为了间接只好恢复,十分难受,望引以为鉴。
public class BaseAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ActionCallback callback { get; set; }
protected BaseAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
//简单动作类
public class LineAction : BaseAction
{
public Vector3 target;
public float speed;
//由Unity来创建动作类,保证内存正确回收
public static LineAction GetBaseAction(Vector3 target,float speed)
{
LineAction action = ScriptableObject.CreateInstance<LineAction>();
action.target = target;
action.speed = speed;
return action;
}
public override void Update()
{
//Debug.Log(this.transform == null);
//Debug.Log("In Line Update");
//Debug.Log("aim position:"+this.transform.position.ToString());
//Debug.Log("target:"+target.ToString() + " speed:" + speed.ToString());
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
if(this.transform.position == target)
{
this.destroy = true;
this.callback.actionDone(this);
}
}
public override void Start()
{
//暂时不实现
}
}
//组合动作实现,例如折线动作
public class SequenceAction : BaseAction, ActionCallback
{
public List<BaseAction> sequence;
public int repeat = 1;//1 is act once, -1 is act forever
public int current = 0;//pointer that points to the beginning
//同简单动作
public static SequenceAction GetBaseAction(int repeat,int point,List<BaseAction> sequence)
{
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
action.repeat = repeat;
action.sequence = sequence;
action.current = point;
return action;
}
public override void Update()
{
if (sequence.Count == 0) return;
if(current<sequence.Count)//执行当前动作
{
sequence[current].Update();
}
}
public void actionDone(BaseAction source)
{
//Debug.Log("actionDone in SequenceAction");
source.destroy = false;
this.current++;
if (this.current >= sequence.Count)
{
this.current = 0;
if ( repeat > 0 ) repeat--;
if ( repeat == 0 )
{
this.destroy = true;
this.callback.actionDone(this);
}
}
}
//开始的时候注册所有动作
public override void Start()
{
foreach(BaseAction action in sequence)
{
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}
void OnDestroy()
{
foreach(BaseAction action in sequence)
{
DestroyObject(action);
}
}
}
基础的动作管理类,十分经典
public class BaseActionManager : MonoBehaviour
{
public Dictionary<int, BaseAction> actions = new Dictionary<int, BaseAction>();
private List<BaseAction> addList = new List<BaseAction>();
private List<int> deleteList = new List<int>();
//动作做完之后自动回收动作
protected void Update()
{
foreach(BaseAction action in addList)
{
actions[action.GetInstanceID()] = action;
}
addList.Clear();
foreach(KeyValuePair<int,BaseAction> keyValue in actions)
{
BaseAction action = keyValue.Value;
if (action.destroy)
{
deleteList.Add(action.GetInstanceID());
}
else if(action.enable)
{
action.Update();
}
}
foreach(int key in deleteList)
{
BaseAction action = actions[key];
actions.Remove(key);
DestroyObject(action);
}
deleteList.Clear();
}
public void addAction(GameObject gameobject,BaseAction action,ActionCallback manager)
{
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
action.Start();
addList.Add(action);
}
protected void Start()
{
//nothing to do in start
}
}
特化的本地管理类,因为方便起见我使用了很多原来的代码,移植了许多原来散步在各个类中的常量。
总体结构大概分为两部分:moveBoat和clickCharacter。moveBoat自然不用多说,clickCharacter中因为移动的位置比较麻烦还调用了characterAction这么一个函数来简化结构。
此外还实现了reset来实现重置操作,以及actionDone函数。关于actionDone函数有一些要提的。一开始的时候我认为actionDone函数不是很必要,因为直接在注册动作的时候修改状态也是可以的,但是我发现有些状态只能在动作完成之后修改,但是动作只要在状态修改之前完成都不会受到影响,为了统一起见所有相关的状态修改都放在了actionDone中,可能有一些因为迁移的缘故还残留在其他地方,会逐一修改。
public class FirstSceneActionManager : BaseActionManager, ActionCallback
{
private float boatSpeed = 20f;
private float personSpeed = 30f;
private float resetSpeed = 100f;
private FirstController firstController;
private ICharacterController characterCache;
private CoastController coastCache;
int status = 0;//0为初始化状态,1为船的运动状态,2为人物运动状态
readonly Vector3 frontmiddle1 = new Vector3(3.5f, 1.25f, 0);
readonly Vector3 frontmiddle2 = new Vector3(3.5f, 0.5f, 0);
readonly Vector3 backmiddle1 = new Vector3(8.5f, 1.25f, 0);
readonly Vector3 backmiddle2 = new Vector3(8.5f, 0.5f, 0);
//两个相对向量,表示相对于船的两个成员的位置(以from岸为基础)
readonly Vector3 front = new Vector3(0.5f, 0.5f, 0);
readonly Vector3 back = new Vector3(-0.5f, 0.5f, 0);
public bool canClick;
protected new void Start()
{
firstController = Director.getInstance().currentSceneController as FirstController;
canClick = true;
}
public void moveBoat()
{
BoatController boat = firstController.boat;
if (firstController.isBoatMove() == false||boat.boatEmpty()==true)
return;
status = 1;
this.canClick = false;
LineAction action = LineAction.GetBaseAction(boat.getDestination(),boatSpeed);
this.addAction(boat.boat, action, this);
}
public void clickCharacter(ICharacterController character)
{
bool ok=firstController.isCharacterMove(character);
BoatController boat = firstController.boat;
if (ok == false)
return;
this.canClick = false;
status = 2;
characterCache = character;
coastCache = firstController.getCharacterCoast();
if (character.onBoat == false)//上船的过程
{
// Debug.Log("function clickCharacter ready to go on boat with parameter:" + charctrl.character.name);
this.characterAction(coastCache,character,false);
}
else
{
// Debug.Log("function clickCharacter ready to go off boat with parameter:" + charctrl.character.name);
this.characterAction(coastCache, character, true);
}
firstController.checkGameover();
}
//status为false时表示上船,status为true时表示下船
public void characterAction(CoastController whichCoast,ICharacterController character,bool status)
{
List<BaseAction> actionList = new List<BaseAction>();
BoatController boat = firstController.boat;
LineAction action1=null, action2=null, targetAction=null;
//制作序列表格actionList
if (status == false)//上船
{
Vector3 relativeVec = this.getBoatEmpty(boat);
//Debug.Log(relativeVec.ToString());
if(boat.boatStatus == 0)
{
action1 = LineAction.GetBaseAction(frontmiddle1, personSpeed);
action2 = LineAction.GetBaseAction(frontmiddle2, personSpeed);
}
else
{
action1 = LineAction.GetBaseAction(backmiddle1, personSpeed);
action2 = LineAction.GetBaseAction(backmiddle2, personSpeed);
}
targetAction = LineAction.GetBaseAction(boat.boat.transform.position+relativeVec,personSpeed);
}
else if(status == true)
{
Vector3 relativeVec;
int pos = whichCoast.getEmpty();
if (whichCoast.coast.name == "from_coast")
{
relativeVec = new Vector3(2.5f - pos, 1.25f, 0);
}
else
{
relativeVec = new Vector3(-2.5f + pos, 1.25f, 0);
}
if (boat.boatStatus == 0)
{
action1 = LineAction.GetBaseAction(frontmiddle2, personSpeed);
action2 = LineAction.GetBaseAction(frontmiddle1, personSpeed);
}
else
{
action1 = LineAction.GetBaseAction(backmiddle2, personSpeed);
action2 = LineAction.GetBaseAction(backmiddle1, personSpeed);
}
targetAction = LineAction.GetBaseAction(whichCoast.coast.transform.position + relativeVec, personSpeed);
}
//注册复杂事件
/*
Debug.Log("register information:");
Debug.Log("action1:" + action1.target.ToString());
Debug.Log("action2:" + action2.target.ToString());*/
//Debug.Log("targetAction:" + targetAction.target.ToString());
actionList.Add(action1);
actionList.Add(action2);
actionList.Add(targetAction);
SequenceAction action = SequenceAction.GetBaseAction(1, 0, actionList);
/*
this.addAction(character.character, action1, this);
this.addAction(character.character, action2, this);
this.addAction(character.character, targetAction, this);*/
this.addAction(character.character, action, this);
}
public Vector3 getBoatEmpty(BoatController boat)
{
if(boat.boatStatus == 0)
{
if (boat.frontCharacter == null)
{
return front;
}
else
{
return back;
}
}
else
{
if (boat.backCharacter == null)
return back;
else
return front;
}
}
public void reset()
{
BoatController boat = firstController.boat;
LineAction action = LineAction.GetBaseAction(new Vector3(4, 0, 0), boatSpeed);
this.addAction(boat.boat, action, this);
ICharacterController[] characters = firstController.characters;
for(int i=0;i<characters.Length;i++)
{
Vector3 relativeVec = new Vector3(2.5f - i, 1.25f, 0);
LineAction chaAction = LineAction.GetBaseAction(firstController.fromCoast.coast.transform.position + relativeVec, resetSpeed);
this.addAction(characters[i].character, chaAction, this);
}
this.canClick = true;
this.status = 0;
}
public void actionDone(BaseAction source)
{
//Debug.Log("actionDone in FirstSceneActionManager");
if(status == 1)
{
BoatController boat = firstController.boat;
boat.switchBoatStatus();
}
else if(status == 2)
{
BoatController boat = firstController.boat;
if (characterCache.onBoat == false)//上船
{
boat.OnBoat(characterCache);
coastCache.OffCoast(characterCache);//离岸
characterCache.character.transform.parent = boat.boat.transform;
}
else
{
boat.OffBoat(characterCache);
coastCache.OnCoast(characterCache, boat.boatStatus);//下船上岸
characterCache.character.transform.parent = null;
}
}
canClick = true;
firstController.checkGameover();
status = 0;
}
}
}