3D游戏编程与设计作业10

环境说明

Unity2019.2.3f1
Win10 x64

Unity3D 导航与寻路

寻路是游戏中经常使用到的一项技术,自动寻路就是AI中的一个十分重要的分支,其算法异常复杂。Unity中提供的一套非常成熟的组件来为我们解决这一难题:Unity 导航系统允许创建给游戏角色导航的游戏世界。它将游戏场景中复杂的结构组织关系简化为带有一定信息的网格 ,在这些网格的基础上通过一系列相应的计算来实现自动寻路。

  • NavMesh (Navigation Mesh) :一种数据结构,它描述了游戏对象可行走的表面。通过三角网格,计算其中任意两点之间的最短路径,用于游戏对象的导航。它是根据场景几何结构自动创建或烘焙构建的
    更多有关NavMesh的介绍更参考Unity 基础寻路-NavMesh
  • NavMesh Agent:寻路导航组件,它创建具有寻路能力的角色
    有关NavMeshAgent组件面板属性的说明可参考NavMeshAgent 寻路导航组件NavMeshAgent以及Unity官方文档说明
  • Off-Mesh Link:这一组件允许将不连接的块之间建立“传送门”。例如,跳过沟渠或围栏,或在穿过它之前打开门,都可以被描述为 Off-Mesh Link
  • Nav Mesh Obstacle:这一组件允许您描述 agent 在移动时应避免的移动障碍。例如,由物理系统控制的桶或箱子就是很典型的障碍。在障碍物移动的过程中,Agent 尽力避开它,但一旦障碍物变得静止,它将在导航网格上开一个洞,以便 Agent 可以改变他们的路径以绕过它,或者静止的障碍物阻塞路径,使得 Agent 找到其他路线
    有关这一组件的更多详细说明可参考NavMesh Obstacle组件Unity官方文档说明

Agent 和 Navmesh 练习

  • 创建一个新Unity3D项目NavPractive
  • 创建一个Plane对象:GameObject->3D Object->Plane
  • 分别创建蓝色Material和粉色Material,命名为bluepink
  • 创建一个Cube对象,赋上blue,制作成wall预制
    WallPrefab.PNG
  • 创建一个Sphere对象,赋上pink,制作成target预制
    TargetPrefab.PNG
  • Assets Store中下载并导入标准资源Standard Assets
  • 添加AI角色:将资源包Standard Assets/Characters/ThirdPersonCharacter/Prefabs/下的AIThirdPersonController拖入场景中
  • 布局:
对象positionrotationscale
Main Camera(0,5,-10)(30,0,0)(1,1,1)
AIThirdPersonController(0,0,-4)(0,0,0)(1,1,1)
target(0,0,-4)(0,0,0)(0.5,0.1,0.5)
wall0(0,0,0)(0,0,0)(3,1,1)
wall1(4,0,0)(0,0,0)(2,1,1)
wall2(-4,0,0)(0,0,0)(2,1,1)

scene.PNG

  • 完成布局后,将target拖入AIThirdPersonController对象的AICharacterControl脚本组件的Target插槽(这里注意是将target对象赋值过去,而非预制
    AICharacterControllerTarget.png

  • 打开Navigation编辑面板窗口:Window->AI->Navigation

  • 配置Planetarget对象的Navigation->Object

    • 勾选Navigation Static
    • 设置Navigation AreaWalkable
  • 配置wall0wall1wall2Navigation->Object

    • 勾选Navigation Static
    • 设置Navigation AreaNot Walkable
  • 点击Navigation/Bake面板中的Bake,可得到如图所示的结果:
    Bake.png
    图中,水蓝色凸多边形构成的网格就是寻路算法的数据结构,agent 将用它来导航。

  • 创建脚本PlaceTarget,代码内容如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PlaceTarget : MonoBehaviour {
    	
    	// Update is called once per frame
    	void Update () {
    		if (Input.GetMouseButtonDown (0)) {    
    			Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);  
    			RaycastHit hit;  
    			if (Physics.Raycast (ray, out hit)) {  
    				Debug.Log (hit.collider.name);  
    				if (hit.collider.name == "Plane") {
    					this.transform.position = hit.point;
    				}
    			}
    		}
    	}
    }
    

    将这一脚本挂载到target对象下

  • 运行:
    Navigation1.gif

Obstacle和Off-Mesh-Link练习

  • 将当前场景另存为scene2
  • 创建一个Cylinder对象,Scale=(0.5,0.01,0.5),黄色,制作成mark预制
  • mark预制拖入场景,创建两个对象,分别命名为mark0(position=(0,0,-1))和mark1(position=(0,0,1)),两者均勾选Navigation Static,并设置Navigation AreaWalkable
  • Creat Empty创建一个empty对象,命名为meshlink,并添加组件Off Mesh Link
  • mark0mark1分别拖入meshlinkOff Mesh Link组件的startend插槽,并设置Cost Override-1
    OffMeshLinkConfig.PNG
  • 创建一个Cube对象,命名为Obstacle,并为其添加组件Nav Mesh Obstacle,在该组件中勾选Carve,它将自动切割导航网格
    ObstacleConfig.PNG
  • 重新布置场景如下:
    Scene2.PNG
  • 运行:
    Navigation2.gif
    本部分完整项目代码见NavPractice

