Unity游戏制作(六)

3D 游戏与编程 Homework 6

实验内容

  1. 智能巡逻兵
    • 提交要求:
    • 游戏设计要求:
      • 创建一个地图和若干巡逻兵(使用动画);
      • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
      • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
      • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
      • 失去玩家目标后,继续巡逻;
      • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
    • 程序设计要求:
      • 必须使用订阅与发布模式传消息
        • subject:OnLostGoal
        • Publisher: ?
        • Subscriber: ?
      • 工厂模式生产巡逻兵
    • 友善提示1:生成 3~5个边的凸多边型
      • 随机生成矩形
      • 在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
      • 5 ?
    • 友善提示2:参考以前博客,给出自己新玩法

实验环境

  • Windows
  • Unity 2020.3.18

技术日记

一、模型与动画

1. 模型与动画基础知识
  • 模型(Model)
    • 物体对象的组合,Unity 映射为游戏对象树
    • 每个物体包含一个网格(Mesh)与蒙皮设置
    • 包含使用的纹理、材质以及一个或多个动画剪辑
    • 模型由 3D 设计人员使用 3D Max 等创建
  • 动画剪辑(Animation Clip)
    • 物体或骨骼的位置等属性在关键帧的时间序列
    • 通过插值算法计算每帧的物体的位置或属性
2. Mecanim动画状态机(CrowAnimatorController)

设计状态机控制器包括 状态变迁参数条件 四个部分设计。

观察状态的属性

  • Motion 动画剪辑
  • Speed 速度(倍率)
  • Foot IK?
  • Transtions
    • 默认:按顺序检测生效转移
    • Solo:优先检测转移
    • Mute:禁止转移
  • 具有质量
  • 具有中心
  • 具有质心(不考虑形状)
    • 假设1:物体是均质的
    • 假设2:中心与质心重合
  • 物体作用力分解为:
    • 作用中心点上的力(Force)
    • 围绕中心点旋转的力矩(Torque)

设置变迁:

  • 注意变迁的顺序
  • 设定 solo 或 mute
  • 设定每个变迁
    • 给变迁命名,便于控制
    • 是否使用动画结束条件
    • 变迁 动画混合(平滑过程)
    • 变迁条件,例如:
      • Fly->exit 条件是 live
      • Not live 从任意状态转死亡

控制变迁条件

  • 使用转移控制变量
    • Float,Int,Bool 类型
    • Trigger 类型
  • 规划转移变量与变迁事件发生条件
    • 转移变量设计
      • 建议多使用 trigger 类型变量
      • 确保转移条件唯一,避免使用顺序决定转移(位操作通常OK)
      • 使用 mute 关闭不用的转移
  • 条件设计,例如:
    • live = false 转入死亡
    • fly_attack_trigger 确定转入攻击
3. 类人动画与动画中级知识

Avatar的意思化身(印度教和佛教中化作人形或兽形的神)。不用说兽幻化成人形,即使是植物,草也会有点头这样的拟人动作。因此Unity 的动画系统具有处理人形角色的特殊功能。因为人形角色在游戏中很常见,所以 Unity 为人形动画提供专门的工作流程以及扩展工具集。

  • 类人骨架是非常常用的特例,并且在游戏中广泛使用
  • 人骨骼结构的类似性,使得把动画从一个类人骨架映射到另一个成为可能。
    • 只要模型的子对象名字一样
    • 如果名字不一样,就需要映射

二、实验部分

1.项目配置过程

新建3d-unity的文件,然后直接把gitee上Assets文件夹替换新项目的Assets文件夹,同时,把scenes中的myScene拖出来,直接点击运行即可开始游戏。

2. 实现思路及核心算法
1)PatrolAction.cs

定义了巡逻兵的行为及运动:

  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
  • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;

主要函数及代码分析如下:

//定义巡逻的方向
private enum Dirction { EAST, NORTH, WEST, SOUTH };
//定义巡逻兵的位置
private float pos_x, pos_z;
//定义巡逻兵的移动的距离
private float move_length;
//定义巡逻兵的移动的速度
private float move_speed = 1.2f;
//定义巡逻兵是否移动
private bool move_sign = true;
//定义巡逻兵的初始移动方向
private Dirction dirction = Dirction.EAST;
private PatrolData data;

//通过move_sign判断巡逻兵是否能够移动,如果能移动则判断是否到达正方形的四个角之一(碰到障碍物),如果到达了,则以自身为原点位置,重新计算下一步的移动位置。
void Gopatrol(){
        if (move_sign){
            switch (dirction){
                case Dirction.EAST:
                    pos_x -= move_length;
                    break;
                case Dirction.NORTH:
                    pos_z += move_length;
                    break;
                case Dirction.WEST:
                    pos_x += move_length;
                    break;
                case Dirction.SOUTH:
                    pos_z -= move_length;
                    break;
            }
            move_sign = false;
        }
        this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
        float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));

        if (distance > 0.9){
            transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);
        }
        else{
            dirction = dirction + 1;
            //转了一圈又回头了
            if(dirction > Dirction.SOUTH)
            {
                dirction = Dirction.EAST;
            }
            move_sign = true;
        }
    }
2) PatrolFollowAction.cs

定义了巡逻兵的跟踪:

  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
void Follow(){
      //巡逻兵朝玩家的方向移动
      transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
        this.transform.LookAt(player.transform.position);
    }
3)PatrolFollowAction.cs

定义了巡逻兵的碰撞——玩家:

  • 巡逻兵若追击到玩家,则会把玩家杀死,游戏则结束;
public class PlayerCollideDetection : MonoBehaviour {
    void OnCollisionEnter(Collision other){
        if (other.gameObject.tag == "Player")
        {
            other.gameObject.GetComponent<Animator>().SetBool("death",true);
            this.GetComponent<Animator>().SetTrigger("shoot");
            Singleton<GameEventManager>.Instance.PlayerGameover();
        }
    }
}

此处调用了Animator的状态机,设置状态机为“death”状态,触发器为“shoot”。

4)PlayerInDetection.cs

定义了巡逻兵与玩家之间的追逐:

  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
  • 失去玩家目标后,继续巡逻;
public class PlayerInDetection : MonoBehaviour{
    void OnTriggerEnter(Collider collider){
        //玩家进入巡逻兵范围
        if (collider.gameObject.tag == "Player"){
            this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = true;
            this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject;
            //巡逻兵追踪玩家
            this.gameObject.transform.parent.GetComponent<Animator>().SetTrigger("shock");
        }
    }
    void OnTriggerExit(Collider collider){
        //玩家离开巡逻兵范围,停止追踪
        if (collider.gameObject.tag == "Player"){
            this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = false;
            this.gameObject.transform.parent.GetComponent<PatrolData>().player = null;
        }
    }
}
5)FirstSceneController.cs

加载预设资源:巡逻兵、记分板、玩家、地图等

public void LoadResources(){
  //加载玩家
	player = _PatorlFactory.LoadPlayer();
  //加载巡逻兵
	patrols = _PatorlFactory.LoadPatrol();
	for (int i = 0; i < patrols.Count; i++){
    //绑定巡逻兵巡逻的动作
		_PatrolActionManager.GoPatrol(patrols[i]);
	}
  //加载地图
	GameObject.Instantiate(Resources.Load("Prefabs/Plane"), new Vector3(0, 0, 0), Quaternion.identity);
 }

发布与订阅模式:

  • 传递分数的变化和游戏状态的变化
//发布与订阅模式
    void OnEnable()
    {
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
    }
    void OnDisable()
    {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
    }

玩家的移动:

  • 游戏未结束,则可以控制玩家。
