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,这样就不会检查触发器了,只会检查碰撞器。