IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【3D RPG Core】学习笔记 -> 正文阅读

[游戏开发]【3D RPG Core】学习笔记

Unity光照效果的一些设置

  1. Windows—Rendering—Light可以打开光照属性的设置,在Environment里可以设置SkyBox、添加雾的效果;
  2. Hierarchy里右键创建Volume—Global Volume可以调整画面后处理效果;
  3. Asset里右键Create—Rendering可以添加URP管线Asset,调整管线的设置。

Global Volume的一些效果

  1. Bloom:过曝、光过量的效果
  2. Tonemapping:对比度更高、光影更强的颜色
  3. Color Adjustments:色彩调整
  4. Chromatic Aberration:鱼眼效果的强度
  5. Depth Of Field:物理相机的调节

序列化

类的对象是指在内存中的二进制代码。序列化是指将对象以字节流的形式存储到硬盘上,反序列化是指将字节流重新解码成二进制对象存放在内存上。
Unity能够在Hierarchy里拖拽给物体使用的类/脚本,一定是继承自MonoBehavior
类里的属性,如果要声明为private,又想在Unity Inspector里查看,就要标注为[SerializeField]
类作为属性,添加[System.Serializable],可以序列化,在Inspector里编辑。

单例模式

有时候场景中仅允许存在一个类型的对象,比如***Manager,或者背景音乐的对象,如果创建多份对象就会造成声音的重叠播放。
单例对象的创建有两种模式:懒汉式与饿汉式。
懒汉式:在需要时才创建实例对象
饿汉式:在类加载时就创建了实例对象。

public class MouseManager : MonoBehaviour
{
    public static MouseManager Instance;

    private void Awake()
    {
        if (Instance != null)
            Destroy(gameObject);
        Instance = this;
	}
}

如上所示,则在别处调用可以写为:

MouseManager.Instance.***

直接写类名调用,因为单例模式独一份,不需要再创建变量赋值。
通过为类设置静态对象,在类加载时就已经在对象内存的BSS区默认初始化Instance为null。然后类对象创建时会执行Awake函数,第一个类对象检测到Instance为null,所以创建Instance实例,第二个乃至第n个对象每次创建时Instance都不为null,于是先把游戏对象删去,再创建一个新的对象实例。以保证类的对象仅有一个。

通常在游戏工程中,把各种Manager都写成单例模式,如果每个Manager都写单例模式比较麻烦,所以写一个泛型的单例模式,然后所有Manager都继承这个单例模式。

UnityEvent

创建一个继承自UnityEvent的类(可以带参数),此类的实例,在启用Invoke()时可以将参数传递给目标对象。

using UnityEngine.Events;

// 创建一个继承自UnityEvent<Vector3>的类
[System.Serializable]
public class EventVector3 : UnityEvent<Vector3> { }

public class XXX : MonoBehaviour{
	// 实例化此Event为OnMouseClicked
	public EventVector3 OnMouseClicked;
	// 将XXX(需为Vector3传递给注册此Event(即OnMouseClicked)的对象)
	OnMouseClicked?.Invoke(XXX);
}

如下图所示,注册OnMouseClicked的对象是DogPoly的NavMeshAgent.destination,Event启动时将XXX传递给destination。在这里插入图片描述
UnityEvent是需要拖拽的,如果事件被很多方法注册,就要拖拽多次,不方便,所以可以改成用delegate事件来做。(如下)

委托与事件event

委托

返回值类型和参数列表相同的函数可以视为同种类型,委托就是同种类型函数指针的链表。
eg:同种类型

void showName(string name)
{
	Console.WriteLine("你先输出了名字,参数:"+name);
}
void showTitle(string title)
{
	Console.WriteLine("你又输出了主题,参数:"+title);
}

用函数指针链表(也就是委托)来将这两个函数入队列:

delegate void func(string f);

func linkedList;

linkedList += showName;
linkedList += showTitle;

向委托传递参数来执行链表中所有函数:
linkedList(“我是参数”)

委托使用+=来添加函数指针,-=来减少函数指针,=来清空函数指针。
调用委托就会执行链表中所有函数指针。

