3D游戏编程与设计——空间与运动章节作业与练习

3D游戏编程与设计——空间与运动章节作业与练习

作业与练习:

C#自学

了解相关C#集合类型的使用:

  1. ArrayList 动态数组

与数组不同的是,ArrayList 可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。

三个构造方法:

ArrayList listl=new ArrayList();
ArrayList list2=new ArrayList(listl);
ArrayList list3=new ArrayList(20);

常用的属性和方法:

  • int Add(object value):向集合中添加 object 类型的元素,返回元素在集合中的下标
  • Capacity:属性,用于获取或设置集合中可以包含的元素个数
  • void Clear() :从集合中移除所有元素
  • bool Contains(object item):判断集合中是否含有 item 元素,若含有该元素则返回 True, 否则返回 False
  • Count:属性,用于获取集合中实际含有的元素个数
  • void Reverse() :将集合中的元素顺序反转
  • void Sort():将集合中的元素排序,默认从小到大排序
  1. Hashtable 哈希表

C# 中的 Hashtable 称为哈希表,也称为散列表,在该集合中使用键值对(key/value)的形式存放值。

构造方法:

Hashtable mp = new Hashtable ();

常用的属性和方法:

  • Count 集合中存放的元素的实际个数
  • void Add(object key,object value) 向集合中添加元素
  • void Remove(object key) 根据指定的 key 值移除对应的集合元素
  • void Clear() 清空集合
  • ContainsKey (object key) 判断集合中是否包含指定 key 值的元素
  • ContainsValue(object value) 判断集合中是否包含指定 value 值的元素
  1. SortedList 有序队列

SortedList 称为有序列表,按照 key 值对集合中的元素排序。

构造方法以及常用属性和方法与Hashtable类似。

作业内容

1、简答并用程序验证【建议做】

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

游戏对象随着每帧的变化在空间上发生运动,其中运动包括了位置的移动以及角度旋转变化,分别对应的就是position和rotation属性。

  • 请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)
  1. 方法1,使用Vector3:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move1 : MonoBehaviour
{
    public int x = 1;   
    public int y = 1;   
    public int t = 1;     
    void Update()
    {
        transform.position += Vector3.right * Time.deltaTime * x;
        transform.position += Vector3.down * Time.deltaTime * Time.deltaTime * y * t;
        t++;
    }
}
  1. 方法2,修改Transform属性:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move2 : MonoBehaviour
{
    public int speed = 2;
    void Update()
    {
        transform.position += new Vector3(Time.deltaTime * speed, -Time.deltaTime * speed * (2 * transform.position.x + Time.deltaTime * speed), 0);
    }
}
  1. 方法3,使用Transform.Translate:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move3 : MonoBehaviour
{
    public int x = 1;  
    public int y = 1;  
    public int t = 1;      
    void Update()
    {
        transform.Translate(Vector3.right * Time.deltaTime * x + Vector3.down * Time.deltaTime * y * Time.deltaTime * t);
        t++;
    }
}
  • 写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

简单分析需求:

  • 创建太阳系中的各星球,并配图。
  • 设置星球的运动,转速不一。
  • 各星球不在一个法平面上。
  1. 步骤一:
    创建相关星球,并配色如下:

在这里插入图片描述

在这里插入图片描述

  1. 步骤二:
    为了让各星球不在同一法平面上旋转,让每个星球旋转的角度不一样

创建一个通用绕转的c#脚本goRound.cs,编写如下:

上述每个星球我都只是x方向在变化,y和z相同,下列代码中,绕转角度,只在y和z方向上发生绕转。

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

public class goRound : MonoBehaviour
{
    public GameObject center;
    public int speed;
    private float yAngle;
    private float zAngle;

    void Start()
    {
        yAngle = Random.Range(10, 60);
        zAngle = Random.Range(10, 60);
    }

    void Update()
    {
        Vector3 axis = new Vector3(0,yAngle, zAngle);
        transform.RotateAround(center.transform.position, axis, speed * Time.deltaTime);
    }

}

将该脚本赋给每个星球并设置好center对象与速度speed(注意月球的center是地球)。

  1. 步骤三:
    然后创建一个自转的c#脚本rotate.cs,编写如下:

