3D游戏-作业三-空间与运动

一、简答并用程序验证

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

游戏对象运动的本质就是它的空间属性的变化,包括空间位置,旋转角度,放缩大小等等

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

方法一 直接修改Transform属性

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

public class method1 : MonoBehaviour
{   
    private float vX = 5;
    private float vY = 0;
    private float g = 10;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        vY += g * Time.deltaTime;
        transform.position += Vector3.right * vX * Time.deltaTime;
        transform.position += Vector3.down * vY * Time.deltaTime;
    }
}

方法二 通过创建Vector3来修改Transform属性
此时只需要修改Update函数部分就可以了

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

方法三 transform的Translate方法
同样修改Update函数,如下

void Update()
    {
        vY += g * Time.deltaTime;
        Vector3 temp = new Vector3(vX * Time.deltaTime, - vY * Time.deltaTime, 0);
        transform.Translate(temp);
    }

方法四 通过修改物体的重力属性
这个方法是我搜索得知对象的重力属性,然后修改这个属性实现的

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

public class method4 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        gameObject.AddComponent<Rigidbody>();
        gameObject.GetComponent<Rigidbody>().velocity = new Vector3(2, 0, 0);
    }

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

3. 写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

首先创建太阳系
在这里插入图片描述
创建好各个行星,并调整它们的位置和大小,贴上相应的图片

编写代码,实现自转和公转
在这里,自转通过函数Rotate来实现,公转通过函数RotateAround来实现,代码如下

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

public class solarSystem : MonoBehaviour
{
    public Transform sun;
    public Transform mercury;
    public Transform venus;
    public Transform earth;
    public Transform mars;
    public Transform jupiter;
    public Transform saturn;
    public Transform uranus;
    public Transform neptune;

    // Start is called before the first frame update
    void Start(){ }

    // Update is called once per frame
    void Update()
    {
        //RotateAround 实现公转
        //Rotate实现自转
        sun.Rotate(Vector3.up * Time.deltaTime * 5);

        mercury.RotateAround(this.transform.position, new Vector3(3, 15, 0), 47 * Time.deltaTime);
		mercury.Rotate(Vector3.up * Time.deltaTime * 300);
		
        venus.RotateAround(this.transform.position, new Vector3(2,10, 0), 35 * Time.deltaTime);
		venus.Rotate(Vector3.up * Time.deltaTime * 280);
		
        earth.RotateAround(this.transform.position, new Vector3(1, 10, 0), 30 * Time.deltaTime);
		earth.Rotate(Vector3.up * Time.deltaTime * 250);
		
        mars.RotateAround(this.transform.position, new Vector3(2, 15, 0), 24 * Time.deltaTime);
		mars.Rotate(Vector3.up * Time.deltaTime * 220);
		
        jupiter.RotateAround(this.transform.position, new Vector3(2, 10, 0), 13 * Time.deltaTime);
		jupiter.Rotate(Vector3.up * Time.deltaTime * 180);
		
        saturn.RotateAround(this.transform.position, new Vector3(1, 10, 0), 9 * Time.deltaTime);
		saturn.Rotate(Vector3.up * Time.deltaTime * 160);
		
        uranus.RotateAround(this.transform.position, new Vector3(2, 10, 0), 6 * Time.deltaTime);
		uranus.Rotate(Vector3.up * Time.deltaTime * 150);
		
        neptune.RotateAround(this.transform.position, new Vector3(3, 15, 0), 5 * Time.deltaTime);
		neptune.Rotate(Vector3.up * Time.deltaTime * 140);
    }
}

此时还有一个问题,就是怎么把地月系统加到现在的这个太阳系里,因为上面这个系统中地球是在自转的,直接把月球绕它转的话,就必须要考虑到自转对月球公转的影响,所以在这里我们采用另一种八法来解决这个问题。
那就是创建一个新对象,他的位置大小都与地球相同,同样绕太阳公转,但是不自转,这样的话,就可以让月球来绕它自转,这样就解决了这个问题。

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

public class solarSystem : MonoBehaviour
{
    public Transform moon;
    public Transform earthclone;

    // Start is called before the first frame update
    void Start(){ }

    // Update is called once per frame
    void Update()
    {
        //实现地月系统
        earthclone.RotateAround(this.transform.position, new Vector3(1, 10, 0), 30 * Time.deltaTime);
	    moon.transform.RotateAround (earthclone.transform.position, new Vector3(0, 12, 0), 500 * Time.deltaTime);
    }
}

