关于门户网站建设方案,唯品会网站建设的目标,枣庄网站seo,防控政策优化简介随着ECS的加入#xff0c;Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来#xff0c;我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS#xff0c;也许不少人需要些时间适应ECS的使用#xff0c;…简介 随着ECS的加入Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS也许不少人需要些时间适应ECS的使用但是ECS会对游戏性能的提升产生很大作用。 面向对象编程是一个很好的编程模式OOP非常容易掌握和易于理解尤其适合初学者。OOP的最大优点是它的可访问性开发者可以在几乎没有任何相关知识的情况下创建类并维护编写的代码。然而OOP方法带有严重的缺点它会给总体性能带来不好的影响因为OOP方法很难避免出现重复的代码造成一定程度上的性能开销而且OOP虽然简单但是却很依赖引用。 面向对象编程是基于特定对象的概念实现的对象的种类由类的实例定义它们通过互相交互来构建程序。对象可以包含属性和方法等形式的数据。 ECS实体组件系统的方法和OOP不同它的数据和行为/方法是明确分离的这样能大大提高内存使用效率从而提高性能。 现在对于Unity而言ECS还处于早期阶段有很大的发展空间但是开发者已经可以开始使用ECS。本文将讲解ECS的常见方法Hybrid ECS和Pure ECS并且介绍ECS的实现方法、语法以及如何开始使用ECS。 ECS方法 对于ECS我们会更多谈论实体Entity而不是游戏对象。或许你会觉得实体和游戏对象没有太大区别因为你可以将实体视为组件的容器Container。但是如果你深入研究的话会发现二者区别很大实体其实只是特定组件的句柄。 OOP和ECS所提到的“组件”是否相同不这二者并不一样。在ECS出现前我们通常将附加到游戏对象上的MonoBehaviour视为组件。MonoBehaviour包含数据和行为它的数据来自所有变量行为来自用于定义和调用游戏对象的函数。ECS是不同的因为实体和组件都没有任何行为逻辑它们只包含数据。 所有逻辑都保存在Managers/Systems中它会获取一组实体然后根据分组实体所包含的数据来执行所请求的行为。这意味着现在不是所有实体都会处理自己的行为而是所有实体行为都会在同一位置进行处理。 要完全理解为什么ECS方法比旧OOP方法的速度更快你需要了解内存管理的相关知识。在OOP方法中数据不会被组织起来而是会分散再整个内存中这是因为使用了自动内存管理功能。 自动内存管理功能介绍像C#这种语言内存的分配和释放过程是通过垃圾收集器自动完成的。该过程开始后Mono平台会从操作系统请求特定容量的内存并使用该部分内存来生成代码可使用的堆内存空间。该堆空间随着代码需要使用更多内存而逐渐增大。如果不再需要之前声明的内存内存会释放回操作系统堆的大小也会随之减小这便是垃圾回收器的工作方式。 对于之后的内存分配如果大小合适的话垃圾回收器会使用之前用于保存数据并在释放后产生的剩余“间隙”。 因此将数据从内存移动到缓存需要消耗较多性能因为必须先找到引用才可以进行移动。 对于内存管理而言ECS更加优化因为ECS的数据会根据类型进行保存而不是根据数据的分配时间。试想一下一个商店根据商品的上架顺序放置商品另一个商店根据商品的分类放置商品你认为哪个商店的做法更好 ECS性能更高效的另一个原因是由于数据已明确地分离出来ECS只会缓存相关数据。这是什么意思呢 当使用OOP时无论何时访问游戏对象即使只需要一个特定属性都必须缓存该对象的所有属性该做法会对性能产生很大影响因为缓存这些类型的话会导致原生系统和托管系统之间的交互而这样做就会产生垃圾导致垃圾回收的发生。 Hybrid ECS 目前想纯粹使用ECS来编写一个完整的游戏还不太现实因为部分Unity功能还未支持ECS但是这个原因不会阻止我们使用ECS。 Hybrid ECS允许我们将ECS逻辑结合到现有的项目中利用ECS的优点而且不会影响还未支持ECS的功能的使用。 Hybrid ECS会和helper类一同工作例如Game Object Entity它会将游戏对象转换为实体并将附带的MonoBehaviour转换为组件。 我们编写的C#脚本派生自MonoBehaviour它只包含数据但没有行为然后我们将这些MonoBehaviour附加到带有Game Object Entity的游戏对象上。 我们的行为会放到Manager/System中该类必须派生自ComponentSystem。我们不需要将该类附加到场景中的游戏对象因为Unity会检测到该类并自动执行它。 在System类中我们将通过定义它附带的组件来将Entity定义为struct。我们不会使用Update函数而是使用OnUpdate函数来获取实体并在实体中进行迭代并执行行为。 ECS Solar System (Hybrid) 我们已经了解到实体组件系统是什么现在来研究一下实际案例。 如前文所所述学习使用ECS对一些人而言很难因为必须改变自己编写程序的方法。希望下面部分内容能够帮助你轻松理解该过程。 我们将使用Hybrid ECS编写一个小型宇宙因为Hybrid ECS非常容易掌握希望看完本案例后你不会对ECS感到恐惧而是更自信地尝试使用ECS。项目准备 本文中使用的版本是Unity 2018.2.0.2首先从资源包管理器获取Entities资源包。打开Window PackageManager找到Entities进行安装。安装完成后Console中会出现报错内容但这是正常现象。我们还要将脚本运行时版本从.Net 3.5改为.Net 4.x方法是打开Build Settings Player Settings修改脚本运行时版本。 预制件 Hybrid ECS拥有能将MonoBehaviour转换为组件的helper类我们来仔细研究一下。 首先我们为星球、行星、椭圆形和月球创建大量预制件。从上图中可以看到该预制件就像普通预制件一样没有什么特别之处。预制件上带有Transform、Mesh Filter、Mesh Renderer和Mesh Collider。 该预制件上添加了Game Object Entity组件。该组件不是自定义编写的脚本而是前面提到的helper类它将把普通游戏对象转换为实体。 Planet Comonent组件代码如下using UnityEngine;public class PlanetComponent : MonoBehaviour{ public float rotationSpeed; public float orbitDuration; public OrbitalEllipse orbit;} Components Job用来保存数据所以在该函数中没有其它内容。下面列出了所有需要的组件这些组件就不进行太多讲解。using System.Collections;using System.Collections.Generic;using UnityEngine;public class MoonComponent : MonoBehaviour { public float movementSpeed; public GameObject parentPlanet;}using System.Collections;using System.Collections.Generic;using UnityEngine;public class OrbitalElipseComponent : MonoBehaviour { public float xExtent; public float yExtent; public GameObject parent;}using System.Collections;using System.Collections.Generic;using UnityEngine;public class StarComponent : MonoBehaviour { [Range(0f, 100f)] public float twinkleFrequency;} 这样组件和预制件就设置好了是不是非常简单。 HybridECSSolarSystem 在我们通过创建组件来处理所有数据之前如果仔细阅读前文就知道现在我们需要处理行为。 我们将添加新C#脚本命名为HybridECSSolarSystem我们不会将该脚本附加到任何对象上实际上它甚至不是MonoBehaviour它派生自ComponentSystem。 Unity会在内部检测并执行该脚本请记得添加using Unity.Entities声明。我们将在该类的开始定义Stars、Planets和Moons的struct前面提到实体是组件分组的句柄所以Stars类型的实体是附带StarComponent和MeshRenderer组件的所有实体。 或许你还注意到我们没有Update()函数而是使用了protected override void OnUpdate()派生自ComponentSystem的每个类都需要拥有OnUpdate()函数我们的实体行为将在该函数中执行。 仔细观察OnUpdate()函数中第一个foreach循环会发现该循环获取了所有Stars类型的实体然后循环处理每个实体。我们通过检查依赖starComponent.twinkleFrequency实体的Random.Range来获得随机性。using UnityEngine;using Unity.Entities;public class HybridECSSolarSystem : ComponentSystem{ struct Stars { public StarComponent starComponent; public MeshRenderer renderer; } struct Planets { public PlanetComponent planetComponent; public Transform transform; } struct Moons { public Transform transform; public MoonComponent moonComponent; } protected override void OnUpdate() { foreach (var starEntity in GetEntitiesStars()) { int timeAsInt (int)Time.time; if(Random.Range(1f, 100f) starEntity.starComponent.twinkleFrequency) { starEntity.renderer.enabled timeAsInt % 2 0; } } foreach (var planetEntity in GetEntitiesPlanets()) { planetEntity.transform.Rotate(Vector3.up * Time.deltaTime * planetEntity.planetComponent.rotationSpeed, Space.Self); planetEntity.transform.position planetEntity.planetComponent.orbit.Evaluate(Time.time / planetEntity.planetComponent.orbitDuration); } foreach (var moonEntity in GetEntitiesMoons()) { Vector3 parentPos moonEntity.moonComponent.parentPlanet.transform.position; Vector3 desiredPos (moonEntity.transform.position - parentPos).normalized * 5f parentPos; moonEntity.transform.position Vector3.MoveTowards(moonEntity.transform.position, desiredPos, moonEntity.moonComponent.movementSpeed); moonEntity.transform.RotateAround(moonEntity.moonComponent.parentPlanet.transform.position, Vector3.up, moonEntity.moonComponent.movementSpeed); } }} 我们没有通过以前的方法使用Update函数而是执行了System类中的Update这就是在使用Hybrid ECS时的区别。 虽然我们已经完成将游戏对象转换为实体所需的操作但我们还需要一个类来实例化银河系现在进入下一部分。 HybridECSInstantiator HybridECSInstantiator类的内容非常简单明了不必进行过多赘述。我们在实例化整个太阳系的场景中设置了一组变量用来创建游戏对象。我们通过使用处理对象位置的onUnitSphere和UniverseRadius将宇宙的大致结构视为球形。通过计算生成椭圆形然后使用LineRenderer组件进行绘制我们便得到了椭圆形轨道。 我们使用了HybridECSSolarSystem.cs类中的Ellipse.Evaluate()函数实现了行星的移动。 对于每个对象不管是Star还是Planet我们只是实例化这些对象设置它们的组件并放置到合适的位置。using System.Collections;using System.Collections.Generic;using UnityEngine;public class HybridECSInstatiator : MonoBehaviour{ [Header(General Settings:)] [SerializeField] float universeRadius; [Header(Sun:)] [SerializeField] GameObject sunPrefab; [SerializeField] Vector3 sunPosition; [Header(Moon:)] [SerializeField] GameObject moonPrefab; [SerializeField] float minMoonMovementSpeed; [SerializeField] float maxMoonMovementSpeed; [Header(Stars:)] [SerializeField] GameObject starPrefab; [SerializeField] float minStarsize; [SerializeField] float maxStarsize; [SerializeField] int starsAmount; [SerializeField] [Range(0, 100)] float minTwinkleFrequency; [SerializeField] [Range(0, 100)] float maxTwinkleFrequency; [Header(Orbital Elipses:)] [SerializeField] int elipseSegments; [SerializeField] float elipseWidth; [SerializeField] GameObject orbitalElipsePrefab; [Header(Planets:)] [SerializeField] ListPlanet planets new ListPlanet(); static HybridECSInstatiator instance; public static HybridECSInstatiator Instance { get { return instance; } } GameObject sun; void Awake() { instance this; PlaceSun(); PlaceStars(); PlacePlanets(); } #region Sun void PlaceSun() { sun Instantiate(sunPrefab, sunPosition, Quaternion.identity); GameObject sunParent new GameObject(); sunParent.name Sun; sun.transform.parent sunParent.transform; } #endregion #region Stars void PlaceStars() { GameObject starParent new GameObject(); starParent.name Stars; for (int i 0; i starsAmount; i) { GameObject currentStar Instantiate(starPrefab); currentStar.transform.parent starParent.transform; currentStar.GetComponentStarComponent().twinkleFrequency Random.Range(minTwinkleFrequency, maxTwinkleFrequency); float randomStarScale Random.Range(minStarsize, maxStarsize); currentStar.transform.localScale new Vector3(randomStarScale, randomStarScale, randomStarScale); currentStar.transform.position Random.onUnitSphere * universeRadius; currentStar.SetActive(true); } } #endregion #region OrbitalElipses void DrawOrbitalElipse(LineRenderer line, OrbitalEllipse ellipse) { Vector3[] drawPoints new Vector3[elipseSegments 1]; for (int i 0; i elipseSegments; i) { drawPoints[i] ellipse.Evaluate(i / (elipseSegments - 1f)); } drawPoints[elipseSegments] drawPoints[0]; line.useWorldSpace false; line.positionCount elipseSegments 1; line.startWidth elipseWidth; line.SetPositions(drawPoints); } #endregion #region Planets void PlacePlanets() { GameObject planetParent new GameObject(); planetParent.name Planets; for (int i 0; i planets.Count; i) { GameObject currentPlanet Instantiate(planets[i].planetPrefab); currentPlanet.transform.parent planetParent.transform; currentPlanet.GetComponentPlanetComponent().rotationSpeed planets[i].rotationSpeed; currentPlanet.GetComponentPlanetComponent().orbitDuration planets[i].orbitDuration; currentPlanet.GetComponentPlanetComponent().orbit planets[i].orbit; GameObject currentElipse Instantiate(orbitalElipsePrefab, sunPosition, Quaternion.identity); currentElipse.transform.parent sun.transform; DrawOrbitalElipse(currentElipse.GetComponentLineRenderer(), planets[i].orbit); if(planets[i].hasMoon) { GenerateMoon(currentPlanet); } } } #endregion #region Moons void GenerateMoon(GameObject planet) { GameObject moonParent new GameObject(); moonParent.name Moons; GameObject currentMoon Instantiate(moonPrefab); currentMoon.transform.parent moonParent.transform; currentMoon.GetComponentMoonComponent().movementSpeed Random.Range(minMoonMovementSpeed, maxMoonMovementSpeed); currentMoon.GetComponentMoonComponent().parentPlanet planet; } #endregion}[System.Serializable]public class OrbitalEllipse{ public float xExtent; public float yExtent; public float tilt; public Vector3 Evaluate(float _t) { Vector3 up new Vector3(0, Mathf.Cos(tilt * Mathf.Deg2Rad), -Mathf.Sin(tilt * Mathf.Deg2Rad)); float angle Mathf.Deg2Rad * 360f * _t; float x Mathf.Sin(angle) * xExtent; float y Mathf.Cos(angle) * yExtent; return up * y Vector3.right * x; }}[System.Serializable]public class Planet{ public GameObject planetPrefab; public OrbitalEllipse orbit; public bool hasMoon; [Header(Movement Settings:)] public float rotationSpeed; public float orbitDuration;} 这样一个小型的宇宙便实现了