前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Unity3d项目]-校园疫情模拟

[Unity3d项目]-校园疫情模拟

作者头像
六月丶
发布2022-12-26 17:43:37
6130
发布2022-12-26 17:43:37
举报
文章被收录于专栏:六月-游戏开发六月-游戏开发

演示

https://hctra.cn/usr/uploads/2020/05/4013421588.mp4

简介

之前看一个用unity3d做的疫情模拟的视频感觉挺有意思的,而我正好也在学这个,眼看现在就要开学了,就想着按照我们学校做一个具体全面一点的疫情模拟。于是就开始了制作。下载地址在最下面。

制作流程

构思

首先第一步不是直接开始创建项目,而是写了一份大纲文档,构思制作的可行性,需要实现哪些功能,界面怎样布局,这一步花了不少时间,但有了它就可以节省开发过程中浪费的大量构思时间(深有体会)。

sp_2020-05-28_11-13-11.png
sp_2020-05-28_11-13-11.png

因为是做疫情模拟,数据量很大,所以其它方面要尽量抽象,突出重点也节省性能,所去找了张校园俯视图,然后绘制了一张抽象地图。 然后是功能,希望在开始模拟前,让一些参数给用户在相应范围内调整,例如学生总数,最初感染的人数,每次接触患者感染的几率,感染后多久后具备传染性,以及口罩类型,分为医用外科口罩、普通棉布口罩、医用防护口罩(N95),并查阅了这些口罩的防护效果,当计算是否感染时防护效果会用到。而有些参数需要用但不能给用户调整,例如碰撞传染检测频率、最小最大倍速、口罩减免效果、游戏时间与现实时间比例、管理行动的时间表等,给用户调整容易乱。还有游戏过程中需要能暂停和调整速度。 然后是UI,分为主界面、测试参数填写,游戏界面和暂停界面。UI和背景配色尽量简约,不是游戏整的太花就很怪,为了适应不同的手机屏幕,还需要给不同的UI设置相应的对齐方式。

开发

创建项目,然后绘制一张地图并用导航网格Back一下用于后面Ai寻路

sp_2020-05-24_19-39-30.png
sp_2020-05-24_19-39-30.png

创建一个Capsule作为学生,挂上NavMeshAgent(用于自动寻路)和rigidbody(用于检测感染)后保存为prefab,便于创建时复制。为了节省性能,关闭了物理效果,碰撞器改为触发器。

sp_2020-05-28_13-21-42.jpg
sp_2020-05-28_13-21-42.jpg

创建编写一些脚本:

  • GameController用于疫情模拟逻辑控制
  • StudentController控制学生行为和存储学生信息
  • EventScript用于UI事件控制
  • SwitchAnimr用于动画过渡
  • GameData保存各项参数
  • UtilFunction编写一些通用的静态方法便于调用
  • AudioManager管理各种音效 主要的就这些,其他的就不举例了。 实现学生作息自动管理
sp_2020-05-28_13-24-56.png
sp_2020-05-28_13-24-56.png

首先定义一个结构体Worktable,存储每个工作的开始时间和工作下标,工作分为上课(0)、回寝(1)、吃饭(2)、娱乐(3)。

代码语言:javascript
复制
public struct WorkTable
{
    public int startTime;       //单位:秒
    public int workIdx;
}

然后创建一个WorkTable数组,按顺序用于存储一天的作息,然后遍历

sp_2020-05-28_13-32-21.jpg
sp_2020-05-28_13-32-21.jpg
代码语言:javascript
复制
void Update(){
    if(gameStart)
    {
         ...
         if (isFree==false&&isAutoWork && gameTime >= workTable[workTableIdx].startTime &&
 (gameTime < workTable[workTable.Length - 1].startTime || (gameTime >= workTable[workTable.Length - 1].startTime && workTableIdx == workTable.Length - 1)))
        {
            //时间一到,所有学生进行该工作
            ChangeAllGoal(workTable[workTableIdx].workIdx);  
            workTableIdx = (workTableIdx + 1) % workTable.Length;
        }
    }
}
private void MakeDefWorkTable()
{
    workTable = new WorkTable[6];
    workTable[0].startTime = 7 * HOUR;workTable[0].workIdx = 1;
    workTable[1].startTime = (int)(11.5 * HOUR); workTable[1].workIdx = 3;
    workTable[2].startTime = (int)(13.5 * HOUR); workTable[2].workIdx = 4;
    workTable[3].startTime = (int)(14.5 * HOUR); workTable[3].workIdx = 1;
    workTable[4].startTime = (int)(17.5 * HOUR); workTable[4].workIdx = 3;
    workTable[5].startTime = (int)(19.5 * HOUR); workTable[5].workIdx = 2;
}