public void MovePlayer(float translationX, float translationZ){
        if(!game_over){
            if (translationX != 0 || translationZ != 0){
                //run动画
                player.GetComponent<Animator>().SetBool("run", true);
            }
            else
            {
                player.GetComponent<Animator>().SetBool("run", false);
            }
            //通过键盘输入移动玩家。
            player.transform.Translate(translationX * player_speed * Time.deltaTime, 0, translationZ * player_speed * Time.deltaTime);

            if (player.transform.position.y != 0){
                player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
            }     
        }
    }

通过UserGUI.cs中读取键盘输入的translationXtranslationZ控制玩家行走。

6)GameEventManager.cs

控制主要的游戏事件。

public class GameEventManager : MonoBehaviour{
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;

    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;

    public void PlayerEscape(){
        //逃脱之后分数增加
        if (ScoreChange != null){
            ScoreChange();
        }
    }

    public void PlayerGameover(){
        //碰撞之后游戏结束
        if (GameoverChange != null){
            GameoverChange();
        }
    }
}
PatrolData.cs

巡逻兵的信息。

public class PatrolData : MonoBehaviour{
    public int sign;
    public bool follow_player = false;
    public int wall_sign = -1;
    public GameObject player;
    public Vector3 start_position;
}
7)UserGUI.cs

游戏界面及键盘输入:

void Update(){
        //读取键盘输入
        float translationX = Input.GetAxis("Horizontal");
        float translationZ = Input.GetAxis("Vertical");
        action.MovePlayer(translationX, translationZ);
    }

    private void OnGUI(){
        GUI.Label(new Rect(10, 5, 200, 50), "Score:", style);
        GUI.Label(new Rect(70, 5, 200, 50), action.GetScore().ToString(), style);
        if(action.GetScore() == 10){
            GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 220, 100, 100), "Pass!", over_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "Restart")){
                action.Restart();
                return;
            }
        }   
        else if(action.GetGameover() && action.GetScore() != 20){
            GUI.Label(new Rect(Screen.width / 2 - 70, Screen.width / 2 - 220, 100, 100), "Game Over!", over_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "Restart")){
                action.Restart();
                return;
            }
        }
    }
8)PatorlFactory.csPatorlActionManager.csScoreRecorder.csSSDirector.csSSAction.csSSActionManager.cs

与前几次作业的差别不大,不再重复分析。

9)接口类:ISenceInterface.csISSActionInterface.csIUserInterface.cs

其中,用户接口IUserInterface.cs,提供用户能直接调用的接口:MovePlayer(), GetScore(), GetGameover(), Restart()。

三、总结

参考:https://blog.csdn.net/C486C/article/details/80153548

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity游戏制作是使用Unity引擎进行游戏开发的过程。在游戏制作中,通常会涉及到不同的层级和功能模块。持久层是负责数据存储和访问的模块,模型层是负责定义数据模型的模块,服务层是前后端沟通的服务级汇总,控制层负责处理前端操作,展现层是纯前端展现的工作。[1] 在Unity游戏制作中,大地图类用于展现大地图和实现地图传送功能,地图类用于定义地图的属性和操作,地图块类用于定义地图上的地图块属性和事件处理,NPC类用于定义NPC的属性和对话功能,玩家类用于记录玩家的属性和状态,敌人类用于定义敌人的属性和AI战斗,小队类用于管理玩家的小队,物品类用于定义游戏中的物品,动画管理类用于管理游戏中的动画,UI类用于处理场景中的UI,操作响应类用于处理摇杆和按键事件,战场类用于定义战场的属性和处理战斗逻辑,战斗玩家类用于记录战斗过程中的临时数据,buff类用于定义游戏中的buff效果。[2][3] 在Unity游戏制作中,需要考虑前端与后端的分离,如何汇总view操作,流程部分的设计,以及如何避免在不该改数据的地方改数据,在不该改界面的地方改界面等问题。同时,可以使用面向切面的方式来实现前端的切入。[1] 总之,Unity游戏制作需要综合考虑不同层级和功能模块的设计和实现,以实现游戏的功能和可用性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值