unity note: 游戏和三维互动内容开发工具,专业游戏引擎
游戏引擎: 一款游戏最核心的代码 包含: 渲染引擎,物理引擎、碰撞检测,音效、脚本引擎、动画系统 人工智能、网络引擎、场景管理。
游戏公司分工 策划、美工、软件工程师、测试工程师、客服
首月:
day1:
环境搭建、C#语言基础、Unity API、物理引擎1、3D数学基础、UGUI
程序界面 Project->Assets文件 Hierarchy-> 对象 Scene 右键旋转 滚轮->前进后退 F键—>居中 Alt+左键 围绕旋转 WSADQE同时:场景漫游Inspector: 检查监视面板 Transform -> Position 坐标
3d->cube 顶点吸附 按V键
Pivot - Center 设计坐标-中心坐标 Global - Local 世界坐标-自身坐标
视图: IOS(2D) - Persp(透视)3D
世界坐标: 场景坐标 本地坐标: 物体自身坐标
场景: 一组相关联的游戏对象的集合。
游戏对象: GameObject ->容器—>组件->{ 功能的模块{ Transform变换组件、 MeshFilter网格过滤器、 Mesh Render 网格渲染器} }
day02: 1.材质material: 色彩,文理、光滑度、透明度、反射率、折射率、发光度。 png使用较多,psd(photoshop文件) 材料模式(Render mode): opaque(不透明的) cutout(去掉透明通道)->剪裁 Fade(渐变) Transparent(透明)要设透明度 shader 着色器: 渲染管线,控制GPU Shader->Material{Texture(材质),Color}->Object 2. 摄像机camera:在一个场景中出现数量不限,显示方法-》视锥 组件: Transoform变换组件 camera flare Layer 耀斑层 audio listener 音频监听器:接收场景输入的音频
camera-> Clear Flags: 处理屏幕空白部分 -> 天空盒 - 模拟天空材质 -> 6面组成,程序的。 material-> shader->skybox ->DepthOnly ->去除白色部分
使用-> a. 摄像机添加-addcomponent-> skybox b. 光照窗口(物体可反射颜色,常用) Windows->Lighting->skybox
-> Procedural 有太阳、只有2种(天、地)。 Culling Mask 去除层。 将某层元素去除。该层不渲染 Projection(投射) Perspective(透视的,三维的视角)/orthographic(直角的)-> 去除纵深 Clipping Plane : Far设置视距 Near: 多远才能看到 Viewport Rect: X,Y,W,H : XY视窗坐标,WH设置视窗大小 可实现分屏。 Depth map的depth大于主摄像机,map摄像机才能显示 (要在前面的深度值高)3.InstantOc 渲染管线: 图形数据在GPU上经过运算最后输出到屏幕的过程 游戏-> 图形API -> GPU -> 顶点处理->… Draw Call : 准备数据并通知GPU的过程,每帧调用显卡渲染物体的次数 = Batches ↓顶点处理: 接收模型顶点数据。顶点转换 ↓图元装配: 组装面 ↓光栅化: 计算三角面上的像素 ↓像素处理: 对每个像素区进行着色 ↓缓存: 帧缓存/深度缓存z-buffer
Occlusion culling: 遮挡剔除@@@ 将摄像机视角内看不到的物体剔除,提升渲染性能 https://blog.csdn.net/qq_33537945/article/details/79159070 步骤-> 将参与剔除的物体标记遮挡静态(Occluder Static/Occludee Static) Windows-> OcclusionCulling 单击下方Bake API IOC layer 层 IOC Tag 层标签 simples 采样率一般在150-500之间 Rays FOV 和摄像机field of view 一致 View Distance 参考摄像机Far Hide Delay 当前物体被剔除时延迟的帧数。一般50-100之间。 PreCull Cheack : 检查采集信息 Realtime Shadows : 实时阴影
LOD: Layer of detail 实现分级渲染 步骤->多精度模型加入父类object,父类object添加LOD Group 优点: 节省GPU 缺点: 占内存
物体添加-> Box Collider 添加碰撞箱
光照系统: Global Illumination 全局光照 GI: 直接光、间接光、环境光、反射光。 light选项: 方向、color、Intensity、Culling Mask Type: Spot聚光灯、Direction方向光、Point(灯泡)、Area Culling mask: 照射选定层<8,10,12章> 《3D数学基础》15章 :图形数学
day03
阴影: Light-> Shadow Type:有无阴影,软硬阴影 strength:强度 Receive Shadow:接收阴影 mesh Render - >Cast shadow-> 只阴影,无阴影,双面阴影 阴影剔除 :节省资源 edit-> project setting -> shadow->distance
环境光: 作用于场景内所有物体的光照。 Lighting -> Environment Lighting -> Ambient source: 环境光来源 Intensitive: 强度
反射光: 根据天空盒计算的作用于 Lighting -> Environment Lighting ->reflection
间接光照 Light-> Bounce Intensity
实时GI 运行时改光源,运行时产生间接光。开发期间做了预计算 Edit -> Perferences->GI Cache
烘焙GI https://blog.csdn.net/leeby100/article/details/99827850 将光线效果预渲染成贴图再作用到物体上模拟光影 Light-> Baking Baked GI 游戏对象设置为Lightmaping Static。 设置Light Baking 属性 7.光源侦测 物体感受bake的光源。 小球储存光影信息。 Creat-> Light Probe Group
8。声源: 支持的音频格式: mp3,ogg,wav,aif,mod,it,s3m,xm 声音分为2D,3D 3D:空间感,2D背景音乐 Audio Listener: 声音监视器 Audio Souce: 声音源 Audio->Audio Source 3D Sound Setting
rolloff 衰减方式
Audio listener Audio
day03: .NET dotnet 新一代多语言开发平台。 c# csharp Mono 支持在其它操作系统下开发.NET程序框架 Unity 借助Mono实现跨平台
day04: P24 C#编程 类->方法 ->属性
命名空间-> 类-> {方法,属性} using … 引入命名空间 CTRL+K+F -> 自动对齐 CTRL+K+C -> 注释选中的代码 CTRL+K+U -> 取消注释
整数 sbyte 1个字节有符号8位 byte 1个字节无符号8位 short 2个字节有符号16位 ushort 2个字节无符号16位 int 4字节 uint 4字节 long 8字节 ulong 8字节
浮点数 float 4字节单精度浮点 double 8字节双精度浮点型 !decimal 128字节,精度28-29位、适用于财务和货币计算 C#的float赋值必须加f decimal赋值必须加m float a = 0.3f; decimal a = 2.9m; ->2.9的意思
->
浮点型运算会有舍入误差
字符 char 2字节,使用单引号 string 字符串,双引号
布尔型 bool 1字节,1,0
调试: 打断点-> F5调试-> F11执行下一行 Shift+F5 结束调试 直接输出结果 ctrl+F5
强制类型转换 字符串等转整数 1.(int)… (这个只适用于浮点数转换) 2.Convert.ToInt32(string) 3.int.Parse(string)或 int.TryParse(String,out int)
输出流:例子 Console.WriteLine(“枪名:”+gunName+ “\t弹匣容量:”+ magazineNum + “\t弹匣内子弹数量:” + currentMagazineNum + “\t当前子弹数量:” + currentbulletNum);
格式占位符 string a = string.Format("{0},{1}",a,b);
{0:d2} 5 -> 05 15-> 15 2位显示 {0:f1} 1.23-> 1.2 1.26-> 1.3 1位小数
" \ -> 转义符
《c#入门经典》 变量与变量表达式 《代码大全2》 10章,11章
day05 dotnet Mono
CLS : 公共语言规范 定义.net平台上运行的语言规范 CLR :公共语言运行库。 exe->CLR编译(汇编)-> 机器码
.cs文件-> CLS -> .exe -> CLR -> 010100011
运算符: 赋值运算符: = 运算符: + - * / %(取模) 比较运算 < > <= >= == != 逻辑运算 && || ! 快捷运算符 += *= /= %=
一元运算符 ++ – 二元运算符 = a + b 三元运算符 string a = 1>2?“3”:“4”
优先级 …
string转换为 其它类型 int.Parse …
Tostring : 任意类型转换为string int number = 18; string str = number.Tostring();
隐式转换, 自动转换; byte b3 = 100; int i3 = b3; -> TRUE
4.显式转换 : 强制转换; int i3 = 100; byte b3 = (byte)i3;
判断一个数奇偶性: bool r3 = a1%2 == 0;
定义字符数组 char[] a = new char[4];
byte b=250; b+= 7; 快捷运算符,可实现,不会自动提升类型 b = (byte) b+7; 会转换为int
语句: 选择语句、循环语句、 if(…) { … } else if(…) { … } else { … } 短路逻辑: && 和|| 与出现0,和或出现1 都不再执行后项
day06 、 !!! C#在函数内定义的变量为局部变量,循环结束后失效
循环语句: for(;😉 {…continue …break…} while(){} … do{} while()
注释// /**/
!创建随机数 Random random = new Random(); 产生一个随机数 int number = random.Next(1,101);
跳转语句: continue、break、return goto
函数(方法):
[访问修饰符][可选修饰符]返回类型 方法名称(参数列表) { … } private static void Fun1(){} //函数首字母大写
返回类型: int double short float string decimal void
day 07: 函数重载,根据参数数量判断。 名称相同,返回值相同。
递归: …函数自调用 优点: 复杂简单化 缺点: 性能差,难以理解
取绝对值: ststem.math.abs()
数组: int[] a; a = new int[6]; -> int[] a = new a[3]{1,2,3}; 初始化+赋值 a.Length Array.Clear(a) 清除元素值 Array.Copy(a) a.CopyTo a.Clone Array.IndexOf(a,num) Array.LastIndexOf(a,num) Array.Sort(a) Array.Reverse(a)
static float[] Createasdas(){ return scoreArray; -> 返回数组 }
day 08: //从头到尾 foreach(int var in arr){
} 二维数组 创建二维数组: int[,] a = new int[3,5]={0}; //初始化后全为0
2048
Arr.Length -> 获得总长度 Arr.GetLength(0或1) ->获得行数量,列数
day 09: 交错数组:每一个元素中间可以套数组。 4元素交错数组 int[][] array02; array02 = new int[3][]; array02[0] = new int[3]; array02[1] = new int[5]; array02[2] = new int[2]; array02: [ 000 00000 00 ] 赋值-> array02[0][0] = 99;
foreach(int[] array in array02){ foreach(int i in array){ … } }
!!参数数组
!!static int Add(params int[] arr) -> 传参数数组 1.可传递数组、传递一组数据类型相同的变量集合 2.可以不传递参数 params int[] arr; 参数数组
可以实现 -> Add(1,23,4,6,8,3,4); 不需要输入数组也可进入函数,自动转换为数组进入函数。
== Add(new int[]{1,23,4,6,8,3,4});
数据类型-> 值类型 -> 数据本身,声明在栈中,数据存储在栈中 结构-> int,bool,char 枚举->
引用类型 -> 存储数据的引用(内存地址),声明在栈中,数据存储在堆中 接口-> 类 -> string,int[],object 栈区:存储正在执行的方法 堆区:存储引用类型的数据 string s1 = "男"; == new string[]{...}; !引用类型声明赋值 相当于new。!!!引用参数: static void Fun2(ref int a){…} 使用 :Fun2(ref a2); 直接对地址能数据进行操作。
!!!输出参数: 按引用传递–传递实参变量的内存地址 static void Fun3(out int a){…} 使用 :Fun3(out a2);
ref out区别:1. 使用ref 前必须对ref赋值 使用out 在函数内部中必须对其赋值,传递之前可不赋值;
使用out实现有多个返回值的函数。
int a; bool re = int.TryParse(“250”,out a);
垃圾回收器: GC 针对托管堆自动回收释放内存的服务
拆装箱: int a = 1; object b = a; //装箱操作: “比较消耗性能” 装箱box: 值类型隐式转换为Object类型。 1.在堆中开辟内存空间。 2.将值类型的数据复制到堆中。 3.返回堆中新分配对象的地址。
int b = (int)o; //拆箱操作: 拆箱unbox: 从object类型到值类型 1.判断是不是装箱时的类型 2.返回已装箱实例中属于原值类型的地址。
形参object,实参值类型,则会发生装箱 通过重载、泛型可避免拆装箱
string str02 = “” + num; //string str02 = string.Contact(params object); 存在装箱操作
bool r1 = object.ReferenceEquals(); //判断存的是不是同一个
字符串池。 object a = 1; a = 2.0; a = “OK”; a = true; //object每次修改都是开辟新的空间
字符串拼接:! 防止造垃圾。 StringBuilder bulider = new StringBuilder(10); for (int i = 0;i<10;i++){ bulider.Append(i); } string result = bulider.Tostring();
bulider.Insert; string 方法: ToArray Insert Contains ToLower ToUpper IndexOf Substring Trim :去除开头的字符串 Split Replaace Join
day10: 枚举: 定义枚举类型: enum MoveDirection{ Up = 0, Down = 1, Left = 2, Right = 3
} 简单枚举: 列举某种数据的所有取值 增强可读性,限定取值
枚举的多选 enmu a | b |按位或运算符。 p91 1.任意多个枚举值做|运算,结果不能和其它枚举值相同 2.定义枚举时,使用[flags],表明可以多选 [Flags] enum PersonStyle{…} :定义标准枚举
&按位与运算 |按位或运算 ^非
Enum.Parse(typeof(PersonStyle),"…");
!类和对象 创建类;
访问级别 class 类名 { 类成员… } 通常每个类在一个独立的c#源文件中 创建新的类意味着在当前项目中创建了一个新的数据类型
this.name = name;
类 对象; 对象 = new 类();
成员变量: 类中方法外的变量,具有默认值,默认为null。
访问修饰符
!字段,属性: 字段首字母小写,属性首字母大写
!属性:对字段起保护作用。可实现只读,只写… public int Age{ get{ return this.age(字段); //获得age字段值返回 } set{ this.age(字段) = value; //value为获得属性,直接写value } }
只读: 只有get 只写:只有set
构造函数:初始化类的数据成员 public 类名(…参数) { … } 对没有构造函数的类会提供一个空构造函数。 构造函数可以重载
如果不希望在类的外部被创建对象, 可以将构造函数私有化。
单例 调用无参数构造函数:继承?
public 类名(…参数):this(…) !调用无参构造,或者this()里面加参数 {
}
构造函数重载+嵌套。
this 关键字:
一个标准类: class User { //字段 private string loginId; //属性 public string LoginId; { get{return this.loginId;} set{ this.loginId = value;} } public string Password{get;set;} //这个是语法糖
//构造函数 public User() { } public User(string id,string password):this(id) { } //方法 public void PrintUser() { console.WriteLine(....); }}
data.CopyTo;
Ctrl+m 提取函数
day10: C# 集合 列表集合: List<数据类型(int,char)>
List list02 = new List(); list02.Add(u1); list02.Add(new User());
字典集合: Dictionary<键,值> Dictionary<string,int>
Dictionary<string,User> dic = new Dictionary<string,User>; dic.Add(“123”,new User(“123”,123)); User lhUser = dic[“lh”];
//prop +tab+tab -> 快速创建属性
!继承: Class Son:Father { … } public:公开,都可以用 private:Father的,Son继承了但不能用。 protected级别:Fahter,Son可以用,别人不能用
//父类型的引用指向子类对象 //只能使用父类成员 Father person02 = new Son();
//要使用子类的要强转 Student stu02 = (Student)person02;
//↓如果转换失败就出错。 Teacher teacher02 = person02 as Teacher;
Static:静态 类变量和成员变量的区别。 Static 变量使用类名调用
private static int ascxv;
修饰成员变量: 静态成员变量属于类,实例成员变量属于对象
!静态: static
静态区: 单独存储。
静态构造函数:初始化类的静态数据成员 static Student() { … } 仅在类加载时调用一次
静态方法:只能访问静态成员。 this在静态方法中无效
静态类 使用static修饰的类: 不能实例化、只能包含静态成员。 不能被继承、但静态方法,属性可以被继承
利:单独空间存储、所有对象共享、可以直接被类名调用。 弊:静态方法只能访问静态成员、共享数据被多个对象访问会出现并发。 适用场合: 1.所有对象要共享的数据。 2.没有对象前要访问的成员。 3.工具类适合做静态(常用,不需要过多数据) (Math.Abs(),…)
结构体: 用于封装小型相关变量的值类型
结构属于值类型,类属于引用类型
struct Direction{ 只有两只方法可以赋值: const(常量)、static
public int RIndex{ get;int; } public Direction(int r,int c):this() { //如果有自动属性 {get;set;},必须先调用this() this.RIndex = r; }} 结构体自带无参构造函数
2048 核心类
day11: Unity编程
脚本: .cs的文本文件 类文件 附加在游戏物体上用于定义游戏对象行为指令代码 using 命名空间; public class 类名:MonoBehaviour { void 方法名() { … }
}
!文件名与类名必须一致 !写好的脚本必须附加到物体上才执行 !附加到游戏物体的脚本类必须从MonoBehaviour类继承
编译过程: 源代码–CLS->中间语言(DLL)–(Mono Runtime) -> 机器码
脚本就是一个类
Update() ->0.02会调用一次 !!!! 父类有Update子类不会调用,解决方法-> 父类-> protected virtual void Update(){
} 子类-> protected override void Update(){
}
public calss Demo01:MonoBehaviour {
}
!脚本生命周期(消息、必然事件): unity脚本从唤醒到销毁的过程。
[SelectionBase] //序列化字段 private int a = 100;
加上标签-> 私有变量显示在编译器中。
[HideInInspector] public int b; 在编译器中隐藏字段
[Range(0,100)] public int c; 在编译器中限制c的取值
unity脚本里面看不到属性。
unity中的脚本: //字段 //方法(函数) //脚本中不写构造函数
/********初始阶段:/ Awake: //执行时机: 创建游戏对象 立即执行 作用:初始化
Start: //执行时机: 游戏对象创建->脚本启用->执行 作用:初始化,充当构造函数
OnEnable: //执行时机:当脚本对象启用时调用
private void Awake() { Debug.log(“Awake” + Time.time + “–”+this.name); }
private void Start() { Debug.log(“Start” + Time.time + “–” + this.name); }
private void OnEnable(){…}
/***物理阶段/
FixedUpdate 固定更新: //执行时机,每固定时间(0.02s)执行一次 脚本启用后:固定时间被调用:适合对对象做物理操作 设置更新频率: “Edit” --> “Project Setting” --> “Time” --> “Fixed Timestep” 值,默认为0.02s
private void FixedUpdate() {//渲染时间不固定(渲染质量不同、配置不同) Debug.Log(Time.time); }Update 更新 //执行时机: 渲染帧执行、执行时间不固定 //适合性: 处理游戏逻辑 private void Update() {
}LateUpdat 延迟更新: //防止发生相机先手人后走的情况发生 //执行时机: 在Update被调用后执行,适用于跟随逻辑 private void LateUpdate() {
}/输入事件*******/ OnMouseEnter 鼠标移入 需要碰撞器 OnMouseOver 鼠标经过 OnMousExit 鼠标离开 OnMouseDown 鼠标按下 OnMouseUp 鼠标抬起
/场景渲染****/ OnBasecameVisible 当可见 当Mesh Renderer 在任何相机上可见时调用
OnBecameInvisible 当不可见: 当Mesh Renderer 在任何相机上不可见时调用
/结束阶段*****/ OnDisable 当不可用 当对象变为不可用或附属游戏对象非激活状态时调用
OnDestory 当销毁: 当脚本销毁或附属游戏对象被销毁时调用
OnApplicationQuit 当程序结束: 应用程序退出时调用
文档-> 索引 -> monobehaviour
脚本生命周期: AWake -> OnEnable -> Start
FixedUpdate->>>>>>OnCollision↓ ↑ <<<<<< ←(或↓)
yield WaitForEndOfFrame->OnApplicationPause … 具体详查脚本生命周期图。 https://www.cnblogs.com/android-blogs/p/6148188.html
-> OnDestory->OnApplicationQuit
Unity编辑器: 1.控制台调试 Debug.Log(变量); print(变量);
定义public变量,在程序运行后在控制台中看
VS调试 //快速监视 //即时窗口
逆向: p118
print继承自monobehaviour
单帧调试 -> 启动调试 运行场景 暂停游戏 加断点 单帧执行 结束调试
!常见API:
Unity核心类图 http://www.ajnote.com/unity-%E6%A0%B8%E5%BF%83%E7%B1%BB%E5%9B%BE/
Component: {
提供了(在当前物体、后代、先辈)查找组件的功能;this.transform.position = new vector3(0,0,10); //改坐标 this.GetComponent().material.color = Color.red; //改颜色/改材质。。
文档->Function->GetCompent
//获取当前物体所有组件
if (GUILayout.Button(“Components”)) {
var AllComponent = this.GetComponents<Component>(); foreach (var item in AllComponent) { print(item.GetType());} 所有子物体,包括后代的同一组件 getComponentsInChildren().color = color.red;
不带s,只找自己的组件。 getComponentInChildren()
//往上找所有祖宗 getComponentsInParent<>(); //只找上一代 getComponentInParent<>();
//对比标签是否一样。可以直接== CompareTag }
Transform: {
//通过当前变换组件获得每个子物体的变换组件 foreach (Transform Child in this.transform) { print(Child.name);
transform.position :在世界坐标系的位置
localPosition: 相对于父物体轴心点的位置
rotation/localrotation
localScale: 相对于父物体的缩放比例 lossyScale: 物体与模型缩放比例,相对于世界(只读) //父3,自身local:2, lossyScale 为6
方法(函数): Translate: 移动指定方向的距离。 this.transform.Translate(0,0,1); //向自身z轴移动一米
this.transform.Translate(0,0,1,Space.World); //基于世界坐标系移动
Rotate: 转动
//y轴转动10度(沿自身,沿世界) this.transform.Rotate(0,10,0) this.transform.Rotate(0,10,0,Space.World)
//沿选定轴旋转旋转 RotateAround this.transform.RotateAround(Vector3.zero,Vector3.up,1); //参数:点、轴、角度 例子:绕零点,y轴转1度,通过repetButton实现连续旋转
//获取根物体: Transform rootTF = this.transform.root; //获取父物体 Transform parentTF = this.transform.parent; //改爸爸-> 拿起物体 this.transform.SetParent(Transform x);
//找孩子根据名称 this.transform.Find(子物体名称); //找孙子 this.transform.Find(子物体名称/子物体名称);
//根据索引找孩子 int count = this.transform.childCount; this.transform.Getchild(i) //i=0;i<count;i++
//解除父子关系 DetachChildren //解除父亲关系 SetParent(null);
//找兄弟索引 GetSiblingIndex
//改变兄弟等级 SetAsFirstSibling SetAsLastSibling
拖物体归属-> 改变变换组件(Transform)
}
!GameObject { ActiveSelf //只读——> 该对象的局部激活状态 activeInHierarchy //场景中的游戏对象是否激活? //父未激活则无效
isStatic layer scene tag transform
方法: 添加组件的几种方法: { 1.gameObject.AddComponent<…>(); 2.[RequireComponent(typeof(…))] 3.直接添加,拖拽 }
!!!!AddComponent{ 添加组件 GameObject lightGO = new gameObject(); Light light = this.gameObject.AddComponent(); light.color = Color.red; light.type = LightType.Point; }
SetActive -> 设置物体的启用和禁用
//在场景中根据名称查找物体(慎用) !GameObject.Find(“游戏对象名称”)
//获取所有使用该标签的物体 FindGameObjectsWithTag;
//获取使用该标签的物体 FindWithTag
this.transform.Find("…");
}
Object { Destroy
删除组件 -> destory Destroy(gameObject,5) //5s后销毁对象
DestroyImmediate 立即删除,不建议使用
DontDestroyOnLoad 加载新场景时不自动清楚该对象
!//根据类型查找一个对象 !FindObjectOfType<>(); //根据类型查找所有对象 FindObjectsOfType<>();
Instantiate-> 克隆原始物体并返回克隆物体
}
task0809_01 :组件-> Hp task0809_02 :按钮对象
day12:
LookAt: 注视旋转
!Time类 { 只有静态变量
time: 以秒计算、游戏开始的时间。
!deltaTime: 完成最后一帧的时间。 !-> △t -> !渲染量-> 帧数多 1s -> 一帧动作小 ! 帧数少 1s -> 一帧动作大 -> 相同距离-> 总速度恒定。 !this.tranform.Rotate(0,1*Time.deltaTime,0);
保证旋转/移动-> 不受渲染影响。
!timescale->时间的倍数->实现倍速,慢动作,暂停
timescale>1 加倍 timescale <1 慢动作 timescale = 0 暂停-> FixedUpdate不再更新 但是Update不受影响(原因-> Update受渲染影响)
timescale=0 -> deltaTime会受影响->实现在Update的暂停
fixedDeltaTime;
unscaledDeltaTime->不缩放的帧时间。 ->实现游戏暂停时部分物体可以运动。
}
在Start中使用 InvokeRepeating(“Timer”,1,1); //重复调用(被执行的方法名称,第一次执行时间,每次执行间隔)
CancelInvoke(…) //取消重复调用
///3种方法实现倒计时 ///1. Tol = Time.time; Tol >NextTime? NextTime = Time.time +1 ->Update ///2. Tol += Time.deltaTime ?.. Tol = 0 ->Update ///3. InvokeRepeat(“Timer”,1,1); CancelInvoke(…) ->Start
Invoke(…); (被执行的方法,开始调用时间) 方法1-> 先做一次 方法2-> 先等 方法3-> 简单循环事件
预制件Prefabs { Select : 通过预制件实例选择对应预制件 Revert :重置为初始状态 Apply: 将该实例的修改引用到所有实例中
从Hierarchy拽到Project面板 一种资源类型->可以多次在场景进行实例,提高开发效率 可以实现特殊值,改单个 }
day13 动画 通过动画视图可以直接创建和修改动画片段
Animation View Window—Animation
创建动画片段 ->物体添加Animation组件
时间线 1:30 -> 1秒和30帧 默认1s->60帧
OnMouseDown-> …
Animation类 { 方法: Play
CrossFade -> 淡入淡出 -> 两动画之间过渡 PlayQueued -> 队列播放 属性 !speed 0 -1 1 2 .... 播放速度->可以实现倒放 anim["Door"].speed = 1; length time wrapMode ->播放模式(一次,循环,往复播放,固定点一次。) (once,loop,ping pong,clamp forever)}
day14 {
敌人 a.需求->分析->实现 {
1.敌人沿指定路线运动 2.受击后减血死亡 3.到达终点攻击玩家 4.运动播放运动动画,攻击动画,死亡动画,间隔动画
分析-> 每个事情分类 脚本1-> 敌人马达EnemyMotor -> 移动,旋转,寻路 脚本2-> 敌人状态信息EnemyStatusInfo -> 血量、提供受伤、死亡等功能 脚本3-> 敌人动画EnemyAnimation -> 定义动画,播放动画 脚本4-> 敌人AI EnemyAI,通过判断状态,执行寻路或攻击 }
敌人生成器 b.需求->分析->实现
1.开始时生成指定数量的敌人 2.为每人随机选择一条可以使用的路线 3.敌人类型 4.玩家打死一个后生成新的,直到生成数量达到上限
分析 创建根路线并添加多条子路线
脚本1-> 敌人生成器 EnemySpawn 附加提供生成敌人的功能,附加到根路线提供生成敌人的功能
类-> 路线WayLine
}
Enemymotor { MoveForward();
LookRotation(); -> 注视旋转 //transform.lookat
bool Pathfinding(); -> 寻路是否完成,沿路线移动 //朝向目标点 //向前移动 }
EmenyStatusInfo { Hp;
maxHp;
Damage();
Death(); //播放死亡动画 //销毁对象
}
EmenyAnimation { string runAnimName; … attackAnimName; … idleAnimName; … runAnimName; … deathAnimName;
//动画行为类 public AnimationAction action; private void Awake() { action = new AnimationAction(); }
}
动画行为类,提供动画行为。 public class AnimationAction { private Animation anim;
获得动画。 public animationAction(Animation anim) { this.anim = anim; }
//播放动画 public void Play(string animName) { anim.CrossFade(animName); }
}
敌人AI EmemyAI { public enum State { Attack, PathFinding
}
private State currentState = State.PathFinding;
Update(){ 判断 执行攻击, 执行寻路 switch(currentState) { case State.PathFinding: //执行寻路 //播放跑步动画 //如果寻路结束切换状态为攻击
case 攻击: //攻击不能一直攻击,攻击一阵之后要播放闲置动画 //
break;
} } }
敌人生成器 先创建路线类 public class WayLine { //创建路点数组 public Vector3[] WayPoint{get;set;}
//路点是否可用 public bool IsUsable{get;set;}
}
EnemySpawn -> 计算所有路线及坐标,生成一个敌人 {
//需要创建的敌人预制件数组 public GameObject[] enemyType;
//最大敌人数量 public int maxCount = 5;
//开始同时创建敌人数目 public int startCount = 2;
//已经创建的敌人数量 private int spawnedCount;
public void GenerateEnemy() { //选择一条可以使用的路线 //延迟时间随机
//Instantiate(敌人预制件,位置,旋转角度); int randomIndex = Random.Ranage(0,enemyType.Length) GameObject AnEenemy =Instantiate(enemyType[Random.Range(0, enemyType.Length)], line.WayPoints[0], Quaternion.identity); //这句话创建了一个敌人对象,并赋初点
//配置信息,马达 go.getConpent<enemyMotor> //传递生成器对象引用[建议使用委托的方法来做] //因为只需要调用对象的方法 //目前传递了对象 AnEenemy.GetComponent<EnemyStatusInfo>().spawn = this; //将本生成器地址传给状态对象}
路线随机,
计算敌人路线 创建所有路线
选择一条可以使用的路线(WayLine-> IsUsable = true) 随机挑出一条 随机时间随机
} day15 { Input类: 包装了输入功能的类
//获取鼠标输入 指定鼠标按钮被按下时返回true //鼠标按钮被按下 bool result = Input.GetMouseButton(0); bool result = Input.GetMouseButtonDown(0); 这两个只有一帧返回true bool result = Input.GetMouseButtonUp(0); 0->左键 1->右键 2->中键
Input 一般在Update调用
//获取键盘输入 bool result = Input.GetKey(KeyCode.A) bool result = Input.GetKeyDown(KeyCode.A) bool result = Input.GetKeyUp(KeyCode.A)
检测在c按下的前提下按d实现 Input.GetKey(KeyCode.C) && Input.GetKeyDown(KeyCode.D)
实现鼠标右键拉近镜头 1.按下镜头拉近,再次按下镜头拉远 (设置Camrea的Fov属性) 2.按下鼠标右键逐渐拉近 3. 缩放等级变化
插值 Mathf.Lerp(起点,终点,比例)
index = (index+1)% zoomLevel.Lengtn;
枪口火光特效-> 火光对象loop(启用->禁用)
//InputManager-> 实现用户自定义按键 虚拟按钮 Vertical-> Edit->ProjectSettings->Input Name-> 虚拟按键名 Negative Button 负向按钮 Positive Button 正向按钮 Alt Negative Button 负向按钮 Alt Positive Button 正向按钮 (一个名称可以绑定4个按钮) Gravity 复位速度-> 逐渐过渡,越大复位越快 Sensitivity 灵敏度 Snap: 若启用,轴收到反向输入信号时,立即置0。 Invert: 反向(游戏里面混乱状态方向颠倒实现) Type 输入来源(KEY,Mouse,)
bool result = input.GetButton(“虚拟轴名”) float value = input.GetAxis(“虚拟轴名”) float value = input.GetAxisRw(“虚拟轴名”)
!!! GetAxis获得的虚拟轴,如果有变化才有值 没变化就没有值,通过这个来进入if
3D数学 { Debug.DrawLine(vector3.point1,vector3.point2);
vector3 a;
求模 a.magnitude; math.sqrt(pow,pow,pow) Vector3.Distance();
求方向 ->向量单位化(归一化,标准化) a.normalized;
向量计算 { a-b -> b指向a (计算子弹轨迹) //通过单位化,防止子弹速度因为距离变化 zidan.translate((a-b).normalized);
a+b -> a 沿 b 的方向走b但单位 (vector3.translate的实现) 向量和标量的乘除 常量k a*k a/k 实现缩放} 角度度量 { 角度Degree/弧度radius
角度->弧度 a * (mathf.pi/180) a * mathf.Deg2Rad; 弧度->角度 a * (180/math.f) a * mathf.Rad2Deg; ->一个常量} 三角函数 { 通过三角关系计算距离 Mathf.Sin(float radian); //Sin,Cos,Tan //返回角度degeee
Mathf.Asin(a/b) //Asin,Acos,Atan //返回弧度radiustranform.TransformPoint(Vector3 a) -> 将相对于自身a向量那么远的点坐标转换为世界坐标并返回
}
点叉乘 { Vector3 operator ->转到运算符定义查看操作方法
Vector3.Dot ->点乘(内积) [x1,y1,z1]丶[x2,y2,z2] = x1x2+y1y2+z1z2 几何意义 a丶b = |a|丶|b|cos<a,b>; ->cos<a,b> = (a丶b)/|a|丶|b|
通过两向量可以求他们的夹角
在实际运用中通常会把两个向量先标准化 -> a丶b = cos<a,b>;s
float dot = Vector2.Dot(a.position.normalized,b.position.normalized) //float angle = mathf.Acos(dot)*Rad2Deg; ->省略可以省性能->直接用弧度来判断
Vector3.Corss ->叉乘 [x1,y1,z1]x[x2,y2,z2]= 行列式写法计算 |i, j ,k| |x1,y1,z1| |x2,y2,z2| -> …i+…j+…k [y1z2-z1-y2,z1x2-x1z2,x1y2-y1*x2]
几何意义-> 两个向量所组成面的垂直向量 符合左手定制
Vector3 cross = Vector3.Cross(a.position,b.position)
通过叉乘可以判断两个物体的相对位置方向 AxB BxA 复合左手定则 结合点乘就可以通过两个物体的相对位置计算夹角
if(cross.y<0) //逆时针超过了180° { angle = 360-angle; }
模长为两向量模长乘积再乘夹角正弦 但是叉乘算角度只能算0-90° |cross| = |a|*|b|*sin<a,b>
mathf.Asin(cross.magnitude)*mathf.Rad2Deg;
this.tranform.forward ->获取物体正前方向量 this.tranform.right ->右方,左方加负号 this.tranform.up
}
欧拉角 { 由三个角度来确定方位(x,y,z) !!! X,Z沿自身旋转, Y沿世界旋转
Unity限制了欧拉角范围 x轴取值范围 -> -90 - 90 y,z取值 -> 0-360 万向节死锁-> 当x为-90或90时 物体自身z轴与世界坐标系y轴重合,此时无论沿y还是沿z旋转,将失去一个自由度 此时规定沿Z轴完成X,Y的全部旋转Y轴为0 Vector3 euler = this.transform.eulerAngles;} 四元数 { Quaternion -> 由三维向量(X,Y,Z)和标量(W)组成 旋转轴V,旋转角度θ x = sin(θ/2)*V.x y = sin(θ/2)*V.y y = sin(θ/2)*V.y w = cos(θ/2)
访问四元数 this.tranform.rotation 欧拉角转换为四元数 this.tranform.rotation = Quaternion.Euler(0,50,0); this.tranform.Rotate(0,1,0); 四元数左乘向量-> 表示该向量按照四元数表示的角度旋转 Vector3 a = new Vector3(0,0,10); Vector3 new_a = Quaternion.Euler(0,30,0) * a; 两个四元数相乘-> 基于物体自身的的坐标系的旋转再加上要旋转的角度 实现旋转的叠加,基于自身坐标系做旋转。 Quaternion.Euler(0,30,0) * this.tranform.rotation;}
Vector3 { right up down forward
magnitude normalized sqrMagnitude -> 返回这个向量的平方 ->计算长度 ->少一个开方省性能
静态函数 { Angle() ClampMagnitude() -> mathf.min(value(长度),vector3); Cross() 叉乘 Dot() 点乘 Lerp() 插值 normalize() 单位化自身 OrthoNormalize 使向量规范化并彼此相互垂直? Project 投影 ProjectOnPlane() Reflect() 反射->(入射向量,法线向量) -> 输出反射向量
MoveTowards() ->匀速移动到终点 Lerp() ->插值,线快到慢 LerpUnclamped() ->不限制终点(有可能超过) //实现自然的移动 ->public animationCurve curve; ->print float x=0; public float duration = 1;//持续时间 x += Time.delatTime/duration; vector3.Lerp(this.tranform.position, rargepPosition,curve.Evaluate(x)); //超过终点 ->vector3.LerpUnclamped(this.tranform.position, rargepPosition,curve.Evaluate(x));}
}
1.欧拉角-> 四元数 //Quaternion.Euler( );
2.四元数 -> 欧拉角 Quaternion qt = this.transform.rotation; vector3 euler = qt.eulerAngles;
3.轴/角旋转 this.tranform.rotation = Quaternion.AngleAxis(angle,axis); //沿着某条轴转angle度,返回一个四元数
4.注视旋转 this.tranform.rotation = Quaternion.LookRotation(方向向量); 注视旋转;
5.插值旋转(慢慢转方向) ->public animationCurve curve; ->public float x=0; public float duration = 3;//持续时间
x += Time.deltaTime/duration; Quaternion qt = Quaternion.LookRotation(tf.position - this.tranform.position); this.tranform.rotation = Quaternion.Lerp(this.tranform.rotation,dir,x);
6.匀速旋转-> Quaternion.RotateTowards();
7.同一性(和世界坐标一致) Quaternion.identity;
8.x轴注视旋转(2D游戏中非Z轴注视旋转) //x轴注视旋转 t1 this.tranform.right = tf.position - this.tranform.position; //实现物体x轴正方向指向目标
//x轴正方向 -> 注视目标 FromToRotation() //获得某个轴到目标位置的方向 !Quaternion dir = Quaternion.FromToRotation(Vector3.rights,tf.position-this.tranform.position); this.tranform.rotation = Quaternion.Lerp(this.tranform.rotation,dir,0.1f);
!!! Quaternion.LookRotation() //旋转后有可能改变z轴方向,默认其为世界正方向
}
}
day16 { unity坐标系
world Space
自身坐标转世界点 tranform.TransformPoint; tranform.transformDirection; tranform.transformVector;Local Space 世界坐标转自身点 tranform.InverseTransformPoint; tranform.InverseTransformDirection; tranform.InverseTransformVector;
Screen Space -> Screen.width,Screen.height -> 可以判断物体是不是在屏幕中。 //屏幕坐标一般只用X,Y轴. Z轴是物体和屏幕的距离 { //获取屏幕坐标 Vector3 localPoint = Camera.main.WorldToScreenPoint() //限制物体运动范围在屏幕内 //Screen.width,Screen.height
//1. 限制移动 //2.左出右进}
Viewport Space -> 视口(摄像机)坐标系,原点在左下 左下(0,0),右上(1,1);
物理引擎 { { 刚体,碰撞器 }
Rigidbody ->刚体 -> mass 质量 -> Drag 阻力 砖头 0.001 羽毛 10 angular Drag 角阻力 Interpolate 插值 -> 缓解刚体运动的抖动 -> 内插值 基于上一帧 -> 外插值 基于下一帧 Collision Detection -> 碰撞检测 Discrete 不连续碰撞检测 Continuous 连续碰撞检测 Continuous Dynamic 连续动态碰撞检测
Constraints: 约束 对刚体运动的约束 -> 冻结某个方向的运动,比如磁悬浮运动
UseGravity -> 重力
不带刚体的对象默认无法运动,绝对质量
Is Kinematic-> 运动学刚体->也拥有绝对质量
collider -> 碰撞器
物理材质 Material -> Physic Material-> -> New -> Physics Material { Dynamic Friction -> 动态摩擦力 Static Friction -> 静态摩擦力 Bounciness -> 弹性 Friction Combine Bounce Combine -> 弹力合成方式
}
碰撞条件 { 两者都具有碰撞组件 collider 运动的物体具有刚体组件 }
碰撞三阶段 { 碰撞执行 OnCollisionEnter(Collision a) 与刚体接触每帧执行 OnCollisionStay(Collision a) 停止碰撞时执行 OnCollisionExit(Collision a)
输入参数碰撞组件 a为 被this碰撞的物体对象 //!!!输入参数是对象,包含接触点数量 //接触点数组 //和Trigger的区别:对象,获取组件需要getcomponent ContactPoint cp = a.contacts[0] -> 获取第一个接触对象 cp.point -> 获取接触点 cp.normal ->接触面法线 !}
触发器-> 带有碰撞器组件且Is Trigger { (可以实现攻击范围判别)
现象-> 没有碰撞效果 条件 { 两者具有碰撞组件 其中之一有刚体组件 其中之一勾选isTrigger } 碰撞三阶段{ 碰撞执行 OnTriggerEnter(Collision a) 与刚体接触每帧执行 OnTriggerStay(Collision a) 停止碰撞时执行 OnTriggerExit(Collision a)
//Trigger输入参数为碰撞对象的碰撞器组件!!!!!! //可以直接使用 a.name。 输入参数碰撞组件 a为 被this碰撞的物体的组件}
子弹检测不到碰撞 -> 开始时做射线检测 //重载15 public LayerMask mask;
RaycastHit hit; Physics.Raycast(this.tranform.position, this.tranform.forward, out hit,150,mask)
!!! mask -> 选择需要检测的层。
返回值为bool . //射线起始位置,方向,返回值out hit(受击信息),最大距离
hit中包含-> point->点 normal->接触面法线 collider->接触面碰撞器
if((this.tranform.position - targetPos).sqrMagnitude<0.1f) { print(“接触”); Destroy(this.gameObject);
}
} 武器模块 { 1.弹夹有子弹,可以发射,否则等待更换弹夹
2.发射子弹时-> 播放音效、动画、显示火花 3.玩家的枪-> 单发或者连发 ,敌人->单发 4.玩家子弹 -> 根据敌人补位减血 -> 子弹飞行到目标点销毁并创建相应特效 5.敌人子弹 -> 击中玩家后减血 -> 子弹飞行到目标点销毁并创建相应特效 -> 朝玩家头部发射,飞行速度较慢,便于玩家躲避 需求分析-> 枪 { 枪Gun-> 开火,更换弹匣 单发枪 SingleGun -> 继承自Gun 连发枪 AutomaticGun -> 继承自Gun } 子弹 { 子弹Bullet-> 计算攻击目标->移动-> 创建接触特效 (玩家检测环境,敌人) (敌人检测环境?) }} } day 17 { [RequireComponent(typeof(AudioSource))] class Gun:MonoBehaviour { 声音源? private AudioSource audioSource; //发射子弹时的声音片段 public AudioClip clip;
//弹匣容量 //当前弹匣内子弹数 //剩余子弹总数 //为子类提供重新方法 protected virtual void Start() { } public void Firing(Vector3 direction) { //玩家-> 枪口方向 //敌人-> 从枪口位置朝向玩家头部 //准备子弹 //判断弹匣是否包含子弹、是否在换枪... // //创建子弹、播放音频、播放特效、播放动画 //动画包括开枪,换弹 audioSource.PlayOneShot(clip); } public void UpdateAmmo() {//更换弹匣函数 // }}
public class SingleGun:Gun {//单发枪
if(Input.getButtonDown(0)) { this.firing(...) //输入枪口方向位置 }} public class AutomaticGun:Gun {//连发枪
//重构 protected override void Start() { base.Start(); .... } if(Input.getButtonDown(0)) { this.firing(...) //输入枪口方向位置 }}
class Bullet:monobehaviour { //计算目标点 //射线检测
//移动 //到达目标点:销毁,创建相关特效 //创建相关特效 //根据目标点物体的标签 //hit.collider.tag; private void GenerateContactEffect() { !!! //通过代码读取资源 //资源必须放到Resources目录下 GameObject prefebGo = Resources.Load<GameObject>("资源名称") //创建资源 }}
public class PlayerBullet:Bullet { //根据敌人部位减血
// base.hit.collider.name}
public class EnemyBullet:Bullet {
private void OnTriggerEnter() { //和玩家接触 //玩家减血 }}
Trail Renderer -> 尾痕追踪
PlayQueued()
配置文件?
对象池?
}
}
day18 {
HTC VIVE { HTC VIVE BT下载
Steam
{ 头戴式显示器 无线手持控制器 定位器 }
人眼接受刷新率 -> 70以上
控制器 { Menu button Trackpad Left Trackpad Up Trackpad Right Trackpad Down System Button Trigger (扳机,确认) (可以获取按下程度值) Grip Button () } 定位器 { 靠激光和光敏传感器确定移动物体位置 }
SteamVR包 { Scripts-> Controler Manger
Track Object}
GetPress GetPressDown GetPressUp
GetTough GetToughDown GetToughUp
GetAxis -> (0,1)
TriggerHapticPulse: 振动,力度为1-4000
GetHairTrigger ->轻微碰到扳机
…-P207 -> VR方面的介绍
创建玩家预制件 P200 19:06
Package 2 VRTK -> P202 控制器激光(贝塞尔曲线) ,指针,玩家传送,控制器抓取,UI
加命名空间!
特殊目录 Plugins -> 插件目录 会提前编译
事件-> 当满足某种条件时自动调用的方法 using VRTK;
<VRTK_ControllerEvents>;
!! 回调事件-> VRTK_ControllerEvents events = GetComponentInParent<VRTK_ControllerEvents>(); events.TriggerPressed += events_TriggerPressed; //+= 后连按两次Tab-> 自动生成方法和值 void events_TriggerPressed(object sender,ControllerInteraactionEvent…) { //。。。要做的事情 }
Brizer Pointer Liner Point
-> BasicTeleport ->只能传送水平位置 HeightAdjustTeleport ->带高度传送
}
}
day19 {
P208 UGUI unity Graphical User Interface
权限布局系统 Rect Transform Layout Group
强大的事件机制 鼠标指针 拖拽类 点选类 输入类
减少GPU负担
淡化了Atlas -直接使用Sprite Packer
没有Tween组件 -> Itween / DoTween 开发效率暂低于NGUI?
Canvas 画布
画布内 -> 后面的盖前面的 画布之间-> Sort Order ->大的盖小的
画布模式-> Screen Space 和摄像机保持静止 { Overload 覆盖 Camera ->摄像机模式,如果摄像机没有提供 泽仍然是Overload模式-> UI属性随着摄像机属性调整 Plane Distance ->摄像机和画布的距离
覆盖模型性能优于摄像机模式
设置UICamera-> 深度值调高 -> 渲染-> Depth Only -> Culling musk -> 只看UI 投射方式-> Projection - > Orthographic
主摄像机-> 不看UI
另一种做法 -> 把UI摄像机丢到很远的地方去
3D物体盖住UI-> }
World Space { 实现3DUI }
Rect Transform { Pos ->控件 轴心点相对于锚点的位置
轴心点为物体中心,影响缩放,旋转 控件转-> 按照z轴转 设置轴心点-> Transform.Pivot.x(y) 取值(0,1)-> 物体内 超过1,0 物体外 中心点-> (0.5,0.5) 锚点 -> 4个小三角 锚点根据屏幕大小变化。 图片固定在右上角 -> 锚点放在右上角 锚点分开-> 根据图片缩放 锚点-> {大小自适应(背景), 位置自适应(血条,地图)} Skretch -> 拉伸 如果画布是overload-> 世界坐标= 屏幕坐标 this.transform.localPostion -> 当前轴心点相对于父UI的轴心点位置 获取RectTransform RectTransform rtf = this.getComponent<RectTransform>(); rtf.position -> 仍然是世界坐标 rtf.anchoredPosotion3D -> 自身轴心点相对于锚点的位置(POS); //获取设置锚点 rtf.anchorMin(anchorMax) //获取UI宽度,高度(只读) rtf.rect.width rtf.rect.height //设置UI宽度 rtf.SetSizeWithCurrentAnchors(里面是枚举类型) //当锚点不分开时,数值可以理解为UI的宽高 rtf.sizeDelta //物体间距减去锚点间距 RectTransformUtility-> { CalculateRelativeRectTransformBounds FlipLayoutAxes //翻转 PixelAdjustPoint PixelAdjustRect RectangleContainsScreenPoint RectangleContainsScreenPoint ScreenPointToRay WorldToScreenPoint } Image { Sprit Color {RGBA} Image Type { Fill Amount ->实现技能冷却 } Set neative Size ->自适应大小 } Text { Front 字体 ... LineSpace 行间距 勾选Rich Text //实现678变色 123456<color = red>678</color>9 <b>粗体</b> <i>斜体</i> <size = 14>字号</size> <color = greem>颜色</color> Alignment -> 位置 Overflow 溢出{horizon,vertical} best fit-> 调整字体 } Button { Trainsition ->图片交互方式 { Color Tint -> 颜色变换 Sprite swap -> 图片变换 Animation -> 动画 {可以实现按钮变大变小} } 按钮取消交互 normal hightling press disable navigation {导航} ->通过键盘来选中按钮 } Toggle { 复选框 ✔ 是否保存密码 Is On } Slider { Interactable -> 交互允许 填充->做血条 滑动条 value - > 程序获取值 } ScroolerBar { 和Slider的区别-> 只有滑块部分有属性 ->做滚动条 } DropDown { 下拉框 } 步骤 Canvas -> CanVasScaler设置为追踪屏幕大小 创建Panel(面板) Grid Layout 游戏控制器 { 1.创建网格 GameObject go = new GameObject(); go.AddComponent<Image>(); go.tranform.SetParent(this.tranform,false); } 复杂界面-> 做成预制件-> Instantiate image带来的Draw数量->sprite决定 精灵打包 -> 减少GPU渲染量 Creat->Sprite Atlas 精灵分割 -> Sprite Mode-> Multiple->Apply-> Edit 切割方式 { 1.手动切割 2.自动切割 -> Slice -> 3种 } 读精灵数组 LoadAll Sprite[] spriteArray = Resources.LoadAll<Sprite>("2048Atlas"); 字典集合,通过哈希表来访问 System.collection.Generic.Dictionary<int,Sprit> SpritDic; 类中的静态构造函数,类在被加载的时候就会被调用一次,类似于Awake 构造函数函数名字要和类名一样!!! 给值类型赋空类型 !!!可空值类型-> int? 只有值类型有可空。 引用类型本身就可空。 int? a=null; 需要获取值类型数据 则用a.value; UGUI事件 { 输入框-> 事件-> 静态参数/动态参数(框中输入的) } 事件注册 { EventSystem ->component->Event Graphical Raycaster 1.编辑器注册。(不常用) 2.AddListener 注册事件-> { var button = this.tranform.Find("Button").GetComponent<Button>(); button.OnvalueChange.AddListener(FUN1) //其中FUN1是一个函数,无输入参数 var input = this.tranform.Find("InputField").GetComponent<InputField>(); input.OnvalueChange.AddListener(FUN2) //其中FUN2是一个函数,有一个输入参数->F12看到 ->public delegate void UnityAction<T0>(T0 arg0); } 3.绑定接口-> { 鼠标指针类{ IPointerEnterHandler IPointerExitHandler IPointerDownHandler IPointerUpHandler IPointerClickHandler } 拖拽类 { IBeginDragHandler IDragHandler IEndDragHandler IDropHandler } 点选类 { IUpdateSelectedHandler ISelectHandler IDeselectHandler } 输入类 { IScrollHandler IMoveHandler ISubmitHandler ICancelHandler } 4.自定义框架 } 各组件上的Raycast-> 射线是否检测(是否去接收事件输入) 接口的创建 class a:MonoBehaviour,UnityEngine.EventSystem.IPointerClickHandler {...} 双击-> public void OnPointerClick(PointerEventData eventData) { if(eventData.clickCount == 2) {...} } 拖拽->public void OnDrag(PointerEventData eventData) { RectTransform parentRtf = this.tranform.parent as RectTransform; Vector3 WorldPOS; -> //(父物体的变换组件,屏幕坐标,摄像机,out 世界坐标) RectTransformUtility.ScreenPointToWorldPointInRectangle(parentRtf,eventData.position,eventData.pressEventCamera,out WorldPOS); } 指定点拖拽-> { 在点击的接口中就要记录轴心点位置 } } iTween 动画库 /DoTween { 调用一次实习渐渐运动 iTween.MoveTo(gameObject , vector3 position,float time) MoveTo -> 物体移动 ColorTo -> 颜色渐变 FadeTo -> 淡入淡出 CameraFadeAdd CameraFadeTo ->摄像机淡入淡出 LookTo -> 注视旋转 RotateTo -> 物体旋转 ScaleTo -> 物体缩放 PunchPosition PunchRotation PunchScale ->晃动 ShakePosition ShakeRotation ShakeScale ->震动 iTween.MoveTo(img.gameObject,iTween.Hash( "position",btnTf.Position //原始位置 "speed",moveSpeed, // "easetype",type //运动曲线 )) }Mouth 02 day01 { 集合
持久化数据
换装案例
协程/多线程
文件IO
寻路系统(网格寻路)
射线
Mecanim(animator)
物理引擎(关节.布料)
美术规范(能够与美工沟通交流)
Shader(Shaderlab基本结构 常用功能 写常用Shader) (修改挪用其他Shader) (反光效果。。。)
NGUI
Unity2D(工作流程)
手机触发方式以及AR (EasyTouch EasyAR ios发布)
} 集合 { 一种数据容器->数据结构
容纳多个数据、大小可变、空间不一定连续
集合两大体系-> 非泛型集合 泛型集合
命名空间: System.Collections ->非泛型集合 System.Collections.Generic ->泛型集合
非泛型缺点 ->性能不好、可能发生装箱 ->类型不安全,发生类型转换异常 ->有时需要做类型转换
列表 (非泛型) ArrayList (泛型)List
字典 (非泛型) Hashtable (泛型)Dictionary<TKey,TValue>
作业1-> 1.删除指定元素 ->remove 2.移除指定索引的两个元素 ->removeRange 3.指定索引插入元素 ->Insert
字典-> 无序输出 Dictionary<string,string> dic01 = new Dictionary<string,string>();
嵌套字典 -> private Dictionary<string,Dictionary<string,string>> dic02 = new Dictionary<string,Dictionary<string,string>>();
dic02.Add("张三",new Dictionary<string,string>()); dic02["张三"].Add("电话","12345"); 主键-> 张三 子键-> 电话 值->12345 使用-> dic02["张三"]["电话"];栈 Stack(后进先出) Stack { Push() 压栈
Pop() 弹栈 Peek() 读栈顶但不移除 count private Stack stac = new Stack(); 练习: 主菜单->选项->游戏选项->难度调节->一般} 队列 Queue(先进先出) Queue { Enqueque () 入队 Dequeue() 出队 Peek 读队首
}
持久化数据 {
PlayerPrefs SetFloat() SetInt() SetString() GetFloat() GetInt() GetString() 存->键值对 取->键 PlayerPrefs.SetInt("Score",number); PlayerPrefs.HasKey("Score"); PlayerPrefs.GetInt("Score"); //!!要在场景中能够切换 //必须要将场景加入buildSetting中 //场景切换 //换关 SceneManger.LoadScene("name") 删除数据->DeleteAll DeleteKey 案例应用->换装 GoblinClothRender.material.mainTexture = Cloth[i % Cloth.Length]; 换武器 { Render Mesh }}
} day02 { NGUI
tranform.GetChild(i).GetComponent().onClick.Add( new EventDelegate(OnButtonClick));
获取当前按下按钮的名字->添加事件 { private void Start() {
for (i = 0; i < this.transform.childCount; i++) { CurrentButton = transform.GetChild(i).GetComponent<Button>(); CurrentButton.onClick.AddListener(GetButtonName); }}
private void GetButtonName() { print(EventSystem.current.currentSelectedGameObject.name); }
}
角色换装实例 {
按钮(分配任务) -> 业务(资源使用) ↓↑ 工厂(资源加载)
}
Replace(“xxx”,""); //将字符串的某一串字删掉 }
} day20: { 协程 ->也是一种程序组件
灵活,可以有多个入口和出口
适合用于合作式任务,迭代器,无限列表
开始一个协程时,执行从被调用的起始处开始,然后 每次协程被调用时,从协程返回(yield return)的位置接着执行 (迭代器?)
协程是单线程的。
实现 IEnumerator 接口 提供迭代 yield return XXX 指定下一次迭代入口 WaitForFixedUpdate 在FixedUpdate后执行 (物理控制) null、0、WaitForSeconds 在每帧Update后执行 (分多帧完成一个任务,如平移) WaitForEndOfFrame 每帧结束后执行 (每帧末尾执行,适合相机控制) WWW 在请求结束后执行 (适合网络下载数据) StartCoroutine 开启协程
private IEnumerator Start() { yield return 1; ->停一帧 print(“1”); yield return new WaitForSeconds(2); ->停2S print(“2”); yield return 1; print(“3”); }
自己定义的协程要使用StartCoroutine开启 自己定义了协程IEnumerator WaitTimeFunc(float TimeCount)
使用-> if(GUILayout.Button(“开始记时”)) { StartCoroutine(WaitTimeFunc(3)); }
WWW 请求结束后使用
url = " 一个网址 " WWW www = new WWW(url); yield return www; //等待在一个网站上下载一个图片
//下载完成、画图 render.material.mainTexture = www.texture;
StartCoroutine(…) … 开启协程 StopAllCoroutines() 关闭所有协程
携程调用参数 不用写类型 StartCoroutine(Fun(a))
//!!协程嵌套 { private void OnGUI() {
if (GUILayout.Button("Start")) { StartCoroutine(MovePath()); print("如果方块还未到终点,显示了我。说明协程是单独运行的。!"); //结果表明方块还没到时就显示了这句话! } } private IEnumerator MovePath() { foreach (var point in Points) { //协程嵌套 //找到目标点后再找下一个点,等待MoveTotarget执行完毕再次进入下一个循环。 yield return (StartCoroutine(MoveToTarget(point))); } } private IEnumerator MoveToTarget(Transform Target) { //注意这个循环执行完毕才会出去->即到了这个点 while (this.transform.position != Target.position) { this.transform.position = Vector3.MoveTowards(transform.position, Target.position, 0.5f); yield return new WaitForFixedUpdate(); } }}
协程的停止-> StopCoroutine(string a) -> 只能停止以StartCoroutine(string a)开启的协程 StopAllCoroutine()-> 停止***本对象***中开启的所有协程
多个协程可以同时运行
协程可以嵌套
多个脚本访问同意协程,可以定义静态协程
!协程不是多线程,运行在同一线程中
可以创建一个随时间进行的协程来进行大量运算
IEnumerator 不能带ref或out参数
} day21 { 文件与IO System.IO中->
文件对应类
System.IO
DriveInfo :有关驱动器信息的类 主要方法: GetDrives
File类 { Creat Delete Move Copy Exists
读写文件 ReadAllText ->String ReadAllLines -> string按行形成 ReadLines -> 一次向下读一行 ReadAllBytes -> 文件内容读入内存,以字节数组的方式保存 WriteAllText ->一次写入 WriteAllLine -> 每写一行换行 WriteAllBytes -> 写入字节}
FileInfo类 有关文件整体操作和读写 比File多了一个Length属性,可以了解一个文件所 占的字节
Directory类 { CreatDirectory 创建文件夹
Delete 删除文件夹
Move 移动文件夹->不能跨驱动器
GetFiles 取得文件夹下所有文件名
GetDirectoies 取得所有子文件夹名
Exists 有无此文件夹
}
DirectoryInfo类 ->区别:所有成员为实例成员
Path类 { GetFileName; 取得文件名,包括扩展名
GetFileNameWithOutExtension 取得文件名,不要扩展名
GetDiectoryName 取得路径目录名称
Combine 将多个字符串合并成为一个路径
}
数据流类 IO
Stream {
FileStream 读取流(Read) 写入流(Write) 流可以支持查找(Seek,Position)}
StreamReader: 流内容读取器 { ReadLine Read ReadToEnd } StreamWriter:内容写入流 { Write WriteLine }
!!使用流前需要创建流对象 { Stream stream = new FileStream(path,fileMode.OpenOrCreate); byte[] data = new byte(stream.Length);
//输入参数 字节数组,读取位置,读取数量 stream.Read(data,0,(int)stream.Length); //流使用完需要关闭,否则资源不会释放 //写 stream.Write(data,0,(int)stream.Length); //刷新 stream.Flush stream.Close(); //解码 return Encoding.UTF8.GetString(data);} Unity 寻路系统 { 路点寻路 单元格寻路 网格寻路
实现寻路方法-> { 确定寻路者 烘焙寻路路面 程序实现寻路算法 } 障碍物->静态(static) Windows->Navigation->右下角Bake 组件->Navigation->Nav Mesh Agent private NavMeshAgent agent; public Transform target; private void Start() { agent = this.getComponent<NavMeshAgent>(); } private void onGui() { if(GUILayout.Button("START")) { agent.SetDestination(target.position); } }}
!!!路径 private string path = @“D:/aa/bb”;
private void Start() { if(!Directory.Exists(path)) {如果没有文件夹 Directory.CreatDirectory(path); } }
Directory.Delete(path,true) //ture则会删除所有子文件夹
//DirectoryInfo ->实例成员 DirectoryInfo info = new DirectoryInfo(path); if(!Info.Exists) DirectoryInfo.Create();
路径 -> @"…"
!!!StreamingAssets文件夹-> ->不加密文件夹,不会放大 ->可以存放配置文件 }
//读取配置文件路径(StreamingAssets文件夹)
string ConfigPath = Path.Combine( Application.streamingAssetsPath ,“Config.txt”);
string[] file02 = File.ReadLines(ConfigPath);
string a = “”;
//相当于python 里面str.strip() //去除前尾字符 a.Trim(); -> 修剪 去除空白行 a.Trim(’’); -> 去除左右两边的
字符数组转字典
private void BuildDic(string[] lines) { string mainKey = null; string subKey = null; string Value = null;
foreach (string line in lines) { string lineProcess = line.Trim(); if(!string.IsNullOrEmpty(lineProcess)) { //该行不为空 if(lineProcess.StratsWith("[")) {//取主键 //mainKey = lineProcess.Substring(1,line.IndexOf("]")-1); mainKey = lineProcess.Trim('[',']'); dic.Add(mainKey,new Dictionary<string,string>()); } else//取子键 { var configValue = lineProcess.Split('=',stringSplitOptions.RemoveEmptyEntries); subKey = configValue[0].Trim(); Value = configValue[1].Trim(); dic[mainKey].Add(subKey,value); } } }}
已经烘焙了之后可以隐藏/删除物体
//搭桥 Off Mesh Link组件 -> 给点 搭桥点需要放在寻路网格上
Nav Mesh Agent { radius ->碰撞半径,设置物体半径,寻路碰撞 height ->高度 Base Offset -> 偏移
Speed -> 移动速度 Angular Speed -> 角速度 一般设置为120 Acceleration ->加速度 Stopping Distance -> 停止距离 Auto Braking -> 自动刹车 Obstacle Avoidance->躲避系数 Quality -> 等级 Priority -> 同级优先度 Area Mask -> 区域遮罩 Cost ->路径成本} Nav Mesh Obstacle 寻路碰撞,能动又能挡寻路物体 { Shape 形状(只针对寻路)
}
Navigation->Bake-> { Agent Radius -> 离障碍物的距离 Agent Height -> 寻路网格高度,基于路面 Max Slope -> 最大坡度
Step Height -> 台阶高度} 代码实现->
GetComponent().areaMask = 2^n; (复选枚举)
}
注意问题-> { 避免重复计算(位置不变不要重新计算)
Unity寻路系统的动态碰撞有局限性}
常见寻路插件 { A* Pathfinding
SimplePath}
day22: { 射线
一个寻路的脚本 {
private void Update() { if (Input.GetMouseButtonDown(0)) { //计算射线点,移动到设置此点位为目标点 //移动到目标点 CalcuteRay(); navAgent.SetDestination(Target); StopAllCoroutines(); StartCoroutine(StartAnim()); } } private void CalcuteRay() { ray = Camera.main.ScreenPointToRay(Input.mousePosition); Debug.DrawLine(ray.origin, hit.point,Color.red); Physics.Raycast(ray, out hit,100f); Target = hit.point; } private IEnumerator StartAnim() { while (Vector3.Distance(this.transform.position, Target) > 0.1f) { anim.CrossFade("Run"); yield return new WaitForEndOfFrame(); } anim.CrossFade("Idle"); yield return 1; }}
public Camera mapCamera; mapCamera.ScreenPointToRay(Input.mousePosition); => 实现小地图点坐标点
canv.gameObject.setActive(true);
camera.main.ScreenPointToRay() camera.main.WorldToScreenPoint()
搬东西 public class MovingHouseDemo : MonoBehaviour { private Transform furnitureTF; //要搬的家具
public Transform PlayerTF;
public Transform CenterPointTF; //玩家操作的准心中点
private RaycastHit hit; //击中对象
//public Camera PlayerCamera; //玩家摄像机
public LayerMask layer;
private bool UporDownFlag = false; //ture拿起 Down放下
private void Update() { PickUpfun();
}
private void PickUpfun() { if (Input.GetMouseButtonDown(0)) { if (UporDownFlag == false) { if (Physics.Raycast(CenterPointTF.position, CenterPointTF.forward,out hit,200F,layer)) //射线击中家具 { furnitureTF = hit.collider.transform; furnitureTF.GetComponent().isKinematic = true; furnitureTF.parent = CenterPointTF;
UporDownFlag = true; } return; } else { furnitureTF.parent = null; furnitureTF.GetComponent<Rigidbody>().isKinematic = false; UporDownFlag = false; return; } }}
添加父子关系 a.tranform.parent = b.tranform;
取消父子关系 a.tranform.parent = null;
画出射线-> Component -> LineRander
纹理偏移-> Material.mainTextureOffset
line.enable = …;
line.SetPosition(0,起始位置) line.SetPosition(1,终点位置)
!!射线 { Physics.RaycastAll() -> 返回射线碰撞的所有物体的hits列表
重叠球射线 -> OverlapSphere 重叠球 ->返回值-> 碰撞器 private collider[] others; others = Physics.OverlapSphere(this.tranform.position,2); //半径为2 CapsuleCast 胶囊射线}
} } !day23??->项目P277-279 {}
day24 { Mecanim 动画系统
针对人形提供了特殊的工作流-> 包含Avatar的创建和对肌肉的调节
动画重定向->方便地把动画从一个角色模型应用到另一个角色模型上
Animator视窗
{ Animator Mecanim动画系统组件
AnimatorController 动画控制器 Avatar Mecanim可识别骨骼 Retargeting 角色之间的动画互用}
预制件-> Rig -> Legacy (使用Animation动画系统) Generic 非人型(使用Animator) Humaniod 人型(使用Animator)
Animator Controller->
Retargeting
Set Default State ->设置默认状态
MakeTransition -> 设置动画衔接
添加动画条件-> 动画参数 parameters + { Float Int Bool Trigger }
anim = getComponent<Animator>(); anim.SetBool(); !anim.GetBool(); 根据当前权重值->判断动画状态 Has Exit Time ->勾选-> 转换动画变为与条件 不勾选则不构成条件 Float 跑会自己转向-> 设置锁定rotation 锁X轴,锁z轴 骨骼匹配-> 1.Pose -> Reset 2.Mapping -> Auto Map 3.Pose -> Enforce T-Pose 肌肉设置-> Muscles& Setting 或条件 -> 拉线 动画混合树 Mecanim BlendTree(混合树) 动作跑步进去或条件,出来与条件 BlendTree2D 2个动画参数} day25 { Layer ->实现多个动画同时执行 ->边跑边攻击
Weight 权重 Mask ->Avatar 遮罩 ->New 一个SetLayerWeight(1,1) ->第一层权重为1
动画事件-> 播放到某种程度时-> 触发方法
需要的动画-> Event -> Add Event ->双击->
死亡-> 与任意状态 AnyState连线
Animator Overide Controller
判断动画是否在播放->
Animator anim; int idle = Animator.StringToHash("Base layer.Idle") AnimatorStateInfo currentBaseStage; currentBaseStage = anim.GetCurrentAnimatorStateInfo(0); //0为默认层 if(currentBaseStage.nameHash == idle && !anim.IsInTransition(0)) //当前的哈希值和获得的相等,且不在过渡线上 { ... }}
day26 物理引擎 { 碰撞,关节,布料…
关节{ 铰链关节 弹性关节 固定关节 角色关节 可配置关节 }
铰链关节 -> Physics-> Hinge Joint { Connected Body
spring -> 弹性 Motor -> 马达 Limit -> 限制-> { //可以实现逐级减少 break Force ->断开力 break Torque -> 断开扭矩 } 加力 other.rigidbody.AddForce(Vector3.forward * 500); //斜上方 other.rigidbody.AddForce((Vector3.forward+Vector3.up) * 500); MonoBehaviour类里面可以找到各种预制函数}
布料引擎 { //会自动添加Skinned Mesh Render //Cloth
Component->Cloth
布料约束
表面嵌入 -> Surface Penetration //点最大嵌入到mesh的程度
}
Stretchomg Stoffness 拉扯硬度 Blending Stiffness 弯曲硬度 Use Tethers 绳索属性 Use Gravity 重力 Damping 阻尼
//模拟风 Extern Acceleration 外部加速度 Random Acceleration 随机加速度
//布料运动时,各加速度影响比例 World Velocity Scale World Acceleration Scale
//碰撞-> 只支撑胶囊和球形碰撞 Friction 摩擦力 Collision Mass 碰撞质量 Continuous Collision 增加消耗,减少直接穿透碰撞几率
} Movie Texture 影片纹理 { //已弃用,使用Video Player代替 using UnityEngine.Video;
Play Stop Pause }
day27 {//城市勇士 小项目
1.开始界面-> 人物换装
2.随机3个诞生点,一共生成3波怪(每波2-3个)
3.怪头顶有血条,随波数增加怪物难度越来越大
4.角色与怪物之间的动画互动(Mecanim动画系统) //角色射线打到怪物,角色播放攻击动画,怪物播放 //受到攻击动画
5.敌人,角色血条效果
6.打敌人,随机掉装备,可实现加血等
7.打完后胜利,
{
通过脚本添加动画事件 AnimationEvent animEvent = new AnimationEvent();
//现在在播放的动画组 AnimatorClipInfo[] clipInfo = anim.GetCurrentAnimatorClipInfo(0);
//动画播放5s添加事件 animEvent.time = 5; animEvent.functionName = “AnimEventFunc”
//当前动画这 clipInfo[0].clip.AddEvent(animEvent); }
}
day28 { 3dMax
1.配环境-> { a.调单位为厘米,自动保存,max点大小 Customize->Unit Setup-> 显示单位为米,系统单位设置为厘米 max和unity单位相差100倍
b.调自动保存 -> Customize->Preference
c.max点大小设置->右键点击物体-> 转换为->可编辑多边形 }
Alt + 中间旋转 一直按住Shift 复制 Z 快速将物体移动到中心
F3 只显示线 F4 线框显示
X 显示/隐藏坐标位置
Alt + X 透明显示 Alt + Q 单独物体显示 (变回来点下面中间的框框)
顶视窗,前视窗,左视窗
步骤-> 1.归原点
加线-> 要加的线连接到的所有线都要加 穿过的线都要选中!!!
挤出-> Extured
补面-> cap
文档->3dmax->autoback->查看前面做的文件
吸附命令->
导出文件-> Export-> FBX格式
unity中pivot 和Gobal https://www.cnblogs.com/dawenhao/p/10406621.html Pivot/Center:现实游戏对象的轴心参考点。Center为以所有选中物体所组成的轴心作为游戏对象的轴心参考点(常用于多物体的整体移动);Pviot为以最后一个选中的游戏对象的轴心为参考点。
Center/Global:显示物体的坐标。Global为所选中游戏对象使用世界坐标;Local为该游戏对象使用自身坐标
镜像->
打组-> Group ->相当于添加父物体
合并-> Attach Attach List ->相当于变为一个物体 分离-> Detach
贴图 UV展开 各个位置贴图展开(得到看起来很奇怪的texture图) { 避免贴图拉伸 不同位置有不同贴图 利于PS上贴图(三维模型点信息转为二维)
搜索框输入UV ->找到UVW ->Edit P288 给物体添加棋盘格->快捷键M(调到材质球) ->材质编辑器->编辑->精简模式 选一个材质球 ->Diffuse漫反射 ->右边的框框->选择Checker(棋盘格) ->上面的小图片中选择->视口中显示阴暗处材质 ->将材质指定给选定对象 设置tiling(瓷砖)-> 调高密度 //贴图 -> 展平贴图 Mapping -> Flatten mapping UV缝合-> tool(工具)->Stitch Selected(缝合) UV保存-> 转Poly}
}
day29 { P311开始。 UV贴图
模型歪了-> 重置变换-> 转换为可编辑多边形
导出UV图
PS里面打开->放图 P312
PS里面 Ctrl+ T 缩小图片 Shift 等比例缩小
放图-> 新材质球->Bitmap(位图)双击-> 选择材质
防止拉伸->复制 (长按Alt)
矩形框选->选中贴图中某一部分-> 防止拉伸 Ctrl+D 取消选择
镜像贴图-> 在UV里面改
(挺好玩的) PS -> 快速选择工具 -> 从已有图里面拿贴图
!可以将背景改成其它的->右上角->Pick Texture 不完全重合->在UV里面改模型,通过背景
!!连贯的部分要缝合!!
Ps操作 { Ctrl + T -> 图片放大/缩小 一直按住Shift 整体放大/缩小 Alt + 滚轮 放缩视图 调整大小时-> 右键旋转 Ctrl + Alt + Z 撤销
长按Alt 复制 Ctrl + D 取消选择}
max中导出-> psd格式(不是性能最好的格式) -> 使用png格式(最常用)
PS中-> 文件-> 存储为->png
分离物体
保存-导入unity
模型设计全过程-> { 原画(设计) -> 建模师(建模-分UV-上贴图) -> 动画师(骨骼匹配 蒙皮) ->
高模(精模) 低模(简模) 次时代技术(凹凸/法线贴图) -> 高模 建模 贴图->法线贴图-> 应用于低模}
先转可编辑多边形-> 关联镜像 -> 镜像 Instance
创建骨骼-> Biped
模型优化->模型面数,贴图数量,模型大小 骨骼优化-> 关节数量
录制动画->自动关键点-> 录制动画->
时间配置(设置播放速度)
骨骼上下移动->运动面板->
动作复制-> 关键帧选中全体,按Shift
动画混合-> 先选择骨骼再到运动面板-> 混合器 右键添加新层->新建剪辑->导入文件 ->添加完成->点击行为树根->计算合成Compute Mixdown
右键->复制到骨骼上Copy to Biped-> Biped 框退出混合模式
}
day30 { P323 { 匹配骨骼-> Bip创建等身骨骼 模型透明化(Alt+X),模型冻结(右键选择)
改变骨骼大小-> 运动中进入体形模式-> 调整骨骼大小与模型适配(缩放,调型) 蒙皮-> 取消冻结-> 修改面板-> Skin 蒙皮 ->骨骼。添加 取消透明显示 权重设置->Skin蒙皮->编辑,勾选点 下面一个钳子的符号 选择灰线(会看到皮肤上的蓝色点,为被影响的点)->设置相应权重。 显示-> 隐藏骨骼 导出-> 路径尽量不要有中文->放入材质}
unity -> 切割动画 { 动画-> Animation->Clips 切割完成 Apply }
unity3d 美术制作规范 { 1.单位比例 没有特殊要求下,单位为米, 3dmax中要设置系统单位为厘米,显示单位为米 https://blog.csdn.net/a1780531/article/details/90933558 unity中一个单位为一米, 3dmax中一个单位为一厘米,相差100倍
坐标轴-> Max中坐标轴调整为y轴朝上 多余面数->删除场景中多余的面(看不见的面)->提高贴图利用率 避免闪面(原因 面与面之间距离太小。)->注意检查共面、漏面、反面(材质在背面去了,需要进行法线反转) 面与面之间的最小距离为当前最大尺度的2千分之1 { 法线反转->修改->法线(Normal) } P328 2.建模规范 树木模型-> 用十字交叉树或简模树 考虑和周围建筑的关系 重点建筑需种简模树,并在原地与之对应的种上十字片树(替换用) 总体原则-> 控制面数 十字交叉树 { 两张树图垂直交叉(透明贴图) } 模型级别-> 模型级别/惊喜程度。 模型质心点 { 1.无特殊要求归于中心 2.所有地形上的建筑质心为底部中心 3.所有角色模型站立在原点 } 面数(移动平台) { ?感觉现在已经不止了 角色 主角模型控制在900-1300三角面,贴图控制在1张最好256*256,最多512*512 小怪 600-900三角面 最大256*256 武器面数 150 256*256/128*128 场景面数 10000 最多2张512*512 所有单个模型不能出现超过20000三角面的情况 否则导出可能出错? } !如果在MAX中对模型进行过缩放,需要加修改编辑器修正 选择缩放后的物体-工具(扳手形状)-> Reset XForm 重置变换->Reset Selected- -> 转位Poly(保存更改) 3.材质贴图规范 3dsMax不是所有的材质都被Unity支持 支持{ Standard Multi/Sub-Object(多维/子物体材质) } P329 贴图通道和类型 { 只支持Bitmap贴图类型 只支持Diffuse(漫反射) 和Self-Illumination(自发光) 贴图通道 } 贴图文件格式和尺寸 { unity支持格式 {PSD,TIFF,JPG,TGA,PNG(性能较好),GIF,BMP} 不支持: JPEG,DDS(不完全) PSD(PhotoShop格式)中层在导入Unity之后会自动合并 GIF导入后只显示第一帧 贴图文件尺寸 必须为2的N次方 最大贴图尺寸不超过2048*2048 UI贴图尺寸可以不遵循2N次方设计 } 4.命名规范 { 1.不能使用中文等 GBK格式命名,不能重名 2.美术资源后缀统一为小写字母 3.材质命名-> "mat"开头 mat_wenli_al 4.贴图命名-> "tex"开头 tex_wenli_al } 5.模型导出 { 1.合并顶点(焊接/!目标合并)、清除场景、删除无用物件 2.无特殊需求不导出灯光、相机 3.按要求导出fbx,导出fbx后导入max看动画是否正确 4.一般导出默认选项,如无动画则不导出动画 }}
}
day31 P330 {
{Shader glass 玻璃 边缘高光(Rim power/Rim color)
法线贴图(Normal map)->表现模型凹凸效果
Unity shader package包 (Effects)
改变shader private Render render; render.material.shader = …;
!!Shader编程 GPU执行,针对3D对象进行操作的程序
shader编程种类: CG: 与DirectX 9.0 以上以及OpenGL完全兼容,运行时编译成GPU汇编代码
HLSL: 主要用于Direct3D 。 Windows平台 GLSL:主要用于OpenGL。 移动平台(IOS,安卓,mac)
unity3d里CG输出: { Windows: Direct3D,GPU汇编代码 mac:OpenGL GPU汇编代码 flash:flash GPU汇编代码 ios/android: CG转换为GLSL代码 }
?缓存-> 一个像素包含如下缓冲 { 颜色缓存 color buffer/pixel buffer 即将显示的颜色 深度缓存 depyh buffer/z buffer 储存深度值 模版缓存 stencil buffer 限制渲染区域 累积缓存 Accumulation Buffer 与颜色缓存类似,为合成多幅图像设计-> 在保持好的颜色分辨率下"多重曝光"。产生许多图像效果来提高图像真实性-> 包括-> 反走样、运动模糊、软阴影、景深、卷积。对图像渲染多次,对场景位置(物体)进行微小的、渐增的改变并累积结果。
} 图元装配(Primitive Assembly): 经过变换的顶点被装配成几何图元 光栅化(栅格化 Rasterization): 三角形图元(矢量)转换为像素碎片的过程。 光栅操作: 碎片处理后,在更新帧前最后执行的一系列操作: 裁剪,深度测试,Alpha(透明通道测试),Alpha混合。 碎片-> 更新像素潜在需要的一个状态,不等于像素。 P333 Shader按管线分类分为固定渲染管线和可编程渲染管线 ->1. 固定渲染管线 : 标准几何&光照,功能固定 2.可编程渲染管线: 对顶点运算和像素运算分别编程处理。 计算机图形学深造-> https://www.zhihu.com/question/41468803 Unity中三种自定义Shader { 1.surface shader 表面着色器 2.vertex and fragment shaders 顶点和片元着色器 3.fixed function shaders 固定功能管线着色器 } ShaderLab基本结构 { shader "name" { Properties{ //属性 //查看ShaderLab:Properties _Cube("Cubmap",CUBE) = ""{} //立方体贴图-> 反射效果 } SubShader{ Tags{"RenderType" = "Opaque"} //描述渲染类型-> 不透明物体 //Tags{"RenderType" = "Opaque" "queue" = "transparent"} // ->透明的物体 //最优,最少要有一个SubShader //查看ShaderReference Pass{ //pass通道 //存储图像中的色彩等 } } SubShader{ //其次,上一个执行失败执行这个 } FallBack "Diffuse" //回退,全部失败,则执行默认 } } Properties 属性 Material 材质 Lighting 光照 Settexture 设置纹理 Pass Pass通道-> 存储图像色彩 shader 不区分大小写。 [_Color] //[]使用参数值 Color(1,0,0,1) //()使用固定值 光 / 高光 ->光照直射位置 / 环境光 !! CGPROGRAM //CG代码块 #pragam surface surf Standard fullforwardshadows //surface 表面着色器 //surf 调用的方法 //Standard 基于物理的光照模型(原来是漫反射) //fullforwardshadows 阴影表现 //pragma target 3.0 //GPU硬件支持3.0 不再默认2.0 sampler2D _MainTex; //CG代码块中需要重新声明 struct Input{ float2 uv_MainTex; //记录UV纹理坐标 } half -> 浮点型 fixed4 -> CG中的四阶向量 void surf(Input IN,inout SurfaceOutputStandard o) { fiXed c = tex2D(_MainTex,IN.uv_MainTex) * _Color; O.Albedo = c.rgb; o.Metallic = _Meaatllic; //金属光泽表现 O.Smoothness = _Glossiness; //高光光泽度 O.Alpha = c.a; } ENDCG Cubemap INTERNAL_DATA; -> 既要反射又要与法线配合时使用 例程 { Shader "Custom/Diffuse Texture" { Properties { _MainTex("Texture",2D) = "white"{} //纹理 _BumpMap("BumpTex",2D) = "bump"{} _Cube("Cubemap",CUBE) = ""{} //立方体贴图 } SubShader { Tags{"RenderType" = "Opaque"} //描述渲染类型-> 不透明物体 //*********************************************** CGPROGRAM #pragma surface surf Lambert //!!! struct Input{ float2 uv_MainTex; float2 uv_BumpMap; float3 worldRefl; INTERNAL_DATA //获取图片内部信息 }; sampler2D _MainTex; //CG里面再声明一遍 sampler2D _BumpMap; samplerCUBE _Cube; void surf(Input IN,inout SurfaceOutput o) { o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb; o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap)); //与法线贴图配合金属反射 //WorldReflectionVector 函数计算逐个像素向量 o.Emission = texCUBE(_Cube,WorldReflectionVector(IN,o.Normal)).rgb; //普通金属反射 //o.Emission = texCUBE(_Cube,IN.worldRefl).rgb; } ENDCG //************************************************ } FallBack "Diffuse" } }}
day32 NGUI P341 -P367 { 先跳过-> 搞不到可用的NGUI包
}
day33 Unity2D P368 {
动画切割-> 精灵切割-> 选中所有切割动画拖到Scene ->形成动画
animation-> 改变动画贴图,动画办法速度(simples)
改变图片大小-> PS-> 新建需要大小的画布-> 拖入图片,缩放 -> 删除背景 - > 保存
unity中的画布背景-> 背景设置为-1 或者-> Sorting Layer
2D碰撞 -> PolyGon Collider 2D刚体 -> { 质量 线性阻力/角度阻力 重力大小 锁定轴 }
!!!使用2D碰撞器触发器 private void OnCollisionEnter2D(Collision2D collision) private void OnTriggerEnter2D(Collision2D other)
P367 unity2D 碰撞效应 Used By Effector 使用碰撞效应
Component-> Area Effector2D 地区效应->特定地区反作用力不同 { Use Collider Mask 遮罩 Use Global Angle 使用全角度 Force Angle 力左右角度 Force Magnitude 应用力的大小 Force Variation 力的大小变化 Drag 阻力 角阻力 }
Buoyancy Effect2D 浮力效应 { Use Collider Mask 遮罩 Surface Level -> 浮力液体表面 Density 密度 Linear Drag 线性阻力 Angle Drag 角度阻力 Flow Angle 流动力在世界坐标的角度方向 }
Point Effect2D 点效应 { Use Collider Mask 遮罩 Force Magnitude 应用力的大小 负值->吸附 正值->排斥
Force Variation 力的大小随机变化 Distance Scale 应用于源和目标之间的距离 Drag 线性阻力 Angular Drag 角度阻力 Force Source 力源点吸引或排斥 Force Target 力应用目标 Force Mode 力计算的模式 Constant 恒定的}
Plane Effect2D 平台效应 { 不要勾选IsTrigger
实现单面碰撞->马里奥里面的单面弹簧}
Surface Effector 表面效应: { 不要勾选IsTrigger 实现传送带效果
}
!!!2D项目需求-> { 1.控制主角上下移动 2.每间隔2s,在两个随机诞生点随机产生2种怪物, 碰到城堡后(碰撞器)城堡掉血并且敌人播放爆炸动画
3.子弹打到敌人,敌人播放爆炸动画 4.随着敌人不断诞生,敌人移动速度越来越快 5.防御技能:3个UI按钮-> 减慢敌人移动速度,城堡加血,销毁所有敌人 6.倒计时->一定时间后产生BOSS 精灵分割-> 锚点定在不动点}
}
day34 安卓发布/平台发布 { P378 1.配置Java开发环境
2.指定Android SDK路径
3.Edit->Preference-> External Tool -> 指定SDK地址->
unity中选择发布平台
4.选择PlayerSetting
5.设置公司名,游戏图标
6.Build生成APK格式
EasyTouch
Input.GetTouch() ->获取触摸方法
}
day35 复习 P391 {
碰撞器 { 两者都要有碰撞器 动的物体有刚体 不勾选IsTrigger Oncollision(Collision collision)
}
触发器 { 两者都有碰撞 动的物体有刚体 最少有一个勾选IsTrigger OnTriggerEnter(Collision other)
}
物理引擎(关节 布料)
美术规范
Shader(Shaderlab 基本结构 常用功能 写常用Shader 挪用其他Shader )
NGUI
unity2D
IOS发布
2D手机项目
}
day36 面向对象 P392 { 项目需求-> 开发一款简单的ARPG游戏项目 ->ARPGDemo
需求调研-> 需求细化 核心系统: { 1.角色系统:角色创建,属性管理 2.运动系统:转向,移动 3.技能系统:技能管理,技能释放 4.动画系统:动画播放,事件处理 5.AI系统 :AI操控,感知,寻路 6.成长系统:等级管理,经验管理 7.任务系统:任务,任务完成统计,任务管理 8.背包系统:装备,物品管理,升级
}
运动系统/技能系统/AI系统 程序框架,人工智能
OOP/Framework/DesignPattern 设计模式 AI 人工智能
C#高级语言
P402
类、对象 封装 { 1.类-> 一组相似事物的统称 2.字段 方法 3.语法 class Student { public Grade g;
private void test(); } !!P404 设计原则 类与类的关系: { 1.泛化: 类与类的继承关系。耦合度高 { 继承:泛化 -> 得到父类 向上 刀,枪-> 武器 特化 -> 得到子类 向下 武器-> 刀,枪 } 2.实现: 实现类与接口之间的关系 3.关联: 整体与部分的关系 -> 一个类作为当前类的成员出现 Head{eye,mouth,nouse} 4.依赖: 类与类的协作关系。另一个类作为当前类型的函数的参数或返回值 Class A{Test(Courese c)} } 6.里式替换原则: 1.父出现的地方可以被子替换。 2.要保护替换前的原有工作,在替换后依然保持不变 override public void F1() { base.F1(); //。。。 console.writeLine("aaa"); //扩展 } 3.子类在重写父方法是(虚方法重写),尽量选择扩展重写。 7.接口隔离原则: 定义小而精的接口,少定义大而全的接口 交互-> 建立交互:找关系 移动->调用马达移动方法->动画系统播放移动动画 Framework 设计-> 步骤1: 发现潜在数据类型、潜在字段、潜在方法 步骤2: 确定对象的数据类型 步骤3: 确定最终的设计结果 设计成果: 系统类图 ->文字、图片 5实现:根据类图 实现代码 1.根据类图,构建框架 2.根据开发经验 -> 实现类的细节: 确定字段准确类型,访问性 确定方法的返回类型参数,访问性 确定方法体 6.类的成员-> 字段、方法、【属性、索引器、事件】 确定对象数据类型-> 枚举/结构/类/接口/委托 确定方法类别 -> 普通方法/抽象方法/虚方法/重写方法/隐藏方法 ArpgDemo实现 P408 类图 -> 使用类图实现可视化框架搭建}
} day37 { !构造函数不能继承
代码复用: 两种方式-> 组合 - 继承
复杂类型字段不在unity属性窗口赋值
p412 EasyTouch事件通知的使用方式-> { 1.设置UI 2.注册事件 3.实现方法 }
注册事件-> public void JoystickMove(MovingJoystick move) { … }
#输入参数-> 委托。后面会介绍 public void JoystickMoveEnd(MovingJoystick move){…}
private void OnEnable() { EasyJoystick.On_JoystickkMove += JoystickMove; //JoystickMove 为要注册的函数名 EasyJoystick.On_JoystickkMoveEnd += JoystickMoveEnd; } private void OnDisable() {…}
移动-> {
转向 前进方向向目标移动: 核心 调用内部组件提供的方法播放运动动画//转向 LookAtTarget(new Vector3(x,0,z));
//向目标运动 move.joystickAxis.x,move.joystickAxis.y
}
class CharacterMotor { private CharacterController charcontroller;
private void Start() { anim = this.GetComponent(); charcontroller = GetComponent(); }
public void Move(float x,float z) { if (x != 0 || z!= 0) { //1.转向 LookAt(new Vector3(x, 0, z)); //2.移动 //-1 -> 模拟重力,保证主角贴合地面不会飘起来 ? Vector3 motion = new Vector3(transform.forward.x,-1, transform.forward.z); //transform.position = Vector3.Lerp(transform.position, motion, moveSpeedTime.deltaTime); //速度控制 charcontroller.Move(motionTime.deltaTime*moveSpeed); //3.播放动画 anim.PlayAnimation(“run”); } else { anim.PlayAnimation(“idle”); } }
/// <summary> /// 转向 /// </summary> public void LookAt(Vector3 target) { Quaternion direction = Quaternion.LookRotation(target); transform.rotation = Quaternion.Lerp(transform.rotation, direction, rotateSpeed*Time.deltaTime); }} class playercontroller { public void JoyStickMove(MovingJoystick move) { playermotor.Move(move.joystickAxis.x,move.joystickAxis.y); }
/// <summary> /// 摇杆停止执行 /// </summary> public void JoyStickMoveEnd(MovingJoystick move) { playermotor.Move(move.joystickAxis.x, move.joystickAxis.y); } private void OnEnable() { EasyJoystick.On_JoystickMove += JoyStickMove; EasyJoystick.On_JoystickMoveEnd += JoyStickMoveEnd; } private void OnDisable() { EasyJoystick.On_JoystickMove -= JoyStickMove; EasyJoystick.On_JoystickMoveEnd -= JoyStickMoveEnd; }继承-> { 实现复用-> 不用每次从头开始 所有类继承Object方法,不需要写
优点-> { 复用代码 概念复用 层次化管理类 } 缺点-> { 耦合度高 不宜继承太深 尽量选择抽象类继承 }
!继承特点-> { 父类中所定义除私有成员都继承给子类 构造方法不会继承 }
1.构造方法不会继承给子类 2.创建子类对象时,自动调用父类构造方法 且父类构造函数先执行,子类后执行 3.调用构造方法-> new 类时 -> 创建对象时
!!!4.当子类创建对象时,默认调用父类的无参构造方法, 若父类有构造方法,则调用此方法,父类不再生成无参方法 父类构造方法有参数且需要调用: 子类中通过base实现调用有参构造函数 public Dog():base(123) {} 传变量 public Dog(int id):base(id) {} 5.构造函数相互调用 public Dog(string ev):this() 本类中调用用this 子类调用父类用base
用继承的方法 至少满足单向is-> A is B or B is A
访问修饰符 private 私有 只有当前类可用 protected 保护 当前类和后代可用 internal 内部 当前项目可用 public 共有 不受限制能放在数据类型 (类) internal(默认) public
能放数据类型的成员 (字段,方法) private(默认) protected internal public
未声明的类的默认访问修饰符为 internal
类中的成员默认访问修饰符 private
保护类的后代调用后代写法有限制,并不能随便使用 { base.Fun() Fun() }
base的作用-> 调用父的构造方法和父的非私有成员
继承组合判断 is/has ?
switch (a):
case 1: case 2: …! //switch实现 1||2执行同一个方法 break;
调用端会单独放在类中
子调父 -> 容易 父调子 -> 难
}
}
day38 多态polymorphic p418 { 多态-> 多种形态,对象的多种形态,行为的多种形态
!体现->子类对象可以通过父类型引用, Animal obj3 = new Dog();
行为的多态体现-> 方法重写,隐藏,重载
!在共性的基础上->体现类型和行为的个性化。
多态-> 复用 选择性要 选择性复用
{ 1.子的方法与父方法重名且重参数-> 隐藏->子方法覆盖父方法 2.子方法与父方法重名但参数不同-> 重载-> 两个方法 3. }
多态实现-> { 1.基本方式: 隐藏,重写 2.其它方式:重载,接口 }
方法隐藏-> { 父类方法在子类不适合,子类定义同名同参数方法 但父方法不能重写(不是override,virtual,abstract)
建议写法 有意隐藏-> new public void Fun() Why: 为了选择性复用(父类方法在子类不适合) What: 多态实现的一种方式 How: 使用步骤 { 1.父类定义普通方法 2.子类中定义隐藏方法(名和参数都相同) new public void Fun(){} } 隐藏的缺点-> 只能 子 a = new 子(); 不能 父 a = new 子();-> 这样仍然用的父的 P427 }方法重写-> { 在基类中定义虚方法-> virtual public void FUN(){} 在子类中定义重写方法-> override public void FUN(){}
虚方法: 用Virtual关键修饰的已经实现的方法 含义:表示一个可以在子类中重写的方法 三种重写 { 1.虚方法重写 2.重写方法重写 3.抽象方法重写 } 三种方法可重写: abstract->方法在子类中必须重写, 除非子类也是抽象类 virtual->方法在子类中可以重写 override-> 已经重写过的方法,在子类中可以继续重写, 除非被标识为sealed 方法重写时必须在方法前加入override P428 1.虚方法重写: 在父类中定义虚方法,子类中定义重写方法 方法名相同,参数相同,返回类型相同 virtual public void FUN(){} 子类-> override public void FUN(){} sealed(密封) 1.用在类的定义上-> 提示当前类不能做父类,也就是任何类都不可继承当前类 sealed class A() 封闭类->可以实例化。 2.用在重写的成员,提示当前类的子类,不能重写该成员 sealed用在方法前,必须和override一起 sealed override public void Fun() 封闭方法 共性:多态实现方式,实现选择性复用 不同: 1. 2. 实现原理不同 隐藏: 静态绑定,性能略高,灵活性差 重写: 动态绑定,性能略低,灵活性好 绑定-> 系统确定一个类型能调用那些方法的过程 静态:运行前确定 动态: 运行期间(过程中)确定 重写 -> 改变方法地址-> 父 obj = new 子() 子 obj = new 子() 都调用子的方法 } 扩展重写-> class Dog:Animal { override public void Run(){ base.Run(); 在父的方法的基础上扩展 ... } }}
day39 抽象类抽象方法接口 { 抽象类 -> 可继承不可实例化 { 例子-> Gun抽象类 Ak47 -> 继承Gun-> 一般类
1.语法用abstract修饰抽象类 abstract class A{} 2.特点: 抽象类不能创建对象(不能实例化) 可以被继承 抽象类可能包含抽象成员(方法,属性) (可为空,可有可无抽象成员) 抽象 具体 是相对的 抽象类运用场景-> 1.表示一个抽象概念Gun->Ak47 2.不希望类创建对象 3.有行为,但是不需要实现的时候 -> 受到伤害,死亡(玩家和怪物是具体实现不同) -> 父类实现管理,子类实现具体功能-> Virtual -> override 实现 abstract public void Run(); -> 定义抽象方法 分析时找角色找共性时 4.有多个概念,需要一致的管理时 5.一些行为,在做法上有多种可能且不希望客户了解。 { 1.希望做基类 2.分析时找角色共性 }}
抽象方法 { 1.只有声明定义,没有实现的方法。 abstract public void Run();
2. 实现类必须实现所有抽象方法 抽象方法必须放在抽象类或接口中 3. 抽象类中的抽象方法必须加abstract ->且不能私有 实现类实现抽象方法必须加override ->且不能私有 class Animal { string name; abstract public void Run(); } class Dog:Animal { override public void Run(){ ...; } } 1.子类也是抽象,子类可以不实现抽象方法 2.父抽象子类实现不能为虚方法。 ->子类override方法可以再次被重写}
抽象方法和虚方法的比较: 1. 未实现:已实现 2. 必须重写:可以重写
!接口 P437 { 使用关键字interface创建的数据类型 接口名建议用"I"开头,其后单词首字母ISwitch,IFly…
1.接口是一组行为的抽象 2.接口是规范,定义一组对外的行为规范。 要求他的实现类必须遵循。 3.接口中只有行为,不能有字段 ->可以包含:行为【方法、属性、索引器、事件】 接口不能有访问修饰符 interface ISwitch { void On(); void Off(); } class Door : ISwitch { public void On(){} public void Off(){} } 接口中的抽象成员,不能加abstract,不能加访问修饰符 实现接口的方法不能加override。 默认抽象、默认公共。 接口的作用—> 1.扩展一个已有类的行为 2.规范不同类型的行为,达到不同类型在啊行为上是一致的 提取不同类别的共性的行为,这个行为实现的最大的复用性 鸟/昆虫 有的会飞有的不会飞 ->建立飞的接口 鸟 共性: 腿 羽毛 【行为】吃 走 昆虫 老鹰:鸟,飞的接口 蝴蝶:昆虫,飞的接口 飞机: 飞的接口 实现类实现可以实现多个接口,用: 接口的使用: 继承: 1.类继承类-> C#只能单继承 2.类继承接口【类实现接口】-> 可以多继承 3.接口继承接口 ->接口继承接口,不可以实现接口功能 P438 interface IMy:IMotherEye { void Eye(); //✔ ✖public void Eye(){} } 4.结构可以实现接口,但不能继承类 struct MyStruct:IMotherEye{} 接口和接口之间可继承,且可以多继承 类与类是单继承,类与接口是多实现,接口与接口是多继承 结构可以实现接口,但不能继承【类】 类继承接口-> 继承了就必须实现接口 { class Son2:IMotherEye { 1.隐式实现-》常用 public void Eye(){} 2.显式实现 -> 不加修饰符,很少用 1.解决接口中的成员对实现类不适应的问题 2.解决多接口实现时的二义性 Void IMotherEye.Eye(){} } } 显示实现 接口调用—> Son:Father,IMotherEye { void IMotherEye.Eye(){} } static void Main(string[] args) { IMotherEye son = new Son(); son.Eye(); }一个例子 namespace Homework10-12 { class Client { static void Main(string[] args){
Shape[] shape = new Shape[3]; shape[0] = new Circle(3f); shape[1] = new Triangle(1f, 2f); shape[2] = new Rectangle(1f, 2f); //!!!!!!!!!!! foreach (IAreaCalculate a in shape) { Console.WriteLine(a.AreaCalculate()); } //!!!!!!!! }}
class Shape { //virtual public float AreaCalculate() { return 0; } }
interface IAreaCalculate { float AreaCalculate(); } class Circle : Shape,IAreaCalculate { float r;
public Circle(float r) { this.r = r; } public float AreaCalculate() { return (float)Math.PI * r * r; }}
class Rectangle : Shape,IAreaCalculate { float height,width;
public Rectangle(float height, float width) { this.height = height; this.width = width; } public float AreaCalculate() { return (float)height * width; }}
class Triangle : Shape, IAreaCalculate { float width,height;
public Triangle(float height, float width) { this.height = height; this.width = width; } public float AreaCalculate() { return (float)height * width*0.5f; }}
}
}
!!!
数据类型 标识 可以被继承 可以被实例化 比较 比较 结构 struct 不可以(只能接口) 可以 值类型 封闭类 sealed class 不可以 不可以 引用类型 一般类 class 可以 可以 抽象类 abstract class 可以 不可以 抽象类中可以有实现【字段,实现方法,抽象方法】,只能被单继承 接口 interface 可以 不可以 只有方法没有成员 接口中只有抽象成员【方法,属性】不能有任何实现,接口可以被多继承 接口是特殊的抽象类
方法类型 标识 调用 比较 能加static吗 能包含在结构中吗 一般方法 无 / 可以 可以 构造方法 无数据类型 new 类名(); 可以 static 类名 可以 析构方法 ~类名(){} 不用写 隐藏方法 new 子类.方法() 可以 不可以,结构不能继承 虚方法 virtual 类.方法() 不可以 不可以 重写方法 override 父 A = New 子();子.方法 不可以,静态成员不支持virtual,override,abstract 不可以 抽象方法 abstract(抽象类中)不能调用 不可以 不可以 无 (接口中) 重载方法 无 与原方法名相同 可以 可以 静态方法 static 类名.方法()
Framework常用接口 { IComparable 比较接口 常用 使类型支持比较大小的功能 IComparer 比较器 常用于排序比较 IEnmuerable 枚举器 常用 使类型支持简单迭代foreach IEnumerator 枚举器 支持MoveNext,可以自己控制迭代节奏 }
Student zs = new Student{Name=“zs”,Age = 20,Tall = 200}; Student ls = new Student{Name=“ls”,Age = 22,Tall = 160};
class Student:IComparable { public int Age{get;set;} public int Name{get;set;} public int Tall{get;set;}
public int CompareTo(object obj) { return this.Age.CompareTo((obj as Student).Age); } }
//IComparer需要引入System.collection命名空间 class TallCompare:IComparer { public int Compare(obj x,obj y) { return (x as Student).Tall.CompareTo((y as Student).Tall); } }
自定义对象的比较 写法1 zs.Age.CompareTo(ls.Age)
写法2 zs.CompareTo(ls); //默认比较年龄 //如果要比较身高 -> TallCompare tallcom = new TallCompare(); int re2 = tallcom.Compare(zs,ls);
泛化特征-> 类-> 鸟 接口-> 飞 接口-> 游泳
}
day40 委托与事件 P447 { 委托 方法的代理-> 委托的是方法,当调用委托时就调用了这个方法 委托->一类行为的抽象。 是方法的引用, 是一种数据类型。 事件 -> 委托的一种使用方式
delegate : 一种【声明了返回类型和参数组成】的一种数据类型 ->与class,enum,struct,interface同级 委托代表/表示/代理/的是方法->代表和委托签名一致的任何方法
签名 含义不同 1.签名 = 返回类型和参数 【】 2.签名 = 方法名和参数【隐藏】 3.签名 = 方法名和参数组成,返回类型【重写】当调用委托时就调用了这个方法
!语法: //步骤1: 声明委托,定义委托 delegate void MyDelegate(); #习惯: 委托命名: xxxxHandler #example SelectHandler
步骤1.定义委托 delegate 返回值 方法签名 delegate void Handler();
步骤2. 创建实例 实例化委托:为委托指定{绑定}方法,只指定方法名 Handler handler = new Handler(Fun);
步骤3. 调用委托【和调方法相似,有参数就写参数,有返回值可以返回】 handler();
委托调用方式 1.单播委托 一个委托关联一个方法 2.多播委托 一个委托关联多个方法
MyDelegateHandler handler = new MyDelegateHandler(SumTwoNumber); //注册委托事件 handler = handler + new MyDelegateHandler(Divided); -> 注册多个委托 int re = handler(1,3); ->多播委托只返回最后一个方法的返回值!委托的几种写法:
1.标准写法 1 Handler handler = new Handler(Fun); 2.标准写法 2 Handler handler = Fun; 3. 匿名方法 适合于简单少量代码可完成且在其它地方不需要复用 Handler handler = delegate(int a) 等号右边表示委托对象 = 匿名方法 { return 888; } 4.!Lambda 表达式 Handler handler = (p) => Console.writeLine(p + 123); 等号右边表示委托对象=Lambad 表达式=匿名方法 写法一 :(参数) => {语句体} //=>goto 写法二:一个参数=> 一行代码; //!return不能写public delegate Person PersonHandler(List pList, int Id);
class Client{ static void Main(string[] args) { List personList = new List(4); for (int i = 0; i < 4; i++) { personList.Add(new Person(i, 15+i)); }
//1. PersonHandler personhandler1 = new PersonHandler(ChooseId); //2. PersonHandler personhandler2 = ChooseId; //3. PersonHandler personhandler3 = delegate (List<Person> personL, int Id) { Person result; foreach (Person i in personL) { if (i.Id == Id) { result = i; return result; } } return null; }; //4. PersonHandler personhandler4 = (List<Person> personL, int Id) => { Person result; foreach (Person i in personL) { if (i.Id == Id) { result = i; return result; } } return null; }; Person a = personhandler1(personList, 0); Person b = personhandler2(personList, 1); Person c = personhandler3(personList, 2); Person d = personhandler4(personList, 3); } static Person ChooseId(List<Person> personL, int Id) { Person result; foreach (Person i in personL) { if (i.Id == Id) { result = i; return result; } } return null; }}
public class Person { public int Id; public int age;
public Person(int Id, int age) { this.Id = Id; this.age = age; }}
!委托的作用 用途 P452 1.方法作为参数传递,将方法执行代码注入带另一个方法中 方法1 (int string 数据数值) 方法2 (方法1语句块)
Handler handerObj = aObj.F1; aObj.F2(handerObj); 等价于-> aObj.F2(aObj.F1); //方法F2注入lambda方法 aObj.F2(()=>{ Console.writeLine("1111"); }); public void F2(Handler handerObj) { handerObj(); Console.writeLine("2222"); Console.writeLine("3333"); } a.暂时定不下来的功能,先通过委托确定 b.有些函数需要客户端确认 public void Fun(handlerOBJ buycai) { buycai();//买菜,但不知道能买到什么 做菜(); }2.实现回调,实现的方式比接口更灵活 回调含义 1 方法 系统调用的 比如->调定点闹钟 2 定义时定不下来,留给客户端确定
3.通过委托实现异步调用 P459 a.同步调用: 排队调用,前一个方法执行时后一个等待 b.异步调用: 前一个方法如果是异步调用-> 后一个方法不必等待它的结束就可启动。 实现方法-> 创建一个新线程。 步骤: 1.为异步调用定义一个相应委托 2.创建委托的引用指向需要异步调用的方法 3.使用委托类型的BeginInvoke方法开始异步调用 a.BeginInvok中的参数IAsyncCallback表示异步调用的方法结束时执行的回调方法 往往用回调方法来处理异步的结果 b.BeginInvok中的参数object表示回调方法中需要用到的数据,该数据被封装在IAsyncResult的AsyncState属性中
4.若方法有返回值,则需要调用EndInvoke取得返回值。 1> delegate void Handler(); class B { //声明外部 AsycCallback callbackObj = null; 2>实例化 A obj = new A(); Handler handlerObj = new Handler(obj.Fun); 3>开始异步调用 //BeginInvoke参数-> //AsycCallback参数->指定一个方法取结果 //object 用来传参数 handlerObj.BeginInvoke(callbackObj,null) callbackObj = new AsycCallback(FunGetResult); static void FunGetResult(IAsyncResult ar) { //4> 结束异步调用【如果有返回值,可以取返回值】 callbackObj.EndInvoke(ar); } } 例子 -> 但是我的平台报错说不支持这种操作 { namespace Homework6{ //通过委托实现异步调用
class Client {
public delegate void asynchronousHandler(); //声明委托 static asynchronousHandler asynchronoushandler = null; static void Main(string[] args) { A obja = new A(); asynchronoushandler = new asynchronousHandler(obja.Fun1); //注册委托 AsyncCallback callbackObj = new AsyncCallback(FunGetResult); asynchronoushandler.BeginInvoke(callbackObj, null); //开启异步调用,结束时调用FunGetResult函数 obja.Fun2(); } static void FunGetResult(IAsyncResult ar) { asynchronoushandler.EndInvoke(ar); }}
class A { public void Fun1() { //执行事件比较长的程序 Thread.Sleep(3000); Console.WriteLine(“1111111111”);
} public void Fun2() { //执行时间短的程序 Console.WriteLine("2222222"); }}
}
} https://blog.csdn.net/tangpeicheng/article/details/1427448应用: 作为方法的参数传入
!!!事件 P456 { 委托是事件实现的基础;
事件是委托的一种使用方式。 对象1 的方法 调用对象2的方法 定义: 偶发,有影响,事件发生后其他对象做出响应 一种天然的通知模式 一个天然观察者模式 一种设计模式 实现两个或多个行为的联动调用。 事件的使用:【事件设计模式】【事件模式】 定义端-> 1,3 调用端2,4 1.定义事件 public event 委托类型 事件名; (event可以省略) 2.注册事件 事件源对象.事件 += 响应方法 传统写法 事件源对象.事件 += new 委托类型(响应方法) += 在这里是注册符号和算数加法不同。 -= 取消注册 3.触发事件 【4】.调用触发事件的方法}
猫和老鼠: 程序设计:猫叫-> 老鼠跑,主人醒 1. 要触发 2. 考虑可扩展性
namespace Homework5 { //实现事件 猫和老鼠
public delegate void CatHandler(); //声明猫的委托句柄
class Client { static void Main(string[] args) { Cat cat = new Cat(); Mouse mouse = new Mouse(); Houser person = new Houser();
cat.CatBark += mouse.Escape; // 注册老鼠跑的事件 cat.CatBark += person.WakeUp; // 注册主人醒的事件 //触发 Random random = new Random(); //注册种子 int j = 0; for (int i = 0; i < 100; i++) { j = random.Next(i - 5, i + 5); if (j == i) { cat.Notify(); //老鼠发现猫 } } }}
class Cat { public event CatHandler CatBark; //声明猫叫的事件
public void Notify() { if (CatBark != null) //如果事件中注册了方法-> 执行 { Console.WriteLine("miao~~~~"); CatBark(); } }}
class Mouse { public void Escape() { Console.WriteLine(“jiji~~~~”); Console.WriteLine(“老鼠逃跑”); }
}
class Houser { public void WakeUp() { Console.WriteLine(“主人醒了”); }
}
} 委托与事件的使用场景
委托: 1.事件设计模式 2.封装静态方法 3.当调用方不需要方法使用该方法的对象 接口: 1.当存在一组可能被调用的相关方法 2.类中只需要方法的单个实现 3.当使用接口的类想要将该接口强制转换为其他接口或类类型时。 接口 obj = (接口) Dogobj; 4.当正在实现的方法链接到类的类型或标识时: -> 比较方法}
day41 泛型 p463 { 泛型类,方法,接口,委托,集合
用于设计一些通用(基于不同类型的复用)的算法
.NET Framework
1.类型参数: 声明泛型的标记,代表任何数据类型 List T: int string Student
2.泛型 :使用类型参数定义的数据类型或方法
3.语法: MyCollection -> 泛型类 方法1. Class My 泛型类 { ArrayList arrList = new ArrayList();
public void Add(T i) 泛型方法 { arrList.Add(i) } } -> 实例化 My<int> myobj = new My<int>(); myobj.Add(1); My<string> myobj = new My<string>(); myobj.Add("123"); 方法2. class You { ArrayList arrList = new ArrayList(); public void Add<T>(T i) //只在方法这里声明泛型 { arrList.Add(i); } } -> 实例化 You youObj = new You(); youObj.Add(1); 例子 { #实现两个相同数据类型交换 static class A<T> { public static void Swap(ref T a ,ref T b) { T temp; temp = a; a = b; b = temp; } } } 泛型和Object区别 优点:通用性好,表示任何数据类型 不同: 泛型不需要类型转换,性能高,无拆装箱。 泛型约束: 用于对泛型的种类进行约束。 让类型参数只代表某些数据类型,限制范围。 通过where实现约束,共6种类型的约束,常用的有两种。 1.T: struct 类型参数必须是值类型—>结构,枚举 class My<T> where T:struct {} 2.T:class 类型必须是引用类型: 类,接口,委托 class My<T> where T:class {} 3.T:<基类名> 类型必须是基类或其派生类 class My<T> where T:Animal {} My<Dog> dog = new My<Dog>(); 4.T:<接口名> 类型必须是指定的接口或实现此接口的类 class My<T> where T:IFlyexample:-> static class Tool { /// /// 使用冒泡算法对Array进行排序,Mode = 0 升序排序,Mode = 1 降序排序 /// /// Comparable /// /// 0,1 public static void Sort(T[] Array,int Mode=0) where T : IComparable { int i, j; if (Mode == 0) { for (i = 0; i < Array.Length; i++) { for (j = i; j < Array.Length - 1; j++) { CompareAndSwap(ref Array[j + 1], ref Array[j]); }
} } else if (Mode == 1) { for (i = 0; i < Array.Length; i++) { for (j = i; j < Array.Length - 1; j++) { CompareAndSwap<T>(ref Array[j + 1], ref Array[j]); } } } else { throw new Exception("ModeError"); } } /// <summary> /// a大于b不变,a小于b,二者互换位置 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="a"></param> /// <param name="b"></param> public static void CompareAndSwap<T>(ref T a, ref T b) where T : IComparable { if (a.CompareTo(b) >= 0) return; T temp = a; a = b; b = temp; } }//使用泛型接口性能更高更安全 class Student:IComparable {
public int Age;
public int CompareTo(Student other) { return this.Age.CompareTo(other.Age);
}
}
}
} day42 { 集合-数据类型
列表 List ArrayList 扩展方法: List list = new List();
字典 Dictionary<Tkey,TValue> Hashtable add,remove,clear 属性:Count,Keys,Values
技能系统设计与实现 Common:工具类【通用性很强,很多项目可以使用】 1.类数据类型 2.通用性强 3.一般不写命名空间,一般定义 Public static的类 4.类中方法一般为public static
制作通用工具类 a. 数组工具类: ArrayHelper i. 排序:升序、降序 ii. 查找: 查找单个,查找全部 iii.选择:选取数组中对象的某些成员组成一个独立数组
引入委托: { public delegate int CompareHandler(T a ,T b)where T:IComparable; //声明委托,输入2个类
public static void Sort(T[] Array, CompareHandler compare,int Mode = 0)
public static void CompareAndSwap(ref T a, ref T b, CompareHandler compare) }
b. 对象池: P481 i. 容器 (用于存放缓存对象) ii. 添加 iii.删除 iv. 取得
概念: 1.对象池 -> 池,缓冲,缓冲技术 2.能放多个元素的容器 3.一种提高程序性能的技术,使用空间换取时间。 占用内存,提高速度 4.如何提高程序性能? 1.一般情况 需要对象-> 创建->不需要,清除 频繁创建->浪费性能 2.使用对象池 创建->使用->返回缓存到对象池 无需再次创建,节省再次创建时间。 5.如何使用对象池 频繁使用-> 子弹,小怪 对象池=池 游戏对象池 针对游戏对象使用池技术 实现-> 字典 实现: 类图实现【字典数据类型+unity游戏物体】 1.创建池 Dictionary<string,List<GameObject>> cache = new Dictionary<string,List<GameObject>>(); 2.创建一个对象并使用对象: 池中有从池中返回,池中没有加载一个,放入池中再返回 pool.CreateObject(子弹,pos,rotation); 3.释放资源: 从池中删除对象 3.1 释放一个对象 public void Clear(string key) { if(cache.containsKey(key)) { foreach (var obj in cache[key]) { GameObject.destory(obj) } cache.Remove(key) } } 3.2 释放全部对象 public void ClearAll() { foreach (var key in cache.Keys) { Clear(key); } } 4.回收对象: 使用完对象将对象返回池中【从画面消失】 -> a. 即时回收 b. 延时回收 通过SetActive来判断物体 对象池 example { public class GameObjectPool:MonoBehaviour{
//创建池,双层 //外层-> 子弹 -> 12345 枪-> 6789 private Dictionary<string, List> cache = new Dictionary<string, List>();
/// /// 创建对象,池中有则直接返回,池中没有,则放入池中 /// /// 键 /// 预制件对象 /// 位置 /// 四元数 /// public GameObject CreateGameObject(string key,GameObject perfab,Vector3 position,Quaternion quaternion) { GameObject WaitLoadGo = FindUsable(key); if (WaitLoadGo != null) { //存在可用的对象 WaitLoadGo.transform.position = position; WaitLoadGo.transform.rotation = quaternion; WaitLoadGo.SetActive(true); //设置启用,启用标志开启后该对象在池中不可用 } else { //池中没有对象,创建对象并放入池中 WaitLoadGo = GameObject.Instantiate(perfab, position, quaternion); WaitLoadGo.SetActive(true); AddGo(key, WaitLoadGo); }
//将对象父物体设为池 WaitLoadGo.transform.parent = this.transform; return WaitLoadGo;}
/// /// 找到可用对象 /// /// 对应键 /// 空对象或者找到可用对象 private GameObject FindUsable(string key) { if (cache.ContainsKey(key)) { //注意这里的Find是列表List的Find return cache[key].Find((a) => (a.activeSelf == false)); //active标志位为false表示该对象还未被激活 } return null;
}
/// /// //将新创建的对象加入池中 /// /// /// private void AddGo(string key, GameObject WaitLoadGo) {
if (cache.ContainsKey(key)) { cache[key].Add(WaitLoadGo); //注意这里的Add是列表List<GameObject>的Add } else { cache.Add(key, new List<GameObject>()); cache[key].Add(WaitLoadGo); }}
/// /// 删除给入键的物体缓存 /// /// public void Clear(string key) { if (cache.ContainsKey(key)) { foreach (var obj in cache[key]) { GameObject.Destroy(obj); //清除对象 } //清除键 cache.Remove(key); }
}
/// /// 删除字典中所有键 /// public void ClearAll() { foreach (var key in cache.Keys) { Clear(key); } }
/// /// 回收对象 /// /// public void RetrieveObj(GameObject Go) { Go.SetActive(false); }
/// /// //通过携程实现延时回收 /// /// /// public void RetrieveObj(GameObject Go, float delay) { StartCoroutine(RetrieveDelayCoroutine(Go, delay)); }
private IEnumerator RetrieveDelayCoroutine(GameObject Go, float delay) { yield return new WaitForSeconds(delay); RetrieveObj(Go);
}
}
}
单例游戏对象池 单例设计模式: 定义: 本质上是一个类,特殊类
特殊1: 构造方法私有 2: 包含一个静态私有字段,返回自己的类型 3. 包含一个静态公共方法 返回自己的类型 用途: 保证自己在应用程序域中 最多只有一个实例【对象】存在 得到唯一对象的唯一通道 class President { private President() {} public string Name{get;set;} static private President obj = new President(); static public President GetObj() { //饿汉模式 return obj; } } -》 static public President GetObj() { //懒汉模式 if(obj == new) obj = new President(); return obj; }创建单例对象池 1.单例对象池 private GameObjectPool() { }
static private GameObjectPool pool; static public GameObjectPool GetGameObjectPool { get { if (pool == null) pool = new GameObjectPool(); return pool; } } 2.借助(继承)EasyTouch的MonoSingleton<T>抽象类,使用Instance实例化 GameObjectPool 可以给空物体public class TransformHelper : MonoBehaviour { public delegate bool FindHandler(Transform tf);
static public Transform FindChild(string name,Transform tf) { if (tf.name == name) return tf; //递归终止条件
int childCount = tf.childCount; if (childCount > 0) { for(int i =0; i< childCount;i++) { Transform re = FindChild(name,tf.GetChild(i)); if (re != null) return re; } } return null;}
static public Transform FindChild(Transform tf, FindHandler findMethod) { if (findMethod(tf)) return tf; //递归终止条件
int childCount = tf.childCount; if (childCount > 0) { for (int i = 0; i < childCount; i++) { Transform re = FindChild(tf.GetChild(i),findMethod); if (re != null) return re; } } return null;}
static public void LookAtTarget(Vector3 target,Transform transform,float rotateSpeed) { if (target != Vector3.zero) { Quaternion dir = Quaternion.LookRotation(target); transform.rotation = Quaternion.Lerp(transform.rotation, dir, rotateSpeed * Time.deltaTime); }
}
}
}
day43 技能系统 P494 { 按下技能按钮-> 调用技能系统释放技能-> 调用动画系统播放动画->处理动画事件 -> 调用小怪受伤->可能调用死亡 ->主角收集经验->升级 引入-> 技能数据 SkillData 技能管理组件 SkillManager 施法器组件
攻击方 挂技能管理组件,选择要释放的技能,(释放器组件)动态创建技能预制件对象。 被击方 暂不实现技能系统框架图
攻击方挂技能管理组件,管理技能数据对象 ->选择要释放的技能
释放器组件 动态创建技能预制件对象,执行技能算法。
技能数据类图: 所有技能共性-> 存在攻击距离,范围,伤害敌人的类型,单攻或群攻,对自身影响,对敌人影响 { 1.技能数值可以调节 2.不同技能释放特性,动画,受击特效不同,可调节 3.玩家可选择随身技能 }
技能数据类 SkillData 类成员: 技能ID (SkillID) 技能名称 (name) 技能描述 (description) 冷却时间 (coolTime) 冷却剩余 (coolRemain) 魔法消耗 (costSP) 攻击距离 (attackDistace) 攻击角度 (attackAngle) 攻击目标tags[] (attackTargetTags) 攻击目标对象数组 (attackTargets) 连击的下一个技能编号 (nextBatterld) 伤害比率 (damage) 持续时间 (durationTime) 伤害间隔 (damageInterval) 技能所属 (Onwer) 技能预制件名称 (perfabName) 预制件对象 (skillPerfab) 动画名称 (animationName) 受击特效名称 (hitFxName) 等级(level) 是否激活(activated) 技能类型(SkillAttackType attackType) :单攻Single,群攻Group 伤害模式(DamageMode damageMode): 圆形Circle,扇形Sector,矩形Rectangle SkillData -> 不直接挂在物体上,间接挂在物体上 P497 [Serializable] -> 可序列化 ->不直接给物体,在其它组件中使用,要在unity窗口访问其字段。 字段-> { 简单类型 在属性窗口赋值 复杂类型 不适合在属性窗口赋值 [HideInInspector]->不在属性窗口出现 } 技能管理类 Skill { 数据: 管理多个技能数据对象 容器 List<SkillData> 方法: 1.初始化 2.准备技能 3.释放技能 4.技能冷却处理 5.获取技能冷却剩余时间 } P503 阶段2: { 3个接口 IAttackSelector ISelfImpact ITargetImpact 施法器组件 { 1.选择目标 2.选择参考点 3.对自身的影响 } P505 AddRange -> List 加集合。 !通过tag查找效率更高。 } P505 技能测试程序{ //玩家正方向 Debug.DrawLine(transform.position, (transform.forward* radius + transform.position), Color.green); //画出物体角度尺。 四元数左乘向量,向量绕该角度旋转。 Debug.DrawLine(transform.position, transform.position + (vector * transform.forward).normalized*radius ); Debug.DrawLine(transform.position, transform.position + (vector1 * transform.forward).normalized * radius); //玩家到敌人位置。 Debug.DrawLine(transform.position, enemytransform.position,Color.red); //(玩家到敌人的向量) } P506 写对自身影响的类,对目标影响的类 { 对自身 : Sp减少 对目标 : 减Hp } P512 { 技能释放器 工厂设计模式: 释放器初始化类(释放器工厂类)。-> { 1.static 返回技能选择对象。调用IAttackSelector接口。 2.创建初始化自身影响的 3.创建初始化目标影响的 public class SkillDeployConfigFactory{
/// <summary> /// 创建选择目标对象的方法 /// </summary> /// <param name="skillData"></param> /// <returns></returns> public static IAttackSelector CreateAttackSelector(SkillData skillData) { IAttackSelector attackSelector = null; switch (skillData.DamageMode) { case DamageMode.Line: break; case DamageMode.Rectangle: break; case DamageMode.Circle: attackSelector = new CircleAttackSelector(); break; case DamageMode.Sector: attackSelector = new SectorAttackSelector(); break; } return attackSelector; } public static List<ISelfImpact> CreateSelfImpactList(SkillData skillData) { List<ISelfImpact> list = new List<ISelfImpact>(); list.Add(new CostSelfImpact()); //...包含自身加状态,自身减蓝,加防御 return list; } public static List<ITragetImpact> CreateTragetImpactsList(SkillData skillData) { List<ITragetImpact> list = new List<ITragetImpact>(); list.Add(new DamageTargetImpact()); //... return list; }}
}
!在释放器中Skill属性的set中对所有要初始化的字段初始化
private SkillData my_skillData;
private GameObject[] attackGameObjects; protected IAttackSelector attackSelector; protected List<ISelfImpact> selfImpacts= new List<ISelfImpact>(); //对自身的影响列表 protected List<ITragetImpact> TargetImpacts = new List<ITragetImpact>(); //对目标的影响列表通过父类(接口)来实例化子类,调用父类(接口)方法
技能释放器-{近战释放器,远程释放器} 释放器工厂类-> 初始化属性 { public SkillData Skill { get { return my_skillData; } set { if (value == null) return; my_skillData = value; attackSelector = SkillDeployConfigFactory.CreateAttackSelector(value); selfImpacts = SkillDeployConfigFactory.CreateSelfImpactList(value); TargetImpacts = SkillDeployConfigFactory.CreateTragetImpactsList(value); } } }
攻击接口-> 圆形扇形
自身影响接口-> 减蓝功能
目标影响接口-> 减血
}}
day44 { 实现技能攻击动画以及外观类 :
事件设计模式-> (观察者模式)。
联动 常规: F1 F2 实现联动调用。 事件: 注册: F1 【F2自动调用】实现 AnimationEventBehaviour 动画事件行为类 包含攻击委托,攻击事件,以及动画组件。 例:按下按钮-> 播放动画->触发动画事件-> 其它行为
AnimationEventBehaviour
P518 !添加技能系统外观类: 技能系统中介。 引入目的-> 方便其它系统使用,保护当前系统的安全性和稳定性。
List a a.AddRange -> 一次加入多个元素。
ARPG 2.6 完成
技能系统tree -> Skill -> { AttackSelect->{IAttackSelector.cs,CircleAttackSelector.cs,SectorAttackSelector.csv,LineAttackSelector.cs} ConfigFactory->{SkillDeployConfigFactory.cs //选择攻击接口,选择自身影响数组,选择目标影响数组} SelfImpact->{ISelfImpact.cs,CostSpImpact.cs…} TargetImpact->{ITargetImpact.cs,DamageTargetImpact.cs…} LongRangeSkillDeployer.cs MeleeSkillDeployer.cs 近战施法器 SkillData.cs //技能基本字段 SkillDeploy.cs //施法器 SkillManager.cs //技能管理类 } Character->{ SkillSystem.cs //技能系统 }
}
day45 P526 { 优化: 资源管理的优化 Assests : 模型,组件,脚本… -> Resources: 预制件资源(动态加载资源) 引入专门的文件: 生成地图文件->资源配置文件 [.txt = .ini] 特点: 键=值 技能名称= 技能的实际路径 BaseMeleeAttackSkill = Skill\BaseMeleeAttackSkill Hit_Ice_Chest = Skill\HitFx\Hit_Ice_Chest
手动生成 自动生成 -> 制作自动生成资源配置文件->使用IO技术。 GenerateResConfig.cs !-> 文件放在Editor文件夹,这些文件只能在编辑器下使用,不会被打包进游戏。 -》 //所有\ 变换为/ ,得到路径 string path = Path.Combine(Application.dataPath,“Resources”).Replace(@"","/"); string[] resFiles = Directory.GetFiles(path,"*.prefab",SearchOption.AllDirection);
使用配置文件-> 资源管理类。
流程-> 加载预制件 { 1.读取资源配置文件 2.得到所需资源路径 3.
}
using UnityEditor -> MenuItem 为unity增加菜单项。
程序集反射:P528 程序集: .NET Framework 构造块 协同工作而生成的类型和资源的集合。
程序集定义 应用程序编译后的结果 : exe dll
控制台-> exe 有入口 类库 -> dll 没有入口 关系 dll+入口 = exe
P529 添加类库项目-> project2Library
项目中添加引用。
跨项目调用->
核心功能写在类库项目中,编译 注意加public在另一个项目中添加【程序集dll】引用引入using命名空间静态程序集-> { 代码,资源,信息(元数据) }
程序集用途: 1》发布部署。 混淆器(加密-> 阻止反编译)
反射-> 获取程序集中信息的技术
提供了描述程序集、模块和类型的对象。
使用反射动态创建对象的实例从现有对象中获取类型并调用其方法。
反射的作用-> { 1.了解类型信息。 2.动态创建类型实例。 3.动态访问实例的成员。 }
反射常用类 取得数据类型Type 对象的方法:-> {using System.Reflection
Type.GetType(“类型全名”) Type objtype = Type.GetType(“namspace.A”); //namspace命名空间中的A MemberInfo[] MArr = objtype.GetMembers(); //获取类中所有的成员 MemberInfo[] MArr2 = objtype.GetMethods(); //获取类中所有的方法 var MArr3 = objtype.GetEvents(); //获取类中所有的事件
obj.GetType(); 适合于类型名未知,类型未知,存在已有对象。 Type objtype = (new A()).GetType(); …
typeof(); Type objtype = typeof(A);
Assembly.Load(“XXX”).GetType(“名字”); 适合于类型在另一个程序集中 “D://…//…//prj2.dll”
Type类常用Get系列方法 Is系列属性
MethodInfo(方法) 重要方法:Invoke
PropertyInfo(属性) 重要方法:SetValue GetValue
FieldInfo (字段) 重要方法:SetValue GetValue
ConstructInfo(构造方法) 重要方法:Invoke
}
练习:使用反射技术,反射string成员有多少个(166个)。 static void Main(string[] args) { Type type = Type.GetType(“System.String”); var resule = type.GetMembers(); }
动态创建对象: Activator.CreateInstance(string 程序集名称,string 类型全名).Unwarp(); 使用反射技术创建对象
//1.得到Type类数据类型的对象 Type typeobj = Type.GetType(“namspace.A”);
//2.动态【使用反射技术】创建对象//或动态调用方法 object obj = Activator.CreateInstance(typeobj); Student stuObj = (Student)obj
//动态【使用反射技术】调用方法 MethodInfo method = typeobj.GetMethods(“Fun”); method.Invoke(stuObj,null); //调用对象构造方法和输入参数没有写null //有参数的方法: method.Invoke(stuObj,new Object[]{…});
//使用反射技术修改字段/属性 FieldInfo field=typeobj.GetField(“Fun”); field.SetValue(stuObj,…);
任务2将技能释放器工厂使用反射技术创建。 string allName = “ARPGDemo.Skill.”+skill.damageMode+“AttackSelector”; Type typeObj = Type.GetType(allName); -》 得到类类型为typeObj MethodInfo method = typeObj.GetMethod("");
//实例化类类型为对象。 object obj = Activator.CreateInstance(typeObj);
public static IAttackSelector CreateAttackSelector(SkillData skillData) {
IAttackSelector attackSelector = null; //获取类型名字全称,包括命名空间 string typeAllName = "ARPGDemo.Skill." + skillData.DamageMode.ToString() + "AttackSelector"; Type typeobj = Type.GetType(typeAllName); //动态反射,使用反射技术创建对象 object obj = Activator.CreateInstance(typeobj,null); //构造函数不需要参数,传入null attackSelector = (IAttackSelector)obj; return attackSelector;}
} day46 网络编程 P533 { tcp,udp 协议不同-> { 1.基于连接与无连接 2.对系统资源的要求TCP高,UDP少 3.UDP结构简单 4.TCP数据正确性高,tcp保证数据顺序,UDP可能丢包 流模式,数据报文模式?
}socket编程步骤 网络如何通讯。
TCP Transmission Control Protocol 传输控制协议 面向连接的协议,在收发数据前和目标建立可靠的连接
UDP User Data Protocol 用户数据报文协议 非连接协议,源端和终端不建立连接,想传送时简单去抓取来自应用程序的数据并。 http HyperText Transfer Protocol 超文本传输协议 网站 asp.net;java;php
FTP File Transfer Protocol 文件传输协议 网络上传输大文件
smTP simple mail transmission protocal 简单邮件协议
传输速度 http最慢 tcp udp最快
底层协议-> TCP,UDP 高层协议-> FTP,SMTP TcpListener Server-> { 启动(开始监听端口) server.Start(); 等待客户端连接 client = server.AcceptTcpClient(); 建立网络数据流 stream = client.GetStream(); 发送(写流) stream.Write()
... 停止监听 server.Stop();} TcpClient() { 建立连接(ip,port) client.Connect(); 获取数据流 client.GetStream(); 接收(都流) stream.Read();
... 关闭 stream.Close(); client.Close();}
组网-> 将许多设备连接成一个网络以共享资源。 System.Net System.Net.Sockets 命名空间-> 实现网络编程。
新建空白解决方案-> NetCodeDemo-> 新建项1(控制台应用程序 客户端),新建项2(控制台应用程序 服务端)
解决方案属性-> 启动项目-> 多项目启动 -> 服务端优先启动。
特点: 1.用到的命名空间多 2.方法调用先后非常严格 3.同时考虑两端【服务端、客户端】端口: 系统服务保留了端口1到端口1024的权力。
//Server static void Main(string[] args) { Console.WriteLine(“Server”); //1.创建服务端通讯对象 监听对象 TcpListener server = new TcpListener(IPAddress.Any,9999); //2.启动监听对象server。 server.Start(); //3.接受客户端连接请求 Socket socket = server.AcceptSocket(); if (!socket.Connected) { Console.WriteLine(“没有连上”); return; }
NetworkStream stream = new NetworkStream(socket); StreamReader reader = new StreamReader(stream); StreamWriter writer = new StreamWriter(stream); //4.数据交换 //发 Console.WriteLine("请输入要发送的信息"); string sendLine = Console.ReadLine(); writer.WriteLine(sendLine); //清空发送缓存 writer.Flush(); writer.Close(); reader.Close(); stream.Close(); socket.Close(); server.Stop();}
//Client static void Main(string[] args) { Console.WriteLine(“Client”); //1.创建客户端对象 TcpClient client = new TcpClient();
//2.连接服务器 client.Connect(IPAddress.Parse("172.31.117.208"),9999); if (!client.Connected) { Console.WriteLine("没有连上"); return; } Socket socket = client.Client; //网络流 NetworkStream stream = new NetworkStream(socket); StreamReader reader = new StreamReader(stream); StreamWriter writer = new StreamWriter(stream); //收数据 string getInfo = reader.ReadLine(); Console.WriteLine("Get Message: " + getInfo); Console.ReadKey(); writer.Close();reader.Close(); stream.Close();socket.Close(); client.Close();}
C#【应用程序源代码】 csc 【exe,dll,】 clr{il编译器} -> 二进制代码 托管型代码 托管donet平台
C语言 源代码 -> 编译器【exe,dll,;二进制代码】非托管 直接OS执行
Thread ->多线程类 unity不支持框架多线程访问场景中的物体。
P539 通信写法扩展 } day47 设计模式 P541 { 设计模式: 模式: 每一个模式描述了一个在我们周围不断重复发生的问题。 一套被反复使用,多数人知晓,经过分类编目的代码设计经验总结。
三大类: 创建型、结构型、行为型
常见设计模式:
创建型设计模式 23个最经典的设计模式 { 1.单例设计模式 (Singleton) 2.工厂设计模式(Factory) 工厂方法(Factory method) 抽象工厂(abstract Factory) 3.生成器设计模式(Bulider) 4. (不常见) 【Prototype 原型设计模式】
a.单例模式: 问题: 一个类只能创建一个对象,且该对象全局共享。 练习: 用控制台写出一个单例模式 class SingletonSample { private static SingletonSample _instance = null; private SingletonSample() { } public static SingletonSample Instance { get { if (_instance == null) _instance = new SingletonSample(); return _instance; } } public void printHelloSin() { Console.WriteLine("Hello-Singlesingle"); } } b.简单工厂SimpleFactory 多选1 P546 问题: 客户在使用对象时,面临在多个类型中选择一个来创建对象,具体的类型可能有变化。 定义一个全局的工厂类,负责类型的选择和对象的创建初始化,从而实现创建和表示分离。 适用性: 当一个类不知道它所必须创建的对象的类的时候。 设计时多做工作,但给系统带来更大的复用性和可维护性。 使用: 1.创建父类产品以及所有的子产品。 练习: 创建专门生产饭的工厂 //吃的 abstract class Meal { } class Noddle : Meal { } class Rice : Meal { } class Cake : Meal { } static class MealChooseFactory { public static Meal ChooseRice(int cho) { Meal meal = null; //重点在这里。 switch (cho) { case 0: meal = new Rice(); break; case 1: meal = new Noddle(); break; case 2: meal = new Cake(); break; default: meal = new Rice(); break; } return meal; } } c.抽象工厂 AbstractFactory 问题: 多个类型中以系列化的方式创建对象。 类似一个KFC的套餐,搭配很多东西。 案例:P547 抽象工厂 学习一个技术方向(Java,C#) {abstract private 学习1(语言); abstract private 学习2(语法); abstract private 学习3(STL); } 具体工厂:继承抽象工厂 具体工厂1 : 学习Java { override private 学习1(Java); ... } 具体工厂2 : 学习C# { ... } 练习: 抽象工厂 -> 生产坦克和战斗机 具体工厂 -> 生产中国或美国的 abstract class Tank { } abstract class Plane { } class ChineseTank : Tank { } class AmericaTank : Tank { } class ChinesePlane : Plane { } class AmericaPlane : Plane { } abstract class Arsenal //兵工厂 -》 抽象工厂 { public string Nation; //带有属性,否则可以直接用接口 public abstract Tank ManfactureTank(); public abstract Plane ManfacturePlane(); } class ChinaArsenal : Arsenal //属于某个组织的兵工厂 ->具体工厂1 { public override Tank ManfactureTank() { return new ChineseTank(); } public override Plane ManfacturePlane() { return new ChinesePlane(); } } class AmericaArsenal : Arsenal //属于某个组织的兵工厂 ->具体工厂2 { public override Tank ManfactureTank() { return new AmericaTank(); } public override Plane ManfacturePlane() { return new AmericaPlane(); } } 调用:-> Arsenal arsenal = new AmericaArsenal(); Tank a = arsenal.ManfactureTank(); Plane b = arsenal.ManfacturePlane(); UML 类图绘制。 d.Builder 构建者,建造器 对象中的成员可能是其它类型的对象 复杂对象的结构是稳定的,但是组成结构的具体对象是变化的。 public class Computer { public string Cpu; public string Gpu; public string Ram; public string Power; } public interface ComputerBulider { void GetGpu(); void GetCpu(); void GetRam(); void GetPower(); Computer GetComputer(); //获取组装进度 } public class Client //顾客 -> Director. { ComputerBulider computerBulider; public Client(ComputerBulider computerBulider) { this.computerBulider = computerBulider; } public void Purchase() { computerBulider.GetCpu(); computerBulider.GetGpu(); computerBulider.GetRam(); computerBulider.GetPower(); } } public class ComputerBuliderChepMan : ComputerBulider { Computer product = new Computer(); public Computer GetComputer() { return product; } public void GetCpu() { product.Cpu = "i3-9100f"; Console.WriteLine("i3-9100f"); } public void GetGpu() { product.Gpu = "Rtx-1080"; Console.WriteLine("Rtx-1080"); } public void GetPower() { product.Power = "GreatWall"; Console.WriteLine("GreatWall"); } public void GetRam() { product.Ram = "Kingston 2G"; Console.WriteLine("Kingston 2G"); } } public class ComputerBuliderExpensiveMan : ComputerBulider { Computer product = new Computer(); public Computer GetComputer() { return product; } public void GetCpu() { product.Cpu = "i10-10000k"; Console.WriteLine("i10"); } public void GetGpu() { product.Gpu = "Rtx-3090"; Console.WriteLine("Rtx-3090"); } public void GetPower() { product.Power = "Huntkey - 1000W"; Console.WriteLine("Huntkey-1000W"); } public void GetRam() { product.Ram = "Huntkey - 1000W"; Console.WriteLine("Kingston 32G"); } } 调用: ComputerBulider bulider1 = new ComputerBuliderExpensiveMan(); Client client = new Client(bulider1); client.Purchase(); var a = bulider1.GetComputer(); builder 模式和抽象工厂模式的区别: builder侧重过程: builder要有相同的生产过程,且有部件需要生产 抽象工厂侧重生产结果。 2.结构型设计模式: a. Facade(外观,门面) 关键词: 向导 P551 问题: 客户代码访问业务代码,需要使用多个业务对象才能实现 此时不能让客户来组织访问流程,而应该设置向导。 案例: SkillSystem.cs b. Adapter 适配器模式: (例子: 电源适配器) 问题: 将一个类的接口转换成客户希望的另外一个接口。 1.创建适配器类 (电源适配器) 2.适配器继承目标类(Target-> 笔记本电脑) 3.适配器中实例化已有类,调用已有类的对象的方法。 public class PowerAdapter: Laptop { private socket220V = new socket220V(); override public void Charge(int a) //重写目标类充电方法 { string b; b = a.Tostring(); //这里解决a,b不兼容问题。 socket220V(b); //实现充电。 } } 调用: !! Laptop laptop = new PowerAdapter(); //声明父类new子类,调用重写方法。 Laptop.Charge(2); 练习: 台灯 (方法: 三个孔) 现有插座(方法: 两个孔) 转接器(继承台灯,字段adaptee,方法: 三个孔) class DeskLamp { virtual public void ThreeSocket() { } } class CurrentSocket { public void TwoSocket() { } } class Adapter : DeskLamp //转接器 { private CurrentSocket adaptee = new CurrentSocket(); public override void ThreeSocket() { /*这里实现接口转换*/ //...转换电压,或者地线去除。。。。 adaptee.TwoSocket(); } } 调用: static void Main(string[] args) { DeskLamp lamp = new Adapter(); lamp.ThreeSocket(); } c. Bridge (桥): 关键词:连接两端变化 问题: 主体类有变化产生不同的派生,主体类成员也有变化 在组合关系上可能会形成排列组合,将两个变化分别独立 再利用关联建立主体与成员的关系。 这个关联就是桥 意图: 使抽象部分(主体成员) 与实现部分分离 abstract class Shape { public abstract void Draw(); } class Circle : Shape { override public void Draw() { Console.Write("Circle"); } } class Rectangle : Shape { override public void Draw() { Console.Write("Rectangle"); } } abstract class DrawPosition { public Shape shape; //重点是这句话,这里实现了桥 virtual public void drawPosition() { shape.Draw(); } } class Papper : DrawPosition { public override void drawPosition() { Console.WriteLine("Papper->"); base.drawPosition(); } } class Blackboard : DrawPosition { public override void drawPosition() { Console.WriteLine("Blackboard->"); base.drawPosition(); } } 调用: static void Main(string[] args) { DrawPosition pos = new Papper(); pos.shape = new Rectangle(); pos.drawPosition(); } d.Composite(组合) 关键词: 树 问题: 将对象组成树形结构。 将对象组成树形结构以表示"部分-整体"的层次结构。 Add(),Remove(), GetChild(int),Insert(),DumpContent()展开 练习: 建立菜单树,实现树 P558 1.公司属于CEO管理 2.3个部门: 研发,市场,客服,都有部门经理 3.研发部:两个小组 ->服务端,客户端 服务端2人,客户端3人,市场2人,客服2人 每个员工类增加名字,薪水。 计算所有人员薪水之和 计算某部门或某小组的人员薪水和 //这题机械劳动太多了。。没写完,回头看看有没有更方便插入结点的方法,这里我自建了一个Insert方法和FindNode方法。 abstract class Component //树的组件,包括叶和结点 { protected string Name; public Component(string name) { Name = name; } public abstract int Add(Component component); //添加新的组件,可能是叶或结点 public abstract void DumpContent(); //下载内容 //其它功能 } class Node : Component { public Node(string s) : base(s) { } private ArrayList arrayList = new ArrayList(); public override int Add(Component component) { return arrayList.Add(component); } public override void DumpContent() { Console.WriteLine("Node -> {0}", Name); foreach (Component component in arrayList) { component.DumpContent(); } } public Node FindNode(string Name) { if (this.Name == Name) return this; Node fin=null; for (int i = 0; i < this.arrayList.Count; i++) { if (arrayList[i].GetType() == typeof(Node)) fin = ((Node)arrayList[i]).FindNode(Name); if (fin != null) return fin; } return null; } public bool Insert(string NodeName,Node node) { Node parentNode = FindNode(Name); if (parentNode == null) return false; parentNode.Add(node); return true; } public bool Insert(string NodeName, Leaf leaf) { Node parentNode = FindNode(Name); if (parentNode == null) return false; parentNode.Add(leaf); return true; } } class Leaf : Component { public Leaf(string s) : base(s) { } public override int Add(Component component) { throw new Exception("Leaf Can't add component"); } public override void DumpContent() { Console.WriteLine("------------>Leaf{0}", Name); } } class Employee : Leaf { public Employee(string Name,int salary) : base(Name) { Salary = salary; } private int salary; public int Salary { get { return salary; } set { salary = value; } } } 调用: class Program { static void Main(string[] args) { Node root = new Node("Company"); buildTree(root); root.DumpContent(); } private static void buildTree(Node root) { Node node; root.Add(new Node("CEO")); node = root.FindNode("CEO"); node.Add(new Node("RandD")); node.Add(new Node("Marketing")); node.Add(new Node("CustomerService")); node = node.FindNode("RandD"); node.Add(new Node("Server")); node.Add(new Node("Client")); node.Add(new Node("RandD-Manager")); node.Add(new Leaf("Test")); //... } //上面的方法很蠢。。。 //下面直接建立层级关系 private static void buildTree(Node root) { Node l11 = new Node("Company"); Node l21 = new Node("CEO"); Node l31 = new Node("RandD"); Node l32 = new Node("Marketing"); Node l33 = new Node("CustomerService") ; Node l41 = new Node("Service"); Node l42 = new Node("Client"); Leaf m1 = new Employee("Manager1", 20000); Leaf m2 = new Employee("Manager2", 20000); Leaf m3 = new Employee("Manager3", 20000); Leaf o1 = new Employee("officer1", 10000); Leaf o2 = new Employee("officer1", 10000); Leaf o3 = new Employee("officer1", 10000); Leaf o4 = new Employee("officer1", 10000); Leaf o5 = new Employee("officer1", 10000); //添加层次关系 l11.Add(l21); l21.Add(l31); l21.Add(l32); l21.Add(l33); l31.Add(l41); l31.Add(l42); l31.Add(m1);l32.Add(m2);l33.Add(m3); l41.Add(o1); l41.Add(o2); l42.Add(o3); l42.Add(o4); l42.Add(o5); ... } e.Decorator(装饰) 关键词: 包装 P560 问题 : 对一个对象动态扩展原有的行为能力,不断包装,不断扩展。 例子: 武器镶嵌不同宝石 获得不同方法/武器技能 abstract class Weapon { public abstract void Damage(); } class Sword : Weapon { public override void Damage() { Console.WriteLine("Damage"); } } abstract class SwordDecorator : Weapon { public Weapon Weapon1 { get; set; } //剑的引用 public SwordDecorator(Weapon a) //装武器 { Weapon1 = a; } public virtual Weapon RemoveDecorator() { return Weapon1; } public virtual void GetDecorator(){} public override void Damage() { Weapon1.Damage(); } } class sapphire : SwordDecorator { public sapphire(Weapon a) : base(a) { } public override void Damage() { base.Damage(); Frozen(); } public override void GetDecorator() { base.GetDecorator(); //装上蓝宝石 } public void Frozen() //冻结 { Console.WriteLine("Frozen"); } } class Ruby : SwordDecorator { public Ruby(Weapon a) : base(a) { } public override void Damage() { base.Damage(); Burn(); } public override void GetDecorator() { base.GetDecorator(); //装上红宝石 } public void Burn() //燃烧 { Console.WriteLine("Burn"); } } 调用 static void Main(string[] args) { Weapon weapon = new Ruby(new sapphire(new Sword())); //装蓝宝石+红宝石 weapon.Damage(); } } f. Proxy (代理) 关键词(幌子) P561 问题:为其它对象提供一种代理以控制对这个对象的访问 由小对象表示大对象(类似继电器) (委托) 行为型-关注对象交互的问题 P561 a.Template method (模板方法) 关键词(骨架) 问题某功能实现的大流程已经确定了 但每一个步骤都可能 有不同的实现 一次性实现算法不变的部分,并将可变的 b.State(状态) 关键词状态决定行为 P564-565 在一个对象内部状态改变时行为发生改变 例子 通过反射动态创建 瓶子状态: 关闭,打开,工作 要求能方便转换 1.定义状态枚举。 2.定义状态抽象基类,实现状态子类(多个) 3.定义状态机-> { 1.要确定当前状态并初始化 CurrentState 2.定义状态切换的方法 3.调用当前状态的方法——》 pour。 4.根据需要定义其它成员。 } abstract class State { public abstract void pour(); } class BottleOpen : State { public override void pour() { Console.WriteLine("water out!"); } } class BottleClose : State { public override void pour() { Console.WriteLine("ERROR! Water Can't be pour!"); } } class BottleWork:State { public override void pour() { Console.WriteLine("pouring.."); } } class BottleState //状态机 { public enum BottleStateenum { Open, Close, Work } private State CurrentState; private State LoadState(BottleStateenum bottleState) { State state = null; string stateName = "State.Bottle" + bottleState.ToString(); //string stateName = "State.Bottle" + bottleState.ToString().Split('.')[1]; Type typeobj = Type.GetType(stateName); state = (State)Activator.CreateInstance(typeobj); return state; /* switch (bottleState) { case BottleState.Open: state = new BottleOpen(); return state; case BottleState.Close: state = new BottleClose(); return state; case BottleState.Work: state = new BottleWork(); return state; default: return null; }*/ } public BottleState() { CurrentState = LoadState(BottleStateenum.Close); } public void SetBottleState(BottleStateenum bottleState) { CurrentState = LoadState(bottleState); } public void Pour() { CurrentState.pour(); } } 调用: static void Main(string[] args) { BottleState bottleState = new BottleState(); bottleState.Pour(); bottleState.SetBottleState(BottleState.BottleStateenum.Open); bottleState.Pour(); }c. Strategy(策略模式) P563 算法是多种实现,
练习: 计算员工工资 程序员/测试,销售 程序员 工资: 底薪10000,补助1000,项目奖金10000 测试 销售 工资: 5000 补助1000 提成(销售总额*0.01) abstract class StrategySalary { protected int baseSalary; protected int subsidy; public virtual int GetSalary() { return baseSalary + subsidy; } } class ProgrammerStrategy : StrategySalary { protected int ProjectBonus; public ProgrammerStrategy(int ProjectNum) { baseSalary = 10000; subsidy = 1000; ProjectBonus = 10000 * ProjectNum; } public override int GetSalary() { return baseSalary + subsidy + ProjectBonus; } } class TesterStrategy : StrategySalary { protected int findBugsSalary; public TesterStrategy(int BugNums) { baseSalary = 10000; subsidy = 1000; findBugsSalary = 100 * BugNums; } public override int GetSalary() { return baseSalary + subsidy + findBugsSalary; } } class SalesManStrategy : StrategySalary { protected int Commission; //提成 public SalesManStrategy(int SaleVolumns) { baseSalary = 10000; subsidy = 1000; Commission = (int)(0.01 * SaleVolumns); } public override int GetSalary() { return baseSalary + subsidy + Commission; } } class Employee { protected string job; StrategySalary strategy; public Employee(StrategySalary strategy) { this.strategy = strategy; } public int GetSalary() { return strategy.GetSalary(); } } 调用: static void Main(string[] args) { ProgrammerStrategy strategy = new ProgrammerStrategy(3); Employee a = new Employee(strategy); Console.WriteLine(a.GetSalary()); } d. observe (观察者模式) 关键词: 事件模式 class Cat { public delegate void CatBarkHandler(); public event CatBarkHandler CatBarkEvent; //声明事件 public void CatBark() { Console.WriteLine("miao~~"); if (CatBarkEvent != null) { CatBarkEvent(); //调用事件 } } } class Mouse { public void Run() { Console.WriteLine("Mouse Run"); } } 调用: static void Main(string[] args) { Cat cat = new Cat(); cat.CatBark(); Console.WriteLine("____________________________"); cat.CatBarkEvent += new Mouse().Run; cat.CatBark(); }}
}
day48 AI P566 {
FSM (Finite-state machine) 有限状态机
Perception 智能感知 寻路
Steering 自动操控
有限状态机: 有限多个状态在不同的条件下相互转换的图
案例: 主角攻击小怪,妖怪要有一些可能性的反应-> 有限状态机 1.待机 2.死亡 3.追逐 4.攻击 5.巡逻
增加抽象状态类,条件类。
在本状态作为当前状态时,还要做状态检查(发现目标,生命为0)。 若发现状态改变条件满足->调用状态机改变状态方法。 状态机-> 包含AI 所有状态的容器。 ->状态对象的初始化,负责状态的切换,实时执行当前状态的行为以及条件检查。状态机----------------------状态类--------------------------条件类 默认状态 状态编号 条件编号 当前状态 条件列表(该状态下要进行的检测) 为状态类或条件类提供的数据 转换映射表
状态管理 初始化 初始化 状态机管理 添加条件 bool检测条件是否达成
切换状态 删除条件 为状态类或条件类提供行为 查找映射 状态行为 条件检测 离开状态 进入状态
例子: 状态机(史莱姆状态机) 状态类(史莱姆Idlel类,史莱姆FindEnemy类…) 条件类(史莱姆生命存活条件类,史莱姆索敌条件类)
创建文件夹-> Scripts/AI/FSM-> Common,Condition,States文件夹 Common/FSMStateID ID状态编号枚举 /FSMTriggerID ID条件编号枚举 /AnimationParameters 动画参数类
//添加抽象条件类 FSMTrigger //添加抽象状态类 FSMStatus P569配置文件方法: ConfigSM(){ 1.创建状态对象 2.添加条件映射 AddTrigger 3.放入状态集合=状态库 } 1.硬编码-> 量大,难修改,代码复用性差
2.动态编码-> 方法,方便 创建Fsm配置文件 Assets 包含规定好的文件夹: 大部分只读 可读可写文件夹: StreamingAssests 步骤: a. 创建文件夹StreamingAssests b. 添加状态配置文件 编写配置文件 c. 添加读取配置文件的类 d. 在状态机的ConfigFSM方法中使用!反射技术实现功能 { 1.创建状态对象 2.添加条件映射 AddTrigger 3.放入状态集合=状态库 } 例子: -> 基于程序集反射实现 private void ConfigFSM() { //使用动态编码方式,实时加载Application.streamingAssetsPath + "/AI_StatusConfig.txt" Dictionary<string, List<string>> AI_StatusConfigDic = new Dictionary<string, List<string>>(); string AI_StatusConfigText = File.ReadAllText( (Application.streamingAssetsPath + "/AI_StatusConfig.txt").Replace(@"\","/")); //去除变量 string[] AI_StatusConfigTexts = AI_StatusConfigText.Trim().Split(new char[] { '[','\r','\n',}, StringSplitOptions.RemoveEmptyEntries); string currentIndex = null; //组成字典 foreach(string i in AI_StatusConfigTexts) { if (i.Contains(']'.ToString())) { currentIndex = i.Split(']')[0]; AI_StatusConfigDic[currentIndex] = new List<string>(); } else { AI_StatusConfigDic[currentIndex].Add(i); } } //开始配置 foreach (var item in AI_StatusConfigDic) { Config(item); } } private void Config(KeyValuePair<string,List<string>> item) { string currentStatus = "AI.FSM."+item.Key + "Status"; Type objType = Type.GetType(currentStatus); FSMStatus Statusobj = (FSMStatus)Activator.CreateInstance(objType); if (Statusobj == null) return; foreach(string TriggerAndStatusID in item.Value) { string[] tempList = TriggerAndStatusID.Split(new char[] { '-','>'}, StringSplitOptions.RemoveEmptyEntries); //测试 if (tempList.Length != 2) continue; //创建TriggerID对象 string TriggerName =tempList[0]; FSMTriggerID triggerID = (FSMTriggerID)Enum.Parse(typeof(FSMTriggerID), TriggerName); //创建StatusID对象 string StatusID = tempList[1]; FSMStatusID statusID = (FSMStatusID)Enum.Parse(typeof(FSMStatusID), StatusID); Statusobj.AddTrigger(triggerID, statusID); } //放入状态集合库 statusLibrary.Add(Statusobj); } using Sys = System -》 取别名。 !通过WWW读取信息 { if(Application.platform == RuntimePlatform.Android) aiConfigFile = "file://" +aiConfigFile; WWW www = new WWW(aiConfigFile); //读取信息 while(true) { if(string.IsNullorEmpty(www.error)) throw new Exception("读取异常"); if(www.isDone) return BuildDic(www.text); } } !枚举类型转换-> Enum.Parse() 数据类型转换: 1.常见类型转换 string-> int 1. Convert.ToInt32(a); 2. (int)a; 2. string -> byte[] System.Text.Encoding.Default.Get?() 截取字符串 line.Substring(开始位,结束位(IndexOf(']')-1)); System.Diagnostics.Debug.Assert() 断言 #endregion}