UnityOvercooked 
Author::Magialeaf/FakeOvercooked: Overcooked仿制版 (github.com) 
 
一、环境配置 1 新建项目 
新建项目新建的是3D(URP)项目。
Windows——Package Manager——可以移除Visual Studio Code Editor,因为版本老。其他的也可以不用管。
Project自带:
Scenes——SmapleScene:简单案例。 
TutorialInfo——Readme:URP相关教程,可以移除。 
Settings:
SampleSceneProfile:默认的SampleScene会有一个Global Volume,其中Volume的Profile需要设置为SampleSceneProfile,也没什么用,可以删除。 
URP*:渲染配置文件,共六个,分别是Performant(性能优先)、Balanced(平衡)、HighFidelity(高精度) 
 
 
UniveralRenderPipelineGlobalSettings:URP全局设置。也可以在Edit——Project Settings——Graphics——URP Global Settings中设置。 
 
设置渲染等级:
Edit——Project Settings——Graphics:选择渲染脚本。 
Edit——Project Settings——Quality:选择渲染质量。 
 
新建URP相关:右键——create——Rendering
 
2 VSCode开发 
如果使用VStudio开发忽略。
配置:
VSCode中配置:File——Preferences——Settings——输入useModernNet,设置为False。 
Unity中配置:Edit——Preferences——External Tools——External Script Editor选择VSCode(没有就Browse到VSCode)。然后设置External Script Editor Args为:"$(ProjectPath)" -g "$(File)":$(Line):$(Column)(否则在VSCode中代码引用会找不到其他文件的引用结果,只能找到同一文件下的引用结果) 
 
安装插件:
EditorConfig for VS Code使用:
安装插件 
在项目根目录下输入新建文件夹:.editorconfig 
输入下列代码 
File——Preferences——Settings——输入editor.formatOnSave,确保Editor: Format On Save勾选,这样每次保存时就会格式化代码。 
 
 
// .editorconfig(使用时把这一段注释删除) root = true [*] indent_ style = space indent_ size = 4 end_ of_ line = crlf charset = utf-8 trim_ trailing_ whitespace = false insert_ final_ newline = false file_ header_ template = "123" 
 
3 VS开发 
编码问题:保存的文件是当前系统的语言(可能是GB2312),而非UTF-8。解决方式:扩展——管理扩展——搜素Force UTF-8 (With BOM)并下载即可。
或者用python脚本
 
