3D游戏作业3
1.使用Unity实现完整的太阳系
要求:行星围绕太阳的转速必须不同,且不在一个法平面上。
过程:首先寻找太阳系各星球的贴图,并将其加载到项目的assets中。[行星贴图网址](Solar Textures | Solar System Scope)
- 创建几个Sphere并命名为太阳系中各个星球的名字,然后将各个贴图拖到对应到星球上,这样就形做成了各个星球的预制。
- 将各行星以太阳为中心拖到其在太阳系中对应的位置,并设置大小。
- 给太阳添加光源效果,即在太阳所在的位置添加一个Point Light起到光源效果,并将光的颜色调整为橙黄色。
- 此时太阳看起来有点暗,所以需要让太阳的表面有发光效果。
将太阳的贴图组件勾选Emission并将颜色设置为橙红。
- 使用代码使得行星围绕太阳,月亮围绕地球运转。
需要注意如果围绕一个自转的星球公转会受到该星球的自转影响,所以需要克隆一个太阳和地球并令这两个克隆星球不自转,并把其他星球公转的对象设置为这两个不自转的星球。
- 为了能够显示行星的运行轨迹,需要给每个行星都添加一个组件Trial Render,具体位置是Component->Effects->Trial Render。
添加后的运行效果图如下
可以看到行星在不同法平面运转。
代码如下。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour {
public Transform mercury;
public Transform venus;
public Transform earth;
public Transform mars;
public Transform jupiter;
public Transform saturn;
public Transform uranus;
public Transform neptune;
public Transform moon;
public Transform earthclone;
public Transform sun;
// Use this for initialization
void Start () {
mercury = this.transform.Find("Mercury");
venus = this.transform.Find("Venus");
earth = this.transform.Find("Earth");
mars = this.transform.Find("Mars");
jupiter = this.transform.Find("Jupiter");
saturn = this.transform.Find("Saturn");
uranus = this.transform.Find("Uranus");
neptune = this.transform.Find("Neptune");
earthclone = this.transform.Find("EarthClone");
moon = earthclone.Find("Moon");
sun = this.transform.Find("Sun");
}
// Update is called once per frame
void Update () {
sun.Rotate(Vector3.up * Time.deltaTime * 30);
mercury.RotateAround(this.transform.position, new Vector3(1, 5, 0), 24 * Time.deltaTime);
mercury.Rotate(Vector3.up * Time.deltaTime * 300);
venus.RotateAround(this.transform.position, new Vector3(2, 12, 0), 33 * Time.deltaTime);
venus.Rotate(Vector3.up * Time.deltaTime * 280);
earth.RotateAround(this.transform.position, new Vector3(1, 10, 0), 19 * Time.deltaTime);
earth.Rotate(Vector3.up * Time.deltaTime * 250);
mars.RotateAround(this.transform.position, new Vector3(2, 11, 0), 24 * Time.deltaTime);
mars.Rotate(Vector3.up * Time.deltaTime * 220);
jupiter.RotateAround(this.transform.position, new Vector3(1, 10, 0), 13 * Time.deltaTime);
jupiter.Rotate(Vector3.up * Time.deltaTime * 180);
saturn.RotateAround(this.transform.position, new Vector3(1, 11, 0), 10 * Time.deltaTime);
saturn.Rotate(Vector3.up * Time.deltaTime * 160);
uranus.RotateAround(this.transform.position, new Vector3(2, 7, 0), 18 * Time.deltaTime);
uranus.Rotate(Vector3.up * Time.deltaTime * 150);
neptune.RotateAround(this.transform.position, new Vector3(3, 10, 0), 22 * Time.deltaTime);
neptune.Rotate(Vector3.up * Time.deltaTime * 140);
earthclone.RotateAround(this.transform.position, new Vector3(1, 10, 0), 30 * Time.deltaTime);
moon.transform.RotateAround (earthclone.transform.position, new Vector3(1, 12, 0), 50 * Time.deltaTime);
}
}
牧师与魔鬼小游戏
游戏说明
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 !
- 游戏中提及的事物
牧师,魔鬼,船,河,两边河岸。
- 用表格列出玩家的动作表
动作 | 效果 | 条件 |
---|---|---|
玩家点击岸上角色 | 角色上船 | 1.船上有位置 2.角色在同一个岸边 3.没有其他角色在移动 |
玩家点击船上角色 | 角色下船 | 1.船已经到岸边 2.没有其他角色在移动 |
玩家点击船 | 船移动到对岸 | 1.船上有任意角色 2.没有其他角色在移动 |
玩家点击重新开始 | 所有角色重置 |
- 将各个角色对象做成预制。
使用有牧师贴图的圆球表示牧师,使用有恶魔贴图的方块表示恶魔。
- 在场景控制器LoadResources方法中加载游戏中的对象
场景控制器
对应的LoadResources代码如下
public void LoadResources(){
//由于有重新开始按钮,所以如果不是第一次加载得把之前的物体摧毁
if(flag){
for(int i = 0; i < 6; i++){
if(roleCtrl_list[i] != null){
Destroy(roleCtrl_list[i].GetModelGameObject());
}
}
for(int i = 0; i < 2; i++){
if(landCtrl_list[i] != null){
Destroy(landCtrl_list[i].GetModelGameObject());
}
}
if(BoatCtrl != null){
Destroy(BoatCtrl.GetModelGameObject());
}
}
// 加载控制器和模型
BoatCtrl = new BoatController();
BoatCtrl.CreateModel();
water1=new Water(new Vector3(0,-3.5f,12));
for(int i = 0; i < 6; i++){
int roleType = (i < 3) ? PRIEST : DEVIL;
RoleController tmp=new RoleController(roleType, rolesID[i]);
roleCtrl_list.Add(tmp);
roleCtrl_list[i].CreateModel();
}
LandController tmp1 = new LandController(LEFTLAND, rolesID);
landCtrl_list.Add(tmp1);
LandController tmp2 = new LandController(RIGHTLAND, rolesID);
landCtrl_list.Add(tmp2);
landCtrl_list[0].CreateModel();
landCtrl_list[1].CreateModel();
MoveCtrl = new MoveController();
flag=true;
flag1=false;
//开始游戏
gameState = PLAYING;
}
//将角色的ID转换成数组的下标
int IDToNumber(int ID){
for(int i = 0; i < 6; i++){
if(rolesID[i] == ID){
return i;
}
}
return -1;
}
//点击船时执行
public void MoveBoat(){
if(BoatCtrl.getEmptySeat()==0)
{
flag1=true;
}
else{
if(gameState != PLAYING || MoveCtrl.IsMoving()) return;
CheckAndSetGameState();
if(BoatCtrl.onLeftside){
MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatRightPos);
for(int i = 0; i < 2; i++){
if(BoatCtrl.seat[i] != -1){
RoleController r = roleCtrl_list[IDToNumber(BoatCtrl.seat[i])];
MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatRightPos[i]);
}
}
}
else{
MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatLeftPos);
for(int i = 0; i < 2; i++){
if(BoatCtrl.seat[i] != -1){
RoleController r = roleCtrl_list[IDToNumber(BoatCtrl.seat[i])];
MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatLeftPos[i]);
}
}
}
BoatCtrl.onLeftside = !BoatCtrl.onLeftside;
}
}
-
使用List集合类型组织对象
比如下面用list组织角色控制类和岸边控制类。
-
由代码生成对象
游戏开始前
游戏开始后
-
介绍本游戏所使用的MVC框架
7.1 Models模型
7.1.1 Click脚本
Click类主要是为其他需要被点击的物体类提供一个功能组件。
public class Click : MonoBehaviour { IObjectController clickAction; public void setClickAction(IObjectController clickAction) { this.clickAction = clickAction; } void OnMouseDown() { clickAction.DealClick(); } }
7.1.2 Postion类
Position类的成员变量用于存储其他物体的位置,要修改其他物体的位置只需修改Positon中的成员变量。
7.1.3 其他类似land和role之类的对象类。
这些类主要是加载预制中的物体,如果该物体需要被点击,就添加上述的Click组件,点击后发生的事件会在后续的control中详细定义。这里以Boat类为例。
public class Boat { public GameObject boat;//船对象 public Boat(Vector3 initPos){ boat = GameObject.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject))) as GameObject; boat.transform.position = initPos; boat.AddComponent<Click>(); } }
7.2 Controller控制器
7.2.1 Director对象
这里采用单实例模式,Director类主要用于获取当前游戏场景,管理游戏全局状态等。第一次调用时会新建一个Director对象,而后面调用的时候不会创建新的对象,而是返回第一次调用时创建的对象,所以全局都 只会有一个Director,方便全局管理。
public class SSDirector : System.Object
{
static SSDirector _instance;
public ISceneController CurrentSceneController {get; set;}
public static SSDirector GetInstance() {
if (_instance == null) {
_instance = new SSDirector();
}
return _instance;
}
}
7.2.2 IUserAction
IUserAction是用户对象接口,用于接收玩家的动作,分离了玩家的操作和游戏内部的逻辑,方便管理。
public interface IUserAction {
void MoveBoat();
void MoveRole(int id);
int GetGameState();
void Restart();
}
7.2.3 ISceneController
ISceneController是场景控制接口,这个接口分离了导演和场景的控制,导演不需要知道如何实现场景控制,只需要让场景控制类去执行某项任务。
public interface ISceneController
{
void Initialize();
void LoadResources();
}
7.2.4 各种类似BoatController,LandController之类的物体控制类
这些物体控制类需要定义各个物体具有的属性以及使用函数实现各个物体需要完成的动作,下面以BoatController为例,其他物体控制类在代码中可以查阅。
BoatController类需要有的属性:
1.bool类型的变量onLeftside, 用于判断船是否位于左岸。
2.长度为2的List类型的数组seat,用于记录船上的两个位置的角色的编号。
3.boat类对象boatmodel,加载了预制的物体。
BoatController类需要完成的动作:
1.Reset() 重置船上的座位。
2.embark(int roleID) 将编号为roleID的角色上船。
3.getEmptySeat() 获取空座位的下标,如果没有则返回-1。
4.disembark(int roleID) 将编号为roleID的角色下船。
5.DealClick() 处理点击事件。
6.GetModelGameObject() 返回boat对象。
7.CreateModel() 创建模型。
7.2.5 FirstController
FirstController是最核心的控制器,主要任务是管理所有游戏对象,响应外部事件,设计游戏内部逻辑等,同时定义了各种事件。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
public static int LEFTLAND = 0;
public static int RIGHTLAND = 1;
public static int BOAT = 2;
public static bool flag = false;
public static bool flag1=false;
public static int PRIEST = 0;
public static int DEVIL = 1;
public static int PLAYING = 0;
public static int WIN = 1;
public static int FAILED = 2;
Water water1;
BoatController BoatCtrl;
public List<RoleController> roleCtrl_list=new List<RoleController>();
public List<LandController> landCtrl_list=new List<LandController>();
MoveController MoveCtrl;
int[] rolesID = new int[6]{0,1,2,3,4,5};
int gameState;
void Awake(){
SSDirector director = SSDirector.GetInstance();
director.CurrentSceneController = this;
director.CurrentSceneController.LoadResources();
}
public void Initialize()
{
LoadResources();
}
public void LoadResources(){
//由于有重新开始按钮,所以如果不是第一次加载得把之前的物体摧毁
if(flag){
for(int i = 0; i < 6; i++){
if(roleCtrl_list[i] != null){
Destroy(roleCtrl_list[i].GetModelGameObject());
}
}
for(int i = 0; i < 2; i++){
if(landCtrl_list[i] != null){
Destroy(landCtrl_list[i].GetModelGameObject());
}
}
if(BoatCtrl != null){
Destroy(BoatCtrl.GetModelGameObject());
}
}
// 加载控制器和模型
BoatCtrl = new BoatController();
BoatCtrl.CreateModel();
water1=new Water(new Vector3(0,-3.5f,12));
for(int i = 0; i < 6; i++){
int roleType = (i < 3) ? PRIEST : DEVIL;
RoleController tmp=new RoleController(roleType, rolesID[i]);
roleCtrl_list.Add(tmp);
roleCtrl_list[i].CreateModel();
}
LandController tmp1 = new LandController(LEFTLAND, rolesID);
landCtrl_list.Add(tmp1);
LandController tmp2 = new LandController(RIGHTLAND, rolesID);
landCtrl_list.Add(tmp2);
landCtrl_list[0].CreateModel();
landCtrl_list[1].CreateModel();
MoveCtrl = new MoveController();
flag=true;
flag1=false;
//开始游戏
gameState = PLAYING;
}
//将角色的ID转换成数组的下标
int IDToNumber(int ID){
for(int i = 0; i < 6; i++){
if(rolesID[i] == ID){
return i;
}
}
return -1;
}
//点击船时执行
public void MoveBoat(){
if(BoatCtrl.getEmptySeat()==0)
{
flag1=true;
}
else{
if(gameState != PLAYING || MoveCtrl.IsMoving()) return;
CheckAndSetGameState();
if(BoatCtrl.onLeftside){
MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatRightPos);
for(int i = 0; i < 2; i++){
if(BoatCtrl.seat[i] != -1){
RoleController r = roleCtrl_list[IDToNumber(BoatCtrl.seat[i])];
MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatRightPos[i]);
}
}
}
else{
MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatLeftPos);
for(int i = 0; i < 2; i++){
if(BoatCtrl.seat[i] != -1){
RoleController r = roleCtrl_list[IDToNumber(BoatCtrl.seat[i])];
MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatLeftPos[i]);
}
}
}
BoatCtrl.onLeftside = !BoatCtrl.onLeftside;
}
}
//角色被点击
public void MoveRole(int id){
flag1=false;
int num = IDToNumber(id);
if(gameState != PLAYING || MoveCtrl.IsMoving()) return;
int seat;
switch(roleCtrl_list[num].roleState){
//角色在左岸的情况
case 0:
if(!BoatCtrl.onLeftside) return;
landCtrl_list[0].LeaveLand(id);
seat = BoatCtrl.embark(id);
roleCtrl_list[num].GoTo(BOAT);
if(seat == -1) return;
MoveCtrl.SetMove(roleCtrl_list[num].GetModelGameObject(), Position.seatLeftPos[seat]);
break;
//角色在右岸的情况
case 1:
if(BoatCtrl.onLeftside) return;
landCtrl_list[1].LeaveLand(id);
seat = BoatCtrl.embark(id);
roleCtrl_list[num].GoTo(BOAT);
if(seat == -1) return;
MoveCtrl.SetMove(roleCtrl_list[num].GetModelGameObject(), Position.seatRightPos[seat]);
break;
//角色在船上的情况
case 2:
if(BoatCtrl.onLeftside){
seat = landCtrl_list[0].getEmptySeat();
BoatCtrl.disembark(id);
landCtrl_list[0].GoOnLand(id);
roleCtrl_list[num].GoTo(LEFTLAND);
MoveCtrl.SetMove(roleCtrl_list[num].GetModelGameObject(), Position.roleLeftPos[seat]);
}
else{
seat = landCtrl_list[1].getEmptySeat();
BoatCtrl.disembark(id);
landCtrl_list[1].GoOnLand(id);
roleCtrl_list[num].GoTo(RIGHTLAND);
MoveCtrl.SetMove(roleCtrl_list[num].GetModelGameObject(), Position.roleRightPos[seat]);
}
break;
default: break;
}
}
//判断游戏状态
public void CheckAndSetGameState(){
if(gameState != PLAYING) return;
//失败的情况
int[,] rolePos = new int[2, 3]{
{0, 0, 0}, {0, 0, 0}};
foreach(RoleController r in roleCtrl_list){
rolePos[r.roleType, r.roleState]++;
}
if((rolePos[0,0]>0 && rolePos[0,0]<rolePos[1,0]) || (rolePos[0,1]>0 && rolePos[0,1]<rolePos[1,1]) ||(rolePos[0,2]>0 && rolePos[0,2] < rolePos[1,2])){
gameState = FAILED;
return;
}
//成功的情况
foreach(RoleController r in roleCtrl_list){
if(r.roleType == 0 && r.roleState != RIGHTLAND){
return;
}
}
gameState = WIN;
return;
}
//重置
public void Restart(){
LoadResources();
gameState = PLAYING;
}
//获取游戏当前状态
public int GetGameState(){
return gameState;
}
}
7.3 View视图
UserGUI是用户程序界面,用于呈现给用户游戏画面以及各种按钮。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
IUserAction userAction;
GUIStyle msgStyle, titleStyle;
void Start()
{
userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
msgStyle = new GUIStyle();
msgStyle.normal.textColor = Color.red;
msgStyle.alignment = TextAnchor.MiddleCenter;
msgStyle.fontSize = 30;
titleStyle = new GUIStyle();
titleStyle.normal.textColor = Color.black;
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.fontSize = 40;
}
// Update is called once per frame
void OnGUI() {
// 重新开始的按钮
if(GUI.Button(new Rect(Screen.width*0.4f, Screen.height*0.85f, Screen.width*0.2f, Screen.height*0.1f), "重新开始")){
userAction.Restart();
}
// 检查是否正确
GUI.Label(new Rect(0, 0, Screen.width, Screen.height*0.2f), "牧师与恶魔", titleStyle);
if(userAction.GetGameState() == FirstController.WIN){
GUI.Label(new Rect(0, Screen.height*0.2f, Screen.width, Screen.height*0.2f), "成功", msgStyle);
}
else if(userAction.GetGameState() == FirstController.FAILED){
GUI.Label(new Rect(0, Screen.height*0.2f, Screen.width, Screen.height*0.2f), "失败", msgStyle);
}
if(FirstController.flag1==true)
{
GUI.Label(new Rect(0, Screen.height*0.2f, Screen.width, Screen.height*0.2f), "船为空,不能行动", msgStyle);
}
}
}
游戏画面
代码地址 我的仓库