3D游戏设计 牧师与魔鬼 动作分离版 unity

游戏设计

游戏简介

规则

        初始左岸有三名牧师和三名魔鬼,一只船靠着左岸;

        船最多载两人,至少载一人才能到对岸;

        如果某岸上牧师数小于魔鬼数,牧师会遇害,游戏失败;

        牧师和魔鬼都到右岸,则游戏胜利;

游戏启动准备:

        脚本UserGUI挂到Main Camera上,FirstSceneController、CCActionManager和Judge挂到一个空对象上。

玩法:

        运行后,玩家可以点击人物(牧师或魔鬼)以及船,指挥人物、船等游戏对象自行根据当前条件完成上下船和划船动作。

动作分离版UML图:

其中蓝色部分为动作管理器的原理实现,负责游戏场景的对象的动作管理;绿色部分为裁判类,用于判断游戏是否结束并通知场景控制器。

一个场景可以有多个动作控制器CCActionManager,负责管理不同类的动作,每个动作控制器都充当其基类SSActionManager的门面类,提供可调用的方法方便场景控制器发命令,负责判断动作是否可行以及规定动作的详细信息,再由SSActionManager处理。

动作队列在SSActionManager中进行处理,动作分为组合动作(CCSequenceAction)和单个动作(CCMoveToAction),处理的方法就是修改动作Action的属性并调用它的Update方法,即给动作开个头让它自己运行,而动作结束后会调用回调函数SSActionEvent(),可以在这个函数中加入运动状态的恢复、游戏结果判断等。

裁判类Judge:一个裁判对应一个(或多个)动作控制器,由与动作控制器协作保证同一时间只有一套动作运行,而checkGame()方法对场景中的情况进行分析,判断游戏是否已经成功或失败,如果是,就发送信息给场景控制器。

上一版本——mvc模式

上一版本将动作管理和判断都放在场景控制器中,且使用按钮界面,场景控制器和用户界面的脚本都比较冗长,不方便调试修改和复用。

地址:3D游戏设计 牧师与魔鬼 mvc版-CSDN博客

游戏演示

动作分离版牧师与魔鬼游戏演示

上面视频中分别演示了失败情况和成功情况。(可能是录屏的原因,鼠标位置似乎偏上了,实际我鼠标是对准船了的)

游戏组成

预制体

材质

一切从简,只换了颜色

代码文件

游戏代码

场景控制器FirstController

加载预制体,监听裁判类信息,实现比按钮方便很多的鼠标点击选择对象功能,若点击人物(用了名字来标识,固定位置),根据人物位置进行上下船方法调用;若点击船,调用划船方法。并实现用户界面接口方法。

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

public class FirstController : MonoBehaviour, ISceneController, IUserAction {

	public CCActionManager actionManager { get; set;}
	public Judge judge { get; set; }
	public GameObject boat_p1=null, boat_p2 = null;

	public List<GameObject> leftObjects=new List<GameObject>(), rightObjects=new List<GameObject>();
	public GameObject boat,scene_obj;
	private string judgeMessage=null;

	// the first scripts
	void Awake () {
		SSDirector director = SSDirector.getInstance ();
		director.setFPS (60);
		director.currentSceneController = this;
		director.currentSceneController.LoadResources ();
		Debug.Log ("awake FirstController!");
	}
	// listen to the Judge
	public void getJudgeMessage(string msg)
	{
		this.judgeMessage = msg;
	}
	// loading resources for first scence
	public void LoadResources () {

		scene_obj = Instantiate<GameObject>(
								Resources.Load<GameObject>("prefabs/scene_obj"),
								Vector3.zero, Quaternion.identity);
		boat = Instantiate<GameObject>(
								Resources.Load<GameObject>("prefabs/boat"),
								Vector3.zero, Quaternion.identity);
		boat.transform.position = new Vector3 (3,0,0);
		boat.name = "boat";
		GameObject obj;
		for(int i = 1;i <= 3;i++)
		{
			obj = Instantiate<GameObject>(
								Resources.Load<GameObject>("prefabs/priest"),
								new Vector3(i*2+5,4,0), Quaternion.identity);
			obj.name = i.ToString();
			leftObjects.Add(obj);
        }
        for (int i = 4; i <= 6; i++)
        {
            obj = Instantiate<GameObject>(
                                Resources.Load<GameObject>("prefabs/devil"),
                                new Vector3( i*2+5, 4, 0), Quaternion.identity);
            obj.name = i.ToString();
            leftObjects.Add(obj);
        }
        ;
	}

