一、下载Fantasy Skybox FREE,构建自己的游戏场景

  1. 在unity资源商店搜索Fantasy Skybox FREE,找到相应的资源并打开页面,点击页面右侧的添加至我的资源

unity3d 3d棋盘_3d




2. 在我的资源中点击在unity中打开,系统将自动启动unity的Package Manager,找到Fantasy Skybox FREE,点击download下载

unity3d 3d棋盘_3d_02




3. 下载好以后点击import,导入材质包

unity3d 3d棋盘_3d_03




4. 在菜单栏中选择Window->Rendering->Lighting,打开后再选择Environment,在Skybox Material字段选择喜欢的天空盒材质

unity3d 3d棋盘_游戏_04




5. 选好以后关闭选项卡,即可看到效果如下图所示:

unity3d 3d棋盘_游戏_05



二、牧师与魔鬼 - 动作分离版

  1. 改进思路

在上一个版本的牧师与魔鬼实现中,我们的控制器要管理的事情太多,不仅要处理用户交互事件,还要游戏加载、游戏规则实现、运动实现等等,显得非常臃肿。
为了改进代码结构,在本次实现中,我们采用 cocos2d 的方案,用一组简单的动作组合成复杂的动作,建立与 CCAtion 类似的类。设计思路如下:

1.设计一个动作管理类来输出组合好的几个简单动作,使得动作的组成成为动作模块内部的事务
2.设计一个抽象类来代表基本动作和组合动作,然后按照组合模式的设计方法实现动作组合
3.通过回调,实现动作完成时的通知。

对比上一个版本的游戏实现,本次设计可以方便地定义动作并实现动作的自由组合,使得:

1.程序更能适应需求变化;
2.对象更容易被复用;
3.程序更易于维护。

改进后动作管理部分类图如下:

unity3d 3d棋盘_unity3d 3d棋盘_06


2. 代码结构优化

本次我们要设计牧师与魔鬼的动作分离版,以下是具体优化及实现。

  • 场景控制与模型控制之中的修改
    由于采用动作分离,所以BoatMove函数不再需要直接在模型中对船的移动进行控制,反之,我们现在只需要让BoatMove函数返回需要移动的位置,然后交给动作控制器进行处理即可。
//改变船的位置
		public Vector3 BoatMove()
		{
			if (boat_sign == -1)
			{
				boat_sign = 1;
				return new Vector3(-0.2F, 0.05F, -0.35F);
				
			}
			else
			{
				boat_sign = -1;
				return new Vector3(-0.2F, 0.05F, -2.2F);
			}
		}

由于BoatMove函数的修改,现在我们在场景控制中只需要调用上述函数得知物体移动的位置,再通过动作管理即可实现对应位置的移动动作。

public void MoveBoat()
	{
		if (boat.IsEmpty() || user_gui.sign != 0) return;
		actionManager.moveBoat(boat.getGameObject(), boat.BoatMove(), boat.move_speed);
		user_gui.sign = judge.Check();
	}

与船的动作管理相同,调用动作管理中的moveRole函数就可以执行一系列的人物移动动作。

public void MoveRole(RoleModel role)
	{
		if (user_gui.sign != 0) return;
		if (role.IsOnBoat())
		{
			LandModel land;
			if (boat.GetBoatSign() == -1)
				land = end_land;
			else
				land = start_land;
			boat.DeleteRoleByName(role.GetName());
			actionManager.moveRole(role.getGameObject(), new Vector3(role.getGameObject().transform.position.x, land.GetEmptyPosition().y, land.GetEmptyPosition().z), land.GetEmptyPosition(), role.move_speed);
			role.GoLand(land);
			land.AddRole(role);

		}
		else
		{
			LandModel land = role.GetLandModel();
			if (boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign()) return;
			land.DeleteRoleByName(role.GetName());
			actionManager.moveRole(role.getGameObject(), new Vector3(boat.GetEmptyPosition().x, role.getGameObject().transform.position.y, boat.GetEmptyPosition().z), boat.GetEmptyPosition(), role.move_speed);
			role.GoBoat(boat);
			boat.AddRole(role);
		}
		user_gui.sign = judge.Check();
	}