将该脚本赋给每个星球

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

public class rotate : MonoBehaviour
{
    void Update()
    {
        transform.RotateAround(this.transform.position, Vector3.up, 1);
    }
}
  1. 步骤四:

设置星球运动轨迹颜色,通过component中的trail render来设置。

在这里插入图片描述

在这里插入图片描述

  1. 步骤五:

为游戏添加一个天空

在资源商店中下载天空资源AllSky Free - 10 Sky / Skybox Set

然后在unity的package manager中选择该资源,进行import,注意:只import其中一个天空资源,我选择了cold sunset主题

在这里插入图片描述

然后在windows——Rendering——Lighting——Environment——Skybox Material中将对应资源的材料拖放到那里,就完成了。

总体效果如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

项目视频MP4

项目gitee源码

2、编程实践

  • 阅读以下游戏脚本

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 结构程序
  • 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

项目结构

  1. 总体架构:项目的总体结构如下
  • 材料(Materials)。
  • 预制(Resources/Prefabs)。所有游戏对象都已预制形式存在
  • 脚本(Scripts)。

另外,构造 Main 空对象,并挂载 FirstController 代码,使得游戏加载行为在你的控制下。启动时,游戏就一个对象有代码。

在这里插入图片描述

  1. 创建预制

先将游戏对象做成预制(魔鬼,牧师,船,河,岸边)

如下图所示:

在这里插入图片描述

  1. 创建MVC-Script

设计MVC构造模式,创建Scripts文件夹,并在其目录下创建Models、Views、Controllers三个文件夹,将各分类功能代码放置在对应的MVC文件夹中。

对应的MVC文件夹结构如下:

在这里插入图片描述

针对Scripts下每个文件夹的文件定义如下:

  • Models

    • 为每一个游戏中的数据对象创建一个model (Boat、Land、River、Role(devil,priest))

    • 游戏所有物体的特殊位置信息储存在PositionModel

    • 还需要一个click脚本来提供点击属性

在这里插入图片描述

  • Controllers

    • 创建SSDirector 类(导演)和XXXSceneController 类(场记)。

    • 为每一个数据可能会变化的model创建一个controller来管理。

    • 人物和船具有可被点击的属性,因此其controller需要实现ClickAction的接口,以此来处理点击事件。

    • 游戏中物体的移动由MoveController统一管理,其携带一个Move脚本。

在这里插入图片描述

  • Views

    • 搭建游戏的UI,只需要一个脚本用来设置UI和接收用户交互。

在这里插入图片描述

代码设计细节

Model

在Model中的类存储游戏对象的各种数据,只有初始化函数,数据的变更由controller来实现。

  1. RoleModel

牧师和魔鬼的数据对象,根据isPriest来判断该对象是牧师还是魔鬼

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

public class RoleModel{ 
    public GameObject role;             //角色的游戏对象
    public int tag;                     //给对象标号,方便查找
    public bool isPriest;               //区分角色是牧师还是恶魔
    public bool isRight;                //区分角色是在左侧还是右侧
    public bool isInBoat;               //区分角色是在船上还是在岸上

    //初始化函数
    public RoleModel(Vector3 position, bool isPriest, int tag)
    {
        // 设置相关属性
        this.isPriest = isPriest;
        this.tag = tag;
        isRight = false;
        isInBoat = false;
        role = GameObject.Instantiate(Resources.Load("Prefabs/"+(isPriest?"priest":"devil"), typeof(GameObject))) as GameObject;
        role.transform.localScale = new Vector3((float)0.7,(float)0.8,(float)0.7);
        role.transform.position = position;
        role.name = "role" + tag;
        // 添加点击和碰撞组件
        role.AddComponent<Click>();
        role.AddComponent<BoxCollider>();
    }
}
  1. BoatModel

与RoleModel实现相似。

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

public class BoatModel {
    public GameObject boat;                     //船对象
    public RoleModel[] roles;                   //船上的角色的指针
    public bool isRight;                        //判断船在左侧还是右侧
    public int priestNum, devilNum;             //船上牧师与恶魔的数量