	private void MoveBoat()
	{

        actionManager.BoatMove();

    }
    private void MovePeople(GameObject obj)
    {
        if (obj.transform.position.x>5|| obj.transform.position.x < -5)
		{
			actionManager.MoveToBoat(obj);
		}
		else
		{
			actionManager.LeaveBoat(obj);
		}

    }

	#region IUserAction implementation
	public void GameOver ()
	{
		SSDirector.getInstance ().NextScene ();
	}
	public string GameOut()
	{
		// 停止游戏
		if(judgeMessage!=null)SSDirector.getInstance ().setFPS(0);
		return judgeMessage;
	}
	public void Click()
	{
		GameObject gameObj = null; 
        
		if (Input.GetMouseButtonDown(0))
		{
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit)) gameObj = hit.transform.gameObject;
		}

		if (gameObj == null) return;
		else if (gameObj.name == "1" || gameObj.name == "2" || gameObj.name == "3"
			|| gameObj.name == "4" || gameObj.name == "5" || gameObj.name == "6")
		{
			Debug.Log(gameObj.name);
			MovePeople(gameObj);
		}
		else if (gameObj.name == "boat") MoveBoat(); 
    }
	#endregion


	// Use this for initialization
	void Start () {
		//give advice first
	}
	
	// Update is called once per frame
	void Update () {
		//give advice first
	}

}

实现的接口:

using System;

public interface IUserAction
{
	void GameOver();

	string GameOut();
	void Click();
}
using System;

public interface ISceneController
{
	void LoadResources();
}

用户GUI界面UserGUI

调用IUserAction方法,实现点击功能、游戏结束提示

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

public class UserGUI : MonoBehaviour {
	private IUserAction action;
    private string gameMessage;
    private GUIStyle style;

	void Start () {
		action = SSDirector.getInstance ().currentSceneController as IUserAction;
	}
    void OnGUI()
    {
        float width = Screen.width / 8;
        float height = Screen.height / 12;

        if (GUI.Button(new Rect(0, 0, width, height), "结束游戏"))
        {
            action.GameOver();
        }
        if (action.GameOut() != null)
        {
            GUI.Box(new Rect(width * 4, height * 5, width, height), action.GameOut());
        }

    }

    void Update()
    {
        action.Click();
    }
}

导演类SSDirector

实现场景切换、场景停止、时间流速设置

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSDirector : System.Object {
	// singlton instance
	private static SSDirector _instance;

	public ISceneController currentSceneController { get; set;}
	public bool running{ get; set;} 

	// get instance anytime anywhare!
	public static SSDirector getInstance() {
		if (_instance == null) {
			_instance = new SSDirector ();
		}
		return _instance;
	}

	public int getFPS() {
		return Application.targetFrameRate;
	}

	public void setFPS(int fps) {
		Application.targetFrameRate = fps;
	}

	public void NextScene(){
		Debug.Log ("Waiting next Scene now...");
		Application.Quit ();
	}
}

动作管理器部分

动作类(抽象类)SSAction

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

public class SSAction : ScriptableObject {

	public bool enable = true;
	public bool destory = false;

	public GameObject gameobject { get; set; }
	public Transform transform { get; set; }
	public ISSActionCallback callback { get; set; }

	protected SSAction () {}

	// Use this for initialization
	public virtual void Start () {
		throw new System.NotImplementedException ();
	}
	
	// Update is called once per frame
	public virtual void Update () {
		throw new System.NotImplementedException ();
	}
		
}

单动作类CCMoveToAction

GetSSAction方法在动作管理器CCActionManager中调用,获取动作信息(目标向量和速度)。Update中进行动作实现。

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

public class CCMoveToAction : SSAction
{
	public Vector3 target;
	public float speed;

	public static CCMoveToAction GetSSAction(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 (this.transform.position, target, speed * Time.deltaTime);
		if (this.transform.position == target) {
			//waiting for destroy
			this.destory = true;  
			this.callback.SSActionEvent (this);
		}
	}

