Stealth——01场景的基本搭建以及基础逻辑
版权申明:
- 本文原创首发于以下网站:
- 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123
- 优梦创客的官方博客:https://91make.top
- 优梦创客的游戏讲堂:https://91make.ke.qq.com
- 『优梦创客』的微信公众号:umaketop
- 您可以自由转载,但必须加入完整的版权声明
搭建场景:
场景布局,设置static属性,设置Layer层,调整属性(Carmera,Lighting)
一般复杂的场景为了渲染效果都会做的很精细(有很多三角形构成),但是碰撞检测这种内部事件就可以简单点,会有场景的一个低模mesh来表征碰撞,降低性能消耗 //mesh collider的convex选项表示该模型是个凸多边形,(一般我们创建的都是凸多边形,像山洞啊什么的可能就为凹多边形)
把场景都设置为Static //光照 导航 遮挡都是静态的 //在unity中参与Lightmaps烘焙的物体必须都是静态的对象
增加灯光,对场景进行烘焙,把烘焙后的内容作为贴图,贴到物体上
设置light的效果:
添加灯光:
Lighting烘焙: 把Light的Baking属性设置为Baked, 在需要烘焙的物体设置为static,可以在Lighting->Scene中把Precomputed realtime GI(预计算的实时光照去掉) //烘焙完成后会生成物体的光照贴图的信息(会有一个Scene同名的文件夹)贴到被设置为Static的物体上
Light中backed Shadow Radius决定了back类型的光照照出来的阴影的散射半径,但其值为0时,表示不散射,此时hard Shadow和soft shadow没有差别,但该值越大,soft shadow的阴影散射的就越厉害
Light中选择realtime下soft shadow的表现形式受到反射次数等参数影响,且需要在Lighting->scene中打开precomputed realtime GI,关闭Baked GI的选项
Light中的Mixed光照模式,包含了实时计算和Baked两个功能,当物体没有动,光照没变化时,采用baked的光照信息;当物体运动时,使用precomputer teatime 实时计算光照shadows //听说用的不多?实现的是静态物体透视到动态物体上的阴影,因为静态物体不会动,所以阴影基本是固定的,bake好阴影贴图,动态的贴到动态物体上?
Light中的Culling Mask表示有灯光作用的物体标签类型,不选择中的标签不受光照影响
增加一盏场景灯和报警灯,场景灯提供场景关照,报警灯当发现玩家后进行报警的视觉和声音效果:
报警灯脚本:
public class AlarmLight : MonoBehaviour
{
public float fadeSpeed = 2f; //灯光报警fade的速度(默认2s变化一次)
public float hightIntensity = 4f; //最高最低亮度
public float lowIntensity = 0.5f;
public float changeMargin = 0.2f; //插值阈值
public bool alarmOn;
private float targetIntensity; //目标亮度值
private Light alarmLight;
private AudioSource audioSource;
private void Awake()
{
alarmLight = GetComponent<Light>();
audioSource = GetComponent<AudioSource>();
alarmLight.intensity = 0;
targetIntensity = hightIntensity;
}
// Update is called once per frame
void Update()
{
if (alarmOn)
{
if (!audioSource.isPlaying)
audioSource.Play();
alarmLight.intensity = Mathf.Lerp(alarmLight.intensity, targetIntensity, fadeSpeed * Time.deltaTime);
if (Mathf.Abs(targetIntensity - alarmLight.intensity) < changeMargin)
{
if (targetIntensity == hightIntensity)
targetIntensity = lowIntensity;
else
targetIntensity = hightIntensity;
}
}
else
{
if (audioSource.isPlaying)
audioSource.Stop();
alarmLight.intensity = Mathf.Lerp(alarmLight.intensity, 0, fadeSpeed * Time.deltaTime);
}
}
}
烘焙场景Lightmaps
//烘焙场景是使用bake类型的灯光对场景进行烘焙,生成贴图贴到物体表面,用于表现灯光效果
Unity的官方解释:
烘焙的意义:单独使用 Unity 实时光源的光线时,这些光线不会自动进行反射。为了使用全局光照等技术创建更逼真的场景,我们需要启用 Unity 的预计算光照解决方案;Unity 可以计算复杂的静态光照效果(使用称为全局光照(简称 GI)的技术)并将它们存储在称为光照贴图的纹理贴图中作为参考。这一计算过程称为烘焙。对光照贴图进行烘焙时,会计算光源对场景中静态对象的影响,并将结果写入纹理中,这些纹理覆盖在场景几何体上以营造出光照效果。
(这些光照贴图既可以包括照射到表面的直射光,也可以包括从场景内其他物体或表面反射的间接光。该光照纹理可与颜色(反照率)和浮雕(法线)之类的对象表面信息材质相关联的着色器一起使用。
使用烘焙光照时,这些光照贴图在游戏过程中无法改变,因此称为“静态”。实时光源可以重叠并可在光照贴图场景上叠加使用,但不能实时改变光照贴图本身。
通过这种方法,我们可在游戏中移动我们的光照,通过降低实时光计算量潜在提高性能,适应性能较低的硬件,如移动平台)
预计算实时全局光照
虽然静态光照贴图无法对场景中的光照条件变化作出反应,但预计算实时 GI 确实为我们提供了一种可以实时更新复杂场景光照的技术。
通过这种方法,可创建具有丰富全局光照和反射光的光照环境,能够实时响光照变化。这方面的一个典型例子是一天的时间系统:光源的位置和颜色随时间变化。如果使用传统的烘焙光照,这是无法实现的
优势和代价
虽然可以同时使用烘焙 GI 光照和预计算实时 GI,但要注意,同时渲染两个系统的性能开销也是各自开销的总和。我们不仅需要在视频内存中存储两组光照贴图,而且还要在着色器中进行解码的处理。
在什么情况下选择什么光照方法取决于项目的性质和目标硬件的性能。例如,在视频内存和处理能力局限性更大的移动端,烘焙 GI 光照方法可能具有更高性能。在具有专用图形硬件的独立计算机或最新款的游戏主机上,很可能可以使用预计算实时 GI,甚至同时使用这两个系统。
必须根据特定项目和所需目标平台的性质来决定采用哪种方法。请记住,在面向一系列不同硬件时,通常情况下,性能最低的硬件将决定选取哪种方法。
添加灯光:
Render Mode表示渲染的类型:Important表示逐像素渲染灯光,Not Impritant表示逐顶点渲染灯光,Auto表示自动
设置渲染质量:Project Setting -> Quality面板,因为我们游戏场景中的资源偏多,可能存在效率问题,所以我们我们渲染质量选择Good; 设置Rendering中 Pixel Light Cout(逐像素渲染的灯光的数量),因为我们灯光都选择的Auto的类型,所有应该Unity会挑6盏灯逐像素渲染,其他都逐顶点渲染(Unity怎么挑的???)
设置光照贴图的设置,之后就可以开始Bake了:
具体参数可以参考Unity官方文档,2018开始Unity的Lightmapper中提供了一种新的烘焙方式Progressive,此方式进行渐进式的光照贴图烘焙,一个简单的场景烘焙时间都到10个小时起
烘焙完成后可以在Scenes下找到和场景同名的文件夹中存放的就是烘焙后的数据,会作为贴图,贴到物体上
烘焙完成后的贴图自动贴到了场景物体上,此时关闭所有灯光,物体依旧具有关照的效果
添加Tag的管理类 //用来定义Tag的字符串的静态变量
- 添加转场效果 //加载场景逐渐变亮,退出场景逐渐变暗
- 使用RawImage遮挡这个画面实现,代码部分:
public class ScreenFadeInOut : MonoBehaviour
{
public float fadeSpeed = 1.5f;
private bool sceneStarting;
private RawImage rawImage;
// Start is called before the first frame update
void Start()
{
sceneStarting = true;
rawImage = this.GetComponent<RawImage>();
}
// Update is called once per frame
void Update()
{
if (sceneStarting)
{
rawImage.color = Color.Lerp(rawImage.color, Color.clear, fadeSpeed * Time.deltaTime);
if (rawImage.color.a <= 0.05f)
{
rawImage.color = Color.clear;
sceneStarting = false;
rawImage.enabled = false;
}
}
}
public void EndScene()
{
rawImage.enabled = true;
rawImage.color = Color.Lerp(rawImage.color, Color.black, fadeSpeed * Time.deltaTime);
if (rawImage.color.a > 0.95f)
SceneManager.LoadScene(0);
}
}
- 添加游戏控制器GameController //负责控制背景音乐播放,角色位置管理
- 为GameController添加Audio文件, //这里我们添加了两个Audio都设置play ON Awake和loop,区别在于normal主音量值为1 ,Panic被发现时的音量值为0 //我们要实现的效果就是当玩家被发现时,主音量逐渐降低,Panic音量逐渐提高
- 具体脚本:
public class LastPlayerSighting : MonoBehaviour
{
public Vector3 position = new Vector3(1000f, 1000f, 1000f); //表示玩家最后一次被发现的位置,如果没有被发现,就设置为默认值
public Vector3 resetPosition = new Vector3(1000f, 1000f, 1000f);
public float lightHighIntensity = 0.25f; //主灯光的亮度范围
public float lightLowIntensity = 0f;
public float lightFadeSpeed = 7f;
public float musicFadeSpeed = 1f; //音乐变化的fade速率
public bool isPlayerFound = false;
private AlarmLight alarmLightScript;
private Light mainLight; //主灯光
private AudioSource mainMusic; //主音乐和panic时播放的音乐
private AudioSource panicMusic;
private AudioSource[] sirens; //报警音乐
private const float muteVolume = 0f; //音乐的变化范围
private const float normalVolume = 0.8f;
// Start is called before the first frame update
void Start()
{
alarmLightScript = GameObject.FindGameObjectWithTag(Tags.ALARM_LIGHT).GetComponent<AlarmLight>();
mainLight = GameObject.FindGameObjectWithTag(Tags.MAIN_LIGHT).GetComponent<Light>();
mainMusic = this.GetComponent<AudioSource>();
panicMusic = this.transform.Find("Secondary_music").GetComponent<AudioSource>();
//sirens = new AudioSource[];
}
// Update is called once per frame
void Update()
{
isPlayerFound = (position != resetPosition);
//当玩家被发现时,调低主灯光,打开报警灯,淡出主音乐,淡入panic音乐, 但玩家脱离危险后恢复;
mainLight.intensity = Mathf.Lerp(mainLight.intensity, isPlayerFound ? lightLowIntensity : lightHighIntensity, lightFadeSpeed * Time.deltaTime);
alarmLightScript.alarmOn = isPlayerFound;
mainMusic.volume = Mathf.Lerp(mainMusic.volume, isPlayerFound ? muteVolume : normalVolume, musicFadeSpeed);
panicMusic.volume = Mathf.Lerp(panicMusic.volume, isPlayerFound ? normalVolume : muteVolume, musicFadeSpeed);
}
}
添加CCTV Carmera
- //CCTV闭路电视 //碰撞的触发依赖于Riggdbody组件,没有Riggdbody就不会触发trigger和collision(为了防止两个单独的collider互相触发)
- 添加Carmera的模型
- 添加mesh collider和spot光源
- 添加碰撞脚本:
public class CCTVCollision : MonoBehaviour
{
private LastPlayerSighting lastPlayerSighting;
private void Start()
{
lastPlayerSighting = GameObject.FindGameObjectWithTag(Tags.GAMECONTROLLER).GetComponent<LastPlayerSighting>();
}
private void OnTriggerStay(Collider other)
{
if (other.tag == Tags.PLAYER)
{
lastPlayerSighting.position = other.transform.position;
}
}
private void OnTriggerExit(Collider other)
{
if (other.tag == Tags.PLAYER)
{
lastPlayerSighting.position = lastPlayerSighting.resetPosition;
}
}
}
- 添加Animator使其旋转, 注意Animator中可以设置curves调整动画变化的速率
添加Laser Grid(激光栅栏)
- //一般Main Camera上会挂一个Audio Listener用于收音(一个场景只允许由一个Audio Listener)
- //在unity当前版本中Audio Source要设置Spatial Blend到3D,下面的3D Sound Settings才会生效(会有一个空间衰减的相关)
- 添加laser的模型(并使其和场景匹配),box collider,Light(红色点光源)和Audio Source
- 为Laser添加脚本,控制激光栅栏的开关和碰撞检测:
- 激光栅栏的开关:
public class LasterBlinking : MonoBehaviour
{
public float onTime; //灯灭的时间间隔
public float offTime; //灯亮的时间间隔
private float timer; //流逝的时间
private Renderer laserRenerer;
private Light laserLight;
// Start is called before the first frame update
void Start()
{
laserRenerer = GetComponent<Renderer>();
laserLight = GetComponent<Light>();
timer = 0;
}
// Update is called once per frame
void Update()
{
timer += Time.deltaTime;
if (laserRenerer.enabled && timer >= onTime)
{
laserRenerer.enabled = false;
laserLight.enabled = false;
timer = 0;
}
else if (!laserRenerer.enabled && timer >= offTime)
{
laserRenerer.enabled = true;
laserLight.enabled = true;
timer = 0;
}
}
}
- 碰撞检测:
public class LaserPlayerDetection : MonoBehaviour
{
private void OnTriggerStay(Collider other)
{
if (other.tag == Tags.PLAYER && this.GetComponent<Renderer>().enabled)
{
LastPlayerSighting.Instance.position = other.transform.position;
}
}
}
Stealth——01场景的基本搭建以及基础逻辑的更多相关文章
- php从入门到放弃系列-01.php环境的搭建
php从入门到放弃系列-01.php环境的搭建 一.为什么要学习php 1.php语言适用于中小型网站的快速开发: 2.并且有非常成熟的开源框架,例如yii,thinkphp等: 3.几乎全部的CMS ...
- ionic搭建与基础
ionic搭建与基础 一.环境搭建 安装 npm install -g cordova npm install -g ionic 创建 项目 ionic start MyProject blank i ...
- 搭建webpack基础配置
搭建webpack基础步骤: 1.去官方网站下载node.js(根据自己电脑的系统类型选择) 2.安装node.js完成后打开cmd命令提示符: 出现版本号证明安装成功 3.cd到工程目录下 npm ...
- 测试那些事儿—Linux搭建环境基础步骤
Linux搭建环境基础步骤 准备工具:SecureCRT工具(Linux工具,连接服务器)FTP传输工具(上传文件到服务器)MySQL连接工具 安装包(以下文件均为压缩包rpm格式和tar.gz):J ...
- 搭建Istio基础环境
需求 搭建istio基础环境(基于1.5.1版本) 安装步骤 在安装 Istio 之前,需要一个运行着 Kubernetes 的环境,安装步骤可以参考前面的文章 下载istio,然后解压,然后将 is ...
- 搭建SSM基础环境>基于idea
目录 搭建SSM基础环境 创建一个Web项目 导入所需要的jar包 在项目目录下创建一个Resources文件夹并设置为类路径 在src目录下创建项目的初始文件夹目录 在resources文件夹下创建 ...
- 黑马程序员_java基础笔记(01)...java的环境搭建
—————————— ASP.Net+Android+IOS开发..Net培训.期待与您交流!—————————— JavaSE(Java Standard Edtion java标准版)技术概况 ...
- JavaSE 学习笔记01丨开发前言与环境搭建、基础语法
本蒟蒻学习过C/C++的语法,故在学习Java的过程中,会关注于C++与Java的区别.开发前言部分,看了苏星河教程中的操作步骤.而后,主要阅读了<Java核心技术 卷1 基础知识>(第8 ...
- 零基础Android学习笔记-01 安卓开发环境搭建
安卓开发环境搭建. 1.首先准备JDK,从官网找到JDK下载地址,原来做.NET不熟悉JAVA,干脆用最新的,下载了JDK 1.7的版本.原来装过1.5还要配置环境变量什么的.但1.7好像很给力,装好 ...
随机推荐
- GStreamer流媒体知识介绍
GStreamer框架 1.GStreamer是什么? 众所周知,Microsoft's Windows和Apple's MacOS对多媒体设备.多媒体创作.播放和实时处理等方面都有很好的支持,而Li ...
- 安装CUDA9.0及对应版本的tensorflow-gpu详细过程(Windows server 2012R2版本也可以)
由于最近跑机器学习相关代码的时候CPU运算速度跟不上,这才利用GPU来运算代码,显然使用GPU来运算速度明显要快很多,但是搭配GPU的使用环境是真的麻烦且头疼.网上有很多牛人的搭建过程,虽然他们都成功 ...
- python多线程同步实例分析
进程之间通信与线程同步是一个历久弥新的话题,对编程稍有了解应该都知道,但是细说又说不清.一方面除了工作中可能用的比较少,另一方面就是这些概念牵涉到的东西比较多,而且相对较深.网络编程,服务端编程,并发 ...
- Java——数据结构(链表)
链表,可扩展长度,泛型. public class Link { Node header = null; //头结点 int length;//当前链表长度 class Node { Node nex ...
- 想转行大数据,开始学习 Hadoop?
学习大数据首先要了解大数据的学习路线,首先搞清楚先学什么,再学什么,大的学习框架知道了,剩下的就是一步一个脚印踏踏实实从最基础的开始学起. 这里给大家普及一下学习路线:hadoop生态圈——Strom ...
- Android——倒计时跳转+sharedpreferences
public class MainActivity extends Activity { // 3秒钟后,从图1跳转到图2(10) private Handler handler=new Handle ...
- Flink Metrics 源码解析
Flink Metrics 有如下模块: Flink Metrics 源码解析 -- Flink-metrics-core Flink Metrics 源码解析 -- Flink-metrics-da ...
- 本地(任意)时间戳转化(转换)标准时间格式 js(eg:2019-05-07 17:49:12)
<script> function getLocalTime(timestamp) { // 如果以秒为单位 // var dateObj = new Date(timestamp * 1 ...
- Salesforce LWC学习(四) 父子component交互 / component声明周期管理 / 事件处理
我们在上篇介绍了 @track / @api的区别.在父子 component中,针对api类型的变量,如果声明以后就只允许在parent修改,son component修改便会导致报错. sonIt ...
- 聊聊我在这家公司设计的SSO
最近小明遇到一个需求:需要将几个独立的系统(子系统)汇总到一个集中的系统(父系统)当中,当用户在父系统登录过后,再点击这几个子系统,就可以免登录跳转到任意一个系统.当时一听,duang~duang~就 ...