…
开发游戏 游戏引擎与API 常用游戏引擎有:Unity,UE4,Cocos,laya,白鹭等。 常用开发API有:DirectX,OpenGL,Vulkan等。
人员分工 程序:
美工:
原画
3D建筑
3D角色
动画
特效
地形编辑
UI 界面
灯光
场景
TA:即懂美术也懂程序,写Shader的
策划:
系统策划
剧情策划
数值策划
关卡策划
任务策划
执行策划:盯着程序员的
Unity 操作 界面介绍 项目:由场景构成 场景 Sence:由游戏对象构成(看见的对象,光源,摄像机等) 游戏对象:由组件构成 组件:拥有多个属性
菜单栏:
File:场景操作和项目操作。
Edit:复制粘贴等
Duplicate:复制,包括Copy与Paste
Prefrence:首选项
Assets:资源
GameObject:游戏对象
Create
2D Object
3D Object
Effects
Light
Audio
Video
UI
Camera
Component:
Mesh
Effects
Physics
Physics 2D
Navigation
Audio
Video
Rendering
Layout
Playables
AR
Miscellaneous
Analytics
UI
Scripts
Events
Window:面板控制
Help
Layout:布局方式 Project:项目面板,管理所有文件 Scene:场景面板,列出场景中的所有对象 Game:游戏面板,玩家看到的面板,由摄像机拍摄 Hierarchy:层级,管理场景中的所有游戏对象 Inspector:显示游戏对象上的组件及组件属性 Layers:
层,用于管理文件组。
系统保留8个层,共有32个层。
坐标系 Untiy 使用左手系。
ISO:正交视图 Persp:透视视图
场景操作 鼠标操作:
旋转:鼠标右键 / Alt + 左键
缩放:Alt + 鼠标右键 / 滚轮
平移:鼠标中键 / 上方手
锁:禁止旋转
选中多个:Shift + 点选
上方工具栏(快捷键):
平移场景 Q
移动物体 W
旋转物体 E
缩放物体 R
2D缩放物体 T
Center / Pivot:坐标轴位置 Z
Global / Local:切换坐标系 X
快捷键:
V:顶点捕捉
Ctrl + D:复制
Ctrl:角度捕捉
Unity 资源 项目目录 Assets:管理资源文件
Animations
Animators
Audio
Done
Fonts
Gizmos
Materials
Models
Prefabs
Scenes
Scripts
Shaders
Textures 特殊
Standard Assets:内置优先加载
Editor:扩展编辑器
Plugins:插件Dll
Resources:资源文件
材质 Material 可以配置颜色,图片。 可以添加着色器。
着色器 Diffuse:Base贴图。 Bumped Diffuse:Base贴图和法线贴图。 Bumped Specular:再加入高光材质。
纹理贴图 尺寸需要被2整除(256*256)。
可以创建法线贴图。 Texture Type:贴图类型 MaxSize:纹理取样尺寸。
Substance Designer:一款材质制作工具。
GUI 纹理显示:
1 2 3 4 function OnGUI ( ){} Resources .Load () Resources .LoadAll ()GUI .DrawTexutre ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private var oneTexture :Texture2D ; private var allTexutre :Object []; function OnGUI ( ){ if (GUI .Button (Rect (0 ,20100 ,60 ), "loadOne" )){ if (oneTexutre == null ){ oneTexture = Resources .Load ("one/Grass" ); } } if (GUI .Button (Rect (0 ,20100 ,60 ), "loadAll" )){ if (allTexutre == null ){ allTexutre = Resources .Load ("all" ); } } if (allTexutre!=null ){ for (var i=0 ;i<allTexture.Length ;i++){ GUI .DrawTexture (Rect (110 +i*120 ,140 ,120 ,120 ), allTexutre[i],ScaleMode .StretchToFill ,true ,0 ); } } }
Moive纹理,是一个动画纹理。导入后转为OGG格式。
模型 FBS 模型一般有FBS,OBJ格式,一般使用FBS。 如果导出的是3DMAX等文件,可以用Blend,Untiy转换为FBX。
编辑时尽量让z轴朝向前方,以便以后程序控制。
对于动画,可以导出一个动画,再在Untiy中分割成多个动画;也可以导出为多个文件。但是多文件时命名要遵循模型@动画
命名规则。
Model:
Meshes:缩放因子 0.01,MeshCompression 网格压缩,ReadWriteEnable 运行时修改,OptimizeMesh 优化,GenerateColliders 碰撞(对固定物体较好),SwapUV,GenerateLightMapUV
Normals & Tangents:法线切线(输入,计算),SplitTangents 分割法线
Materials:导入材质,材质命名,材质搜索 Rig:动画类型,人/物。 Animations:动画如何分割。
模型:
控制模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Move : MonoBehaviour { void Update () { float horizontal = Input.GetAxis("Horizontal" ); float vertical = Input.GetAxis("Vertical" ); Vector3 direction = new Vector3(horizontal, 0 , vertical); if (direction!=Vector3.zero){ transform.GetComponent<Animation>().CrossFade("walk" ); transform.rotation = Quaternion.LookRotation(direction); transform.Translate(Vector3.forward * 1 * Time.deltaTime); }else { transform.GetComponent<Animation>().CrossFade("idle" ); } } void OnCollisionEnter (Collision collision ) { if (collision.gameObject.name == "Plane" ){ return ; } transform.GetComponient<Animation>().CrossFade("bite" ); collision.transofrm.GetComponent<Animation>("die" ); Destroy(collision.gameObject, 5f ); } }
声音 是否为3D声音:有距离感。 载入方式。
音源组件:发出声音的物体。 声音监听组件:听到声音的物体。
预设体 Prefabs 用来批量管理游戏对象。 也可以批量添加预设物体。
可以将游戏对以文件形式存储起来:从层级面板拖入项目目录即可。
Select:快速找到该预设体。 Revert:还原为预设体中的设置。 Apply:将修改后的设置放入预设体。
游戏对象 层级关系: 子物体的Transform是相对于父物体的
基本对象: Cube Sphere Plane:由多个三角形构成 Quad:由2个三角形构成 Terrain:地形
Tag:属于某一个类。 Layer:属于某一个层。
搜索对象:在层级面板双击 / 单击,在场景中按 F
对象组件: Transform:位置 / 旋转 / 缩放 Mesh Filter:决定游戏对象的形状 Mesh Rendeer:决定游戏对象的外观展示
刚体碰撞组件 Rigidbody:刚体组件,模拟物理效果
Mass:质量
Drag:拉力,阻力
Angular Drag:旋转拉力
Use Gravity:重力
Is Kinematic:开启运动学,不受力
Interpolate:插值
Collision Detection:碰撞检测
Constraints:约束 Collider:碰撞体组件
用于碰撞检测
可以编辑碰撞体
is trigger
material:物理材质
dynamic friction:动态摩擦力
static friction:静态摩擦力
bounciness:弹力
friction combine:组合摩擦力
bounce combine:组合弹力
center
size
地形 一种游戏对象。
Terrain:地形
灯光 灯光可以是游戏对象,也可以是组件。
灯光类型:
Directional Light:影响所有物体
Point
Spot
Area
项目设置->Player,可以在不同平台上设置渲染路径。 Vertex List Forward Deferred Lighting
灯光类型:点,聚光灯Spot,方向灯,区域灯光。 阴影类型。 灯光耀斑:Draw Halo 渲染重要性:Render Mode
摄像机 ClearFlag:天空盒,实体色,深度信息(多摄像机叠加)等。 投影方式。 前后截面。 视口尺寸。 深度:深度大的会叠加到深度小的摄像机上。
脚本 GameObject包含的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 tranform rigibody camera light render animation audio 其他 this .gameObject.transformother.GetComponent("filename" ) other.GetComponent(filename) other.GetComponent<filename>() GameObject.FindGameObjectWithTag("tag" ) gameObject.SendMessageUpwards("ApplyDamage" , 5.0 );
Time对象,记录各种间隔时间。
协程 1 2 3 4 5 6 7 8 9 10 void Start () { StartCoroutine(Routine()); } IEnumerator Routine () { yield return new WaitForSeconds (1f ) ; }
GUI 所有GUI相关都在OnGUI中实现。 常用控件:
Label
Button:点击后,返回True
RepeatButton:按下后持续执行。
TextField:返回string
Toggle:开关
HorizontalSlider:滑块 复合控件:
Toolbar
SelectionGrid
GUI.changed:GUI更新 GUI.skin:GUI风格
定义组:
GUI.BeginGroup()
GUI.EndGroup()
GUILayout.Button GUILayout.Box
HUD 角色屏幕上固定的界面。
Add Component,GUITexture,然后调整位置大小。
特效 使用粒子系统添加特效。 OpenEditor:打开独立编辑器。+
->Show All Modules:显示所有特效 基本参数:
Duration:粒子持续时间
Loging:循环
Prewarm:预热,一般不需要
Start XXX:启动设置,可以设置曲线(随机数范围)
Gravity Multiplier:重力
Inherit Velocity:继承速度,没用
Simulation Space:本地坐标,世界坐标,粒子是否随着发射器移动
Play On Awake:是否初始激活
Max Particles:最大粒子数 Emission:
Rate:发射速率 / 距离
Bursts:爆炸 Shape:
Shape:形状
Angle:角度
Radius:半径
Length:长度
EmitFromShell:发射面
Random Direction:随机方向 XXX Over LifeTime:速度,限制速度,力,颜色,大小,旋转
XYZ:速度方向
Space:坐标
Dampen:阻尼
Separate Axis:锁定轴 XXX By Speed:颜色,大小,旋转 External Forces:力学,不用 Collision:碰撞,消耗资源
Planes:碰撞面板
Scale Plane
Dampen:阻尼
Bounce:摩擦
Lifetime Less:减少的寿命
Min Kill Speed
Particle Radius:碰撞位置偏移
Send Collision Message:发送碰撞信息 Sub Emitters:下一个粒子系统
Birth
Collision
Death Texture Sheet Animation:贴图UV动画
Tiles
Animation
Frame Over TIme
Cycles Render
Render Mode:板,拉伸,平行,垂直,模型
Normal Direction
Material:材质
Sort Mode
Sorting Fudge:优先渲染
Case Shadows
Receive Shadows
Max Particles Size:最大大小
角色 CharacterController:角色控制器 CharacterMotor:角色引擎
动画 Mecanim
射线碰撞 Raycast
资源包 导出:在项目目录 Export Package
导入:在项目目录 Import Package
自定义资源包
内置资源包(Standard Asserts)
Asset Store 在线资源库。
高通 AR 首先在高通AR 上下载资SDK(需要注册)。
将下载的SDK资源包导入到Unity中。如果中途提示升级,不需要升级。
配置License Key,要到官网申请License Key,并将其配置到项目中。
在Develop -> License Manager中申请(Development)。
点击创建的项目,进入项目详情,可以看到License Key。复制Key,进入项目目录,在Resources->VuforiaConfiguration中加入Key。
之后上传图片:将要扫描的图片上传到高通,进入Target Manager -> Database,创建数据库,Device,然后进入数据库,Add Target,Single Image,上传图片。
之后下载数据库并导入项目,Download Database,Unity,将下载的数据库导入到项目。
在项目中,首先删掉 Main Camera,并导入AR Camera。Vuforia Prefabs AR Camera,和 Image Target。
再配置Image Target,在其插件中配置Database,Image Target,高和宽,
再配置 VuforiaConfiguration,Datasets,Load并Active。
放置要出现的虚拟物体,作为Image Target的子物体。
C Sharp 字符串是引用类型。字符串操作:
1 2 string str = $"this is {box} " ;string str = @"C:\Program files\" ;
控制台输入输出
1 2 3 string str = Console.ReadLine("{0}" , box);Console.WriteLine("{0}" , box); Console.ReadKey();
遍历数组
1 2 foreach (int x in array) { }
param关键字:用来修饰形参
1 2 3 4 method(1 , 2 , 3 ); method(param int [] array) { }
引用,ref可以不赋值,但是out必须赋值,且out默认没有值,不可以直接使用。
1 2 3 4 5 6 7 method(ref a); method(ref int a) { } method(out a); method(out int a) { }
类继承后,重写父类方法,可以用new;虚函数virtual可以重写override。
类和结构体的区别是,类在堆上,结构体在栈上(值类型)。 结构体必须是有参构造方法,并要给所有字段赋值。
类的默认类型是internal,在项目内部可见。
Sealed:密封,不允许继承,可以用于修饰类和方法。
命名空间中只有类,结构体,枚举,接口。
运算符重载
1 2 3 public static Point operator +(Point p1, Point p2) { }
抽象类abstract类
接口Interface:一系列的规范,接口实现必须是public
委托可以直接赋值
1 TestDelegate oneDelegate = TestMethod;
匿名函数
1 2 TestDelegateX method = delegate (){}; TestDelegateX method = () => {};
用委托可以实现回调。
泛型,泛型不可继承
集合:是容器,变长的,类型可不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ArrayList list = new ArrayList(); list.Add(1 ); list.Add("abc" ); list.AddRange(new ICollection()); list.Remove(); list.RemoveAt(); list.RemoveRange(); list.SetRange(0 , new int []{1 , 2 , 3 }); list.IndexOf(); list.LastIndexOf(); list.BinarySearch(); list.Sort(); list.Reverse(); list.Contains(); list.Insert(0 , "a" ); list.InsertRange(0 , int []{1 , 2 });
实现ICompareable可以实现排序。
List:
1 2 3 4 5 list.RemoveAll(Predicate<in T>(T obj)); list.RemoveAll(name => name=="abc" ); Exists(name => name=="abc" ) FindXXX(name => name=="abc" ) GetRange()
其他类型: Stack Queue Hashtable:键值对,内部按照Key的Hash排序 Dictionary:字典,必须指定类型
1 2 3 4 5 6 Hashtable table = new Hashtable(); table.Add("key" , "value" ); foreach (DictionaryEntry entry in table){ entry.Key; entry.Value; }
1 2 3 4 5 6 Dictionary<string , string > dic = new Dictionary<string , string >(); dic.Add("key" , "value" ); foreach (KeyValuePair<string , string > pair in dic){ pair.Key; pair.Value; }
正则表达式:
1 2 3 4 5 6 7 using System.Text.RegularExpressions;public class Test { public void Test () { bool result = Regex.IsMatch("a string" , "^[1-9]\\d{4,10}$" ); } }
^
: 字符串开头$
: 字符串结尾[]
: 匹配括号中的一位字符[^]
: 匹配括号以外的一位字符+
: 匹配1次或多次*
: 匹配0次或多次?
: 匹配0次或1次{m}
: 恰好出现m次{m,}
: 至少出现m次{m,n}
: 出现m到n次\d
: [0-9]
.
: 匹配任意字符()
: 分组,返回Match,多个分组
异常处理:如果Catch异常有继承关系,父类应放到最后。
反射:可以通过类名,字段来实例化对象,操作类成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Type { FieldInfo, MethodInfo, Constructor, } Type t = Type.GetType("Space.Person" ); object obj = Activator.CreateInstance(t); object obj = Activator.CreateInstance(t, true ); object obj = Activator.CreateInstance(t, para1, para2); object obj = Activator.CreateInstance(t, BindingFlags.NonPublic|BindingFlags.Instance, null , new object []{1 ,2 }, null ); FileInfo name = t.GetField("name" ); FileInfo name = t.GetField("name" , BindingFlags.NonPublic|BindingFlags.Instance); name.SetValue(obj, "abc" ); object x = name.GetValue(obj);MethodInfo method = t.GetMethod("Show" , BindingFlags.NonPublic|BindingFlags.Instance); method.Infoke(null , null ); MethodInfo method = t.GetMethod("Show" , BindingFlags.NonPublic|BindingFlags.Instance, null , new Type[]{typeof (string ), typeof (double )}, null ); object result = method.Infoke(obj, new object []{"1" , 2.0 });
Mono脚本的生命周期 编辑器 Reset:当脚本被附加或重置时执行。
初始化 Awake:初始化后的第一步操作,整个声明周期只执行一次。 OnEnable:启动后的操作,可以多次执行。 Start:当第一帧启动前执行一次。
物理引擎 从物理引擎开始,每一帧都要执行。 物理引擎部分一帧可能执行多次。 物理引擎是个非常消耗资源的部分。
FixedUpdate:按固定时间间隔执行。 yield WaitForFixedUpdate OnTriggerXXX OnCollisionXXX
OnMouseXXX:响应设备事件。
游戏逻辑 Update yield null yield WWW yield WaitForSeconds yield StartCoroutine 内部动画更新。 LateUpdate
场景渲染 OnWillRenderObject:渲染前 OnPreCull OnBecameVisible OnBecamingVisible OnPreRender OnRenderObject OnPostRender OnRenderImage
Gizmos 渲染 OnDrawGizmos
GUI 渲染 OnGUI:可循环多次,但不建议使用
帧结束 yield WaitForEndOfFrame
暂停 OnApplicationPause
关闭 OnDisable
销毁 OnApplicationQuit OnDelete OnDestory
程序设计 设计原则 单一职责 对象的职能只有一个。
开闭原则 对象应该对于扩展是开放的,但是对于修改是封闭的。
依赖倒置原则 首先了解需求,画图表示;写出代码。
接口隔离原则 接口里面的单一原则,也就是接口只负责一个功能。
合成复用原则 父类解决共有问题,子类解决特有问题。
是什么的问题改为有什么的问题。
能用现有的类,就不要创建新类。
迪米特法则 最少知识原则:尽量不牵扯到其他的类
设计模式 单例模式 整个软件的声明周期,有且只有一个实例。
拖拽一次,实例化一次。
用在组织框架管理者身上:WWW管理器,UI管理器,音效管理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Single : MonoBehaviour { public static Single instance; void Awake () { instance = this ; } void Start () { } } public class Single { private static Single instance; public static Single Instance{ get { if (instance == null ){ instance = new Single; } return instance; } } }
工厂模式 交给它任务,它自动生产,最后返回一个产品。
不关心生产过程,用于创建新的对象。
例如生产金币,怪物。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Factory { Sprite[] allSprite; Transform parents; public Factory () { Initial(); } public void Initial () { allSprite = Resources.LoadAll<Sprite>("res" ); parents = GameObject.FindGameObjectWithTag("MainCanvas" ).transform; } public GameObject CreateImage (int index, Vector3 pos ) { GameObject obj = new GameObject(); obj.name = index.ToString(); obj.transform.SetParent(parents, false ); obj.transform.SetAsSiblingIndex(2 ); obj.transform.SetAsLastSibling(); obj.transform.SetAsFirstSibling(); obj.transform.localPosition = pos; Image img = obj.AddComponent<Image>(); index = index % allSprite.Length; img.sprite = allSprite[index]; return obj; } } public class UseFactory : MonoBehaviour { Factory factory; void Start () { factory = new Factory(); } void Update () { int cnt = 0 ; if (Input.GetKeyDown(KeyCode.Space)){ facotry.CreateImage(++cnt, Vector3.one * cnt * 50 ); } } }
观察者模式 不断地询问。
例如:播放一个动作,在1s的时候播放音效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Obstor : MonoBehaviour { Animation animal; float timeCount = 0 ; void Start () { animal = GetComponent<Animation>(); } public void PlayAction () { animal.Play("Action" ); timeCount = 0 ; } void Update () { if (!animal.isPlaying){ } timeCount += Time.deltaTime; if (timeCount > 1.0f ){ timeCount = 0 ; } } }
代理模式 使用回调(代理)的方式调用对方。B留出回调指针,M给B的回调指针加上A的方法。当触发到某一事件时,B调用回调函数,就会间接调用A。
例如:按钮事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using UnityEngine.UI;using UnityEngine.Events;public class UseFactory : MonoBehaviour { Factory factory; void Start () { factory = new Factory(); Button = btn = transform.GetComponent<Button>(); btn.onClick.AddListener(new UnityAciton(BtnOnClick)); btn.onClick.AddListener(BtnOnClick); } public void BtnOnClick () { Debug.Log("Btn Clicked" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Master { Worker worker; public void CallWorker () { worker.Task(); } public void OnWorkerCall () { Debug.Log("Work Finish" ); } } public class Worker { public delete void TaskPointer () ; public TaskPointer taskPointer; public void Task () { taskPointer(); } } public class UseDelegate : MonoBehaviour { void Start () { Master master = new Master(); Worker worker = new Worker(); master.CallWorker(); worker.taskPointer += master.OnWorkerCall; } }
策略者模式 决策制可以根据不同对象的输入得到不同的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class AbsBase { public flaot pay; public virtual void CalculateTex () { } } public class AbsPerson : AbsBase { public override void CalculateTex () { base .CalculateTex(); pay *= 0.08f ; } } public class AbsCompany : AbsBase { public overrie void CalculateTex () { base .CalculateTex(); pay *= 0.12f ; } } public void TestAbstrator: MonoBehaviour{ public void CalculateTex (AbsBase bs ) { bs.CalculateTex(); } void Start () { AbsPerson person = new AbsPerson(); CalculateTex(person); AbsCompany company = new AbsCompany(); CalculateTex(company); } }
建造者模式 将一个大的东西拆分成小的东西。
例如:MVC框架。
由顶部的UI Manager管理下层的对象。对模块的管理,用单例模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Master { public static Master instance; public static Master Instance{ get { if (instance == null ){ instance = new Master(); } return instance; } } public void CreateTask () { Manager manager = new Manager(); manager.ResolveTask(); } } public class Manager () { public void ResolveTask () { Worker worker = new Worker(); worker.ExecuteTask(); } } public class Worker () { public void ExecuteTask () { } }
中介者模式 防止类直接互相影响。解决耦合性问题。
例如:怪物和玩家之间的互相攻击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class MiddleBase { public float hp; public virtual void reduce (float bias ) { hp -= bias; } } public class MiddleFirst : MiddleBase { public override void reduce (float bias ) { hp -= bias * 1.3f ; } } public class MiddleSecond : MiddleBase { public override void reduce (float bias ) { hp -= bias * 0.7f ; } } } public class Main : MonoBehaviour { public void CalculateHP (MiddleBase first, MiddleBase second ) { first.reduce(1 ); } public void Update () { MiddleFirst first = new MiddleFirst(); MiddleSecond second = new MiddleSecond(); CalculateHP(first, second); CalculateHP(second, first); } }
外观模式 将各种不同类型的东西放在一起,聚合,形成特定的功能。
例如:红绿灯,日期时间控件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class UI : MonoBehaviour { private GameObject Red; private GameObject Blue; private bool stateRed = false ; public void Start () { Button btn = GetComponent<Button>(); btn.onClick.AddListener(OnClick); } public void OnClick () { if (stateRed){ Red.SetActive(true ); Blue.SetActive(false ); }else { Red.SetActive(false ); Blue.SetActive(true ); } stateRed = !stateRed; } }
组合模式 将相同类型的东西组合一起,形成特有功能。
例如:构成一辆车的轮胎,框架,发动机等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class CarBase { public virtual void Forward () { } public virtual void Backword () { } } public class Wheel : CarBase { public override void Forward () ; public override void Backword () ; } public class Engine : CarBase { public override void Forward () ; public override void Backword () ; } public class Main : MonoBehaviour { List<CarBase> car; void Start () { car = new List<CarBase>(); car.Add(new Engine()); car.Add(new Wheel()); } void Update () { for (int i = 0 ; i < car.Count; i++){ car[i].Forward(); } } }
状态者模式 FSM 有限状态机或命令者模式。
可以用来制作动画。
模型文件: Obj 只有模型(顶点,法线,切线),没有动画。 Fbx 带动画和模型 Dae 只有模型
这里用Fbx文件。
模型系统: Scale Factor:缩放因子,Unity以米为单位,因此需要缩放。 Mesh Compression Read Write Enable Optimize Mesh:优化,采用四边形 Import BlendShapes
Normals:法线,Import,模型自带法线 Tangents:切线(切线:右向量,副切线:前向量),Import,自带
Materials:导入材质
动画系统: 关节动画:骨骼(子级在父级的坐标 Transform)动画。
MeshFilter 筛选定点
MeshRender 渲染选出的的定点 蒙皮动画:皮肤动画。
SkinMeshRender 将模型传递给GPU 顶点动画:对模型的顶点做动画。
Rig 动画类型:
Legacy 关节动画,通过代码控制
Generic 状态机动画,非人形动画,动画不能通用
Humanoid 状态机动画,人形动画,可以复用动画
一般模型,动画分开两个文件保存。
动画文件,一般都是原地动画,也有带位移的动画。
动画事件通过代码实现。
fbx和meta文件一起放入目录。
控制器代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class OrcCtrl : MonoBehaviour { CharacterController charCtrl; Animator animator; void OnAnimatorMove () { charCtrl.SimpleMove(animator.deltaPosition); charCtrl.SimpleMove(animator.deltaRotation); charCtrl.SimpleMove(animator.rootPosition); charCtrl.Move(Vector3.forward); } void Start () { charCtrl = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); } }
Shader 开发语言和工具 开发语言: HLSL:专用于Direct3D和XNA。 Cg:C for Graphics,由NVIDIA开发 GLSL:用于OpenGL ShaderLab:Unity使用,类似CgFX和Direct3D的效果框架。
开发工具: FX Composer Render Monkey MonoDevelop:Unity使用 第三方IDE
网络 基本介绍 UDP:传输顺序不一定。 RUDP:保证顺序一定的UDP,发送数据后等待却认。 TCP:消耗资源较大,有拥塞控制。
MMORPG:多人在线游戏,状态同步,实时同步状态(发送状态而不是位置)到所有客户端。网络延迟时,每3-5秒同步一次。网络掉线时,重连,可以用Unity 的API判断。 Moba:采用RUDP,使用网络帧同步,50ms - 60ms 一次。使用Lock Step技术,服务器等待所有结果,一台机器卡,所有机器都卡。使用乐观锁,不等待客户端,只下发。 ARPG:角色扮演 PVP:对战 PVE:打怪 SLG:策略游戏
创建连接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void Start () { Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("192.168.1.1" ), 8000 ); IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 8000 ); server.Bind(endPoint); Thread thread = new Thread(() => { while (true ){ try { server.BeginAccept((IAsyncResult callback) => { Socket client = server.EndAccept(callback); }, null ); }catch (Exception e){ Debug.Log(e.ToString()); } Thread.Sleep(50 ); } }); thread.Start(); } void Use () { WebCamDevice cam = WebCamTexture.devices[0 ]; WebCamTexture tex = new WebCamTexture(cam.name, 300 , 400 , 10 ); transform.GetComponent<Renderer>().material.mainTexture = tex; tex.Play(); } byte [] GetBytes () { Texture2D raw = new Texture2D(300 , 400 ); for (int i=0 ;i...){ for (){ Color c = tex.GetPixel(j, i); raw.SetPixel(j, i, c); } } return raw.EncodeToJPG(); } void Send () { UDPSocket send = new UDPSocket(port, size, new UDPSocket.RecvDelegate(()=>{ send.SendBytes(GetBytes(), "ip" ); })); } Buffer.BlockCopy();
接收后更新M层数据,在主线程。
示例模块 一个游戏一般包含:
Audio 音效
WWW 网络链接/本地资源文件
DB 数据库
Effect 特效
UI 用户界面
Animation 动画(FSM 有限自动机)
网络传输模块 例如进入游戏之后,先检查游戏更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class TestHttp : MonoBehaviour { public IEnumerator SendGet (string url ) { WWW www = new WWW(url); yield return www; if (string .IsNullOrEmpty(www.error)){ Debug.Log(www.txt); } } public IEnumerator SendPost (string url, WWWForm form ) { WWW www = new WWW(url, form); yield return www; if (string .IsNullOrEmpty(www.error)){ Debug.Log(www.txt); } } public IEnumerator DownloadLocalFile (string url ) { WWW www = new WWW(url, form); yield return www; if (string .IsNullOrEmpty(www.error)){ Debug.Log(www.txt); } } public string InitUrl (string url ) { if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer){ url = "file:///" + url; }else if (Application.platform == RuntimePlatform.Android){ url = "jar:file://" + url; }else { url = "file://" + url; } return url; } void Start () { string url = "" ; StartCoroutine(SendGet(url)); WWWForm form = new WWWForm(); form.AddField("arg" , "value" ); StartCoroutine(SendPost(url, form)); string filepath = Application.dataPath + "/filename.txt" ; StartCoroutine(DownloadLocalFile(InitUrl(filepath))) } }
封装模块:
下载任务基类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class WWWItem { public virtual void BeginDownload () ; public virtual void DownlaodFinish (WWW www ) ; public virtual void DownloadError (WWWItem item ) ; private string url; public string Url{ get {return url;} set {url = value ;} } public IEnumerator Download () { BeginDownload(); WWW www = new WWW(Url); yield return www; if (string .IsNullOrEmpty(www.error)){ DownlaodFinish(www); }else { DownloadError(this ); } } }
下载队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class WWWHelper : MonoBehaviour { public static WWWHelper Instance; Queue<WWWItem> allTask; void Start () { Instance = this ; allTask = new Queue<WWWItem>(); } public void AddTask (WWWItem item ) { allTask.Enqueue(item); if (allTask.Count == 1 ){ StartCoroutine(DownloadItems()); } } public IEnumerator DownloadItems () { while (allTask.Count > 0 ){ WWWItem item = allTask.Dqeueue(); yield return item.Download(); } } }
下载TXT文件模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class WWWTxt : WWWItem { public WWWText (string url ) { url = InitUrl(url); } public string InitUrl (string url ) { if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer){ url = "file:///" + url; }else if (Application.platform == RuntimePlatform.Android){ url = "jar:file://" + url; }else { url = "file://" + url; } return url; } public override void BeginDownload () { Debug.Log("Start ..." ); } public override void DownlaodFinish (WWW www ) { Debug.Log(www.txt); } public override void DownloadError (WWWItem item ) { WWWHelper.Instance.AddTast(item); } }
使用:
1 2 3 string url = Application.dataPath + "/filename" ;WWWTxt txtTask = new WWWTxt(url); WWWHelper.Instance.AddTask(txtTask);
音效模块 需要监听者,声源。
Audio Listener:相当于摄像机,唯一。
Audio Source:可以播放Audio Clip,非常占用空间,继承MonoBehavior,属性较多。
因此: - 应该使用有限个Audio Source播放所有Clips(对象池) Audio 模块:
SourceManager:管理AudioSoruce对象,寻找空闲的对象,播放Clip
拿出空闲的Audio Source
释放空闲的Audio Source
播放或停止播放
AudioSoruce:3个
ClipsManager:管理Clip对象
Clip:无数个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 public class AudioManger : MonoBehaviour { public static AudioManger Instance; private SourceManager sourceManager; private ClipManager clipManager; void Start () { Instance = this ; sourceManager = new SourceManager(gameObject); clipManager = new ClipManager(); } void Play (string audioName ) { AudioSource source = sourceManage.GetFreeAudio(); SingleClip singleClip = clipManager.FindClipByName(audioName); singleClip.Play(source); } void Stop (string audioName ) { sourceManager.Stop(audioName); } } public class SourceManager { List<AudioSource> allSources; GameObject owner; public SourceManager (GameObject own ) { owner = own; InitAudioSourceList(); } public void InitAudioSourceList () { allSources = new List<AudioSource>(); for (int i=0 ;i<3 ;i++){ AudioSource source = owner.AddComponent<AudioSource>(); allSources.Add(source); } } public AudioSource GetFreeAudio () { if (int i=0 ;i<allSources.Count;i++){ if (!allSources[i].isPlaying){ return allSources[i]; } } AudioSource source = owner.AddComponent<AudioSouce>(); allSources.Add(source); return source; } public void ReleaseFreeAudio () { int cnt = 0 ; List<AudioSource> releaseSources = new List<AudioSource>(); for (int i=0 ;i<allSources.Count;i++){ if (!allSources[i].isPlaying){ cnt++; if (cnt > 3 ){ releaseSources.Add(allSources[i]); } } } for (i=0 ;i<releaseSources.Count;i++){ allSources.Remove(releaseSources[i]); GameObject.Destroy(releaseSources[i]); } releaseSources.Clear(); releaseSources = null ; } public void Stop (string name ) { for (int i=0 ;i<allSources.Count;i++){ if (allSources[i].isPlaying && allSources[i].clip.name.Equals(name)){ allSources[i].Stop(); } } } } public class ClipManager { string [] clipName = null ; string [] clipFile = null ; int clipCount = 0 ; SingleClip[] allSingleClip; public ClipManager () { ReadConfigFile(); LoadClips(); } public void ReadConfigFile () { var fileAddress = Application.streamingAssertsPath + "/AudioConfig.txt" ; FileInfo info = new FileInfo(fileAddress); if (info.Exists){ with () StreamReader sr = new StreamReader(fileAddress); string lineCountStr = sr.ReadLine(); int lineCount = 0 ; if (int .TryParse(lineCountStr, out lineCount)){ clipName = new string [lineCount]; clipFile = new string [lineCount]; for (int i=0 ;i<lineCount;i++){ string [] line = sr.ReadLine().Split("\t" .ToCharArray()); clipName[i] = line[0 ]; clipFile[i] = line[1 ]; } }else { } } } public void LoadClips () { allSingleClip = new SingleClip[clipCount]; for (int i=0 ;i<clipCount;i++){ AudioClip clip = Resources.Load<AudioClip>(clipFile[i]); SingleClip sclip = new SingleClip(clip); allSingleClip[i] = sclip; } } public SingleClip FindClipByName (string name ) { for (int i=0 ;i<clipCount;i++){ if (clipName[i] == name){ return allSingleClip[i]; } } return null ; } }
配置文件AudioConfig.txt
:
1 2 3 4 3 ClipButton ClipButton.mp3 River River.mp3 UIMusic UIMusic.mp3
Clip 存储:
1 2 3 4 5 6 7 8 9 10 11 public class SingleClip { AudioClip clip; public SingleClip (AudioClip tclip ) { clip = tclip; } public void Play (AudioSource source ) { source.clip = clip; source.play(); } }
UI 模块 从效果图入手,切图成为碎图。 使用Texture Packer打大图:
Data Format:Unity Texture2D Texture format:PNG Image format:RGBA8888: 每个通道8bit。
Geometry Max Size:2048*2048 (1024*1024)
其他保持默认,然后将碎图文件夹拖入其中,得到拼图(tpsheet)
在Unity中,加载插件codeandweb.com
放入根目录。
之后根据拼图拼UI,以Panel为单位。
使用时,使用九宫格拉伸。
最后写代码:
Controller: UIManager管理所有Panel及其子控件。 Panel包含部分子控件。 子控件消息通信通过UIManager直接查询对方。 子控件主动报告数据。
子控件设计:实现UIBehaviour,实现向UIManager注册自己 UIManager:被注册后,以Panel为单位划分,挂载子控件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 public class UIBehaviour : MonoBehaviour { void Awake () { var panel = transform.GetComponentInParent<UIBase>(); UIManager.Instance.RegistGameObject(panel, transform.name, gameObject) } public void AddButtonListener (UnityAction action ) { Button btn = transform.GetComponent<Button>(); if (btn != null ){ btn.onClick.AddListener(action); } } public void AddDragInterface (UnityAction<BaseEventData> action ) { EventTrrigger trigger = gameObject.GetComponent<EventTrigger>(); if (trigger == null ){ trigger = gameObject.AddComponent<EventTrigger>(); } EventTrigger.Entry entry = EventTrigger.Entry(); entry.eventID = EventTriggerType.Drag; entry.callback = new EventTrigger.TriggerEvent(); entry.callback.AddListener(action); trigger.triggers.Add(entry); } } public class UIBase : MonoBehaviour { void Awake () { Transform[] allChd = transform.GetComponentsInChildren<Transform>(); for (int i=0 ;i<allChd.Lenght;i++){ if (allChd[i].name.EndsWith("_N" )){ allChd[i].gameObject.AddComponent<UIBehaviour>(); } } } void OnDestory () { UIManager.Instance.UnRegistPanel(transform.name); } public GameObject GetWidget (string widget ) { return UIManager.Instance.GetWidget(transform.name, widget); } public UIBehaviour GetBehaviour (string widget ) { GameObject obj = GetWidget(widget); if (obj != null ){ return obj.GetComponent<UIBehaviour>(); } return null ; } public void AddButtonListener (string widget, UnityAction action ) { UIBehaviour bvr = GetBehaviour(widget); if (bvr != null ){ bvr.AddButtonListener(action); } } public void AddDrag (string widget, UnityAction<BaseEventData> action ) { UIBehaviour bvr = GetBehaviour(widget); if (bvr != null ){ bvr.AddDragInterface(action); } } } public class UIManager : MonoBehaviour { public static UIManager Instance; Dictionary<string , Dictionary<string , GameObject>> allWidgets; public void RegistGameObject (string panel, string widget, GameObject obj ) { if (allWidgets.ContainsKey(panel)){ allWidgets[panel] = new Dictionary<string , Dictionary<string , GameObject>(); } allWidgets[panel].Add(widget, obj) } public void UnRegistGameObject (string panel, string widget, GameObject obj ) { if (allWidgets.ContainsKey(panel)){ if (allWidgets[panel].ContainsKey(widget)){ allWidgets[panel].Remove(widget); } } } public void UnRegistPanel (string panel ) { if (allWidgets.ContainsKey(panel)){ allWidgets[panel].Clear(); allWidgets[panel] = null ; } } public GameObject GetWidget (string panel, string widget ) { if (allWidgets.ContainsKey(panel)){ return allWidgets[panel][widget]; } return null ; } void Awake () { Instance = this ; allWidgets = new Dictionary<string , Dictionary<string , GameObject>>(); } } public class Regist : UIBase { RegistModel registModel; public void OnClick () { } void Start () { AddButtonListener("btn_N" , OnClick); } }
Model:
1 2 3 4 public class RegistModel { public string username; public string password; }
资源加载: 在Loading界面,要将资源加载到内存。用的时候再实例化。
在列表类的控件,会在子控件加载多个同类型的子单元控件。因此需要批量加载初始化。
现在让子控件称为UISubManager,来管理下级单位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class UISubManager : MonoBehaviour { Dictionary<string , GameObject> allChd; void Awake () { UIBase bs = transform.GetComponetInParent<UIBase>(); UIManager.Instance.RegistGameObject(,) Transform[] chd = transform.GetComponentsInChildren<Transform>(); for (int i=0 ;i<chd.Length;i++){ if (chd[i].name.EndsWith("_S" )){ allChd.Add(chd[i].name, chd[i]); } } } public Transform GetChidTransform (string widget ) { return allChd[widget]; } void OnDestory () { if (allChd != null ){ allChd.Clear(); allChd = null ; } } }
摇杆 EasyTouch 首先获取鼠标到圆心的距离,与方向。 考虑移动范围:圆内任意移动,不可超出圆。
小地图 可以使用相机,修改属性: Viewport Rect:显示在屏幕上的位置和大小 Depth:相机前后关系
但是以上方法渲染两次,消耗资源。
因此可以使用等比例映射,也就是用图片做小地图。
创建一张图片,使其(锚点)对齐到右上角。
然后让物体的位置映射到小地图上。
物体相对于地形的位置,按照比例缩小到小地图。
GL GL.PushMatrix() 位置变换 GL.MultMatrix(transform.localToWorldMatrix) 正交投影 GL.LoadOrtho() 画线 GL.Begin(GL.LINES) 也可以画三角形,多边形等。 颜色 GL.Color(Color.red) 位置 GL.Vertex3(0.1f, 0.1f, 0) 结束 GL.End() GL.PopMatrix()
小地图上的摄像机可视区域 得到相机的可视区域,映射到小地图上面。
将相机的四个角对应到世界位置上:从相机的四个角发出4条射线到地面,得到4个交点。再映射到小地图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class CameraView : MonoBehaviour { void Update () { Ray first = Camera.main.ViewportPointToRay(Vector3.zero); Ray second = Camera.main.ViewportPointToRay(Vector3.right); Ray third = Camera.main.ViewportPointToRay(Vector3.one); Ray forth = Camera.main.ViewportPointToRay(Vector3.up); RaycastHit hitout; if (Physics.Raycast(first, out hitout, 1000 )){ if (hitout.transform.CompareTag("Ground" )){ hitout.point } } } }