P&D 过河游戏智能帮助实现

程序具体要求:

  • 实现状态图的自动生成
  • 讲解图数据在程序中的表示方法
  • 利用算法实现下一步的计算

状态图

参考Unity学习之P&D 过河游戏智能帮助实现,以游戏过程中的开始陆地的情况为例,其状态图如下:
Status.jpg
其中,P代表牧师,D代表魔鬼,B代表船,数字代表角色个数,例如:3P3DB表示开始陆地有3个牧师和3个魔鬼,同时船也停在开始陆地。箭头指向即为状态的转化方向。

从而我们可以很容易想到这样设计算法:点击Tip之后,分析当前场景的状态,读取可行路径上的下一个状态(可通过随机选择函数进行选择/亦可设计算法选择具有最短路径者/或直接自行指定,我这里采用的是直接指定),将这一可行的状态返回给用户。

从而我们可以设计如下数据结构:

//表示下一状态要用船运走的角色
public enum BoatAction { P, D, PP, DD, PD }
/* P:船运载一个牧师
 * D:船运载一个恶魔
 * PP:船运载两个牧师
 * DD:船运载两个恶魔
 * PD:船运载一个牧师一个恶魔
 */

//船将要承载的船员的情况及船的状态
public struct nextPassenger
{
    public int boat_sign;//船在左岸(end)为-1,右岸(start)为1
    public BoatAction boat_action;
}

AI算法

  • 首先获取当前状态

    next.boat_sign = boat.GetBoatSign();
    
  • 根据状态图,计算下一状态:

    if (next.boat_sign == 1 && start_priest == 3 && start_devil == 3)
    {
        next.boat_action = BoatAction.DD;
    }
    else if (next.boat_sign == -1 && start_priest == 3 && start_devil == 1)
    {
        next.boat_action = BoatAction.D;
    }
    else if (next.boat_sign == -1 && start_priest == 3 && start_devil == 2)
    {
        next.boat_action = BoatAction.D;
    }
    else if (next.boat_sign == -1 && start_priest == 2 && start_devil == 2)
    {
        next.boat_action = BoatAction.P;
    }
    else if (next.boat_sign == 1 && start_priest == 3 && start_devil == 2)
    {
        next.boat_action = BoatAction.DD;
    }
    else if (next.boat_sign == -1 && start_priest == 3 && start_devil == 0)
    {
        next.boat_action = BoatAction.D;
    }
    else if (next.boat_sign == 1 && start_priest == 3 && start_devil == 1)
    {
        next.boat_action = BoatAction.PP;
    }
    else if (next.boat_sign == -1 && start_priest == 1 && start_devil == 1)
    {
        next.boat_action = BoatAction.PD;
    }
    else if (next.boat_sign == 1 && start_priest == 2 && start_devil == 2)
    {
        next.boat_action = BoatAction.PP;
    }
    else if (next.boat_sign == -1 && start_priest == 0 && start_devil == 2)
    {
        next.boat_action = BoatAction.D;
    }
    else if (next.boat_sign == 1 && start_priest == 0 && start_devil == 3)
    {
        next.boat_action = BoatAction.DD;
    }
    else if (next.boat_sign == -1 && start_priest == 0 && start_devil == 1)
    {
        next.boat_action = BoatAction.D;
    }
    else if (next.boat_sign == 1 && start_priest == 0 && start_devil == 2)
    {
        next.boat_action = BoatAction.DD;
    }
    else if (next.boat_sign == 1 && start_priest == 1 && start_devil == 1)
    {
        next.boat_action = BoatAction.PD;
    }
    

其他设计修改

  • IUserAction:根据分析我们可以知道,用户动作列表应多一个“获取帮助”的操作

    //IUserAction.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public interface IUserAction //用户动作“列表”接口
    {
        void MoveBoat();//移动船
        void MoveRole(RoleModel role);//移动角色
        void Restart();//重新开始
        string getTips();//获取帮助
    }
    
    

    此外,在Controller.cs中,getTips()的实现如下:

    public string getTips()
    {
        if (user_gui.sign != 0) return "";
        getNextPassenger();//计算得到下一状态值
    
        //get helping contents
        string text = "";
        if (next.boat_action == BoatAction.D)
        {
            text = "运载一个魔鬼到对岸";
        }
        else if (next.boat_action == BoatAction.P)
        {
            text = "运载一个牧师到对岸";
        }
        else if (next.boat_action == BoatAction.DD)
        {
            text = "运载两个魔鬼到对岸";
        }
        else if (next.boat_action == BoatAction.PP)
        {
            text = "运载两个牧师到对岸";
        }
        else if (next.boat_action == BoatAction.PD)
        {
            text = "运载一个牧师、一个魔鬼到对岸";
        }
        return text;
    }
    
  • UserGUI:添加Tips的按钮及其响应处理代码

    if (GUI.Button(new Rect(80,10,60,30), "Tips", button_style))
    {
        helping_text = action.getTips();
        if (isTip)
            isTip = false;
        else
            isTip = true;
    }
    if (isTip)
    {
        GUI.Label(new Rect(10, 50, 200, 50), helping_text);
    }
    

本部分完整项目代码见PriestsAndDevils2

演示视频

前往b站观看演示视频

其他参考资料

潘老师的课程网站 第十章 游戏智能

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值