	public override void Start () {
	}
}

组合动作类CCSequenceAction

一组动作,repeat代表重复次数,-1则不断重复。start即动作列表的索引,Upadate方法中调用单个动作的Update来逐步完成动作。

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

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

	public static CCSequenceAction GetSSAction(int repeat, int start , List<SSAction> sequence){
		CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction> ();
		action.repeat = repeat;
		action.sequence= sequence;
		action.start = start;
		return action;
	}
		
	// Update is called once per frame
	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.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); }
		}
	}

	// Use this for initialization
	public override void Start () {
		foreach (SSAction action in sequence) {
			action.gameobject = this.gameobject;
			action.transform = this.transform;
			action.callback = this;
			action.Start ();
		}
	}

	void OnDestory() {
		//TODO: something
		Destroy(this);
	}
}

动作控制器基类SSActionManager

RunAction方法将待执行动作加入执行队列中,在Update中执行,执行过的动作加入销毁队列销毁。不过游戏要求同一时间只有一个动作,所以用了裁判类判断来限制了动作的生成和积累。

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

public class SSActionManager : MonoBehaviour {

	private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction> ();
	private List <SSAction> waitingAdd = new List<SSAction> (); 
	private List<int> waitingDelete = new List<int> ();

	// Update is called once per frame
	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.destory) { 
				waitingDelete.Add(ac.GetInstanceID()); // release action
			} else if (ac.enable) { 
				ac.Update (); // update action
			}
		}

		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 ();
	}


	// Use this for initialization
	protected void Start () {
	}
}

动作控制器类CCActionManager

start中和场景控制器配对,并实现划船、上船下船的方法,既生成动作,还要在动作生成之前判断条件是否合适。实现了回调函数SSActionEvent,在动作结束后会更新场景中动作状态并检查游戏是否结束。同时不断调用基类的Update及时处理动作。

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

public class CCActionManager : SSActionManager, ISSActionCallback {
	
	private FirstController sceneController;
	private CCMoveToAction moveToA , moveToB, moveToC, moveToD,moveToE,moveToF;

	public void BoatMove()
	{
        //检查是否在做动作
		if (sceneController.judge.checkMove()) return;
        //检查船上是否有人
        if (sceneController.boat == null || (sceneController.boat_p1 == null && sceneController.boat_p2 == null)) return;
		float next_x = sceneController.boat.transform.position.x*-1;
		// 船、船上的人的下一个位置和移动速度
		moveToA = CCMoveToAction.GetSSAction(new Vector3(next_x, 0, 0), 6);
        moveToB = CCMoveToAction.GetSSAction(new Vector3(next_x - 1, 3, 0), 6);
		moveToC = CCMoveToAction.GetSSAction(new Vector3(next_x + 1, 3, 0), 6);
        // boat moves
		this.RunAction(sceneController.boat, moveToA, this);
        // set ismoving
        sceneController.judge.setIsMoving(true);
        // boat_p move
        if (sceneController.boat_p1 !=null)
		{
			GameObject p = sceneController.boat_p1;
			float x = p.transform.position.x;
            if (x > 0) sceneController.leftObjects.Remove(p);
            else sceneController.rightObjects.Remove(p);
            if (x < 0) sceneController.leftObjects.Add(p);
            else sceneController.rightObjects.Add(p);

            this.RunAction(sceneController.boat_p1 , moveToB, this);
        }
        if (sceneController.boat_p2 != null)
		{
            GameObject p = sceneController.boat_p2;
            float x = p.transform.position.x;
            if (x > 0) sceneController.leftObjects.Remove(p);
            else sceneController.rightObjects.Remove(p);
            if (x < 0) sceneController.leftObjects.Add(p);
            else sceneController.rightObjects.Add(p);

            this.RunAction(sceneController.boat_p2, moveToC, this);

            
        }
		
    }