    public BoatModel(Vector3 position)
    {
        // 船只刚开始无人,在左侧
        isRight = false;
        priestNum = devilNum = 0;
        roles = new RoleModel[2];
        boat = GameObject.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject))) as GameObject;
        boat.name = "boat";
        boat.transform.position = position;
        boat.transform.localScale = new Vector3(3, (float)1.5, 3);
        // 添加点击和碰撞组件
        boat.AddComponent<Click>();
        boat.AddComponent<BoxCollider>();
    }
}
  1. RiverModel

与BoatModel实现相似

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

public class RiverModel
{
    private GameObject river;  //河流的游戏对象

    public RiverModel(Vector3 position)
    {
        river = Object.Instantiate(Resources.Load("Prefabs/river", typeof(GameObject))) as GameObject;
        river.name = "river";
        river.transform.position = position;
        river.transform.localScale = new Vector3(15, 2, 3);
    }
}
  1. LandModel

与RiverModel实现类似

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

public class LandModel
{
    public GameObject land;  //岸的游戏对象
    public int priestNum, devilNum;  //岸上牧师与恶魔的数量
    public LandModel(string name, Vector3 position)
    {
        priestNum = devilNum = 0;
        land = GameObject.Instantiate(Resources.Load("Prefabs/land", typeof(GameObject))) as GameObject;
        land.name = name;
        land.transform.position = position;
        land.transform.localScale = new Vector3(13,5,3);
    }
}
  1. PositionModel

储存游戏当中对象的特殊位置,包括两侧岸的位置,河流位置,船在两侧的位置,角色在岸及船上的位置。

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

public class PositionModel
{
    public static Vector3 right_land = new Vector3(15, -4, 0);                  //右侧岸的位置
    public static Vector3 left_land = new Vector3(-13, -4, 0);                  //左侧岸的位置
    public static Vector3 river = new Vector3(1, -(float)5.5, 0);               //河流的位置
    public static Vector3 right_boat = new Vector3(7, -(float)3.8, 0);          //船在右边的位置
    public static Vector3 left_boat = new Vector3(-5, -(float)3.8, 0);          //船在左边的位置
    //角色在岸上的相对位置(6个)
    public static Vector3[] roles = new Vector3[]{new Vector3((float)-0.2, (float)0.7, 0) ,new Vector3((float)-0.1, (float)0.7,0), new Vector3(0, (float)0.7,0),new Vector3((float)0.1, (float)0.7,0),new Vector3((float)0.2, (float)0.7,0),new Vector3((float)0.3, (float)0.7,0)};
    //角色在船上的相对位置(2个)
    public static Vector3[] boatRoles = new Vector3[] { new Vector3((float)-0.1, (float)1.2, 0), new Vector3((float)0.2, (float)1.2, 0) };
}
  1. ClickAction

通过该接口调用DealClick函数

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

public interface ClickAction
{
    void DealClick();
}
  1. Click

通过该类,设置动作的接口。当物体被点击时会通过这个接口来处理点击事件。

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

public class Click : MonoBehaviour
{
    ClickAction clickAction;

    public void setClickAction(ClickAction clickAction)
    {
        // 设置接口
        this.clickAction = clickAction;
    }

    void OnMouseDown()
    {
        // 响应鼠标事件
        clickAction.DealClick();
    }
}
Controller
  1. RoleModelController

用于处理RoleMode对应的事件,实现了ClickAction接口与IUserAction接口。

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

public class RoleModelController :ClickAction
{
    RoleModel roleModel;                                       
    IUserAction userAction;                                     

    public RoleModelController()
    {
        userAction = SSDirector.GetInstance().CurrentSenceController as IUserAction;
    }

    public void CreateRole(Vector3 position, bool isPriest, int tag)
    {
        if (roleModel != null)
            Object.DestroyImmediate(roleModel.role);
        // 初始化对象
        roleModel = new RoleModel(position, isPriest, tag);
        // 设置点击事件
        roleModel.role.GetComponent<Click>().setClickAction(this);
    }

    public RoleModel GetRoleModel()
    {
        // 返回对象
        return roleModel;
    }

