【游戏开发】unity教程2 空间与运动 牧师与魔鬼小游戏

简答

  • 游戏对象运动的本质是什么?

        游戏对象运动的本质是随着每一帧刷新,对象的positoin、rotation离散地进行变化。

  • 请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)

        无论用什么样的方法,我们都要让运动满足抛物线的定义。即x轴上做匀速直线运动,y轴上匀变速直线运动。所以首先我们都要定义好的就是x轴上的速度,y轴上的加速度和初始速度。

        首先是最简单的通过vector3向量来改变position。

public class test : MonoBehaviour {    
    public float vY = 3.0f;
    public float aY = 1.0f;
    public float vX = 1.0f;
    
    void Start(){
        Debug.Log("Start!");
    }    

    void Update(){
        Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
        this.transform.position += change;
        vY -= aY * Time.deltaTime; 
    }
}

        然后是使用Vector3.Lerp()函数。

public class test : MonoBehaviour {    
    public float vY = 3.0f;
    public float aY = 1.0f;
    public float vX = 1.0f;
    
    void Start(){
        Debug.Log("Start!");
    }    

    void Update(){
        Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
        this.transform.postion = Vector3.Lerp(this.transform.position, this.transform.position + move, 1);
        vY -= aY * Time.deltaTime; 
    }
}

        最后一种方法是使用transform.translate()函数

public class test : MonoBehaviour {    
    public float vY = 3.0f;
    public float aY = 1.0f;
    public float vX = 1.0f;
    
    void Start(){
        Debug.Log("Start!");
    }    

    void Update(){
        Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
        this.transform.Translate(change);
        vY -= aY * Time.deltaTime; 
    }
}
  • 写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

        首先需要分别创建太阳系中所有的恒星和行星,在Start中导入并确认Sun的位置。然后在Update中用RotateAround实现公转,Rotate实现自传。

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

public class NewBehaviourScript1 : MonoBehaviour
{
    GameObject Sun, Moon, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune;
    
    void Start(){
        Sun = GameObject.Find("Sun");
        Moon = GameObject.Find("Moon");
        Mercury = GameObject.Find("Mercury");
        Venus = GameObject.Find("Venus");
        Earth = GameObject.Find("Earth");
        Mars = GameObject.Find("Mars");
        Jupiter = GameObject.Find("Jupiter");
        Saturn = GameObject.Find("Saturn");
        Uranus = GameObject.Find("Uranus");
        Neptune = GameObject.Find("Neptune");
    }

    void Update(){
        Sun.transform.Rotate(Vector3.up * Time.deltaTime * 5 );

        Mercury.transform.RotateAround(Sun.transform.position, new Vector3(0.1f, 1, 0), 60 * Time.deltaTime);
        Mercury.transform.Rotate(Vector3.up * 10000/58 * Time.deltaTime);
        
        Venus.transform.RotateAround(Sun.transform.position, new Vector3(0, 1, -0.1f), 55 * Time.deltaTime);
        Venus.transform.Rotate(Vector3.up * 10000/243 * Time.deltaTime);
        
        Earth.transform.RotateAround(Sun.transform.position, Vector3.up, 50 * Time.deltaTime);
        Earth.transform.Rotate(Vector3.up * 10000 * Time.deltaTime);
        Moon.transform.RotateAround(Earth.transform.position, Vector3.up, 5 * Time.deltaTime);
        Moon.transform.Rotate(Vector3.up * 10000/27 * Time.deltaTime);
        
        Mars.transform.RotateAround(Sun.transform.position, new Vector3(0.2f, 1, 0), 45 * Time.deltaTime);
        Mars.transform.Rotate(Vector3.up * 10000 * Time.deltaTime);
        
        Jupiter.transform.RotateAround(Sun.transform.position, new Vector3(-0.1f, 2, 0), 38 * Time.deltaTime);
        Jupiter.transform.Rotate(Vector3.up * 10000/0.3f * Time.deltaTime);
        
        Saturn.transform.RotateAround(Sun.transform.position, new Vector3(0, 1, 0.2f), 36 * Time.deltaTime);
        Saturn.transform.Rotate(Vector3.up * 10000/0.4f * Time.deltaTime);
        
        Uranus.transform.RotateAround(Sun.transform.position, new Vector3(0, 2, 0.1f), 35 * Time.deltaTime);
        Uranus.transform.Rotate(Vector3.up * 10000/0.6f * Time.deltaTime);
        
        Neptune.transform.RotateAround(Sun.transform.position, new Vector3(-0.1f, 1, -0.1f), 33 * Time.deltaTime);
        Neptune.transform.Rotate(Vector3.up * 10000/0.7f * Time.deltaTime); 
    }
}

 

 