这样就完成了太阳系模拟。

显示轨迹
虽然已经完成了太阳系模拟的过程,但是在运行的时候可以发现,我们看着这样杂乱无章的效果很头疼,完全看不到行星运动轨迹,这压根的话就不能体现这个太阳系的行星轨道之间的区别等等,所以通过搜索,我找到了一个解决办法。
那就是通过Trail Render来显示运动轨迹。
这里是参考博客
在这里插入图片描述
以上就是最后实现的效果。

二、 编程实践-牧师与魔鬼

Priests and Devils
Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game, you can click on them to move them and click the go button to move the boat to the other direction. If the priests are out numbered by the devils on either side of the river, they get killed and the game is over. You can try it in many > ways. Keep all priests alive! Good luck!

程序需要满足的要求

play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )
列出游戏中提及的事物(Objects)
用表格列出玩家动作表(规则表),注意,动作越少越好
请将游戏中对象做成预制
在场景控制器 LoadResources 方法中加载并初始化 长方形、正方形、球 及其色彩代表游戏中的对象。
使用 C# 集合类型 有效组织对象
整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。 违背本条准则,不给分
请使用课件架构图编程,不接受非 MVC 结构程序
注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

游戏中提及的事物

牧师(3个)、魔鬼(3个)、船、河流、河岸(两侧)

玩家动作表(规则表)

玩家动作条件运行结果
点击岸上的牧师或魔鬼当前船在岸边,且船未满点击对象上船
点击船上的牧师或魔鬼当前船在岸边点击对象上岸
点击船当前船在岸边,且船上至少有一个牧师或魔鬼船移动到对岸

游戏对象预制

预制结果如下
在这里插入图片描述

根据MVC架构编程

MVC架构
在这里插入图片描述

实现过程

首先实现SSDirector
它的功能是控制场景切换,在这里它继承于System.Object,保持导演类一直存在,不被Unity内存管理而管理
实现代码如下:

public class SSDirector : System.Object
{
	private static SSDirector _instance;
	public ISceneController CurrentScenceController { get; set; }
	public static SSDirector GetInstance()
	{
		if (_instance == null)
		{
			_instance = new SSDirector();
		}
		return _instance;
	}
}

接下来就是创建接口
接口的实现,方便了其他模块调用此命名空间。分别是场景控制器的接口,利用这个接口,得知当前场景是由哪个控制,然后向场景控制器传达要求,以及用户动作的接口,用户通过键盘、鼠标等对游戏发出指令,这个指令会触发游戏中的一些行为,这一部分由IUserAction来声明。

namespace interfacecon{
	public interface ISceneController                      
	{
		void LoadResources();
	}
	public interface IUserAction                           
	{
		void MoveBoat();                                   
		void Restart();                                    
		void MoveRole(RoleModel role);                     
		int Check();                                       
	}
}

创建用户界面
在这里,包括左上角的游戏规则介绍按钮,以及在游戏结束之后的弹出按钮和标签等等

public class UserGUI : MonoBehaviour {

	private IUserAction action;
	public int sign = 0;