    public void DealClick()
    {
        // 实现接口,处理事件
        userAction.MoveRole(roleModel);
    }
}
  1. BoatController

用于处理BoatModel对应的事件,实现了ClickAction接口与IUserAction接口。特别的,AddRole与RemoveRole函数处理Role上下船的事件。

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

public class BoatController : ClickAction
{
    BoatModel boatModel;
    IUserAction userAction;

    public BoatController()
    {
        userAction = SSDirector.GetInstance().CurrentSenceController as IUserAction;
    }
    public void CreateBoat(Vector3 position)
    {
        if (boatModel != null)
            Object.DestroyImmediate(boatModel.boat);
        // 初始化BoatModel
        boatModel = new BoatModel(position);
        // 设置点击组件
        boatModel.boat.GetComponent<Click>().setClickAction(this);
    }

    public BoatModel GetBoatModel()
    {
        // 返回对象
        return boatModel;
    }

    // 将角色加到船上,返回接下来角色应该到达的位置
    public Vector3 AddRole(RoleModel roleModel)
    {
        //船上有两个位置,分别判断两个位置是否为空
        if (boatModel.roles[0] == null)
        {
            boatModel.roles[0] = roleModel;
            roleModel.isInBoat = true;
            // 设置相对坐标
            roleModel.role.transform.parent = boatModel.boat.transform;
            // 更新船上牧师、魔鬼数量
            if (roleModel.isPriest)
                boatModel.priestNum++;
            else
                boatModel.devilNum++;
            // 返回Boat第一个位置的坐标
            return PositionModel.boatRoles[0];

        }
        if (boatModel.roles[1] == null)
        {
            boatModel.roles[1] = roleModel;
            roleModel.isInBoat = true;
            // 设置相对坐标
            roleModel.role.transform.parent = boatModel.boat.transform;
            // 更新船上牧师、魔鬼数量
            if (roleModel.isPriest)
                boatModel.priestNum++;
            else
                boatModel.devilNum++;
            // 返回Boat第二个位置的坐标
            return PositionModel.boatRoles[1];
        }
        // Boat的两个位置都不为空的时候,不移动
        return roleModel.role.transform.localPosition;
    }

    // 将角色从船上移除
    public void RemoveRole(RoleModel roleModel)
    {
        // 分别判断船上的哪个Role需要离开
        if (boatModel.roles[0] == roleModel)
        {
            boatModel.roles[0] = null;
            if (roleModel.isPriest)
                boatModel.priestNum--;
            else
                boatModel.devilNum--;
        }
        if (boatModel.roles[1] == roleModel)
        {
            boatModel.roles[1] = null;
            if (roleModel.isPriest)
                boatModel.priestNum--;
            else
                boatModel.devilNum--;
        }
    }

    public void DealClick()
    {
        // 至少要有一个牧师或者魔鬼,船才能移动到另一边
        if (boatModel.roles[0] != null || boatModel.roles[1] != null)
            userAction.MoveBoat();
    }
}
  1. LandModelController

用于处理LandModel对应的事件。特别的,AddRole与RemoveRole函数处理Role上岸和离岸的事件。

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

public class LandModelController
{
    private LandModel landModel;

    public void CreateLand(string name, Vector3 position)
    {
        if (landModel==null)
            landModel = new LandModel(name, position);
        landModel.priestNum = landModel.devilNum = 0;
    } 

    public LandModel GetLandModel()
    {
        // 返回对象
        return landModel;
    }

    // 将人物添加到岸上,返回角色在岸上的相对坐标
    public Vector3 AddRole(RoleModel roleModel)
    {
        if (roleModel.isPriest)
            landModel.priestNum++;
        else
            landModel.devilNum++;
        roleModel.role.transform.parent = landModel.land.transform;
        roleModel.isInBoat = false;
        // 返回该对象对应的在岸上的站位
        return PositionModel.roles[roleModel.tag];
    }

    // 将角色从岸上移除
    public void RemoveRole(RoleModel roleModel)
    {
        if (roleModel.isPriest)
            landModel.priestNum--;
        else
            landModel.devilNum--;
    }
}
  1. SSDirector