import  osimport  codecsdef  detect_and_convert (file_path, target_encoding='utf-8-sig'  ):    detected_encoding = None      try :                  with  codecs.open (file_path, 'r' , encoding='gb2312' ) as  f:             f.read()               detected_encoding = 'gb2312'      except  UnicodeDecodeError:                  try :             with  codecs.open (file_path, 'r' , encoding='utf-8-sig' ) as  f:                 f.read()                 detected_encoding = 'utf-8-sig'          except  UnicodeDecodeError:             print (f"Cannot determine encoding for '{file_path} ', skipping." )             return           if  detected_encoding and  detected_encoding != target_encoding:         try :             with  codecs.open (file_path, 'r' , encoding=detected_encoding) as  f:                 content = f.read()             with  codecs.open (file_path, 'w' , encoding=target_encoding) as  f:                 f.write(content)             print (f"Converted '{file_path} ' from {detected_encoding}  to {target_encoding} " )         except  Exception as  e:             print (f"Error converting '{file_path} ': {str (e)} " ) def  convert_cs_files_to_utf8_with_bom (root_folder ):    for  folder_name, _, file_names in  os.walk(root_folder):         for  file_name in  file_names:             if  file_name.endswith('.cs' ):                 file_path = os.path.join(folder_name, file_name)                 try :                     detect_and_convert(file_path)                 except  Exception as  e:                     print (f"Error detecting encoding of '{file_path} ': {str (e)} " ) if  __name__ == "__main__" :    folder_path = '.'        convert_cs_files_to_utf8_with_bom(folder_path) 
 
 
二、前期处理 1 后处理效果 
在Hierarchy找到Global Volume——在Inspector中的Volume找到Profile——New。然后在Project中可以看到出现了一个和scene名一样的文件下,文件夹下有一个文件Global Volume Profile。
然后在Global Volume的Inspector中点击Add Override可以添加后处理的效果。
后处理效果如下:
Tonemapping:色调映射,给场景做一个HDR处理。勾选Mode后选择属性可以加上Mode:None(无)、Neutral(自然)、ACES(一种标准) 
Color Adjustments:色彩调整,给色彩做一个基本调整。
Post Exposure(曝光度):值越大越亮。 
Contrast(色差):值越大,亮的地方越亮,暗的地方越暗。 
Color Filter(色温):整体颜色。 
Hue Shift(色调):图像的相对明暗程度。 
Saturation(饱和度):值越大,颜色越鲜艳。 
 
 
Bloom:泛光效果。
Threshold(阈值):某个物体的亮度超过这个阈值才会出现泛光效果。 
Intensity(强度):泛光的强度。 
 
 
Vignette:相机四角阴影效果。(暗角处理,有利于视线集中在中心)
Intensity(强度):越高阴影越大。 
smoothness(平滑度):越高阴影边界越柔和。 
 
 
 
 
2 抗锯齿处理 
Hierarchy——Camera——Rendering——Anti-allasing:FXAA(粗糙的模糊处理) / SMAA(性耗比最佳的处理)。(内置渲染管线)
Project——Settings——URP-HighFidelity——Quality——Anti-allasing(MSAA)。(URP渲染)
如果使用了URP,且同时使用了Camera和URP抗锯齿,URP的会覆盖Camera的。
Project——Settings——URP-HighFidelity-Renderer——Screen Space Ambient Occlusion:修改软阴影(物体拐角地方的阴影)
 
三、脚本 1 函数技巧 transform.forward = Vector3.Lerp(transform.forward, director, Time.deltaTime * moveSpeed); transform.forward = Vector3.SLerp(transform.forward, director, Time.deltaTime * moveSpeed); 
 
2 输入系统 
使用新版输入系统:在Edit——Project Settings——Player——Active Input Handing中选择Input System Package(New)或者Both。
设置了新版输入系统后要导入包:Window——Package Manager——Packages:Untiy Registry——Input System。
然后在Project中新建Input Actions,在里面设置映射。
在Input Actions的Inspector中,可以点击Generate C# class生成类直接使用
重新绑定注意:
重新绑定的数据不会直接影响默认的GameControl,因为程序是new GameControl(),影响的是新建出来的对象,所以重新启动后按键绑定会恢复。 
重新绑定时,需要先gameControl.Player.Disable()禁用对象,再进行绑定,后续再启动对象。 
 
 
 
public  class  GameInput  : MonoBehaviour {     private  GameControl gameControl;     public  void  Awake ()     {         gameControl = new  GameControl();                  gameControl.Player.Enable();                       }     public  Vector3 GetMovementDirectionNormalized ()     {                  Vector2 inputVector2 = gameControl.Player.Move.ReadValue<Vector2>();         Vector3 direction = new  Vector3(inputVector2.x, 0 , inputVector2.y);         direction = direction.normalized;         return  direction;     } } 
 
private  void  Update (){     if  (Input.GetMouseButtonDown(0 ))     {         print("开始绑定" );         gameControl.Player.Disable();         gameControl.Player.Move.PerformInteractiveRebinding(1 ).OnComplete(callback =>         {                          print(callback.action.bindings[1 ].path);                          print(callback.action.bindings[1 ].overridePath);                          callback.Dispose();             print("绑定完成" );                          gameControl.Player.Enable();         }).Start();     } } 
 