事件(event Action实现

事件就是委托,只不过事件修改了委托的封装性,事件仅可以由拥有这个事件的对象执行。
如下所示,用delegate void System.Action<in T>(T obj)来声明一个事件OnMouseClicked并使用的例子。

using System;
...
public event Action<Vector3> OnMouseClicked;
...
OnMouseClicked?.Invoke(XXX); // XXX 是Vector3类型的变量

当事件启动时,注册/订阅事件的所有方法都会被传参、调用。
如上所示,当OnMouseClicked事件Invoke的时候,所有订阅此事件的方法会被传值XXX并调用。

事件

事件就是委托,只不过事件修改了委托的封装性,事件仅可以由拥有这个事件的对象执行。

class Program
{
    //0.声明一个委托类型
    delegate void MyDelegateHanDler(String str);
    //1.基于委托类型声明一个事件
    static event MyDelegateHanDler MyEvent;
    static void Main(string[] args)
    {
        //2.为事件绑定方法
        MyEvent += FunA;
        MyEvent += FunB;
        MyEvent("参数");//3.执行事件
        MyEvent -= FunB;//解除绑定
        //3.Invoke 方法可以显式执行事件中的方法(每次 MyEvent()相当于  MyEvent.Invoke()    
        MyEvent?.Invoke("参数2");
    }

    static void FunA(String str)
    {
        Console.WriteLine("执行方法A:" + str);
    }
    static void FunB(String str)
    {
        Console.WriteLine("执行方法B:" + str);
    }
}

MouseManager需要的事件包括OnMouseClicked,当我们希望一个游戏对象在单击鼠标的时候干些什么事,我们就可以把该对象干的事情注册到OnMouseClicked事件中,然后由MouseManager执行事件。

IEnumerable和IEnumerator

IEnumerable是可枚举的意思,IEnumerator是枚举器的意思;
IEnumerable源码:

 public interface IEnumerable
 {
     IEnumerator GetEnumerator();
 }

继承IEnumerable接口的类可以获得一个Enumerator来枚举这个类中集合中的元素。比如List< T >,ArrayList,Dictionary等继承了IEnumerable接口的类。
IEnumerator源码:

 public interface IEnumerator
 {
     object Current { get; }
     bool MoveNext();
     void Reset();
 }

添加链接描述

Yeild return

Yield Return是可以让当前协程的进程状态切换到阻塞状态,并且保存当前函数的上下文,然后把CPU交给当前的主进程,这样就转而执行调用方的函数。把一个比较大的任务拆到每一帧里去执行,而不会在程序运行的时候出现卡顿、等待的问题。
yield return 可以接null、一个IEnumerable类型(比如说List)的集合中的一个变量、一个需要分散在各个帧里执行的协程、一个必须等待的时间…
eg1:直接返回一个IEnumerable类型(比如说List)的集合中的一个变量
比如打印输出100000个数据,我们需要得到里面的偶数。

 //测试求1到100之间的全部偶数
 private List<int> _numArray;
 
 ... _numArray赋值 ...
 
 static  public void TestMethod()
 {
     foreach (var item in GetAllEvenNumber()) 
     {
         Console.WriteLine(item); //输出偶数测试
     }
 }

 //测试我们使用Yield Return情况下拿到全部偶数的方法
static  IEnumerable<int> GetAllEvenNumber()
 {

     foreach (int num in _numArray) 
     {
         if(num % 2 == 0) //判断是不是偶数
         {
             yield return num; //返回当前偶数

         }
     }        
     yield break;  //当前集合已经遍历完毕,我们就跳出当前函数,其实你不加也可以
     //这个作用就是提前结束当前函数,就是说这个函数运行完毕了。
}

当我们通过yield return,得到一个偶数会立马显示在控制台上,而不是等待把所有偶数都找到之后再一一遍历输出,这样打印数据会让用户感受到有延迟。这样假如我们很多数据,就会一直等这个数据加载好了,才可以拿到这个数据显示给用户。

添加链接描述

eg2: 异步加载中返回需要分布在各个帧里执行的协程
异步场景加载时,使用协程 + yield return实现异步加载。

using UnityEngine.SceneManagement;
void Test()
{
	StartCoroutine(LoadOtherScene(otherScene.name));
}
IEnumerator LoadOtherScene(string sceneName)
{
	if(SceneManager.GetActiveScene().name != sceneName)
	{
		yield return SceneManager.LoadSceneAsync(sceneName);
		yield break;
	}
}

eg3:返回一个必须等待的时间(通过new WaitForSeconds)

IEnumerator LoadSceneAsync(string sceneName)
{
    // yield return 后跟 new WaitForSeconds(t)
    // 表示协程至少等待 t 时间才会结束
    yield return new WaitForSeconds(1);

    AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName);
    // Action<AsyncOperation> AsyncOperation.completed 是一个事件
    // 添加OnLoadScene,表示事件触发(场景加载完成后)调用的OnLoadScene。
    asyncOperation.completed += OnLoadScene;
}

下个结论:Yield Return关键字的作用就是退出当前函数,并且会保存当前函数的执行上下文。下次执行这个函数从断点开始继续执行。
但是一般的return result;之后,之后再调这个函数是会从第一步开始重新执行的,不会记录上次执行的地方。

协程

