Unity学习记录——空间与运动

Unity学习记录——空间与运动

前言


​ 本文是中山大学软件工程学院2020级3d游戏编程与设计的作业3

编程题


1. 模拟太阳系

​ 首先向unity中Assets添加所找的贴图素材Solar Textures | Solar System Scope

在这里插入图片描述

​ 再在Scene中建立十个Sphere以表示八个行星和月球太阳

Unity学习记录——空间与运动_第1张图片

​ 建立完成十个SPhere之后,将对应的星球贴图拖动到星球之上。此时Assets之中会生成Materials文件夹,里面为十个星球的材质。

Unity学习记录——空间与运动_第2张图片

​ 完成简单的贴图以及位置排序之后各个行星排列如下所示

Unity学习记录——空间与运动_第3张图片

​ 对模拟太阳系进行进一步优化,如下

​ 删除原先默认建立的光源,在太阳上建立一个点光源,如下

Unity学习记录——空间与运动_第4张图片

​ 再在太阳的材质处进行修改,添加Emission,使之发光

Unity学习记录——空间与运动_第5张图片

​ 此时在Assets区建立C#代码,命名为MoveBehavior,用于进行八大行星和月球的自转以及公转。

​ 代码如下:

public class MoveBehaviourScript : MonoBehaviour
{
    // 公转中心
    public Transform center;
    // 公转速度
    public float around_speed;
    // 自转速度
    public float self_speed;
    // 旋转所绕的轴
    public float x = 0, y, z;
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        Vector3 axis = new Vector3(x, y, z);
        // 自转
        this.transform.Rotate(Vector3.up * self_speed * Time.deltaTime);
        // 公转
        this.transform.RotateAround(center.position, axis, around_speed * Time.deltaTime);
    }
}

​ 将各个代码拖入进行自转与公转的行星,设置旋转中心以及速度等数值

​ 给各个行星对象设置拖尾渲染器,使得行星运行效果更为明显

Unity学习记录——空间与运动_第6张图片

​ 再将摄像机的背景色设置为黑色

Unity学习记录——空间与运动_第7张图片

运行效果如下:

gif太大无法上传至csdn,可于gitee查看,链接如下:
hw3/img/P9.gif · XiaoChen04_3/3D_Computer_Game_Programming

2.牧师与魔鬼

  • 游戏规则如下:

​ 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 ovet You can ry it in many→ ways. Keep all priests alive! Good luck!

  • 游戏中提及的事物(Object)包括:牧师,魔鬼,船,河,陆地

  • 玩家动作表如下

动作 条件 结果
点击角色(牧师或魔鬼) 角色与船同岸;角色在船上 角色上岸
点击角色(牧师或魔鬼) 角色与船同岸;角色在岸上 角色上船
点击船 船上存在一或两个角色 船开到对岸
  • MCV架构以及其介绍

    • M是Model:代表游戏中所有的游戏对象,它们受各自的Controller控制。Model是游戏中存在的实体。
    • V是View:代表游戏呈现出的界面。主要包括GUI和Click两部分,GUI负责展示游戏结果,Click负责处理用户点击事件。
    • C是Controller:代表游戏中的控制器,分为FitstController(场景控制器)和SSDirector(导演)。由于本游戏只有一个场景,所以导演只用负责退出和暂停;场景控制器需要控制场景中的所有物体。
  • 各部分代码及其介绍

1. Model

​ 此部分代码用于模型的初始化加载,项目中的Model包括Role(牧师,魔鬼),Boat(船),Water(河),Land(陆地)。此处代码都整合至Model.cs之中。

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

namespace ModelApplication
{
    public class Water
    {
        GameObject obj_water;
        public Water()
        {
            obj_water = Object.Instantiate(Resources.Load("Prefabs/Water"),
                Vector3.zero, Quaternion.identity) as GameObject;
            obj_water.name = "water";
        }
    }
    public class Land
    {
        public GameObject obj_land;
        public int landSign;
        public Vector3[] positions;
        public Land(string str)
        {
            positions = new Vector3[] {
                new Vector3(6.35F,2F,0), new Vector3(7.35f,2F,0), new Vector3(8.35f,2F,0),
                new Vector3(9.35f,2F,0), new Vector3(10.35f,2F,0), new Vector3(11.35f,2F,0)
            };
            if (str == "start") landSign = 1;
            else landSign = -1;
            obj_land = Object.Instantiate(Resources.Load("Prefabs/Land"),
                    new Vector3(9 * landSign, 1, 0), Quaternion.identity) as GameObject;
            obj_land.name = str + "Land";
        }
    }
    public class Boat
    {
        public GameObject obj_boat;
        public int boatSign;
        public Vector3[] startPos, endPos;