导演,获取主控制器。

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

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

游戏的主控制器,负责游戏主要运行逻辑以及协调各控制器之间的工作。主控制器的成员变量,包括各副控制器、河流Model、游戏进行状态和游戏进行时间。

  • LoadResources用于导入场景需要的资源。函数内部会对各控制器、Model、变量进行初始化。
  • MoveBoat实现船的移动。首先判断是否可以移动,接着将船移向相反的位置。
  • Restart用于重置游戏,对成员变量进行初始化,以及调用各控制器的初始化函数。
  • Check用于检测当前游戏是否胜利或者失败。
  • Update用于游戏计时,每次调用都会减去一帧的时间。
  • Awake对游戏主控制器进行初始化。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    private LandModelController rightLandRoleController; // 右岸控制器
    private LandModelController leftLandRoleController; // 左岸控制器
    private RiverModel riverModel; // 河流
    private BoatController boatRoleController; // 船控制器
    private RoleModelController[] roleModelControllers; // 人物控制器集合
    private MoveController moveController; // 移动控制器
    private bool isRuning; // 游戏进行状态
    private float time; // 游戏进行时间

    // 导入资源
    public void LoadResources()
    {
        // 人物初始化
        roleModelControllers = new RoleModelController[6];
        for (int i = 0; i < 6; i++)
        {
            roleModelControllers[i] = new RoleModelController();
            roleModelControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);
        }
        // 左右岸初始化
        leftLandRoleController = new LandModelController();
        leftLandRoleController.CreateLand("left_land", PositionModel.left_land);
        rightLandRoleController = new LandModelController();
        rightLandRoleController.CreateLand("right_land", PositionModel.right_land);
        // 将人物添加并定位至左岸  
        foreach (RoleModelController roleModelController in roleModelControllers)
        {
            roleModelController.GetRoleModel().role.transform.localPosition = leftLandRoleController.AddRole(roleModelController.GetRoleModel());
        }
        // 河初始化
        riverModel = new RiverModel(PositionModel.river);
        // 船初始化
        boatRoleController = new BoatController();
        boatRoleController.CreateBoat(PositionModel.left_boat);
        // 移动控制器初始化
        moveController = new MoveController();
        // 数据初始化
        isRuning = true;
        time = 60;
    }

    // 移动船
    public void MoveBoat()
    {
        // 判断当前游戏是否在进行,同时是否有对象正在移动
        if ((!isRuning) || moveController.GetIsMoving()) return;
        // 判断船在左侧还是右侧
        if (boatRoleController.GetBoatModel().isRight)
            moveController.SetMove(PositionModel.left_boat, boatRoleController.GetBoatModel().boat);
        else
            moveController.SetMove(PositionModel.right_boat, boatRoleController.GetBoatModel().boat);
        // 移动后,将船的位置取反
        boatRoleController.GetBoatModel().isRight = !boatRoleController.GetBoatModel().isRight;
    }

    // 移动人物    
    public void MoveRole(RoleModel roleModel)
    {
        // 判断当前游戏是否在进行,同时是否有对象正在移动
        if ((!isRuning) || moveController.GetIsMoving()) return;
        if (roleModel.isInBoat)
        {
            // 若人在船上,则将其移向岸上
            if (boatRoleController.GetBoatModel().isRight)
                moveController.SetMove(rightLandRoleController.AddRole(roleModel), roleModel.role);
            else
                moveController.SetMove(leftLandRoleController.AddRole(roleModel), roleModel.role);
            roleModel.isRight = boatRoleController.GetBoatModel().isRight;
            boatRoleController.RemoveRole(roleModel);
        }
        else
        {
            // 若人在岸上,则将其移向船
            if (boatRoleController.GetBoatModel().isRight == roleModel.isRight)
            {
                if (roleModel.isRight)
                    rightLandRoleController.RemoveRole(roleModel);
                else
                    leftLandRoleController.RemoveRole(roleModel);
                moveController.SetMove(boatRoleController.AddRole(roleModel), roleModel.role);
            }
        }
    }

    // 游戏重置
    public void Restart()
    {
        // 对各数据进行重置
        time = 60;
        leftLandRoleController.CreateLand("left_land", PositionModel.left_land);
        rightLandRoleController.CreateLand("right_land", PositionModel.right_land);
        for (int i = 0; i < 6; i++)
        {
            roleModelControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);
            roleModelControllers[i].GetRoleModel().role.transform.localPosition = leftLandRoleController.AddRole(roleModelControllers[i].GetRoleModel());
        }
        boatRoleController.CreateBoat(PositionModel.left_boat);
        isRuning = true;
    }

    // 检测游戏状态
    public void Check()
    {
        if (!isRuning) return;
        this.gameObject.GetComponent<UserGUI>().gameMessage = "";
        // 判断是否已经胜利
        if (rightLandRoleController.GetLandModel().priestNum == 3)
        {
            this.gameObject.GetComponent<UserGUI>().gameMessage = "You Win!";
            isRuning = false;
        }
        else
        {
            // 判断是否已经失败
            // 若任意一侧,牧师数量不为0且牧师数量少于恶魔数量,则游戏失败
            int leftPriestNum, leftDevilNum, rightPriestNum, rightDevilNum;
            leftPriestNum = leftLandRoleController.GetLandModel().priestNum + (boatRoleController.GetBoatModel().isRight ? 0 : boatRoleController.GetBoatModel().priestNum);
            leftDevilNum = leftLandRoleController.GetLandModel().devilNum + (boatRoleController.GetBoatModel().isRight ? 0 : boatRoleController.GetBoatModel().devilNum);
            if (leftPriestNum != 0 && leftPriestNum < leftDevilNum)
            {
                this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";
                isRuning = false;
            }
            rightPriestNum = rightLandRoleController.GetLandModel().priestNum + (boatRoleController.GetBoatModel().isRight ? boatRoleController.GetBoatModel().priestNum : 0);
            rightDevilNum = rightLandRoleController.GetLandModel().devilNum + (boatRoleController.GetBoatModel().isRight ? boatRoleController.GetBoatModel().devilNum : 0);
            if (rightPriestNum != 0 && rightPriestNum < rightDevilNum)
            {
                this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";
                isRuning = false;
            }
        }
    }

    void Awake()
    {
        // 加载资源
        SSDirector.GetInstance().CurrentSenceController = this;
        LoadResources();
        this.gameObject.AddComponent<UserGUI>();
    }

    void Update()
    {
        if (isRuning)
        {
            // 更新UserGUI组件的数据
            time -= Time.deltaTime;
            this.gameObject.GetComponent<UserGUI>().time = (int)time;
            if (time <= 0)
            {
                this.gameObject.GetComponent<UserGUI>().time = 0;
                this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";
                isRuning = false;
            }
        }
    }

}
  1. ISceneController

