该项目的目的是同时测试和演示来自 Unity DOTS 技术堆栈的多个新包。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

491 行
23 KiB

using System;
using Unity.Burst;
using Unity.Collections;
using Unity.DebugDisplay;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Sample.Core;
using RaycastHit = Unity.Physics.RaycastHit;
public static class AbilityOwner
{
[ConfigVar(Name = "abilityowner.showstate", DefaultValue = "0", Description = "show state")]
public static ConfigVar ShowInfo;
public struct State : IComponentData
{
public int foo;
}
// List of all owned collection
public struct OwnedCollection : IBufferElementData
{
public Entity collection;
public bool enabled;
}
public struct OwnedAbility : IBufferElementData
{
public Entity entity;
public Ability.AbilityTagValue tagValue;
public bool isAction;
public bool isActive;
}
private static int GetActiveButtonIndex(UserCommand cmd, ref AbilityCollection.AbilityEntry abilityEntry)
{
if(cmd.buttons.IsSet(abilityEntry.ActivateButton0))
return 0;
if(cmd.buttons.IsSet(abilityEntry.ActivateButton1))
return 1;
if(cmd.buttons.IsSet(abilityEntry.ActivateButton2))
return 2;
if(cmd.buttons.IsSet(abilityEntry.ActivateButton3))
return 3;
return -1;
}
abstract class UpdateAbilityOwnership : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps.Complete();
var AbilityCollectionStateFromEntity = GetComponentDataFromEntity<AbilityCollection.State>();
var ItemInputStateFromEntity = GetComponentDataFromEntity<Item.InputState>(true);
var AbilityEntryFromEntity = GetBufferFromEntity<AbilityCollection.AbilityEntry>(true);
var AbilityTagFromEntity = GetComponentDataFromEntity<Ability.AbilityTag>(true);
var AbilityActionFromEntity = GetComponentDataFromEntity<Ability.AbilityAction>(true);
var AbilityStateActiveFromEntity = GetComponentDataFromEntity<Ability.AbilityStateActive>(true);
Entities
.WithReadOnly(ItemInputStateFromEntity)
.WithReadOnly(AbilityEntryFromEntity)
.WithReadOnly(AbilityTagFromEntity)
.WithReadOnly(AbilityActionFromEntity)
.WithReadOnly(AbilityStateActiveFromEntity)
.ForEach((Entity ownerEntity, ref DynamicBuffer<OwnedCollection> ownedCollections, ref DynamicBuffer<OwnedAbility> ownedAbilities,
in Inventory.State inventoryState, in DynamicBuffer<Inventory.ItemEntry> items) =>
{
//
// Update owned collections
//
// Rebuild owned collection buffer
// TODO (mogensh) This should only run when needed (set of AbilityCollection change). Store last active in inventory or sumtin ?
ownedCollections.Clear();
// Add root ability collection
if (AbilityCollectionStateFromEntity.HasComponent(ownerEntity))
{
ownedCollections.Add(new OwnedCollection
{
collection = ownerEntity,
enabled = true,
});
}
// Add inventory collections
var activeSlot = inventoryState.activeSlot;
for (int i = 0; i < items.Length; i++)
{
var itemEntity = items[i].entity;
if (itemEntity == Entity.Null)
continue;
if (!AbilityCollectionStateFromEntity.HasComponent(itemEntity))
continue;
var item = ItemInputStateFromEntity[itemEntity];
var collectionActive = (item.slot == activeSlot);
ownedCollections.Add(new OwnedCollection
{
collection = itemEntity,
enabled = collectionActive,
});
}
// Make sure all collections have owner as owner
for (int i = 0; i < ownedCollections.Length; i++)
{
var collection = ownedCollections[i].collection;
var abilityCollection = AbilityCollectionStateFromEntity[collection];
abilityCollection.abilityOwner = ownerEntity;
AbilityCollectionStateFromEntity[collection] = abilityCollection;
}
//
// Update owned abilities
//
ownedAbilities.Clear();
for (int i = 0; i < ownedCollections.Length; i++)
{
var collectionEntity = ownedCollections[i].collection;
var abilityEntries = AbilityEntryFromEntity[collectionEntity];
for (int j = 0; j < abilityEntries.Length; j++)
{
var abilityEntity = abilityEntries[j].entity;
var abilityTag = AbilityTagFromEntity[abilityEntity];
ownedAbilities.Add(new OwnedAbility
{
entity = abilityEntity,
tagValue = abilityTag.Value,
isAction = AbilityActionFromEntity.HasComponent(abilityEntity),
isActive = AbilityStateActiveFromEntity.HasComponent(abilityEntity)
});
}
}
}).Run();
return default;
}
}
[UpdateInGroup(typeof(AbilityUpdateSystemGroup))]
[UpdateBefore(typeof(PrepareOwnerForAbilityUpdate))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class InitialUpdateAbilityOwnership : UpdateAbilityOwnership { }
[UpdateInGroup(typeof(AbilityUpdateSystemGroup))]
[UpdateAfter(typeof(AbilityUpdateCommandBufferSystem))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class FinalUpdateAbilityOwnership : UpdateAbilityOwnership { }
[UpdateInGroup(typeof(AbilityUpdateSystemGroup))]
[UpdateBefore(typeof(BehaviourRequestPhase))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class PrepareOwnerForAbilityUpdate : JobComponentSystem
{
[NativeDisableParallelForRestriction] ComponentDataFromEntity<AbilityCollection.State> m_AbilityCollectionStateData;
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps.Complete();
var PostUpdateCommands = new EntityCommandBuffer(Allocator.TempJob);
var PredictingTick = World.GetExistingSystem<GhostPredictionSystemGroup>().PredictingTick;
var AbilityCollectionAbilityEntryData = GetBufferFromEntity<AbilityCollection.AbilityEntry>(true);
var EnabledAbilityData = GetComponentDataFromEntity<Ability.EnabledAbility>(true);
Entities
.WithReadOnly(AbilityCollectionAbilityEntryData)
.WithReadOnly(EnabledAbilityData)
.WithNativeDisableContainerSafetyRestriction(PostUpdateCommands)
.ForEach((Entity entity, DynamicBuffer<OwnedCollection> ownedCollections, ref State state,
in PlayerControlled.State playerControlledState, in PredictedGhostComponent predictionData) =>
{
if (!GhostPredictionSystemGroup.ShouldPredict(PredictingTick, predictionData))
return;
// Remove enabledAbility from all disabled collections // TODO (mogensh) avoid doing this every frame
for (int i = 0; i < ownedCollections.Length; i++)
{
var collection = ownedCollections[i].collection;
var collectionEnabled = ownedCollections[i].enabled;
var abilities = AbilityCollectionAbilityEntryData[collection];
for (int j = 0; j < abilities.Length; j++)
{
var ability = abilities[j].entity;
var abilityEnabled = EnabledAbilityData.Exists(ability);
if (abilityEnabled != collectionEnabled)
PostUpdateCommands.RemoveComponent<Ability.EnabledAbility>(ability);
}
}
// Update enabledEntity
// TODO (mogensh) merge loop with loop above ??
// TODO (mogensh) for now we setup reference to character on abilities every frame. Should we only detect collection set change and do this?
for (int collIndex = 0; collIndex < ownedCollections.Length; collIndex++)
{
if (!ownedCollections[collIndex].enabled)
continue;
var abilityEntries =
AbilityCollectionAbilityEntryData[ownedCollections[collIndex].collection];
for (int j = 0; j < abilityEntries.Length; j++)
{
var entry = abilityEntries[j];
var abilityEntity = entry.entity;
var enabledAbility = Ability.EnabledAbility.Default;
enabledAbility.owner = entity;
enabledAbility.activeButtonIndex =
GetActiveButtonIndex(playerControlledState.command, ref entry);
if (EnabledAbilityData.Exists(abilityEntity))
PostUpdateCommands.SetComponent(abilityEntity, enabledAbility);
else
PostUpdateCommands.AddComponent(abilityEntity, enabledAbility);
}
}
}).Run();
PostUpdateCommands.Playback(EntityManager);
PostUpdateCommands.Dispose();
return default;
}
}
[UpdateInGroup(typeof(AbilityUpdateSystemGroup))]
[UpdateAfter(typeof(BehaviourRequestPhase))]
[UpdateBefore(typeof(MovementUpdatePhase))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class SelectActiveBehavior : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps.Complete();
// Cooldown to Idle
Entities
.ForEach((ref Ability.AbilityControl control, ref Ability.EnabledAbility enabled, ref Ability.AbilityStateCooldown cooldown) =>
{
if (cooldown.requestIdle)
control.behaviorState = Ability.AbilityControl.State.Idle;
}).Run();
// Active to Cooldown
Entities
.ForEach((ref Ability.AbilityControl control, ref Ability.EnabledAbility enabled, ref Ability.AbilityStateActive active) =>
{
if (active.requestCooldown)
control.behaviorState = Ability.AbilityControl.State.Cooldown;
}).Run();
// Select
var abilityEntryBufferFromEntity = GetBufferFromEntity<AbilityCollection.AbilityEntry>(true);
var abilityStateIdleFromEntity = GetComponentDataFromEntity<Ability.AbilityStateIdle>(true);
var abilityStateActiveFromEntity = GetComponentDataFromEntity<Ability.AbilityStateActive>(true);
var abilityControlFromEntity = GetComponentDataFromEntity<Ability.AbilityControl>(false);
var PredictingTick = World.GetExistingSystem<GhostPredictionSystemGroup>().PredictingTick;
Entities
.WithReadOnly(abilityEntryBufferFromEntity)
.WithReadOnly(abilityStateIdleFromEntity)
.WithReadOnly(abilityStateActiveFromEntity)
.WithNativeDisableParallelForRestriction(abilityControlFromEntity)
.ForEach((DynamicBuffer<OwnedCollection> ownedCollections, ref State state, ref PredictedGhostComponent predictionData) =>
{
if (!GhostPredictionSystemGroup.ShouldPredict(PredictingTick, predictionData))
return;
// Initialize ability info
var abilityEntries = new NativeList<AbilityCollection.AbilityEntry>(Allocator.Temp);
var oldAbilityEntryStates = new NativeList<Ability.AbilityControl.State>(Allocator.Temp);
var newAbilityEntryStates = new NativeList<Ability.AbilityControl.State>(Allocator.Temp);
var deactivateRequested = new NativeList<bool>(Allocator.Temp);
var activatableIndices = new NativeList<int>(Allocator.Temp);
var activeIndices = new NativeList<int>(Allocator.Temp);
for (int collIndex = 0; collIndex < ownedCollections.Length; collIndex++)
{
if (!ownedCollections[collIndex].enabled)
continue;
var abilityEntryBuffer = abilityEntryBufferFromEntity[ownedCollections[collIndex].collection];
for (int i = 0; i < abilityEntryBuffer.Length; i++)
{
var ability = abilityEntryBuffer[i].entity;
if (abilityStateIdleFromEntity.HasComponent(ability))
{
if (abilityStateIdleFromEntity[ability].requestActive)
{
activatableIndices.Add(abilityEntries.Length);
abilityEntries.Add(abilityEntryBuffer[i]);
oldAbilityEntryStates.Add(Ability.AbilityControl.State.Idle);
newAbilityEntryStates.Add(Ability.AbilityControl.State.Idle);
deactivateRequested.Add(false);
}
continue;
}
if (abilityStateActiveFromEntity.HasComponent(ability))
{
activeIndices.Add(abilityEntries.Length);
abilityEntries.Add(abilityEntryBuffer[i]);
oldAbilityEntryStates.Add(Ability.AbilityControl.State.Active);
newAbilityEntryStates.Add(Ability.AbilityControl.State.Active);
deactivateRequested.Add(false);
continue;
}
}
}
// Attempt to activate abilities
for (int i = 0; i < activatableIndices.Length; i++)
{
var activatableIndex = activatableIndices[i];
var activatableAbilityEntry = abilityEntries[activatableIndex];
var canActivate = true;
for (int j = 0; j < activeIndices.Length; j++)
{
var activeIndex = activeIndices[j];
if (newAbilityEntryStates[activeIndex] != Ability.AbilityControl.State.Active)
continue;
var activeAbilityEntry = abilityEntries[activeIndex];
// Can new ability *not* run with active ability ?
if ((activatableAbilityEntry.abilityType & ~activeAbilityEntry.canRunWith) > 0)
{
// Can new ability interrupt ?
if ((activeAbilityEntry.abilityType & activatableAbilityEntry.canInterrupt) > 0)
{
newAbilityEntryStates[activeIndex] = Ability.AbilityControl.State.Cooldown;
}
else
{
// Not allowed to run, so request deactivate
deactivateRequested[activeIndex] = true;
canActivate = false;
}
}
}
if (canActivate)
{
newAbilityEntryStates[activatableIndex] = Ability.AbilityControl.State.Active;
activeIndices.Add(activatableIndex);
}
}
// Update ability state
for (int i = 0; i < abilityEntries.Length; i++)
{
if (oldAbilityEntryStates[i] == Ability.AbilityControl.State.Idle)
{
if (newAbilityEntryStates[i] == Ability.AbilityControl.State.Active)
{
// Idle to active
var abilityCtrl = abilityControlFromEntity[abilityEntries[i].entity];
abilityCtrl.behaviorState = Ability.AbilityControl.State.Active;
abilityCtrl.requestDeactivate = deactivateRequested[i];
abilityControlFromEntity[abilityEntries[i].entity] = abilityCtrl;
}
}
else if (oldAbilityEntryStates[i] == Ability.AbilityControl.State.Active)
{
if (newAbilityEntryStates[i] == Ability.AbilityControl.State.Cooldown)
{
// Active to cooldown
var abilityCtrl = abilityControlFromEntity[abilityEntries[i].entity];
abilityCtrl.behaviorState = Ability.AbilityControl.State.Cooldown;
abilityControlFromEntity[abilityEntries[i].entity] = abilityCtrl;
}
else if (deactivateRequested[i])
{
// Deactivate requested
var abilityCtrl = abilityControlFromEntity[abilityEntries[i].entity];
abilityCtrl.requestDeactivate = true;
abilityControlFromEntity[abilityEntries[i].entity] = abilityCtrl;
}
}
}
abilityEntries.Dispose();
oldAbilityEntryStates.Dispose();
newAbilityEntryStates.Dispose();
deactivateRequested.Dispose();
activatableIndices.Dispose();
activeIndices.Dispose();
}).Run();
return default;
}
}
[UpdateInGroup(typeof(AbilityUpdateSystemGroup))]
[UpdateAfter(typeof(SelectActiveBehavior))]
[UpdateBefore(typeof(MovementUpdatePhase))]
[DisableAutoCreation]
[AlwaysSynchronizeSystem]
class PrintAbilityStatusBehavior : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps.Complete();
var PredictingTick = World.GetExistingSystem<GhostPredictionSystemGroup>().PredictingTick;
if (AbilityOwner.ShowInfo.IntValue > 0)
{
Entities
.WithoutBurst() // Debug output
.ForEach((Entity entity, ref PredictedGhostComponent predictedEntity, ref State state) =>
{
if (!GhostPredictionSystemGroup.ShouldPredict(PredictingTick, predictedEntity))
return;
var ownedCollections = EntityManager.GetBuffer<OwnedCollection>(entity);
int x = 1;
int yIdle = 2;
int yActive = 2;
int yCooldown = 2;
int indent = 30;
Overlay.Managed.Write(x, 1, "Ability controller");
Overlay.Managed.Write(x, yIdle++, "Idle");
Overlay.Managed.Write(x + indent, yActive++, "Active");
Overlay.Managed.Write(x + indent * 2, yCooldown++, "Cooldown");
for (int collIndex = 0; collIndex < ownedCollections.Length; collIndex++)
{
// Overlay.Managed.Write(x, y++, " AbilityCollection");
var abilityEntries =
EntityManager.GetBuffer<AbilityCollection.AbilityEntry>(ownedCollections[collIndex].collection);
for (int i = 0; i < abilityEntries.Length; i++)
{
var abilityEntry = abilityEntries[i];
var abilityEntity = abilityEntry.entity;
var abilityCtrl = EntityManager.GetComponentData<Ability.AbilityControl>(abilityEntity);
if (EntityManager.HasComponent<Ability.AbilityStateIdle>(abilityEntity))
{
PrintAbility(x, ref yIdle, abilityEntity, ref abilityCtrl, ref abilityEntry);
}
if (EntityManager.HasComponent<Ability.AbilityStateActive>(abilityEntity))
{
PrintAbility(x + indent, ref yActive, abilityEntity, ref abilityCtrl, ref abilityEntry);
}
if (EntityManager.HasComponent<Ability.AbilityStateCooldown>(abilityEntity))
{
PrintAbility(x + indent * 2, ref yCooldown, abilityEntity, ref abilityCtrl, ref abilityEntry);
}
}
}
}).Run();
}
return default;
}
void PrintAbility(int x, ref int y, Entity entity, ref Ability.AbilityControl abilityCtrl, ref AbilityCollection.AbilityEntry entry)
{
Overlay.Managed.Write(x, y++, " Entity:" + entity);
Overlay.Managed.Write(x, y++, " Type:" + Convert.ToString(entry.abilityType, 2));
Overlay.Managed.Write(x, y++, " RunWith:" + Convert.ToString(entry.canRunWith, 2) + " Intrupt:" + Convert.ToString(entry.canInterrupt, 2));
Overlay.Managed.Write(x, y++, " request deactivate:" + abilityCtrl.requestDeactivate);
}
}
}