using  System;using  System.Collections;using  System.Collections.Generic;using  System.IO;using  UnityEngine;using  UnityEngine.EventSystems;using  UnityEngine.InputSystem;public  class  GameInput  : MonoBehaviour {     public  static  GameInput Instance { get ; private  set ; }     private  const  string  GAME_INPUT_BINDINGS = "GameInputBindings" ;     public  event  EventHandler OnInteractAction;     public  event  EventHandler OnOperationAction;     public  event  EventHandler OnPauseAction;     private  GameControl gameControl;     public  enum  BindingType     {         Up,         Down,         Left,         Right,         Interact,         Operation,         Pause     }     public  void  Awake ()     {         Instance = this ;         gameControl = new  GameControl();         if  (PlayerPrefs.HasKey(GAME_INPUT_BINDINGS))         {                          gameControl.LoadBindingOverridesFromJson(PlayerPrefs.GetString(GAME_INPUT_BINDINGS));         }         gameControl.Player.Enable();         gameControl.Player.Interact.performed += Interact_Performed;         gameControl.Player.Operation.performed += Operation_Performed;         gameControl.Player.Pause.performed += Pause_Performed;     }     private  void  OnDestroy ()     {         gameControl.Player.Interact.performed -= Interact_Performed;         gameControl.Player.Operation.performed -= Operation_Performed;         gameControl.Player.Pause.performed -= Pause_Performed;         gameControl.Dispose();     }     private  void  Interact_Performed (UnityEngine.InputSystem.InputAction.CallbackContext obj )  => OnInteractAction?.Invoke(this , EventArgs.Empty);     private  void  Operation_Performed (UnityEngine.InputSystem.InputAction.CallbackContext obj )  => OnOperationAction?.Invoke(this , EventArgs.Empty);     private  void  Pause_Performed (UnityEngine.InputSystem.InputAction.CallbackContext obj )  => OnPauseAction?.Invoke(this , EventArgs.Empty);     public  Vector3 GetMovementDirectionNormalized ()     {                  Vector2 inputVector2 = gameControl.Player.Move.ReadValue<Vector2>();         Vector3 direction = new  Vector3(inputVector2.x, 0 , inputVector2.y);         direction = direction.normalized;         return  direction;     }     public  void  ReBinding (BindingType bindingType, Action onComplete )     {         InputAction inputAction = null ;         int  index = -1 ;         switch  (bindingType)         {             case  BindingType.Up:                 index = 1 ;                 inputAction = gameControl.Player.Move;                 break ;             case  BindingType.Down:                 index = 2 ;                 inputAction = gameControl.Player.Move;                 break ;             case  BindingType.Left:                 index = 3 ;                 inputAction = gameControl.Player.Move;                 break ;             case  BindingType.Right:                 index = 4 ;                 inputAction = gameControl.Player.Move;                 break ;             case  BindingType.Interact:                 index = 0 ;                 inputAction = gameControl.Player.Interact;                 break ;             case  BindingType.Operation:                 index = 0 ;                 inputAction = gameControl.Player.Operation;                 break ;             case  BindingType.Pause:                 index = 0 ;                 inputAction = gameControl.Player.Pause;                 break ;             default :                 break ;         }         gameControl.Player.Disable();         inputAction.PerformInteractiveRebinding(index).OnComplete(callback =>         {             callback.Dispose();             gameControl.Player.Enable();             onComplete?.Invoke();                          PlayerPrefs.SetString(GAME_INPUT_BINDINGS, gameControl.SaveBindingOverridesAsJson());                          PlayerPrefs.Save();         }).Start();     }     public  string  GetBindingDisplayString (BindingType bindingType )     {         switch  (bindingType)         {             case  BindingType.Up:                 return  gameControl.Player.Move.bindings[1 ].ToDisplayString();             case  BindingType.Down:                 return  gameControl.Player.Move.bindings[2 ].ToDisplayString();             case  BindingType.Left:                 return  gameControl.Player.Move.bindings[3 ].ToDisplayString();             case  BindingType.Right:                 return  gameControl.Player.Move.bindings[4 ].ToDisplayString();             case  BindingType.Interact:                 return  gameControl.Player.Interact.bindings[0 ].ToDisplayString();             case  BindingType.Operation:                 return  gameControl.Player.Operation.bindings[0 ].ToDisplayString();             case  BindingType.Pause:                 return  gameControl.Player.Pause.bindings[0 ].ToDisplayString();             default :                 return  string .Empty;         }     } } 
 