场景控制器接口,实现导入资源功能

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

public interface ISceneController
{
    void LoadResources();    
}
  1. IUserAction

用户交互接口,需要实现移动船、移动Role、重开游戏、检查游戏状态的函数。

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

public interface IUserAction
{
    // 移动船只
    void MoveBoat();
    // 移动Role
    void MoveRole(RoleModel roleModel);
    // 重开游戏
    void Restart();
    // 检查游戏状态
    void Check();
}
  1. Move

设置了中转地址,用途是为了让Role移动的时候能先离开岸边再跳上船,反过来亦然,防止直接直线移动到目标位置而导致穿模。

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

public class Move : MonoBehaviour
{
    public bool isMoving = false; // 判断当前对象是否在移动
    public float speed = 5; // 移动速度
    public Vector3 destination; // 目的地
    public Vector3 mid_destination; // 中转地址   

    void Update()
    {
        //已到达目的地,不进行移动
        if (transform.localPosition == destination)
        {
            isMoving = false;
            return;
        }
        isMoving = true;
        if (transform.localPosition.x != destination.x && transform.localPosition.y != destination.y)
        {
            //还未到达中转地址,向中转地址移动
            transform.localPosition = Vector3.MoveTowards(transform.localPosition, mid_destination, speed * Time.deltaTime);
        }
        else
        {
            //以到达中转地址,向目的地移动
            transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination, speed * Time.deltaTime);
        }
    }
}
  1. MoveController

