https://github.com/Unity-Technologies/DOTS-training-samples
Original
代码清单:
- Box.cs
- CameraControl.cs
- Cannonball.cs
- CannonballPrefab.cs
- Game.cs
- Options.cs
- OptionsToggle.cs
- Parabola.cs
- Player.cs
- SliderProp.cs
- Tank.cs
- TerrainArea.cs
1. Box.cs
构成场地的各个立方体。
namespace JumpTheGun {
public class Box : MonoBehaviour {
//立方体位置的间隔,默认为1,当立方体长度也为1时,正好无缝
public const float SPACING = 1;
// 竖直方向的偏移,没什么用
public const float Y_OFFSET = 0;
// 最低高度
public const float HEIGHT_MIN = .5f;
//最低高度的颜色
public static Color MIN_HEIGHT_COLOR = Color.green;
//最高高度的颜色
public static Color MAX_HEIGHT_COLOR = new Color(99 /255f, 47 /255f, 0 /255f);
//当前box处于第几行、第几列
public int col { get; private set; }
public int row { get; private set; }
//高度访问器,修改高度时,调用UpdateTransform
public float height {
get {
return _height;
}
set {
_height = value;
UpdateTransform();
}
}
// 顶部的高度(local space)
public float top {
get {
return transform.localPosition.y + height / 2;
}
}
// 颜色访问器
public Color color {
get {
return meshRenderer.material.color;
}
set {
meshRenderer.material.color = value;
}
}
// 设置box的行、列、高度,并调用UpdateTransform
public void SetBox(int col, int row, float height){
this.col = col;
this.row = row;
this.height = height;
UpdateTransform();
}
//检测box是否与参数传入的正方体碰撞
public bool HitsCube(Vector3 center, float width) {
//box的bounds
Bounds boxBounds = new Bounds(transform.localPosition, new Vector3(SPACING, height, SPACING));
//正方体的bounds
Bounds cubeBounds = new Bounds(center, new Vector3(width, width, width));
return boxBounds.Intersects(cubeBounds);
}
//更新transform组件,且根据高度改变颜色
// 访问Game.instance.maxTerrainHeight获得最高高度
public void UpdateTransform(){
Vector3 pos = new Vector3(col * SPACING, height / 2 + Y_OFFSET, row * SPACING);
Vector3 scale = new Vector3(1, height, 1);
transform.localPosition = pos;
transform.localScale = scale;
// 改变颜色,根据高度线性插值
if (Mathf.Approximately(Game.instance.maxTerrainHeight, HEIGHT_MIN)) {
color = MIN_HEIGHT_COLOR;
} else {
color = Color.Lerp(MIN_HEIGHT_COLOR, MAX_HEIGHT_COLOR, (height - HEIGHT_MIN) / (Game.instance.maxTerrainHeight - HEIGHT_MIN));
}
}
//box被炮弹打中后,降低一定高度
public void TakeDamage(){
height = Mathf.Max(HEIGHT_MIN, height - Game.instance.boxHeightDamage);
}
// Use this for initialization
void Awake() {
meshRenderer = GetComponent<MeshRenderer>();
}
// 高度
private float _height = 1;
// Mesh renderer引用,用来修改颜色
private MeshRenderer meshRenderer;
}
}
2. Cannonball.cs && CannonballPrefab.cs
- CannonballPrefab.cs: 单例,引用加农炮prefab资源,之后可用
CannonballPrefab.instance.cannonballPrefab
访问 - Cannonball.cs 静态成员和方法实现对象池。发射方法,根据抛物线更新自身位置,检测碰撞。依赖TerrainArea类、Game类
3. Parabola.cs
抛物线类。只有两个静态方法。
其余省略,面向对象大家都比较熟悉就不赘述了。本来想画下类之间的依赖关系图。但其实就是一团乱麻。当然,你可以想方设法让代码看起来“漂亮”些,但其实复杂度的本源是问题本身。你对问题的数据分析和建模,以及采用的算法才是你该着重思考的部分。传统的面向对象经常使我们没法跳出框架,尝试用不同的角度去分析问题,更不用说,它经常使我们陷入一些无意义的框架陷阱。
Ported
代码清单:
- BlockTransformSystem.cs
- CannonballSystem.cs
- ComponentTypes.cs
- FollowPlayer.cs
- NewGameProxy.cs
- NewGameSystem.cs
- Options.cs
- Parabola.cs
- PlayerBouceSystem.cs
- PlayerPositionCacheSystem.cs
- TankAimSystem.cs
- TankFireSystem.cs
- TerrainSystem.cs
主要是找ECS和非ECS之间的状态(数据)传递的方法。虽然传统的OOP有很多弊端,但并不是一无是处。同样的,ECS也不是解决所有问题的银色子弹。我们要灵活地应用它们来解决问题。
Method 1: Cache State in System (ECS -> Monobehavior)
我们可以将数据保存一份在system中,之后我们可以在Monobehavior中使用World.Avtive.GetOrCreateSystem
得到system实例就能访问数据了。ECS有明确要求Component不能有方法,但System里是可以有成员变量的(并不是鼓励,我们需要仔细考虑)。关于System间的依赖关系,我们尽可能通过系统的执行顺序,和读写ComponentData来减少。
Method 2: Save reference in System (Monobehavior <-> ECS)
其实跟方法1本质相同,当我们允许在System保存变量后,就可以使用GameObject.Find
方法在ComponentSystem中获得Monobehavior的实例引用。
Method 3 : Singleton or Global Variable
单例和全局变量很显然也可以轻易的获得引用。但我们要警惕,全局的mutable变量很有可能带来灾难。
Method 4: Singleton Component/Entity ?
ECS的很大优势在于其内存布局,大大加快我们遍历数据的速度。但这有个前提,需要许多同类型的对象,“Where there is one,there is many”。可在游戏中,我们同样有许多“manager”,它们只需要一个实例。对于这种情况,应该与普通的Component区别对待。Unity还在完善和优化这种少量Entity的情况。
代码详情:
1. ComponentType.cs
所有Component Data类型。
namespace JumpTheGun {
// Use a 2D position for block entities, mainly to avoid the default
// EndFrameTransformSystem (in favor of BlockTransformSystem).
struct BlockPositionXZ : IComponentData
{
public float2 Value;
}
struct BlockHeight : IComponentData
{
public float Value;
}
// Index of a block in the terrain cache array.
struct BlockIndex : IComponentData
{
public int Value;
}
//抛物线轨迹
struct ArcState : IComponentData
{
public float3 Parabola; // a, b, c parameters
public float StartTime;
public float Duration;
public float3 StartPos;
public float3 EndPos;
}
//坦克开火cd
struct TankFireCountdown : IComponentData
{
public float SecondsUntilFire;
};
// Marks blocks whose LocalToWorld matrix need to be updated after
// their height changes.`
struct UpdateBlockTransformTag : IComponentData {}
// Tags to differentiate tank base & tank cannon entities.
// TODO(@cort): use IJobChunk to filter based on MeshInstanceRenderer value instead?
struct TankBaseTag : IComponentData {}
struct TankCannonTag : IComponentData {}
}
2. BlockTransformSystem.cs
Block指构成地形的立方块。这个系统的作用是更新blocks的transform,即LocalToWorld Matrix。
Jobs List:
1)BlockTransformJob 。
Query:
权限 | ComponentType |
---|---|
Read | BlockPositionXZ |
Read | BlockHeight |
Read | UpdateBlockTransformTag |
ReadWrite | LocalToWorld |
因为我们需要在Job中删除UpdateBlockTransformTag组件,所以必须使用EntityCommandBuffer。这里我们使用BeginSimulationEntityCommandBufferSystem提供command buffer。
namespace JumpTheGun
{
// For blocks marked with UpdateBlockTransformTag, use their position
// and height to compute a new LocalToWorld matrix.
[UpdateAfter(typeof(TerrainSystem))]
[UpdateBefore(typeof(TransformSystemGroup))]
public class BlockTransformSystem : JobComponentSystem
{
//[BurstCompile] // Can't currently add/remove components in Burst jobs
struct BlockTransformJob : IJobForEachWithEntity<BlockPositionXZ, BlockHeight, UpdateBlockTransformTag, LocalToWorld>
{
public EntityCommandBuffer.Concurrent CommandBuffer;
public void Execute(Entity entity, int index, [ReadOnly] ref BlockPositionXZ position,
[ReadOnly] ref BlockHeight height, [ReadOnly] ref UpdateBlockTransformTag _, ref LocalToWorld localToWorld)
{
float3 fullPos = new float3(position.Value.x, 0.0f, position.Value.y);
float3 fullScale = new float3(1.0f, height.Value, 1.0f);
// TODO(cort): Use WriteGroups here instead
localToWorld = new LocalToWorld
{
Value = math.mul(float4x4.Translate(fullPos), float4x4.Scale(fullScale))
};
CommandBuffer.RemoveComponent<UpdateBlockTransformTag>(index, entity);
}
}
private BeginSimulationEntityCommandBufferSystem _barrier;
protected override void OnCreateManager()
{
_barrier = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var handle = new BlockTransformJob
{
CommandBuffer = _barrier.CreateCommandBuffer().ToConcurrent(),
}.Schedule(this, inputDeps);
_barrier.AddJobHandleForProducer(handle);
return handle;
}
}
}
网友评论