	bool isShow = false;
	void Start()
	{
		action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnGUI()
	{
		GUIStyle text_style;
		GUIStyle button_style;
		text_style = new GUIStyle()
		{
			fontSize = 30
		};
		button_style = new GUIStyle("button")
		{
			fontSize = 15
		};
		if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
		{
			if (isShow)
				isShow = false;
			else
				isShow = true;
		}
		if(isShow)
		{
			GUI.Label(new Rect(Screen.width / 2 - 85, 10, 200, 50), "让全部牧师和恶魔都渡河");
			GUI.Label(new Rect(Screen.width / 2 - 120, 30, 250, 50), "每一边恶魔数量都不能多于牧师数量");
			GUI.Label(new Rect(Screen.width / 2 - 85, 50, 250, 50), "点击牧师、恶魔、船移动");
		}
		if (sign == 1)
		{
			GUI.Label(new Rect(Screen.width / 2-90, Screen.height / 2-120, 100, 50), "Gameover!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
		else if (sign == 2)
		{
			GUI.Label(new Rect(Screen.width / 2 - 80, Screen.height / 2 - 120, 100, 50), "You Win!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
	}
}

实现控制器
它对场景中的具体对象进行操作,是游戏对象的行为改变的核心。

public class Controller : MonoBehaviour, ISceneController, IUserAction
{
	public LandModel start_land;            
	public LandModel end_land;              
	public BoatModel boat;                  
	private RoleModel[] roles;              
	UserGUI user_gui;
	void Start ()
	{
		SSDirector director = SSDirector.GetInstance();
		director.CurrentScenceController = this;
		user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
		LoadResources();
	}

	public void LoadResources()              
	{
		GameObject water = Instantiate(Resources.Load("Prefabs/Water", typeof(GameObject)), new Vector3(0,-10,-2), Quaternion.identity) as GameObject;
		water.name = "water";       
		start_land = new LandModel("start");
		end_land = new LandModel("end");
		boat = new BoatModel();
		roles = new RoleModel[6];

		for (int i = 0; i < 3; i++)
		{
			RoleModel role = new RoleModel("priest");
			role.SetName("priest" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}

		for (int i = 3; i < 6; i++)
		{
			RoleModel role = new RoleModel("devil");
			role.SetName("devil" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}
	}

	public void MoveBoat()                  
	{
		if (boat.IsEmpty() || user_gui.sign != 0) return;
		boat.BoatMove();
		user_gui.sign = Check();
	}

	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());
			role.Move(land.GetEmptyPosition());
			role.GoLand(land);
			land.AddRole(role);
		}
		else
		{                                
			LandModel land = role.GetLandModel();
			if (boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign()) return;   
			land.DeleteRoleByName(role.GetName());
			role.Move(boat.GetEmptyPosition());
			role.GoBoat(boat);
			boat.AddRole(role);
		}
		user_gui.sign = Check();
	}

	public void Restart()
	{
		SceneManager.LoadScene(0);
	}

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

接下来就是ClickController
它的功能是检测船和牧师与魔鬼是否被点击,然后进行操作。

public class Click : MonoBehaviour
{
	IUserAction action;
	RoleModel role = null;
	BoatModel boat = null;
	public void SetRole(RoleModel role)
	{
		this.role = role;
	}
	public void SetBoat(BoatModel boat)
	{
		this.boat = boat;
	}
	void Start()
	{
		action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnMouseDown()
	{
		if (boat == null && role == null) return;
		if (boat != null)
			action.MoveBoat();
		else if(role != null)
			action.MoveRole(role);
	}
}

最后是对具体游戏对象的操作
首先是对船的操作,包括船的运动,以及牧师与魔鬼上下船的绑定

public class BoatModel
	{
		GameObject boat;                                          
		Vector3[] start_empty_pos;                                    
		Vector3[] end_empty_pos;                                      
		Move move;                                                    
		Click click;
		int boat_sign = 1;                                                     
		RoleModel[] roles = new RoleModel[2];                                  

		public BoatModel()
		{
			boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(25, -2.5F, 0), Quaternion.identity) as GameObject;

			boat.name = "boat";
			move = boat.AddComponent(typeof(Move)) as Move;
			click = boat.AddComponent(typeof(Click)) as Click;
			click.SetBoat(this);
			start_empty_pos = new Vector3[] { new Vector3(18, 4, 0), new Vector3(32,4 , 0) };
			end_empty_pos = new Vector3[] { new Vector3(-32, 4, 0), new Vector3(-18,3 , 0) };
		}

		public bool IsEmpty()
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null)
					return false;
			}
			return true;
		}

		public void BoatMove()
		{
			if (boat_sign == -1)
			{
				move.MovePosition(new Vector3(25, -2.5F, 0));
				boat_sign = 1;
			}
			else
			{
				move.MovePosition(new Vector3(-25, -2.5F, 0));
				boat_sign = -1;
			}
		}

		public int GetBoatSign(){ return boat_sign;}

		public RoleModel DeleteRoleByName(string role_name)
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null && roles[i].GetName() == role_name)
				{
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int GetEmptyNumber()
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] == null)
				{
					return i;
				}
			}
			return -1;
		}

		public Vector3 GetEmptyPosition()
		{
			Vector3 pos;
			if (boat_sign == -1)
				pos = end_empty_pos[GetEmptyNumber()];
			else
				pos = start_empty_pos[GetEmptyNumber()];
			return pos;
		}

		public void AddRole(RoleModel role)
		{
			roles[GetEmptyNumber()] = role;
		}

		public GameObject GetBoat(){ return boat; }

		public int[] GetRoleNumber()
		{
			int[] count = { 0, 0 };
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] == null)
					continue;
				if (roles[i].GetSign() == 0)
					count[0]++;
				else
					count[1]++;
			}
			return count;
		}
	}