伴随着另外一些事情同步运行的,通常都会采用协程来做;
需要while的,用协程来做;
加载场景,用协程来做。
Unity协程原理

 void Update() {
     Debug.Log("001");
     StartCoroutine("Demo");
     Debug.Log("003");
 }

 private void LateUpdate() {
     Debug.Log("005");
 }

 IEnumerator Demo() {
     Debug.Log("002");
     yield return 0;
     Debug.Log("004");
 }

运行结果:
在这里插入图片描述
可以看出,yield return 0后的内容轮到下一个Unity的生命周期调用,且调用位置是Update()LateUpdate()之后。

Script Object

游戏数值、设置类型可以写作ScriptObject,这样可以直接在asset里生成对应数值的模板,方便添加和管理数值。
eg:

// ChracterData_SO.cs
[CreateAssetMenu(fileName="New Data",menuName="Chracters Stats/Data")]
public class ChracterData_SO : ScriptableObject{
	public int maxHealth;
	public int curHealth;
}

完成以上语句后就可以在asset里Create Chracters Stats下的Data文件,文件名为New Data,然后可以直接在New Data里进行maxHealth和curHealth的赋值。
但是继承自ScriptObject的类不能挂载给物体,所以还要创建继承自MonoBehavior的类,用来get、set读写SO的值。

get、set到属性

有些属性设置为public不是很安全,不想被别的代码随意更改,但是又想控制它的读写权限,就可以用get、set来做,控制它属性的读写。

接口模式A

接口模式有点像C++里的虚继承,不能有自己的实例。接口里面只做方法的定义,不做方法的实现。
在这里插入图片描述

public interface IKillable{
	void Kill();
}

public interface IDamageable<T>{
	void Damage(T damageTaken);
}

接口模式的要求:实现接口的类必须公开声明这个接口中的所有函数方法。

public class Avatar : MonoBehaviour, IKillable, IDamageable<float>{
	public void Kill(){...}
	public void Damage(float damageTaken){...}
}

Q 为什么有类了还需要接口?

  1. 类可以继承多个接口,但不能继承多个类;
  2. 接口用于跨多个不相关的类定义通用功能。
    比如wall和car,继承自单个类有点怪,但是继承自IDamageable表示它们都是可破坏的;或者当Player死了之后,所有角色都停止攻击、游戏结束。

通过接口实现观察者模式

观察者模式是指将继承接口的类称为观察者,对其进行收集,比如加入列表,当某个事件发生,让列表中的所有对象都执行接口中的方法(意即发出广播),而这个过程中,这些继承接口的类就被认为订阅 / 成为观察者。
eg:

// IEndGameObserver.cs
public interface IEndGameObserver{
	void EndNotify();
}
// GameManager.cs
...
List<IEndGameObserver> endGameObservers = new List<IEndGameObserver>();
...// when something happen
public void NotifyObservers(){
	foreach(var observer in endGameObservers){
		observer.EndNotify();
	}
}
// EnemyController.cs
public class EnemyController:MonoBehaviour, IEndGameObserver{
	void OnEnable(){
		GameManager.Instance.endGameObservers.Add(this);
	}
	void EndNotify(){
		// 执行游戏结束的动作
	}
}

Timeline制作动画效果

可以用在游戏里播放动画的效果。
Windows—Sequence—Timeline打开Timeline,给物体添加Playable Director组件控制Timeline或者在Timeline窗口里Create一个Timeline文件。
然后可以将物体拖入Timeline左侧,录制模式启动,添加关键帧动画(比如在Transform里Add Key添加,控制相机的位移效果);还可以直接将现成的anim素材拖入轨道,但是一个轨道只能控制一个效果,所以如下所示,为Player的RunForwardBattle轨道点击三个小点添加Override Track,通过关键帧控制Transform。
在这里插入图片描述
在代码里using UnityEngine.Playables,可以通过PlayableDirector获取场景中的Timeline控制。

ActionTrack

Timeline中添加ActionTrack控制是否启动某个物体。比如可以为EventSystem添加ActionTrack,设置不启动,那么在播放动画的时候EventSystem就不能工作、影响动画了。

Shader效果

遮挡剔除

使用Shader Graph中的 ** Fresnel+Dither(噪点来实现) **角色被遮挡后的视觉效果。
在这里插入图片描述
创建好Shader Graph之后,根据此shader创建Material(如名为Occlusion),在管线设置 / Universal Render Pipeline Asset Renderer中添加Renderer Feature,Material设置为Occlusion,Layer Mask设置为需要实现此效果的Layer,深度测试选Greater(当这些Layer的深度更远,即被遮挡时使用此Renderer Feature渲染);
在这里插入图片描述
实现此效果后,这些Layer的物体本身也会被自遮挡,所以再创建一个Renderer Feature,点选Layer Mask,其余保持,则自遮挡就无了。在这里插入图片描述

半角向量指的是光入射方向和观察视线的中间向量

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 21:12:26  更:2022-10-08 21:14:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/17 5:53:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码