牧师与魔鬼

所谓MVC架构,是指将程序分为三个部分:

  • 模型(Model):数据对象及关系
    • 游戏对象、空间关系
  • 控制器(Controller):接受用户事件,控制模型的变化
    • 一个场景一个主控制器
    • 至少实现与玩家交互的接口(IPlayerAction)
    • 实现或管理运动
  • 界面(View):显示模型,将人机交互事件交给控制器处理
    • 处收 Input 事件
    • 渲染 GUI ,接收事件

简单来说,MVC架构很好地降低了系统的耦合性,是一种好的设计。在接下来这个小游戏的设计中,我们也采用这种架构,具体的UML图大概如下图:

其中场景中的所有GameObject就是MVC中的Model,也即上图中被FirstController直接控制的GameModels,包括boat、coast以及character;同时为了降低耦合性,每种Object都有对应的controller,最终整合在FirstController中,也即MVC中的C。而MVC中的V是指View,就是GUI界面,具体在这次的程序中就是UserGUI和ClickGUI,用于与用户进行交互。

在具体的代码中,director就直接使用单例模式。

public class SSDirector : System.Object {
		// singlton instance
		private static SSDirector _instance;

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

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

然后是ISceneControl和IUserAction两个接口。这里结合应用场景,用户的行为只有点击按钮移动船,点击character移动上船下船,以及点击按钮重置;而SceneControl要做的也就只有加载资源。

    public interface IUserAction {
		void moveBoat();
		void characterIsClicked(MyCharacterController characterCtrl);
		void restart();
	}

	public interface ISceneController {
		void loadResources ();
	}

接下来就是针对三种对象分别写三个controller,分别写上需要的操作函数,然后整合在firstController中。这里就不把代码全部贴上来了,我们直接看firstController。

public class FirstController : MonoBehaviour, ISceneController, IUserAction {

	readonly Vector3 water_pos = new Vector3(0,0.5F,0);


	UserGUI userGUI;

	public CoastController fromCoast;
	public CoastController toCoast;
	public BoatController boat;
	private MyCharacterController[] characters;

	void Awake() {
		SSDirector director = SSDirector.getInstance ();
		director.currentSceneController = this;
		userGUI = gameObject.AddComponent <UserGUI>() as UserGUI;
		characters = new MyCharacterController[6];
		loadResources ();
	}