如果用户想自己来控制学生作息,点击按钮后直接调用ChangeAllGoal(工作序号)方法即可。

实现学生自由行动 当点击自由行动后,在GameController中调用所有学生的FreeWork()方法,在该方法中,先会随机给学生一个工作,然后用Invoke,在一定时间后再次调用该方法。直到用户点击管理行动后,在GameController取消所有学生的Invoke该方法。

代码语言:javascript
复制
//随机进行一个工作,并在1.5~3.5小时(游戏时间)后切换下一个,以此往复
public void FreeWork()
{
    int workIdx = Random.Range(1, 5);
    GameObject goal = null;
    switch (workIdx)
    {
        case 1:goal = teachingBuilding;break;
        case 2:goal = dormitory;break;
        case 3:goal = gc.canteens[Random.Range(0, gc.canteens.Length)];break;
        case 4:goal = gc.sports[Random.Range(0, gc.sports.Length)];break;
    }
    nav.SetDestination(goal.transform.position);
    Invoke(nameof(FreeWork), Random.Range(1.5f * 3600 / GameData.timeMultiple, 3.5f * 3600 / GameData.timeMultiple));
}

实现暂停和加速 暂停和加速只要修改Time.timeScale的值即可,但需要注意的是,iTween动画的速度也会随着时间速度的改变而改变,当Time.timeScale为0时,Invoke方法和iTween动画也暂停了,如果要让iTween动画不受时间速度所影响,可以在调用iTween动画时添加ignoretimescale参数并设为true即可。 实现视角移动 视角移动分为垂直移动和水平移动。 垂直移动:直接根据游戏界面右下角Handle移动的y值/可移动范围的一半,得出的比例乘以垂直移动速度,最后让相机坐标的y轴加上这个值即可。 水平移动: 在用户拖拽的每一帧,用该帧用户触碰到的点相对于上一帧触碰的点的偏移赋给一个Vector2变量moveVec,然后让相机坐标的z和x分别减去moveVec的y和x即可。优化:为了让不同的高度都保持同样的屏幕移动速度(避免出现相机拉近时屏幕移动飞快拉远移动缓慢),moveVec需要先乘以相机高度和一个移动系数,我实验得出的是0.00107f就刚好能让拖拽前点中的位置在拖拽过程中始终和地图上的点对应。

代码语言:javascript
复制
public void OnBeginDrag(PointerEventData eventData)
{
    carmBeginPos = Camera.main.transform.position;
}

public void OnDrag(PointerEventData eventData)
{
    //eventData.delta = 自上次更新以来的指针坐标增量变化。
    moveVec = eventData.delta * Camera.main.transform.position.y * ms;           //越高移动越快
    Camera.main.transform.position -= Vector3.forward * moveVec.y + Vector3.right * moveVec.x;
}

感染判断 感染概率计算公式: 在没有口罩的情况下,每次接触,感染概率为 取一个[0,1)之间的随机数a,再取一个[0,感染概率2]之间的随机数b,判断a是否小于b,小于就感染。以感染概率为5%为例,每次感染的平均概率就为5%。 而有口罩的话,b就还需要乘以1-口罩阻挡病毒比例。 Rand(0f,1f) < Rand(0f,infectedProbability2) * (wearMask?1-maskMulProbability:1)

代码语言:javascript
复制
public void Infected(bool _isInfected = false)
{
    if (isInfected) return;
    //感染判断
    if (_isInfected||Random.Range(0f,1f) < Random.Range(0f,GameData.infectedProbability*2)*(gc.wearMask?1-GameData.maskMulProbability:1))
    {
        isInfected = true;
        material.color = Color.yellow;
        Invoke(nameof(Contagion), GameData.ContagionTime / GameData.timeMultiple);
        FindObjectOfType<GameController>().Normal2Infected();
    }
}

大概就这些。

下载地址

Windows: https://lanzouw.com/iMqHEd977ub Android: https://lanzouw.com/id7q66d

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020 年 05 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 演示
  • 简介
  • 制作流程
    • 构思
      • 开发
      • 下载地址
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档