然后是对牧师与魔鬼(即role)的操作
包括上船、上岸

public class RoleModel
	{
		GameObject role;
		int role_sign;             
		Click click;
		bool on_boat;              
		Move move;
		LandModel land_model = (SSDirector.GetInstance().CurrentScenceController as Controller).start_land;
		public RoleModel(string role_name)
		{
			if (role_name == "priest")
			{
				role = Object.Instantiate(Resources.Load("Prefabs/Priests", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -70, 0)) as GameObject;
				role_sign = 0;
			}
			else
			{
				role = Object.Instantiate(Resources.Load("Prefabs/Devils", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -70, 0)) as GameObject;
				role_sign = 1;
			}
			move = role.AddComponent(typeof(Move)) as Move;
			click = role.AddComponent(typeof(Click)) as Click;
			click.SetRole(this);
		}
		public int GetSign() { return role_sign;}
		public LandModel GetLandModel(){return land_model;}
		public string GetName() { return role.name; }
		public bool IsOnBoat() { return on_boat; }
		public void SetName(string name) { role.name = name; }
		public void SetPosition(Vector3 pos) { role.transform.position = pos; }
		public void Move(Vector3 vec)
		{
			move.MovePosition(vec);
		}
		public void GoLand(LandModel land)
		{  
			role.transform.parent = null;
			land_model = land;
			on_boat = false;
		}
		public void GoBoat(BoatModel boat)
		{
			role.transform.parent = boat.GetBoat().transform;
			land_model = null;          
			on_boat = true;
		}
	}

对陆地的操作
用于控制角色上下岸,船的离开和停靠

public class LandModel
	{
		GameObject land;                                
		Vector3[] positions;                            
		int land_sign;                                  
		RoleModel[] roles = new RoleModel[6];           
		public LandModel(string land_mark)
		{
			positions = new Vector3[] {new Vector3(46F,14.73F,-4), new Vector3(55,14.73F,-4), new Vector3(64F,14.73F,-4),
				new Vector3(73F,14.73F,-4), new Vector3(82F,14.73F,-4), new Vector3(91F,14.73F,-4)};
			if (land_mark == "start")
			{
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = 1;
			}
			else if(land_mark == "end")
			{
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(-70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = -1;
			}
		}

		public int GetEmptyNumber()                      
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] == null)
					return i;
			}
			return -1;
		}

		public int GetLandSign() { return land_sign; }

		public Vector3 GetEmptyPosition()               
		{
			Vector3 pos = positions[GetEmptyNumber()];
			pos.x = land_sign * pos.x;                  
			return pos;
		}

		public void AddRole(RoleModel role)             
		{
			roles[GetEmptyNumber()] = role;
		}

		public RoleModel DeleteRoleByName(string role_name)      
		{ 
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null && roles[i].GetName() == role_name)
				{
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int[] GetRoleNum()
		{
			int[] count = { 0, 0 };                    
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null)
				{
					if (roles[i].GetSign() == 0)
						count[0]++;
					else
						count[1]++;
				}
			}
			return count;
		}	
	}

最后,只需要将Controller挂载到新建的空对象上,就可以运行游戏了,示例一局的结果如下:
在这里插入图片描述

在这里插入图片描述

思考题

使用向量与变换,实现并扩展 Tranform 提供的方法,如 Rotate、RotateAround 等

法一 直接通过公式计算
根据旋转后点的位置的变换公式,可以直接计算得到旋转后的位置,然后修改物体的空间属性就可以实现旋转

void Update () {
		float delta = Mathf.Atan (this.transform.position.y / this.transform.position.x);
		float temp_x = this.transform.position.x;
		float temp_y = this.transform.position.y;
		this.transform.position.x = Mathf.Cos (delta) * temp_x - Mathf.Sin (delta) * temp_y;
		this.transform.position.y = Mathf.Sin (delta) * temp_x + Mathf.Cos (delta) * temp_y;
}

法二 通过vector3来实现

int angle;
void Update () {
		angle -= Mathf.Atan (this.transform.position.y / this.transform.position.x);
		transform.Translate(new Vector3(Time.deltaTime * 10*Mathf.Cos(angle), Time.deltaTime * 10*Mathf.Sin(angle), 0));  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值