	public void loadResources() {
		GameObject water = Instantiate (Resources.Load ("Perfabs/Water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
		water.name = "water";

		fromCoast = new CoastController ("from");
		toCoast = new CoastController ("to");
		boat = new BoatController ();

		// load characters
		for (int i = 0; i < 3; i++) {
			MyCharacterController newCharacter = new MyCharacterController ("priest");
			newCharacter.setName("priest" + i);
			newCharacter.setPosition (fromCoast.getEmptyPosition ());
			newCharacter.getOnCoast (fromCoast);
			fromCoast.getOnCoast (newCharacter);

			characters [i] = newCharacter;
		}

		for (int i = 0; i < 3; i++) {
			MyCharacterController newCharacter = new MyCharacterController ("devil");
			newCharacter.setName("devil" + i);
			newCharacter.setPosition (fromCoast.getEmptyPosition ());
			newCharacter.getOnCoast (fromCoast);
			fromCoast.getOnCoast (newCharacter);

			characters [i+3] = newCharacter;
		}
	}

	public void moveBoat() {
		if (boat.isEmpty ())
			return;
		boat.Move ();
		userGUI.status = check_game_over ();
	}

	public void characterIsClicked(MyCharacterController characterCtrl) {
		if (characterCtrl.isOnBoat ()) {
			CoastController whichCoast;
			if (boat.get_to_or_from () == -1) { // to->-1; from->1
				whichCoast = toCoast;
			} else {
				whichCoast = fromCoast;
			}

			boat.GetOffBoat (characterCtrl.getName());
			characterCtrl.moveToPosition (whichCoast.getEmptyPosition ());
			characterCtrl.getOnCoast (whichCoast);
			whichCoast.getOnCoast (characterCtrl);

		} else {									// character on coast
			CoastController whichCoast = characterCtrl.getCoastController ();

			if (boat.getEmptyIndex () == -1) {		// boat is full
				return;
			}

			if (whichCoast.get_to_or_from () != boat.get_to_or_from ())	// boat is not on the side of character
				return;

			whichCoast.getOffCoast(characterCtrl.getName());
			characterCtrl.moveToPosition (boat.getEmptyPosition());
			characterCtrl.getOnBoat (boat);
			boat.getOnBoat (characterCtrl);
		}
		userGUI.status = check_game_over ();
	}

	int check_game_over() {	// 0->not finish, 1->lose, 2->win
		int from_priest = 0;
		int from_devil = 0;
		int to_priest = 0;
		int to_devil = 0;

		int[] fromCount = fromCoast.getCharacterNum ();
		from_priest += fromCount[0];
		from_devil += fromCount[1];

		int[] toCount = toCoast.getCharacterNum ();
		to_priest += toCount[0];
		to_devil += toCount[1];

		if (to_priest + to_devil == 6)		// win
			return 2;

		int[] boatCount = boat.getCharacterNum ();
		if (boat.get_to_or_from () == -1) {	// boat at toCoast
			to_priest += boatCount[0];
			to_devil += boatCount[1];
		} else {	// boat at fromCoast
			from_priest += boatCount[0];
			from_devil += boatCount[1];
		}
		if (from_priest < from_devil && from_priest > 0) {		// lose
			return 1;
		}
		if (to_priest < to_devil && to_priest > 0) {
			return 1;
		}
		return 0;			// not finish
	}

	public void restart() {
		boat.reset ();
		fromCoast.reset ();
		toCoast.reset ();
		for (int i = 0; i < characters.Length; i++) {
			characters [i].reset ();
		}
	}
}

每次启动首先调用Awake函数,创建导演等实例,然后加载资源。其他的就是一些完成程序运行的功能函数如chek_game_over()来检查游戏是否结束以及最终的游戏结果。另外值得一提的是移动character的部分,为了降低耦合性,要分别调用三个对象对应的函数,包括载coast中移除或加入character、移动character本身,以及在船上移除或加入character。然后在每一次移动了character或者boat之后,都要做一次check_game_over的判定。

还有值得一提的是,为了降低代码的重复性,这里将船和人与移动有关的代码封装到一个movable类中,然后在两个controller中分别调用。

/*-----------------------------------Moveable------------------------------------------*/
	public class Moveable: MonoBehaviour {
		
		readonly float move_speed = 20;

		// change frequently
		int moving_status;	// 0->not moving, 1->moving to middle, 2->moving to dest
		Vector3 dest;
		Vector3 middle;

		void Update() {
			if (moving_status == 1) {
				transform.position = Vector3.MoveTowards (transform.position, middle, move_speed * Time.deltaTime);
				if (transform.position == middle) {
					moving_status = 2;
				}
			} else if (moving_status == 2) {
				transform.position = Vector3.MoveTowards (transform.position, dest, move_speed * Time.deltaTime);
				if (transform.position == dest) {
					moving_status = 0;
				}
			}
		}
		public void setDestination(Vector3 _dest) {
			dest = _dest;
			middle = _dest;
			if (_dest.y == transform.position.y) {	// boat moving
				moving_status = 2;
			}
			else if (_dest.y < transform.position.y) {	// character from coast to boat
				middle.y = transform.position.y;
			} else {								// character from boat to coast
				middle.x = transform.position.x;
			}
			moving_status = 1;
		}

		public void reset() {
			moving_status = 0;
		}
	}

最终游戏的效果如下图所示:

视频地址:https://www.bilibili.com/video/av68411117

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值