        public Boat()
        {
            obj_boat = Object.Instantiate(Resources.Load("Prefabs/Boat"),
                new Vector3(5, 0.5f, 0), Quaternion.identity) as GameObject;
            boatSign = 1;
            startPos = new Vector3[] { new Vector3(5.5f, 1, 0), new Vector3(4.5f, 1, 0) };
            endPos = new Vector3[] { new Vector3(-4.5f, 1, 0), new Vector3(-5.5f, 1, 0) };
            obj_boat.name = "boat";
        }
    }
    public class Role
    {
        public GameObject obj_role;
        public int roleSign;
        public int onBoat;
        public int landSign;
        public Role(string name, Vector3 pos)
        {
            if (name == "priest")
            {
                obj_role = Object.Instantiate(Resources.Load("Prefabs/Priest"),
                    pos, Quaternion.Euler(0, -90, 0)) as GameObject;
                roleSign = 0;
            }
            else
            {
                obj_role = Object.Instantiate(Resources.Load("Prefabs/Devil"),
                    pos, Quaternion.Euler(0, -90, 0)) as GameObject;
                roleSign = 1;
            }
            landSign = 1;
        }
    }
}
2. Controller

SSDirector

​ SSDirector:使用单例模式创建,并且继承于System.Object保持一直存在,不受Unity内存管理。导演类负责控制当前场景的运行,切换,游戏的暂停恢复和退出,设定配置等等

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

ModelController

ModelController:Model的进一步操作的函数,包括:

​ LandModel:控制陆地模型。可以通过LandModel添加或删除本块Land上的Role(即Role上下陆地),以及判断Land上面的Role数量,此处使用一个二元int数组才记录本块陆地上的Role数量,即[牧师数量,魔鬼数量],以此判断游戏的进行程度

public class LandModel
{
    Land land;
    List roles = new List(){null,null,null,null,null,null};
    public LandModel(string str)
    {
        land = new Land(str);
    }
    public int getLandSign()
    {
        return land.landSign;
    }
    public int getEmptyNumber()
    {
        for (int i = 0; i < 6; i++)
        {
            if (roles[i] == null)
                return i;
        }
        return -1;
    }
    public Vector3 getEmptyPos()
    {
        Vector3 res = land.positions[getEmptyNumber()];
        res.x = res.x * land.landSign;
        return res;
    }
    public int[] getRoleNum()
    {
        int[] res = { 0, 0 };
        for (int i = 0; i < 6; i++)
        {
            if (roles[i] != null)
            {
                if (roles[i].getSign() == 0) res[0]++;
                else res[1]++;
            }
        }
        return res;
    }
    public void addRole(RoleModel role)
    {
        roles[getEmptyNumber()] = role;
    }
    public RoleModel deleteRole(string name)
    {
        for (int i = 0; i < 6; i++)
        {
            if (roles[i] != null && roles[i].getName() == name)
            {
                RoleModel res = roles[i];
                roles[i] = null;
                return res;
            }
        }
        return null;
    }
}

​ BoatModel:控制船模型。BoatModel中含括了船移动的函数,由于一条船只能承载两个Role,所以使用getEmptyNumber()函数判断船是否满员。同时使用与上面LandModel中类似的函数进行上下船操作。同时使用了两个固定的位置作为船移动的起点终点进行船的移动。

