ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Unity实现罗斯科风格城市建造游戏:从着色器到建造系统全流程

Unity实现罗斯科风格城市建造游戏:从着色器到建造系统全流程 你好我是专注于游戏开发实战的技术博主。最近一款名为《寓言之地》的模拟建造游戏因其独特的艺术风格和轻松的玩法吸引了大量玩家。许多开发者朋友在交流时都提到很想知道如何用现代游戏引擎复现这种类似“罗斯科”风格的极简、抽象且富有情感的城市建造游戏。本文将为你系统拆解这一技术实现路径从核心美术风格解析到 Unity 引擎下的完整实战涵盖材质、着色器、光照与游戏逻辑助你从零打造属于自己的诗意栖居地。1. 罗斯科风格与《寓言之地》的美术核心解析在开始敲代码之前我们必须先理解我们想要复现的视觉风格究竟是什么。这并非简单的“卡通渲染”或“低多边形”而是一种更深层的艺术表达。1.1 什么是罗斯科风格马克·罗斯科是20世纪抽象表现主义的代表画家。他的作品特点鲜明大色块画面由几个巨大的、边缘柔和的矩形色块叠加构成。色彩的感性颜色本身是主角不同色彩的并置与叠加能引发观者强烈的情感共鸣如宁静、忧郁或振奋。模糊的边界色块之间没有清晰锐利的线条而是通过微妙的渐变、晕染过渡形成一种“呼吸感”和空间深度。极简的构图摒弃一切不必要的细节形式极度简化将全部表现力集中于色彩关系和画面氛围。《寓言之地》等游戏汲取了这种风格的精髓将其转化为3D游戏世界的语言建筑不再是砖瓦的堆砌而是几何体块树木和云朵是柔和的色块整个世界的光影不是为了模拟真实而是为了烘托情绪。1.2 游戏化转换的关键技术点将罗斯科绘画风格应用到可交互的3D城市建造游戏中需要解决几个核心问题体块化建模所有模型房屋、树木、人物都需要简化为基本的几何体立方体、圆柱体、球体避免复杂拓扑和细节。纯色与渐变材质物体表面使用单一或简单的渐变色彩而非复杂的贴图。这是风格化的基础。边缘光与轮廓由于缺乏细节需要一种方式来定义物体的轮廓和体积感。“边缘光”技术至关重要它能在物体边缘添加一道柔和的光晕清晰分离物体与背景同时保持风格的统一。情绪化光照与雾效全局光照不追求物理准确而是服务于情绪。例如使用温暖的色调和柔和的阴影营造温馨感或使用冷色调与雾效营造朦胧、宁静的氛围。动态元素的抽象化流动的河水、飘动的烟雾、行走的居民都需要用抽象的色块和粒子效果来表现。2. 开发环境与项目初始化我们将使用 Unity 2022.3 LTS 或更高版本进行开发这是目前最稳定且功能全面的版本之一。渲染管线选择URP因为它对风格化渲染的支持更好配置更轻量且跨平台兼容性优异。2.1 环境准备与项目创建安装 Unity Hub 和 Unity Editor从 Unity 官网下载并安装。创建新项目打开 Unity Hub点击“新建项目”。选择“核心”模板下的“3D (URP)”模板。这确保我们从一个配置好URP的项目开始。为项目命名例如FableLikeCityBuilder并选择保存路径。2.2 初始URP设置与关键资产导入创建项目后我们需要进行一些基础设置来奠定美术风格的基调。检查URP资产在 Project 窗口找到Assets/Settings文件夹下的UniversalRP-HighQuality资产名称可能略有不同这是默认的URP渲染管线配置。创建风格化材质球在 Project 窗口右键 - Create - Material命名为Mat_PureColor。选中该材质在 Inspector 窗口中将其 Shader 改为“Universal Render Pipeline/Lit”。将 “Surface Type” 设置为Transparent为了后续实现柔和的边缘和叠加效果。将 “Base Map” 的颜色调为你喜欢的纯色如一种柔和的米白或浅蓝。关闭 “Metallic” 和 “Smoothness”。用同样的方法创建多个不同颜色的纯色材质如Mat_Grass,Mat_Wood,Mat_Roof。3. 核心渲染技术实现罗斯科视觉风格这是本教程的技术核心我们将通过修改着色器和配置后期处理来实现标志性的视觉特征。3.1 自定义着色器边缘光与纯色渲染我们将编写一个简单的URP着色器为物体添加自定义的边缘光效果。创建自定义着色器在 Project 窗口右键 - Create - Shader - Universal Render Pipeline - Unlit Shader命名为Shader_RothkoStyle。用代码编辑器如VSCode打开它进行修改。以下是核心代码片段// 文件Shader_RothkoStyle.shader Shader Custom/RothkoStyle { Properties { _BaseColor (Base Color, Color) (1,1,1,1) _EdgeColor (Edge Color, Color) (1,0.5,0,1) // 边缘光颜色如暖橙色 _EdgeWidth (Edge Width, Range(0, 2)) 0.5 _FresnelPower (Fresnel Power, Range(0, 10)) 5 } SubShader { Tags { RenderTypeTransparent QueueTransparent RenderPipelineUniversalPipeline } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off LOD 100 Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; }; struct Varyings { float4 positionHCS : SV_POSITION; float3 normalWS : TEXCOORD0; float3 viewDirWS : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _EdgeColor; float _EdgeWidth; float _FresnelPower; CBUFFER_END Varyings vert (Attributes IN) { Varyings OUT; VertexPositionInputs vertexInput GetVertexPositionInputs(IN.positionOS.xyz); OUT.positionHCS vertexInput.positionCS; OUT.normalWS TransformObjectToWorldNormal(IN.normalOS); OUT.viewDirWS GetWorldSpaceViewDir(vertexInput.positionWS); return OUT; } half4 frag (Varyings IN) : SV_Target { // 计算菲涅尔效应视线与法线夹角越大边缘因子越强 float fresnel pow(1.0 - saturate(dot(normalize(IN.normalWS), normalize(IN.viewDirWS))), _FresnelPower); float edgeFactor smoothstep(0, _EdgeWidth, fresnel); // 混合基础色和边缘光色 half4 finalColor lerp(_BaseColor, _EdgeColor, edgeFactor); finalColor.a _BaseColor.a; // 保持基础透明度 return finalColor; } ENDHLSL } } }应用着色器回到 Unity创建一个新材质Mat_RothkoBuilding。将其 Shader 选择为我们刚创建的“Custom/RothkoStyle”。调整_BaseColor和_EdgeColor你会在材质预览中立刻看到物体边缘出现了光晕。3.2 后期处理配置营造氛围后期处理是统一画面风格、烘托情绪的关键。添加 Volume 组件在 Hierarchy 中右键 - Volume - Global Volume。配置 Volume Profile选中 Global Volume在 Inspector 中点击 “New” 创建一个 Profile。点击 “Add Override”添加以下效果Bloom轻微增强让光源和明亮边缘产生柔和的辉光模拟色块的“呼吸感”。阈值调低强度适中。Color AdjustmentsPost Exposure微调让画面更明亮或柔和。Color Filter选择一个全局色调如淡淡的暖黄色或浅蓝色为整个世界定下情绪基调。Vignette轻微添加暗角将视觉焦点引向画面中心增强绘画感。Film Grain添加微弱的颗粒感模仿画布纹理避免画面过于“数码感”。4. 实战构建你的第一个罗斯科风格场景现在让我们将理论付诸实践搭建一个包含地形、建筑和简单动画的小场景。4.1 创建抽象化地形与植被地形作为色块我们不使用复杂的地形工具。直接创建多个扁平的 Cube 或 Plane缩放并拼接涂上不同的材质Mat_Grass,Mat_Dirt来代表草地、道路、水域。通过高低错落形成简单的层次。抽象树木创建一个圆柱体作为树干使用Mat_Wood。创建一个球体缩放并压扁作为树冠使用Mat_Grass或一个更鲜绿的颜色材质。将两者组合成一个空物体下做成预制体Prefab_Tree。4.2 建造罗斯科风格的房屋建模使用 Cube 进行拼接。一个房子可以由1个主体方块、1个扁平的方块作为屋顶、几个小方块作为烟囱和门窗构成。材质为房屋主体应用Mat_RothkoBuilding材质并赋予它一个宁静的基色如浅灰蓝。为屋顶应用另一个纯色材质。预制体将组装好的房屋做成预制体Prefab_House。4.3 添加简单的生命感动画与粒子静态色块缺乏生机我们需要添加抽象的动态元素。抽象居民创建一个胶囊体应用一个对比色强的材质。为其编写一个简单的脚本让它在道路上随机移动。// 文件SimpleWander.cs using UnityEngine; public class SimpleWander : MonoBehaviour { public float moveSpeed 2f; public float changeDirectionInterval 3f; private Vector3 targetPosition; private float timer; void Start() { GenerateNewTarget(); } void Update() { timer Time.deltaTime; if (timer changeDirectionInterval) { GenerateNewTarget(); timer 0; } // 向目标点平滑移动 transform.position Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime); // 简单朝向移动方向 if ((targetPosition - transform.position).sqrMagnitude 0.01f) transform.forward (targetPosition - transform.position).normalized; } void GenerateNewTarget() { // 在XZ平面生成一个随机目标点 targetPosition new Vector3( transform.position.x Random.Range(-5f, 5f), transform.position.y, transform.position.z Random.Range(-5f, 5f) ); } }氛围粒子创建 Particle System模拟飘动的花瓣或光点。将渲染器模块的 Material 设置为一个使用我们自定义着色器或简单Unlit/Transparent着色器的材质颜色柔和。调整大小、速度、生命周期使其运动缓慢、稀疏如同画面中漂浮的色点。5. 核心游戏逻辑城市建造系统框架一个建造游戏的核心是“放置”逻辑。我们将实现一个基础的网格建造系统。5.1 网格管理与建造控制器创建网格数据定义一个GridManager单例来管理世界网格。// 文件GridManager.cs using UnityEngine; public class GridManager : MonoBehaviour { public static GridManager Instance; public float gridSize 2.0f; // 每个网格单元的大小 public LayerMask buildableLayer; // 可建造的地面层 void Awake() { Instance this; } // 将世界坐标转换为网格坐标 public Vector3 SnapToGrid(Vector3 worldPosition) { float x Mathf.Round(worldPosition.x / gridSize) * gridSize; float z Mathf.Round(worldPosition.z / gridSize) * gridSize; // Y轴可以通过射线检测获取地面高度 RaycastHit hit; if (Physics.Raycast(new Vector3(x, 100, z), Vector3.down, out hit, 200, buildableLayer)) { return new Vector3(x, hit.point.y, z); } return new Vector3(x, worldPosition.y, z); } // 检查网格点是否空闲 public bool IsGridCellEmpty(Vector3 gridPosition) { Collider[] colliders Physics.OverlapBox(gridPosition, Vector3.one * gridSize * 0.45f); foreach (var collider in colliders) { // 如果碰撞体有“建筑”标签则认为该位置被占用 if (collider.CompareTag(Building)) return false; } return true; } }建造控制器处理玩家输入和建筑预览、放置。// 文件BuildController.cs using UnityEngine; public class BuildController : MonoBehaviour { public GameObject buildingPrefab; // 当前要建造的建筑预制体 private GameObject previewBuilding; // 预览用的幽灵建筑 public Material previewMaterialValid; // 有效位置材质半透绿 public Material previewMaterialInvalid; // 无效位置材质半透红 void Update() { if (buildingPrefab null) return; // 从屏幕中心发射射线 Ray ray Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)); RaycastHit hit; if (Physics.Raycast(ray, out hit, 100, GridManager.Instance.buildableLayer)) { Vector3 targetGridPos GridManager.Instance.SnapToGrid(hit.point); bool isValid GridManager.Instance.IsGridCellEmpty(targetGridPos); // 更新或创建预览建筑 if (previewBuilding null) { previewBuilding Instantiate(buildingPrefab, targetGridPos, Quaternion.identity); // 禁用预览建筑的所有功能脚本只保留渲染 foreach (var comp in previewBuilding.GetComponentsMonoBehaviour()) comp.enabled false; } previewBuilding.transform.position targetGridPos; // 根据有效性改变预览材质 var renderers previewBuilding.GetComponentsInChildrenRenderer(); foreach (var rend in renderers) { rend.material isValid ? previewMaterialValid : previewMaterialInvalid; } // 放置建筑 if (Input.GetMouseButtonDown(0) isValid) { GameObject realBuilding Instantiate(buildingPrefab, targetGridPos, Quaternion.identity); realBuilding.tag Building; // 标记为建筑用于网格占用检测 // 可以在这里触发建造动画、消耗资源等逻辑 } } // 取消建造 if (Input.GetKeyDown(KeyCode.Escape)) { if (previewBuilding ! null) Destroy(previewBuilding); buildingPrefab null; } } }5.2 资源系统与游戏循环简化示例一个完整的建造游戏需要资源收集与消耗。这里提供一个极简框架。// 文件ResourceManager.cs using UnityEngine; using UnityEngine.UI; public class ResourceManager : MonoBehaviour { public static ResourceManager Instance; public int wood 100; public int stone 50; public Text resourceText; // UI文本用于显示资源 void Awake() { Instance this; UpdateUI(); } public bool TrySpendResources(int woodCost, int stoneCost) { if (wood woodCost stone stoneCost) { wood - woodCost; stone - stoneCost; UpdateUI(); return true; } Debug.Log(资源不足); return false; } void UpdateUI() { if (resourceText ! null) resourceText.text $木材: {wood} | 石材: {stone}; } }然后在BuildController的放置建筑逻辑中调用ResourceManager.Instance.TrySpendResources(10,5)来判断资源是否足够。6. 常见问题与优化指南在实现过程中你可能会遇到以下问题6.1 视觉与渲染问题问题现象可能原因解决方案边缘光效果不显示或太弱1. 着色器编译错误。2._FresnelPower值太大或太小。3. 模型法线不正确。1. 检查 Console 窗口是否有着色器错误。2. 调整_FresnelPower和_EdgeWidth参数_FresnelPower越小边缘光范围越大。3. 在3D建模软件中检查并重置模型法线或在Unity中勾选模型导入设置中的“Read/Write Enabled”。透明物体渲染顺序错乱透明物体渲染顺序由深度缓冲和渲染队列共同决定。1. 确保着色器中QueueTransparent。2. 对于复杂的重叠透明物体可能需要手动控制渲染顺序通过Material的Render Queue。3. 尽量避免大面积的透明物体深度交叉。Bloom效果导致整体画面发白Bloom阈值过低强度过高。在Volume的Bloom设置中提高“Threshold”降低“Intensity”。使用“Scatter”参数控制光晕的扩散柔和度。6.2 性能优化建议批处理与合批大量使用相同材质的静态建筑和树木务必标记为Static在Inspector右上角以便Unity进行静态批处理。对于动态物体确保它们共享材质以促进动态批处理。LOD多层次细节虽然模型简单但对于远距离的大量物体如远处的树林可以创建更简化的LOD模型甚至是一个面片以降低渲染负载。粒子系统优化控制粒子系统的最大数量使用简单的着色器对于屏幕外的粒子可以设置自动销毁或停止模拟。脚本优化在Update中避免每帧进行昂贵的操作如FindGameObjectWithTag、GetComponent。在Start或Awake中缓存引用。对于像SimpleWander这样的脚本可以使用对象池管理居民而不是频繁实例化和销毁。7. 风格深化与项目扩展方向完成基础框架后你可以从以下方向深化游戏使其更接近《寓言之地》的体验更丰富的建筑与升级链设计多种功能的建筑住房、伐木屋、采石场、市集并为其设计视觉上统一的升级形态如从小屋变大屋颜色和形态发生阶段性变化。居民需求系统为抽象的小人添加简单的状态饥饿、快乐建筑可以影响周围居民的状态形成正反馈循环。环境叙事与季节系统通过后期处理的 Color Adjustments 和 Lighting Settings动态切换色调和光照模拟晨昏、四季变化用色彩讲述故事。音效与音乐寻找或制作环境音效风声、鸟鸣和氛围音乐。音响应是抽象、舒缓的与视觉风格保持一致共同营造沉浸感。UI风格统一游戏UI也应采用色块和柔和边缘的设计避免使用复杂的图标和锐利的边框保持整个体验的和谐。打造一款罗斯科风格的城市建造游戏是一次将艺术表达与游戏设计相结合的有趣挑战。核心在于克制克制细节的添加克制色彩的滥用将交互反馈和情感体验置于复杂机制之上。从本文介绍的核心渲染技术和建造框架出发不断迭代和打磨你的色彩世界你也能创造出属于自己的一片宁静、治愈的“寓言之地”。
返回列表