需要注意的是,由于我们有了动作管理器,这本质上替代了之前move类的工作,因此我们就可以将之前的move类删去。

  • 动作基类SSAction类
    由上面的UML图我们可以知道,SSAction类是所有动作的基类,SSAction继承了ScriptableObject,这里的ScriptableObject 是不需要绑定 GameObject 对象的可编程基类。这些对象受 Unity 引擎场景管理。除此之外,通过protected 可以防止用户自己 new 对象;使用 virtual 声明虚方法,通过重写实现多态。这样继承者就明确使用 Start 和 Update 编程游戏对象行为;利用接口(ISSACtionCallback)实现消息通知,避免与动作管理者直接依赖。
    动作基类SSAction的子类有Sequence Action和MoveToAction,根据门面模式将其功能抽象为SSActionManager。
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();
	}
}
  • 简单动作实现MoveToAction
    MoveToAction类负责实现某一具体的动作,将一个物体移动到目标位置,并通知任务完成。这里的设计要点是让 Unity 创建动作类,确保内存正确回收。另外就是当动作完成时,需要发出事件通知管理者,并期望管理程序自动回收运行对象。
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>();
		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类负责实现一个动作组合序列,顺序播放动作,设计要点有:






public class SequenceAction : SSAction, ISSActionCallback
{
	public List<SSAction> sequence;
	public int repeat = -1;
	public int start = 0;

	public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence)
	{
		SequenceAction action = ScriptableObject.CreateInstance<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();
		}
	}

	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()
	{

	}
}
  • 动作事件接口定义
    接口作为接收通知对象的抽象类型,设计要点有:事件类型定义,使用了枚举变量;定义了事件处理接口,所有事件管理者都必须实现这个接口,来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。
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);
}
  • 动作管理基类 SSActionManager
    这是动作对象管理器的基类,实现了所有动作的基本管理。它通过创建MonoBehaviour来管理一个动作集合,动作做完自动回收动作。该类拥有一个核心方法RunAction,通过该方法来添加新动作,使得游戏对象与动作之间得到绑定,并且绑定该动作事件的消息接收者。
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();

		foreach (KeyValuePair<int, SSAction> kv in actions)
		{
			SSAction ac = kv.Value;
			if (ac.destroy)
			{
				waitingDelete.Add(ac.GetInstanceID());
			}
			else if (ac.enable)
			{
				ac.Update();
			}
		}

		foreach (int key in waitingDelete)
		{
			SSAction ac = actions[key];
			actions.Remove(key);
			DestroyObject(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)
	{

	}
}
  • 场景下动作管理MySceneActionManager
    MySceneActionManager类负责当前场景下的动作管理的具体实现,在场景控制类中调用它的方法,实现对当前场景的动作管理,详情见下面的代码注释:
//需要管理两个动作,一个是场景中船的移动,还有一个动作是人物的移动
public class MySceneActionManager : SSActionManager
{

	private SSMoveToAction moveBoatToEndOrStart;
	private SequenceAction moveRoleToLandorBoat;

	public Controller sceneController;

	protected void Start()
	{
		sceneController = (Controller)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)
	{
		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);
	}
}

最后是设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束,具体功能见下面的代码注释:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using modelcon;

public class Judge
{
	LandModel start_land;
	LandModel end_land;
	BoatModel boat;
	
	//构造函数初始化
	public Judge(LandModel start_, LandModel end_, BoatModel boat_)
	{
		start_land = start_;
		end_land = end_;
		boat = boat_;
	}
	
	//检查游戏是否满足结束条件
	public int Check()
	{
		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;
	}
}

修改完毕后运行游戏,效果图如下所示:

unity3d 3d棋盘_unity3d 3d棋盘_07



三、参考资料

[1]. unity官方文档 [2]. 师兄的博客(牧师与魔鬼)