public class BoatModel
{
    Boat boat;
    List roles = new List(){null,null,null,null,null,null};
    Move move;
    Click click;
    public BoatModel()
    {
        boat = new Boat();
        move = boat.obj_boat.AddComponent(typeof(Move)) as Move;
        click = boat.obj_boat.AddComponent(typeof(Click)) as Click;
        click.setBoat(this);
    }
    public int getBoatSign()
    {
        return boat.boatSign;
    }
    public int getEmptyNumber()
    {
        for (int i = 0; i < 2; i++)
        {
            if (roles[i] == null)
                return i;
        }
        return -1;
    }
    public Vector3 getEmptyPos()
    {
        Vector3 res;
        if (boat.boatSign == 1) res = boat.startPos[getEmptyNumber()];
        else res = boat.endPos[getEmptyNumber()];
        return res;
    }
    public int[] getRoleNum()
    {
        int[] res = { 0, 0 };
        for (int i = 0; i < 2; i++)
        {
            if (roles[i] != null)
            {
                if (roles[i].getSign() == 0) res[0]++;
                else res[1]++;
            }
        }
        return res;
    }
    public void addRole(RoleModel role)
    {
        roles[getEmptyNumber()] = role;
    }
    public RoleModel deleteRole(string name)
    {
        for (int i = 0; i < 2; i++)
        {
            if (roles[i] != null && roles[i].getName() == name)
            {
                RoleModel res = roles[i];
                roles[i] = null;
                return res;
            }
        }
        return null;
    }
    public void boatMove()
    {
        if (boat.boatSign == -1) move.MovePosition(new Vector3(5, 0.5f, 0));
        else move.MovePosition(new Vector3(-5, 0.5f, 0));
        boat.boatSign *= -1;
    }
    public GameObject getBoat()
    {
        return boat.obj_boat;
    }

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

​ RoleModel:Role模型的移动以及判断大部分代码都在上面的LandModel和BoatModel中实现,所以此处RoleModel中大部分函数都是对上面进行的移动以及判断提供必要的返回值和对一部分数据进行重设

public class RoleModel
{
    Role role;
    Move move;
    Click click;
    public RoleModel(string name, Vector3 pos)
    {
        role = new Role(name, pos);
        move = role.obj_role.AddComponent(typeof(Move)) as Move;
        click = role.obj_role.AddComponent(typeof(Click)) as Click;
        click.setRole(this);
    }
    public string getName()
    {
        return role.obj_role.name;
    }
    public int getLandSign()
    {
        return role.landSign;
    }
    public void setLandSign(int land)
    {
        role.landSign = land;
    }
    public int getSign()
    {
        return role.roleSign;
    }
    public int getOnBoat()
    {
        return role.onBoat;
    }
    public void setOnBoat(int a)
    {
        role.onBoat = a;
    }
    public void setName(string name)
    {
        role.obj_role.name = name;
    }
    public GameObject getRole()
    {
        return role.obj_role;
    }
    public void roleMove(Vector3 pos)
    {
        move.MovePosition(pos);
    }
}

FirstController

​ FirstController负责游戏中第一个场景的搭载,即Start(),在其中调用LoadResoureces()动态生成本次项目的场景。在FirstController之中还编写了MoveRole()MoveBoat()实现游戏项目之中仅有的两种操作。同时也在FirstController中实现了游戏成败的判断,以及重新开始按钮的实现

using UnityEngine.SceneManagement;
using UnityEngine;
using ModelApplication;
using ControllerApplication;
using InterfaceApplication;
using System.Collections.Generic;

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    public LandModel startLand;
    public LandModel endLand;
    public Water water;
    public BoatModel boat;
    public List roles;
    public UserGUI GUI;
	// 资源加载
    public void LoadResoureces()
    {
        water = new Water();
        startLand = new LandModel("start");
        endLand = new LandModel("end");
        boat = new BoatModel();
        roles = new List();
        for (int i = 0; i < 3; i++)
        {
            RoleModel role = new RoleModel("priest", startLand.getEmptyPos());
            role.setName("priest" + i);
            startLand.addRole(role);
            roles.Add(role);
        }
        for (int i = 0; i < 3; i++)
        {
            RoleModel role = new RoleModel("devil", startLand.getEmptyPos());
            role.setName("devil" + i);
            startLand.addRole(role);
            roles.Add(role);
        }
    }
    // 移动船
    public void moveBoat()
    {
        if (boat.empty() || GUI.sign != 0) return;
        boat.boatMove();
        GUI.sign = check();
    }
    // 移动角色
    public void moveRole(RoleModel role)
    {
        if (GUI.sign != 0) return;
        if (role.getOnBoat() == 1)
        {
            boat.deleteRole(role.getName());
            role.setOnBoat(0);
            role.getRole().transform.parent = null;
            if (boat.getBoatSign() == 1)
            {
                role.roleMove(startLand.getEmptyPos());
                role.setLandSign(1);
                startLand.addRole(role);
            }
            else
            {
                role.roleMove(endLand.getEmptyPos());
                role.setLandSign(-1);
                endLand.addRole(role);
            }
        }
        else
        {
            if (role.getLandSign() == 1)
            {
                if (boat.getEmptyNumber() == -1 || role.getLandSign() != boat.getBoatSign()) return;
                startLand.deleteRole(role.getName());
                role.roleMove(boat.getEmptyPos());
                role.getRole().transform.parent = boat.getBoat().transform;
                role.setOnBoat(1);
                boat.addRole(role);
            }
            else
            {
                if (boat.getEmptyNumber() == -1 || role.getLandSign() != boat.getBoatSign()) return;
                endLand.deleteRole(role.getName());
                role.roleMove(boat.getEmptyPos());
                role.getRole().transform.parent = boat.getBoat().transform;
                role.setOnBoat(1);
                boat.addRole(role);
            }
        }
        GUI.sign = check();
    }
    // 重新开始
    public void reStart()
    {
        SceneManager.LoadScene(0);
    }
    // 游戏进度判断
    public int check()
    {
        int[] boatRole = boat.getRoleNum();
        Debug.Log(string.Format("{0},{1}",boatRole[0],boatRole[1]));
        int[] startRole = startLand.getRoleNum();
        Debug.Log(string.Format("{0},{1}",startRole[0],startRole[1]));
        int[] endRole = endLand.getRoleNum();
        Debug.Log(string.Format("{0},{1}",endRole[0],endRole[1]));

        if (endRole[0] + endRole[1] == 6) return 1;

        if (boat.getBoatSign() == 1)
        {
            startRole[0] += boatRole[0];
            startRole[1] += boatRole[1];
        }
        else
        {
            endRole[0] += boatRole[0];
            endRole[1] += boatRole[1];
        }

        if ((endRole[0] > 0 && endRole[1] > endRole[0]) || (startRole[0] > 0 && startRole[1] > startRole[0]))
            return -1;
        return 0;
    }
    // 游戏开始
    void Start()
    {
        SSDirector director = SSDirector.GetInstance();
        director.CurrentSceneController = this;
        GUI = gameObject.AddComponent() as UserGUI;
        LoadResoureces();
    }
}