3 数据存储 
数据对象直接继承ScriptableObject,这样的话这个脚本可以直接在Unity中创建这个对象,同时可以在本地进行持久化保存。
文件夹命名文件夹是ScriptObjects,脚本是...SO
 
[CreateAssetMenu ] public  class  KitchenObjectSO  : ScriptableObject {     public  GameObject prefab;     public  Sprite sprite;     public  string  objectName; } 
 
 
四、UI 1 字体制作 
中文字体制作:Window——TextMeshPro——Font Asset Creator。Source Font File选择中文字体ttf文件(文件名要纯英文),Character Set 选择 Characters from File(文件名要纯英文),然后Character FIle选择一个Text文件,Text中写上需要用的字符(一般是需要用的中文+英文大小写+数字+下划线),然后点击Generate Font Atlas,生成后点Save。
其他选项:
Atlas Resolution:字体大小,一般在保证清晰的情况下越小越好。 
 
使用:在TextMeshPro对象中,修改Font Asset为自己制作的Font Asset即可。
后续修改:找到字体文件(TMP_Font Asset),点击Update Atlas Texture,重新制作后点击Save即可。
 
 
五、Shader 1 Shader Graph 
新建:Assets中——Create——Shader Graph——URP——Lit Shader Graph
打开:双击打开。
简单Shader步骤:
在界面中右键,新建一个Node(直接搜索Sample Texture 2D)。 
在左边的MovingVisual中新建一个Texture 2D,并拖动到面板中。 
输入:连接两者,连接的地方是Node的Texture(T2)的位置。 
输出:点击Node的RGBA(4),这是输出的贴图,连接到Fragment的Base Color(3)上。 
点击左边的MovingVisual中的对象,在右边的Node Settings中,为它设置一张Default贴图。 
 
其他操作:
Main Preview中会显示材质结果,右键可以切换显示的对象,比如切换成Cube等。 
左上角Save Asset进行保存。 
 
使用Shader Graph:
在Asset中创建一个Material,然后选择Shader。 
新建物体,使用这个Material。 
 
 
 
六、网络 1 Netcode for GameObjects 
安装:Windows——Package Manager——Netcode for GameObjects。
注意版本(2021.3对应包版本1.2,安装最新的可能报错):Windows——Package Manager——左上角"+"——Add package by name…——name输入的是com.unity.netcode.gameobjects,version输入1.2.0。
使用:新建对象NetworkManager(不能作为子物体),然后添加脚本NetworkManager。
配置脚本Network Manager:
Player Prefab:添加网络中传递的预制件(可以是Player,然后给Player加上Network Object脚本)。 
Select transport:选择Unity Transport。 
 
配置Unity Transport:
Connection Data:连接的ip和port 
 
 
 
七、问题 1 碰撞 
Q1:小人碰到物体后会倒下。
A1:Position锁定Z轴,因为是平面游戏。Rotation锁定X、Z轴,因为小人只需要饶Y轴旋转。
Q2:小人移动卡墙闪烁。
A1:因为刚体碰撞和Update()检测频率不一致导致的。把移动放在FixedUpdate()即可(一般物理移动代码是放在FixedUpdate(),其他的是Update())。
 
2 Canvas 
Q1:Canvas有一个白色边框。
A1:在Game面板右上角——Gizmos——取消掉Canvas(找不到就搜索)。
 
3 粒子 
Q1:粒子直接挂载在人物下不显示。
A1:带有刚体的物体会影响粒子的播放。可以选择将粒子直接放到根目录下,然后挂上FollowTarget的脚本。
 
using  UnityEngine;public  class  FollowTarget  : MonoBehaviour {     [SerializeField ] private  Transform target;     private  void  FixedUpdate ()     {         transform.position = target.position;     } } 
 
4 触发器 
Q1:做塔防时,塔的触发器遮挡住了鼠标的射线检测,无法放下防御塔。
A1:Edit——Project Settings——Physics——取消Queries Hit Triggers,这样就不会检查触发器了,只会检查碰撞器。