    public void MoveToBoat(GameObject obj)
    {
        //检查是否在做动作
        if (sceneController.judge.checkMove()) return;
        int j = sceneController.boat_p1 == null ? 1 : (sceneController.boat_p2 == null ? 2 : 0);
		if (j == 0) return;
        else if(j==1) sceneController.boat_p1 = obj;
        else sceneController.boat_p2= obj;
		float next_x = sceneController.boat.transform.position.x-3+j*2;
        // 按上-左/右-下的顺序平移
		moveToD = CCMoveToAction.GetSSAction(new Vector3(obj.transform.position.x, 9, 0), 6);
        moveToE = CCMoveToAction.GetSSAction(new Vector3(next_x, 9, 0), 6);
        moveToF = CCMoveToAction.GetSSAction(new Vector3(next_x, 3, 0), 6);
        CCSequenceAction ccs = CCSequenceAction.GetSSAction(1, 0, new List<SSAction> { moveToD, moveToE,moveToF });
        this.RunAction(obj, ccs, this);
        // set ismoving
        sceneController.judge.setIsMoving(true);
    }

    public void LeaveBoat(GameObject obj)
    {
        //检查是否在做动作
        if (sceneController.judge.checkMove()) return;
        if (obj == sceneController.boat_p1) sceneController.boat_p1=null;
        else if(obj == sceneController.boat_p2) sceneController.boat_p2=null;
        int i = int.Parse(obj.name);
		float next_x = (i * 2 + 5)*(sceneController.boat.transform.position.x>0?1:-1);
        // 按上-左/右-下的顺序平移
        moveToD = CCMoveToAction.GetSSAction(new Vector3(obj.transform.position.x, 9, 0), 5);
        moveToE = CCMoveToAction.GetSSAction(new Vector3(next_x, 9, 0), 5);
        moveToF = CCMoveToAction.GetSSAction(new Vector3(next_x, 4, 0), 5);
        CCSequenceAction ccs = CCSequenceAction.GetSSAction(1, 0, new List<SSAction> { moveToD, moveToE, moveToF });
        this.RunAction(obj, ccs, this);
        // set ismoving
        sceneController.judge.setIsMoving(true);
    }

    protected new void Start() {
		sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
		sceneController.actionManager = this;
	}

	// Update is called once per frame
	protected new void Update ()
	{
		base.Update ();
	}
		
	#region ISSActionCallback implementation
	public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
	{
        Debug.Log("finishmove");
        // 一个操作的动作结束,可以进行下一个操作
        sceneController.judge.setIsMoving(false);
        // 检查游戏是否结束
        sceneController.judge.checkGame();

        Destroy(source);
	}
	#endregion
}

实现的接口ISSActionCallback:

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

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);
}


裁判类

start中和场景控制器配对。通过获取场景控制器信息来判断游戏情况并通知场景控制器。

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

public class Judge : MonoBehaviour
{
    private bool move=false;
    private FirstController sceneController;
    public void checkGame()
    {
        //检查游戏成功
        if(sceneController.rightObjects.Count == 6)
        {
            sceneController.getJudgeMessage("You Win!");
        }

        //检查游戏失败
        //左岸
        int p=0, d=0;
        for(int i=0;i<sceneController.leftObjects.Count;i++)
        {
            if (sceneController.leftObjects[i].name=="1"
                || sceneController.leftObjects[i].name == "2"
                || sceneController.leftObjects[i].name == "3")
            {
                p++;
            }
            else
            {
                d++;
            }
        }
        if (p < d && p!=0) { Debug.Log(p); Debug.Log(d); sceneController.getJudgeMessage("You Lose!"); }

        //右岸
        p = 0; d = 0;
        for (int i = 0; i < sceneController.rightObjects.Count; i++)
        {
            if (sceneController.rightObjects[i].name == "1"
                || sceneController.rightObjects[i].name == "2"
                || sceneController.rightObjects[i].name == "3")
            {
                p++;
            }
            else
            {
                d++;
            }
        }
        if (p < d && p != 0) { Debug.Log(p); Debug.Log(d); sceneController.getJudgeMessage("You Lose!"); }
    }

    public void setIsMoving(bool move)
    {
        this.move = move;
    }
    public bool checkMove()
    {
        return this.move;
    }
    
    // Start is called before the first frame update
    void Start()
    {
        sceneController = SSDirector.getInstance().currentSceneController as FirstController;
        sceneController.judge = this;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值