ClickController

​ Click类负责检测船和角色是否被点击,通过ModelController中相关函数的调用来响应用户的点击以进行游戏

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().CurrentSceneController as IUserAction;
    }
    void OnMouseDown()
    {
        if (boat == null && role == null) return;

        if (boat != null) action.moveBoat();
        else if (role != null) action.moveRole(role);
    }
}

Interface

​ 其中包含两个类,为其他类提供接口。其中ISceneController提供场景接口,实现场景的生成;IUserAction提供用户交互接口,包括移动船、移动人物、重开、检查游戏状态的函数

using ControllerApplication;
namespace InterfaceApplication
{
    public interface ISceneController
    {
        void LoadResoureces();
    }
    public interface IUserAction
    {
        void moveBoat();
        void moveRole(RoleModel role);
        void reStart();
        int check();
    }
}
3. View

OnGui

​ Views搭建游戏的UI,设置UI与用户进行交互,主要为重新开始按钮,以及游戏成功与失败的提示。

using UnityEngine;
using InterfaceApplication;

public class UserGUI : MonoBehaviour
{
    public IUserAction action;
    public int sign = 0;
    void Start()
    {
        action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
    }
    void OnGUI()
    {
        GUIStyle text_style;
        GUIStyle button_style;
        GUIStyle titleStyle;
        text_style = new GUIStyle()
        {
            fontSize = 30,
            alignment = TextAnchor.MiddleCenter,
        };
        button_style = new GUIStyle("button")
        {
            fontSize = 15,
            alignment = TextAnchor.MiddleCenter,
        };
        titleStyle = new GUIStyle()
        {
            fontSize = 40,
            alignment = TextAnchor.MiddleCenter,
        };
        titleStyle.normal.textColor = Color.black;
        if (sign == -1)
        {
            GUI.Label(new Rect(0, 0, Screen.width, Screen.height * 0.5f), "Gameover!", text_style);
        }
        else if (sign == 1)
        {
            GUI.Label(new Rect(0, 0, Screen.width, Screen.height * 0.5f), "You Win!", text_style);
        }
        if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 200, 100, 50), "Restart", button_style))
        {
            action.reStart();
            sign = 0;
        }
        GUI.Label(new Rect(0, 0, Screen.width, Screen.height * 1.6f), "牧师与恶魔", titleStyle);
    }
}

运行效果

​ 运行效果如下:

​ (选择了简略抽象路线的牧师与魔鬼,白色正方体为牧师,红色球为魔鬼,棕色长方形为陆地,蓝色长方形为水,黄色长方形为船)
Unity学习记录——空间与运动_第8张图片

代码位置


​ 代码以及文档均已经上传至hw3 · XiaoChen04_3/3D_Computer_Game_Programming - gitee

​ Asset中包含了PriestsAndDevils和SolarSystem两个文件夹,分别为本次两道编程题目所对应的代码。

​ 查看代码效果,只需要将Assets文件夹复制到本地unity项目之中并打开对应Scene文件即可。

你可能感兴趣的:(unity,游戏引擎)