控制游戏中的移动事件

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

public class MoveController
{
    private GameObject moveObject; // 移动对象

    // 判断当前是否在移动
    public bool GetIsMoving()
    {
        return (moveObject != null && moveObject.GetComponent<Move>().isMoving);
    }

    // 设置新的移动
    public void SetMove(Vector3 destination, GameObject moveObject)
    {
        // 判断新的对象是否已携带Move脚本,若不携带,则为其添加
        Move test;
        this.moveObject = moveObject;
        if (!moveObject.TryGetComponent<Move>(out test))
            moveObject.AddComponent<Move>();
        // 设置目的地
        this.moveObject.GetComponent<Move>().destination = destination;
        // 设置中转地址
        if (this.moveObject.transform.localPosition.y > destination.y)
            this.moveObject.GetComponent<Move>().mid_destination = new Vector3(destination.x, this.moveObject.transform.localPosition.y, destination.z);
        else
            this.moveObject.GetComponent<Move>().mid_destination = new Vector3(this.moveObject.transform.localPosition.x, destination.y, this.moveObject.transform.localPosition.z);
    }
}
  1. MoveCamera

调整摄像机的位置。

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

public class MoveCamera : MonoBehaviour
{
    void Start()
    {
        // 设置摄像机位置
        transform.position = new Vector3((float)1.11,1, (float)-18.32);        
    }
}
View
  1. UserGUI

主要用于绘制整个画面

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

public class UserGUI : MonoBehaviour
{
    private IUserAction userAction; // 实现接口
    public string gameMessage;
    public int time; // 时间
    void Start()
    {
        time = 60;
        userAction = SSDirector.GetInstance().CurrentSenceController as IUserAction;
    } 
    void OnGUI()
    {
        // 检查游戏状态
        userAction.Check();
        // 大字体
        GUIStyle style = new GUIStyle();
        style.normal.textColor = Color.white;
        style.fontSize = 30;
        // 小字体
        GUIStyle bigStyle = new GUIStyle();
        bigStyle.normal.textColor = Color.white;
        bigStyle.fontSize = 50;
        // 初始化字体
        GUI.Label(new Rect(180, 30, 50, 200), "Priests and Devils", bigStyle);
        GUI.Label(new Rect(300, 100, 50, 200), gameMessage, style);
        GUI.Label(new Rect(0, 0, 100, 50), "Time: " + time, style);
        // 初始化按钮
        if (GUI.Button(new Rect(320, 160, 100, 50), "Restart"))
        {
            userAction.Restart();
        }
    }
}

美化

为游戏添加一个天空

在资源商店中下载天空资源AllSky Free - 10 Sky / Skybox Set

然后在unity的package manager中选择该资源,进行import,注意:只import其中一个天空资源,我选择了Epic_GloriousPink主题

在这里插入图片描述

然后在windows——Rendering——Lighting——Environment——Skybox Material中将对应资源的材料拖放到那里,就完成了。

在这里插入图片描述

在这里插入图片描述

程序运行

将FirstController脚本拖放到Main空对象上,将MoveCamera脚本拖放到摄影机上(将摄影机移动到最佳角度)。

效果演示

在这里插入图片描述

项目源码与视频链接

项目视频MP4

项目gitee源码

3、思考题【选做】

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

  1. Rotate的实现

借助AngleAxis和Vector3

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

public class myRotate : MonoBehaviour
{
    void Update()
    {
        Quaternion q = Quaternion.AngleAxis(100 * Time.deltaTime, Vector3.up);
        transform.localRotation *= q;
    }
}
  1. RotateAround的实现

借助AngleAxis和Vector3,同时记得将被绕对象拖放到t变量中。

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

public class myRotateAround : MonoBehaviour
{
    public Transform t; // 被绕对象
    void Update()
    {
        Quaternion rotation = Quaternion.AngleAxis(100 * Time.deltaTime, Vector3.up);
        Vector3 direction = transform.position - t.position;
        direction = rotation * direction;
        transform.position = t.position + direction;
        transform.rotation *= rotation;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值