浏览代码

Grid Sensor (#4399)

Co-authored-by: Chris Elion <chris.elion@unity3d.com>
/MLA-1734-demo-provider
GitHub 4 年前
当前提交
a117c932
共有 50 个文件被更改,包括 7711 次插入24 次删除
  1. 8
      com.unity.ml-agents.extensions/CHANGELOG.md
  2. 1
      com.unity.ml-agents.extensions/Tests/Editor/Unity.ML-Agents.Extensions.EditorTests.asmdef
  3. 60
      ml-agents-envs/mlagents_envs/rpc_utils.py
  4. 48
      ml-agents-envs/mlagents_envs/tests/test_rpc_utils.py
  5. 1001
      Project/Assets/ML-Agents/Examples/FoodCollector/Prefabs/GridFoodCollectorArea.prefab
  6. 7
      Project/Assets/ML-Agents/Examples/FoodCollector/Prefabs/GridFoodCollectorArea.prefab.meta
  7. 949
      Project/Assets/ML-Agents/Examples/FoodCollector/Scenes/GridFoodCollector.unity
  8. 7
      Project/Assets/ML-Agents/Examples/FoodCollector/Scenes/GridFoodCollector.unity.meta
  9. 1001
      Project/Assets/ML-Agents/Examples/FoodCollector/TFModels/GridFoodCollector.nn
  10. 11
      Project/Assets/ML-Agents/Examples/FoodCollector/TFModels/GridFoodCollector.nn.meta
  11. 223
      com.unity.ml-agents.extensions/Documentation~/Grid-Sensor.md
  12. 115
      com.unity.ml-agents.extensions/Runtime/Sensors/CountingGridSensor.cs
  13. 11
      com.unity.ml-agents.extensions/Runtime/Sensors/CountingGridSensor.cs.meta
  14. 876
      com.unity.ml-agents.extensions/Runtime/Sensors/GridSensor.cs
  15. 11
      com.unity.ml-agents.extensions/Runtime/Sensors/GridSensor.cs.meta
  16. 396
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotPerceiveTests.cs
  17. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotPerceiveTests.cs.meta
  18. 93
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotShapeTests.cs
  19. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotShapeTests.cs.meta
  20. 395
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelPerceiveTests.cs
  21. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelPerceiveTests.cs.meta
  22. 70
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelShapeTests.cs
  23. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelShapeTests.cs.meta
  24. 147
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorPerceiveTests.cs
  25. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorPerceiveTests.cs.meta
  26. 41
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorShapeTests.cs
  27. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorShapeTests.cs.meta
  28. 113
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridObservationPerceiveTests.cs
  29. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridObservationPerceiveTests.cs.meta
  30. 161
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridSensorTestUtils.cs
  31. 11
      com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridSensorTestUtils.cs.meta
  32. 8
      com.unity.ml-agents.extensions/Tests/Utils.meta
  33. 26
      config/ppo/GridFoodCollector.yaml
  34. 28
      config/sac/GridFoodCollector.yaml
  35. 1001
      com.unity.ml-agents.extensions/Documentation~/images/gridobs-vs-vectorobs.gif
  36. 20
      com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example-camera.png
  37. 94
      com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example-gridsensor.png
  38. 67
      com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example-raycast.png
  39. 79
      com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example.png
  40. 504
      com.unity.ml-agents.extensions/Documentation~/images/persp_ortho_proj.png
  41. 8
      com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents.meta
  42. 9
      com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/GridSensorDummyData.cs
  43. 11
      com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/GridSensorDummyData.cs.meta
  44. 14
      com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/SimpleTestGridSensor.cs
  45. 11
      com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/SimpleTestGridSensor.cs.meta
  46. 15
      com.unity.ml-agents.extensions/Tests/Utils/Unity.ML-Agents.Extensions.TestUtils.asmdef
  47. 7
      com.unity.ml-agents.extensions/Tests/Utils/Unity.ML-Agents.Extensions.TestUtils.asmdef.meta

8
com.unity.ml-agents.extensions/CHANGELOG.md


and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
* Initial version
* Initial version
### Minor Changes
#### com.unity.ml-agents.extensions (C#)
- Added the GridSensor and GridSensor tests
#### ml-agents-envs (Python)
- Modified the rpc_utils.py to support multiple PNGs

1
com.unity.ml-agents.extensions/Tests/Editor/Unity.ML-Agents.Extensions.EditorTests.asmdef


"name": "Unity.ML-Agents.Extensions.EditorTests",
"references": [
"Unity.ML-Agents.Extensions.Editor",
"Unity.ML-Agents.Extensions.TestUtils",
"Unity.ML-Agents.Extensions",
"Unity.ML-Agents"
],

60
ml-agents-envs/mlagents_envs/rpc_utils.py


from PIL import Image
PNG_HEADER = b"\x89PNG\r\n\x1a\n"
def behavior_spec_from_proto(
brain_param_proto: BrainParametersProto, agent_info: AgentInfoProto
) -> BehaviorSpec:

@timed
def process_pixels(image_bytes: bytes, gray_scale: bool) -> np.ndarray:
def process_pixels(image_bytes: bytes, expected_channels: int) -> np.ndarray:
:param gray_scale: Whether to convert the image to grayscale.
:param expected_channels: Expected output channels
with hierarchical_timer("image_decompress"):
image_bytearray = bytearray(image_bytes)
image = Image.open(io.BytesIO(image_bytearray))
# Normally Image loads lazily, this forces it to do loading in the timer scope.
image.load()
s = np.array(image, dtype=np.float32) / 255.0
if gray_scale:
image_bytearray = bytearray(image_bytes)
if expected_channels == 1:
# Convert to grayscale
with hierarchical_timer("image_decompress"):
image = Image.open(io.BytesIO(image_bytearray))
# Normally Image loads lazily, load() forces it to do loading in the timer scope.
image.load()
s = np.array(image, dtype=np.float32) / 255.0
return s
return s
image_arrays = []
bytes_read = 0
# Read the images back from the bytes (without knowing the sizes).
while True:
# TODO avoid creating a new array here. Unfortunately, Pillow doesn't respect the current state of the buffer
# and always starts with seek(0), but we should be able to wrap BytesIO with something that lets us adjust
# the "start" offset.
buffer = io.BytesIO(image_bytearray[bytes_read:])
with hierarchical_timer("image_decompress"):
image = Image.open(buffer)
image.load()
image_arrays.append(np.array(image, dtype=np.float32) / 255.0)
# Look for the next header, starting from the current stream location
try:
offset = buffer.getvalue().index(PNG_HEADER, buffer.tell())
bytes_read += offset
except ValueError:
# Didn't find the header, so must be at the end.
break
img = np.concatenate(image_arrays, axis=2)
# We can drop additional channels since they may need to be added to include
# numbers of observation channels not divisible by 3.
actual_channels = list(img.shape)[2]
if actual_channels > expected_channels:
img = img[..., 0:expected_channels]
return img
@timed

raise UnityObservationException(
f"Observation did not have the expected shape - got {obs.shape} but expected {expected_shape}"
)
gray_scale = obs.shape[2] == 1
expected_channels = obs.shape[2]
img = process_pixels(obs.compressed_data, gray_scale)
img = process_pixels(obs.compressed_data, expected_channels)
# Compare decompressed image size to observation shape and make sure they match
if list(obs.shape) != list(img.shape):
raise UnityObservationException(

48
ml-agents-envs/mlagents_envs/tests/test_rpc_utils.py


def generate_compressed_data(in_array: np.ndarray) -> bytes:
image_arr = (in_array * 255).astype(np.uint8)
im = Image.fromarray(image_arr, "RGB")
byteIO = io.BytesIO()
im.save(byteIO, format="PNG")
return byteIO.getvalue()
bytes_out = bytes()
num_channels = in_array.shape[2]
num_images = (num_channels + 2) // 3
# Split the input image into batches of 3 channels.
for i in range(num_images):
sub_image = image_arr[..., 3 * i : 3 * i + 3]
if (i == num_images - 1) and (num_channels % 3) != 0:
# Pad zeros
zero_shape = list(in_array.shape)
zero_shape[2] = 3 - (num_channels % 3)
z = np.zeros(zero_shape, dtype=np.uint8)
sub_image = np.concatenate([sub_image, z], axis=2)
im = Image.fromarray(sub_image, "RGB")
byteIO = io.BytesIO()
im.save(byteIO, format="PNG")
bytes_out += byteIO.getvalue()
return bytes_out
def generate_compressed_proto_obs(in_array: np.ndarray) -> ObservationProto:

def test_process_pixels():
in_array = np.random.rand(128, 64, 3)
byte_arr = generate_compressed_data(in_array)
out_array = process_pixels(byte_arr, False)
out_array = process_pixels(byte_arr, 3)
assert (in_array - out_array < 0.01).all()
assert np.allclose(in_array, out_array, atol=0.01)
def test_process_pixels_multi_png():
height = 128
width = 64
num_channels = 7
in_array = np.random.rand(height, width, num_channels)
byte_arr = generate_compressed_data(in_array)
out_array = process_pixels(byte_arr, num_channels)
assert out_array.shape == (height, width, num_channels)
assert np.sum(in_array - out_array) / np.prod(in_array.shape) < 0.01
assert np.allclose(in_array, out_array, atol=0.01)
out_array = process_pixels(byte_arr, True)
out_array = process_pixels(byte_arr, 1)
assert (in_array.mean(axis=2, keepdims=True) - out_array < 0.01).all()
assert np.allclose(in_array.mean(axis=2, keepdims=True), out_array, atol=0.01)
def test_vector_observation():

for obs_index, shape in enumerate(shapes):
arr = _process_vector_observation(obs_index, shape, list_proto)
assert list(arr.shape) == ([n_agents] + list(shape))
assert (np.abs(arr - 0.1) < 0.01).all()
assert np.allclose(arr, 0.1, atol=0.01)
def test_process_visual_observation():

ap_list = [ap1, ap2]
arr = _process_visual_observation(0, (128, 64, 3), ap_list)
assert list(arr.shape) == [2, 128, 64, 3]
assert (arr[0, :, :, :] - in_array_1 < 0.01).all()
assert (arr[1, :, :, :] - in_array_2 < 0.01).all()
assert np.allclose(arr[0, :, :, :], in_array_1, atol=0.01)
assert np.allclose(arr[1, :, :, :], in_array_2, atol=0.01)
def test_process_visual_observation_bad_shape():

1001
Project/Assets/ML-Agents/Examples/FoodCollector/Prefabs/GridFoodCollectorArea.prefab
文件差异内容过多而无法显示
查看文件

7
Project/Assets/ML-Agents/Examples/FoodCollector/Prefabs/GridFoodCollectorArea.prefab.meta


fileFormatVersion: 2
guid: b5339e4b990ade14f992aadf3bf8591b
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

949
Project/Assets/ML-Agents/Examples/FoodCollector/Scenes/GridFoodCollector.unity


%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.8, g: 0.8, b: 0.8, a: 1}
m_AmbientEquatorColor: {r: 0.6965513, g: 0, b: 1, a: 1}
m_AmbientGroundColor: {r: 1, g: 0.45977026, b: 0, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 3
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.44971228, g: 0.49977815, b: 0.57563734, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 11
m_GIWorkflowMode: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 1
m_LightmapEditorSettings:
serializedVersion: 10
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_Padding: 2
m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000,
type: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 0
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVRFilteringMode: 2
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ShowResolutionOverlay: 1
m_LightingDataAsset: {fileID: 112000002, guid: 03723c7f910c3423aa1974f1b9ce8392,
type: 2}
m_UseShadowmask: 1
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1001 &190823800
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_Name
value: GridFoodCollectorArea
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_RootOrder
value: 6
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
--- !u!1 &273651478
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 273651479}
- component: {fileID: 273651481}
- component: {fileID: 273651480}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &273651479
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 273651478}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 1799584681}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &273651480
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 273651478}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!222 &273651481
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 273651478}
m_CullTransparentMesh: 0
--- !u!1 &378228137
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 378228141}
- component: {fileID: 378228140}
- component: {fileID: 378228139}
- component: {fileID: 378228138}
m_Layer: 5
m_Name: Canvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &378228138
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 378228137}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &378228139
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 378228137}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!223 &378228140
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 378228137}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 0
m_SortingLayerID: 0
m_SortingOrder: 0
m_TargetDisplay: 0
--- !u!224 &378228141
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 378228137}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_Children:
- {fileID: 1799584681}
- {fileID: 1086444498}
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!1001 &392794583
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_Name
value: GridFoodCollectorArea (1)
objectReference: {fileID: 0}
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.y
value: -50
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_RootOrder
value: 7
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
--- !u!1 &499540684
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 499540687}
- component: {fileID: 499540686}
- component: {fileID: 499540685}
m_Layer: 0
m_Name: EventSystem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &499540685
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 499540684}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &499540686
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 499540684}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!4 &499540687
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 499540684}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &916917435
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalRotation.x
value: 0.31598538
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalRotation.y
value: -0.3596048
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalRotation.z
value: 0.13088542
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_LocalRotation.w
value: 0.8681629
objectReference: {fileID: 0}
- target: {fileID: 4943719350691982, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 5889392e3f05b448a8a06c5def6c2dec, type: 3}
--- !u!1 &1009000883
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1009000884}
- component: {fileID: 1009000887}
m_Layer: 0
m_Name: OverviewCamera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1009000884
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1009000883}
m_LocalRotation: {x: 0.2588191, y: 0, z: 0, w: 0.9659258}
m_LocalPosition: {x: 0, y: 75, z: -140}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 30, y: 0, z: 0}
--- !u!20 &1009000887
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1009000883}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 2
m_BackGroundColor: {r: 0.46666667, g: 0.5647059, b: 0.60784316, a: 1}
m_projectionMatrixMode: 1
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 30
orthographic: 0
orthographic size: 35.13
m_Depth: 2
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 1
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!1001 &1043871087
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_Name
value: GridFoodCollectorArea (2)
objectReference: {fileID: 0}
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.y
value: -100
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_RootOrder
value: 8
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
--- !u!1 &1086444495
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1086444498}
- component: {fileID: 1086444497}
- component: {fileID: 1086444496}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1086444496
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1086444495}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text:
--- !u!222 &1086444497
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1086444495}
m_CullTransparentMesh: 0
--- !u!224 &1086444498
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1086444495}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 378228141}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -1000, y: -239.57645}
m_SizeDelta: {x: 160, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1574236047
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1574236049}
- component: {fileID: 1574236048}
m_Layer: 0
m_Name: FoodCollectorSettings
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1574236048
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1574236047}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: be4599983abb14917a1c76329db0b6b0, type: 3}
m_Name:
m_EditorClassIdentifier:
agents: []
listArea: []
totalScore: 0
scoreText: {fileID: 1086444496}
--- !u!4 &1574236049
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1574236047}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0.71938086, y: 0.27357092, z: 4.1970553}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1799584680
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1799584681}
- component: {fileID: 1799584683}
- component: {fileID: 1799584682}
m_Layer: 5
m_Name: Panel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &1799584681
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1799584680}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 273651479}
m_Father: {fileID: 378228141}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1799584682
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1799584680}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!222 &1799584683
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1799584680}
m_CullTransparentMesh: 0
--- !u!1001 &1985725465
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_Name
value: GridFoodCollectorArea (3)
objectReference: {fileID: 0}
- target: {fileID: 1819751139121548, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.y
value: -150
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_RootOrder
value: 9
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4688212428263696, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: b5339e4b990ade14f992aadf3bf8591b, type: 3}
--- !u!1001 &2124876351
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_RootOrder
value: 5
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_AnchorMax.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_Pivot.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_Pivot.y
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 3ce107b4a79bc4eef83afde434932a68, type: 3}

7
Project/Assets/ML-Agents/Examples/FoodCollector/Scenes/GridFoodCollector.unity.meta


fileFormatVersion: 2
guid: 07d4146bc5bfaba4ab42d159c49f4d7b
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

1001
Project/Assets/ML-Agents/Examples/FoodCollector/TFModels/GridFoodCollector.nn
文件差异内容过多而无法显示
查看文件

11
Project/Assets/ML-Agents/Examples/FoodCollector/TFModels/GridFoodCollector.nn.meta


fileFormatVersion: 2
guid: 699f852e79b5ba642871514fb1fb9843
ScriptedImporter:
fileIDToRecycleName:
11400000: main obj
11400002: model data
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 19ed1486aa27d4903b34839f37b8f69f, type: 3}

223
com.unity.ml-agents.extensions/Documentation~/Grid-Sensor.md


# Summary
The Grid Sensor combines the generality of data extraction from Raycasts with the image processing power of Convolutional Neural Networks. The Grid Sensor can be used to collect data in the general form of a "Width x Height x Channel" matrix which can be used for training Reinforcement Learning agents or for data analysis.
# Motivation
In MLAgents there are 2 main sensors for observing information that is "physically" around the agent.
**Raycasts**
Raycasts provide the agent the ability to see things along prespecified lines of sight, similar to LIDAR. The kind of data it can extract is open to the developer from things like:
* The type of an object (enemy, npc, etc)
* The health of a unit
* the damage-per-second of a weapon on the ground
This is simple to implement and provides enough information for most simple games. When few are used, they are computationally fast. However, there are multiple limiting factors:
* The rays need to be at the same height as the things the agent should observe
* Objects can remain hidden by line of sight and if the knowledge of those objects is crucial to the success of the agent, then this limitation must be compensated for by the agents networks capacity (i.e., need a bigger brain with memory)
* The order of the raycasts (one raycast being to the left/right of another) is thrown away at the model level and must be learned by the agent which extends training time. Multiple raycasts exacerbates this issue.
* Typically the length of the raycasts is limited because the agent need not know about objects that are at the other side of the level. Combined with few raycasts for computational efficiency, this means that an agent may not observe objects that fall between these rays and the issue becomes worse as the objects reduce in size.
**Camera**
The Camera provides the agent with either a grayscale or an RGB image of the game environment. It goes without saying that there non-linear relationships between nearby pixels in an image. It is this intuition that helps form the basis of Convolutional Neural Networks (CNNs) and established the literature of designing networks that take advantage of these relationships between pixels. Following this established literature of CNNs on image based data, the MLAgent's Camera Sensor provides a means by which the agent can include high dimensional inputs (images) into its observation stream.
However the Camera Sensor has its own drawbacks as well.
* It requires render the scene and thus is computationally slower than alternatives that do not use rendering
* It has yet been shown that the Camera Sensor can be used on a headless machine which means it is not yet possible (if at all) to train an agent on a headless infrastructure.
* If the textures of the important objects in the game are updated, the agent needs to be retrained.
* The RGB of the camera only provides a maximum of 3 channels to the agent.
These limitations provided the motivation towards the development of the Grid Sensor and Grid Observations as described below.
# Contribution
An image can be thought of as a matrix of a predefined width (W) and a height (H) and each pixel can be thought of as simply an array of length 3 (in the case of RGB), `[Red, Green, Blue]` holding the different channel information of the color (channel) intensities at that pixel location. Thus an image is just a 3 dimensional matrix of size WxHx3. A Grid Observation can be thought of as a generalization of this setup where in place of a pixel there is a "cell" which is an array of length N representing different channel intensities at that cell position. From a Convolutional Neural Network point of view, the introduction of multiple channels in an "image" isn't a new concept. One such example is using an RGB-Depth image which is used in several robotics applications. The distinction of Grid Observations is what the data within the channels represents. Instead of limiting the channels to color intensities, the channels within a cell of a Grid Observation generalize to any data that can be represented by a single number (float or int).
Before jumping into the details of the Grid Sensor, an important thing to note is the agent performance and qualitatively different behavior over raycasts. Unity MLAgent's comes with a suite of example environments. One in particular, the [Food Collector](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Learning-Environment-Examples.md#food-collector), has been the focus of the Grid Sensor development.
The Food Collector environment can be described as:
* Set-up: A multi-agent environment where agents compete to collect food.
* Goal: The agents must learn to collect as many green food spheres as possible while avoiding red spheres.
* Agents: The environment contains 5 agents with same Behavior Parameters.
When applying the Grid Sensor to this environment, in place of the Raycast Vector Sensor or the Camera Sensor, a Mean Reward of 40-50 is observed. This performance is on par with what is seen by agents trained with RayCasts but the side-by-side comparison of trained agents, shows a qualitative difference in behavior. A deeper study and interpretation of the qualitative differences between agents trained with Raycasts and Vector Sensors verses Grid Sensors is left to future studies.
<img src="images/gridobs-vs-vectorobs.gif" align="middle" width="3000"/>
## Overview
There are 3 main phases to the Grid Sensor:
1. **Collection** - data is extracted from observed objects
2. **Encoding** - the extracted data is encoded into a grid observation
3. **Communication** - the grid observation is sent to python or used by a trained model
These phases are described in the following sections.
## Collection
A Grid Sensor is the Grid Observation analog of a Unity Camera but with some notable differences. The sensor is made up of a grid of identical Box Colliders which designate the "cells" of the grid. The Grid Sensor also has a list of "detectable objects" in the form of Unity gameobject tags. When an object that is tagged as a detectable object is present within a cell's Box Collider, that cell is "activated" and a method on the Grid Sensor extracts data from said object and associates that data with the position of the activated cell. Thus the Grid Sensor is always orthographic:
<img src="images/persp_ortho_proj.png" width="500">
<cite><a href="https://www.geofx.com/graphics/nehe-three-js/lessons17-24/lesson21/lesson21.html">geofx.com</a></cite>
In practice it has been useful to center the Grid Sensor on the agent in such a way that it is equivalent to having a "top-down" orthographic view of the agent.
Just like the Raycasts mentioned earlier, the Grid Sensor can extract any kind of data from a detected object and just like the Camera, the Grid Sensor maintains the spacial relationship between nearby cells that allows one to take advantage of the CNN literature. Thus the Grid Sensor tries to take the best of both sensors and combines them to something that is more expressive.
### Example of Grid Observations
A Grid Observation is best described using an example and a side by side comparison with the Raycasts and the Camera.
Lets imagine a scenario where an agent is faced with 2 enemies and there are 2 "equipable" weapons somewhat behind the agent. Lets also keep in mind some important properties of the enemies and weapons that would be useful for the agent to know. For simplicity, lets assume enemies represent their health as a percentage (0-100%). Lets also assume that enemies and weapons are the only 2 kind of objects that the agent would see in the entire game.
<img src="images/gridsensor-example.png" align="middle" width="3000"/>
#### Raycasts
If a raycast hits an object, not only could we get the distance (normalized by the maximum raycast distance) we would be able to extract its type (enemy vs weapon) and if its an enemy then we could get its health (e.g., .6).
There are many ways in which one could encode this information but one reasonable encoding is this:
```
raycastData = [isWeapon, isEnemy, health, normalizedDistance]
```
For example, if the raycast hit nothing then this would be represented by `[0, 0, 0, 1]`.
If instead the raycast hit an enemy with 60% health that is 50% of the maximum raycast distance, the data would be represented by `[0, 1, .6, .5]`.
The limitations of raycasts which were presented above are easy to visualize in the below image. The agent is unable to see where the weapons are and only sees one of the enemies. Typically in the MLAgents examples, this situation is mitigated by including previous frames of data so that the agent observes changes through time. However, in more complex games, it is not difficult to imagine scenarios where an agent would not be able to observe important information using only Raycasts.
<img src="images/gridsensor-example-raycast.png" align="middle" width="3000"/>
#### Camera
Instead, if we used a camera, the agent would be able to see around itself. It would be able to see both enemies and weapons (assuming its field of view was wide enough) and this could be processed by a CNN to encode this information. However, ignoring the obvious limitation that the game would have to be rendered, the agent would not have immediate access to the health value of the enemies. Perhaps textures are added to include "visible damage" to the enemies or there may be health bars above the enemies heads but both of these additions are subject to change, especially in a game that is in development. By using the camera only, it forces the agent to learn a different behavior as it is not able to access what would otherwise be accessible data.
<img src="images/gridsensor-example-camera.png" align="middle" width="3000"/>
#### Grid Sensor
The data extraction method of the Grid Sensor is as open-ended as using the Raycasts to collect data. The `GetObjectData` method on the Grid Sensor can be overridden to collect whatever information is deemed useful for the performance of the agent. By default, only the tag is used.
```csharp
protected virtual float[] GetObjectData(GameObject currentColliderGo, float typeIndex, float normalizedDistance)
```
Following the same data extraction method presented in the section on raycasts, if a Grid Sensor was used instead of Raycasts or a Camera, then not only would the agent be able to extract the health value of the enemies but it would also be able to encode the relative positions of those objects as is done with Camera. Additionally, as the texture of the objects is not used, this data can be collected without rendering the scene.
<img src="images/gridsensor-example-gridsensor.png" align="middle" width="3000"/>
At the end of the Collection phase, each cell with an object inside of it has `GetObjectData` called and the returned values (named `channelValues`) is then processed in the Encoding phase which is described in the next section.
#### CountingGridSensor
The CountingGridSensor builds on the GridSesnor to perform the specific job of counting the number of object types that are based on the different detectable object tags. The encoding and is meant to exploit a key feature of the Grid Sensor. In both the Channel and the Channel Hot DepthTypes, the closest detectable object, in relation to the agent, that lays within a cell is used for encoding the value for that cell. In the CountingGridSensor, the number of each type of object is recorded and then normalized according to a max count, stored in the ChannelDepth.
An example of the CountingGridSensor can be found below.
## Encoding
In order to support different ways of representing the data extracted from an object, multiple "depth types" were implemented. Each has pros and cons and, depending on the use-case of the Grid Sensor, one may be more beneficial than the others.
The data stored that is extracted during the *Collection* phase, and stored in `channelValues`, may come from different sources. For instance, going back the Enemy/Weapon example in the previous section, an enemy's health is continuous whereas the object type (enemy or weapon) is categorical data. This distinction is important as categorical data requires a different encoding mechanism than continuous data.
The Grid Sensor handles this distinction with 4 properties that define how this data is to be encoded:
* DepthType - Enum signifying the encoding mode: Channel, ChannelHot
* ObservationPerCell - the total number of values that are in each cell of the grid observation
* ChannelDepth - int[] describing the range of each data within the `channelValues`
* ChannelOffset - int[] describing the number of encoded values that come before each data within `channelValues`
The ChannelDepth and the DepthType are user defined and gives control to the developer to how they can encode their data. The ChannelDepth and ChannelOffset are both initialized and used in different ways depending on the ChannelDepth and the DepthType.
How categorical and continuous data is treated is different between the different DepthTypes as will be explored in the sections below. The sections will use an on-going example similar to example mentioned earlier where, within a cell, the sensor observes: `an enemy with 60% health`. Thus the cell contains 2 kinds of data: categorical data (object type) and the continuous data (health). Additionally, the order of the observed tags is important as it allows one to encode the tag of the observed object by its index within list of observed tags. Note that in the example, the observed tags is defined as ["weapon", "enemy"].
### Channel Based
The Channel Based Grid Observations is perhaps the simplest in terms of usability and similarity with other machine learning applications. Each grid is of size WxHxC where C is the number of channels. To distinguish between categorical and continuous data, one would use the ChannelDepth array to signify the ranges that the values in the `channelValues` array could take. If one sets ChannelDepth[i] to be 1, it is assumed that the value of `channelValues[i]` is already normalized. Else ChannelDepth[i] represents the total number of possible values that `channelValues[i]` can take.
Using the example described earlier, if one was using Channel Based Grid Observations, they would have a ChannelDepth = {2, 1} to describe that there are two possible values for the first channel and the 1 represents that the second channel is already normalized.
As the "enemy" is in the second position of the observed tags, its value can be normalized by:
```
num = detectableObjects.IndexOfTag("enemy")/ChannelDepth[0] = 2/2 = 1;
```
By using this formula, if there wasn't an object within the cell then the value would be 0.
As the ChannelDepth for the second channel is defined as 1, the collected health value (60% = 0.6) can be encoded directly. Thus the encoded data at this cell is:
`[1, .6]`
At the end of the Encoding phase, the resulting Grid Observation would be a WxHx2 matrix.
### Channel Hot
The Channel Hot DepthType generalizes the classic OneHot encoding to differentiate combinations of different data. Rather than normalizing the data like in the Channel Based section, each element of `channelValues` is represented by an encoding based on the ChannelDepth. If ChannelDepth[i] = 1, then this represents that `channelValues[i]` is already normalized (between 0-1) and will be used directly within the encoding. However if ChannelDepth[i] is an integer greater than 1, then the value in `channelValues[i]` will be converted into a OneHot encoding based on the following:
```
float[] arr = new float[ChannelDepth[i] + 1];
int index = (int) channelValues[i] + 1;
arr[index] = 1;
return arr;
```
The `+ 1` allows the first index of `arr` to be reserved for encoding "empty".
The encoding of each channel is then concatenated together. Clearly using this setup allows the developer to be able to encode values using the classic OneHot encoding. Below are some different variations of the ChannelDepth which create different encodings of the example:
##### ChannelDepth = {3, 1}
The first element, 3, signifies that there are 3 possibilities for the first channel and as the "enemy" is 2nd in the detected objects list, the "enemy" in the example is encoded as `[0, 0, 1]` where the first index represents "no object". The second element, 1, signifies that the health is already normalized and, following the table, is used directly. The resulting encoding is thus:
```
[0, 0, 1, 0.6]
```
##### ChannelDepth = {3, 5}
Like in the previous example, the "enemy" in the example is encoded as `[0, 0, 1]`. For the "health" however, the 5 signifies that the health should be represented by a OneHot encoding of 5 possible values, and in this case that encoding is `round(.6*5) = round(3) = 3 => [0, 0, 0, 1, 0]`.
This encoding would then be concatenated together with the "enemy" encoding resulting in:
```
enemy encoding => [0, 0, 1]
health encoding => [0, 0, 0, 1, 0]
final encoding => [0, 0, 1, 0, 0, 0, 1, 0]
```
The table below describes how other values of health would be mapped to OneHot encoding representations:
| Range | OneHot Encoding |
|------------------|-----------------|
| health = 0 | [1, 0, 0, 0, 0] |
| 0 < health < .3 | [0, 1, 0, 0, 0] |
| .3 < health < .5 | [0, 0, 1, 0, 0] |
| .5 < health < .7 | [0, 0, 0, 1, 0] |
| .7 < health <= 1 | [0, 0, 0, 0, 1] |
##### ChannelDepth = {1, 1}
This setting of ChannelDepth would throw an error as there is not enough information to encode the categorical data of the object type.
### CountingGridSensor
As introduced above, the CountingGridSensor inherits from the GridSensor for the sole purpose of counting the different objects that lay within a cell. In order to normalize the counts so that the grid can be properly encoded as PNG, the ChannelDepth is used to represent the "maximum count" of each type. For the working example, if the ChannelDepth is set as {50, 10}, which represents that the maximum count for objects with the "weapon" and "enemy" tag is 50 and 10, respectively, then the resulting data would be:
```
encoding = [0 weapons/ 50 weapons, 1 enemy / 10 enemies] = [0, .1]
```
## Communication
At the end of the Encoding phase, all of the data for a Grid Observation is placed into a float[] referred to as the perception buffer. Now the data is ready to be sent to either the python side for training or to be used by a trained model within Unity. This is where the Grid Sensor takes advantage of 2D textures and the PNG encoding schema to reduce the number of bytes that are being sent.
The 2D texture is a Unity class that encodes the colors of an image. It is used for many ways through out Unity but it has 2 specific methods that the Grid Sensor takes advantage of:
`SetPixels` takes a 2D array of Colors and assigns the color values to the texture.
`EncodeToPNG` returns a byte[] containing the PNG encoding of the colors of the texture.
Together these 2 functions allow one to "push" a WxHx3 normalized array to a PNG byte[]. And indeed, this is how the Camera Sensor in Unity MLAgents sends its data to python. However, the grid sensor can have N channels so there needs to be a more generic way to send the data.
The core idea behind how a Grid Observation is encoded is the following:
1. split the channels of a Grid Observation into groups of 3
2. encode each of these groups as a PNG byte[]
3. concatenate all byte[] and send the combined array to python
4. reconstruct the Grid Observation by splitting up the array and decoding the sections
Once the bytes are sent to python, they are then decoded and used as a tensor of the correct shape within the mlagents python codebase.

115
com.unity.ml-agents.extensions/Runtime/Sensors/CountingGridSensor.cs


using System;
using UnityEngine;
using UnityEngine.Assertions;
namespace Unity.MLAgents.Extensions.Sensors
{
public class CountingGridSensor : GridSensor
{
/// <inheritdoc/>
public override void InitDepthType()
{
ObservationPerCell = ChannelDepth.Length;
}
/// <summary>
/// Overrides the initialization ofthe m_ChannelHotDefaultPerceptionBuffer with 0s
/// as the counting grid sensor starts within its initialization equal to 0
/// </summary>
public override void InitChannelHotDefaultPerceptionBuffer()
{
m_ChannelHotDefaultPerceptionBuffer = new float[ObservationPerCell];
}
/// <inheritdoc/>
public override void SetParameters(string[] detectableObjects, int[] channelDepth, GridDepthType gridDepthType,
float cellScaleX, float cellScaleZ, int gridWidth, int gridHeight, int observeMaskInt, bool rotateToAgent, Color[] debugColors)
{
this.ObserveMask = observeMaskInt;
this.DetectableObjects = detectableObjects;
this.ChannelDepth = channelDepth;
if (DetectableObjects.Length != ChannelDepth.Length)
throw new UnityAgentsException("The channels of a CountingGridSensor is equal to the number of detectableObjects");
this.gridDepthType = GridDepthType.Channel;
this.CellScaleX = cellScaleX;
this.CellScaleZ = cellScaleZ;
this.GridNumSideX = gridWidth;
this.GridNumSideZ = gridHeight;
this.RotateToAgent = rotateToAgent;
this.DiffNumSideZX = (GridNumSideZ - GridNumSideX);
this.OffsetGridNumSide = (GridNumSideZ - 1f) / 2f;
this.DebugColors = debugColors;
}
/// <summary>
/// For each collider, calls LoadObjectData on the gameobejct
/// </summary>
/// <param name="foundColliders">The array of colliders</param>
/// <param name="cellIndex">The cell index the collider is in</param>
/// <param name="cellCenter">the center of the cell the collider is in</param>
protected override void ParseColliders(Collider[] foundColliders, int cellIndex, Vector3 cellCenter)
{
GameObject currentColliderGo = null;
Vector3 closestColliderPoint = Vector3.zero;
for (int i = 0; i < foundColliders.Length; i++)
{
currentColliderGo = foundColliders[i].gameObject;
// Continue if the current collider go is the root reference
if (currentColliderGo == rootReference)
continue;
closestColliderPoint = foundColliders[i].ClosestPointOnBounds(cellCenter);
LoadObjectData(currentColliderGo, cellIndex,
Vector3.Distance(closestColliderPoint, transform.position) / SphereRadius);
}
}
/// <summary>
/// Throws an execption as this should not be called from the CountingGridSensor class
/// </summary>
/// <param name="currentColliderGo">The current gameobject to get data from</param>
/// <param name="typeIndex">the index of the detectable tag of this gameobject</param>
/// <param name="normalizedDistance">The normalized distance to the gridsensor</param>
/// <returns></returns>
protected override float[] GetObjectData(GameObject currentColliderGo, float typeIndex, float normalizedDistance)
{
throw new Exception("GetObjectData isn't called within the CountingGridSensor");
}
/// <summary>
/// Adds 1 to the counting index for this gameobject of this type
/// </summary>
/// <param name="currentColliderGo">the current game object</param>
/// <param name="cellIndex">the index of the cell</param>
/// <param name="normalizedDistance">the normalized distance from the gameobject to the sensor</param>
protected override void LoadObjectData(GameObject currentColliderGo, int cellIndex, float normalizedDistance)
{
for (int i = 0; i < DetectableObjects.Length; i++)
{
if (currentColliderGo != null && currentColliderGo.CompareTag(DetectableObjects[i]))
{
if (ShowGizmos)
{
Color debugRayColor = Color.white;
if (DebugColors.Length > 0)
{
debugRayColor = DebugColors[i];
}
CellActivity[cellIndex] = new Color(debugRayColor.r, debugRayColor.g, debugRayColor.b, .5f);
}
/// <remarks>
/// The observations are "channel count" so each grid is WxHxC where C is the number of tags
/// This means that each value channelValues[i] is a counter of gameobject included into grid cells where i is the index of the tag in DetectableObjects
/// </remarks>
int countIndex = cellIndex * ObservationPerCell + i;
m_PerceptionBuffer[countIndex] = Mathf.Min(1f, m_PerceptionBuffer[countIndex] + 1f / ChannelDepth[i]);
break;
}
}
}
}
}

11
com.unity.ml-agents.extensions/Runtime/Sensors/CountingGridSensor.cs.meta


fileFormatVersion: 2
guid: bf4578dd22673114ebe87bd08bd84c94
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

876
com.unity.ml-agents.extensions/Runtime/Sensors/GridSensor.cs


using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using Unity.MLAgents.Sensors;
namespace Unity.MLAgents.Extensions.Sensors
{
/// <summary>
/// Grid-based sensor.
/// </summary>
public class GridSensor : SensorComponent, ISensor
{
/// <summary>
/// Name of this grid sensor.
/// </summary>
public string Name;
//
// Main Parameters
//
/// <summary>
/// The width of each grid cell.
/// </summary>
[Header("Grid Sensor Settings")]
[Tooltip("The width of each grid cell")]
[Range(0.05f, 1000f)]
public float CellScaleX = 1f;
/// <summary>
/// The depth of each grid cell.
/// </summary>
[Tooltip("The depth of each grid cell")]
[Range(0.05f, 1000f)]
public float CellScaleZ = 1f;
/// <summary>
/// The width of the grid .
/// </summary>
[Tooltip("The width of the grid")]
[Range(2, 2000)]
public int GridNumSideX = 16;
/// <summary>
/// The depth of the grid .
/// </summary>
[Tooltip("The depth of the grid")]
[Range(2, 2000)]
public int GridNumSideZ = 16;
/// <summary>
/// The height of each grid cell. Changes how much of the vertical axis is observed by a cell.
/// </summary>
[Tooltip("The height of each grid cell. Changes how much of the vertical axis is observed by a cell")]
[Range(0.01f, 1000f)]
public float CellScaleY = 0.01f;
/// <summary>
/// Rotate the grid based on the direction the agent is facing.
/// </summary>
[Tooltip("Rotate the grid based on the direction the agent is facing")]
public bool RotateToAgent;
/// <summary>
/// Array holding the depth of each channel.
/// </summary>
[Tooltip("Array holding the depth of each channel")]
public int[] ChannelDepth;
/// <summary>
/// List of tags that are detected.
/// </summary>
[Tooltip("List of tags that are detected")]
public string[] DetectableObjects;
/// <summary>
/// The layer mask.
/// </summary>
[Tooltip("The layer mask")]
public LayerMask ObserveMask;
/// <summary>
/// Enum describing what kind of depth type the data should be organized as
/// </summary>
public enum GridDepthType { Channel, ChannelHot };
/// <summary>
/// The data layout that the grid should output.
/// </summary>
[Tooltip("The data layout that the grid should output")]
public GridDepthType gridDepthType = GridDepthType.Channel;
/// <summary>
/// The reference of the root of the agent. This is used to disambiguate objects with the same tag as the agent. Defaults to current GameObject.
/// </summary>
[Tooltip("The reference of the root of the agent. This is used to disambiguate objects with the same tag as the agent. Defaults to current GameObject")]
public GameObject rootReference;
//
// Hidden Parameters
//
/// <summary>
/// The total number of observations per cell of the grid. Its equivalent to the "channel" on the outgoing tensor.
/// </summary>
[HideInInspector]
public int ObservationPerCell;
/// <summary>
/// The total number of observations that this GridSensor provides. It is the length of m_PerceptionBuffer.
/// </summary>
[HideInInspector]
public int NumberOfObservations;
/// <summary>
/// The offsets used to specify where within a cell's allotted data, certain observations will be inserted.
/// </summary>
[HideInInspector]
public int[] ChannelOffsets;
/// <summary>
/// The main storage of perceptual information.
/// </summary>
protected float[] m_PerceptionBuffer;
/// <summary>
/// The default value of the perceptionBuffer when using the ChannelHot DepthType. Used to reset the array/
/// </summary>
protected float[] m_ChannelHotDefaultPerceptionBuffer;
/// <summary>
/// Array of Colors needed in order to load the values of the perception buffer to a texture.
/// </summary>
protected Color[] m_PerceptionColors;
/// <summary>
/// Texture where the colors are written to so that they can be compressed in PNG format.
/// </summary>
protected Texture2D m_perceptionTexture2D;
//
// Utility Constants Calculated on Init
//
/// <summary>
/// Number of PNG formated images that are sent to python during training.
/// </summary>
private int NumImages;
/// <summary>
/// Number of relevant channels on the last image that is sent/
/// </summary>
private int NumChannelsOnLastImage;
/// <summary>
/// Radius of grid, used for normalizing the distance.
/// </summary>
protected float SphereRadius;
/// <summary>
/// Total Number of cells (width*height)
/// </summary>
private int NumCells;
/// <summary>
/// Difference between GridNumSideZ and gridNumSideX.
/// </summary>
protected int DiffNumSideZX = 0;
/// <summary>
/// Offset used for calculating CellToPoint
/// </summary>
protected float OffsetGridNumSide = 7.5f; // (gridNumSideZ - 1) / 2;
/// <summary>
/// Half of the grid in the X direction
/// </summary>
private float HalfOfGridX;
/// <summary>
/// Half of the grid in the z direction
/// </summary>
private float HalfOfGridZ;
/// <summary>
/// Used in the PointToCell method to scale the x value to land in the calculated cell.
/// </summary>
private float PointToCellScalingX;
/// <summary>
/// Used in the PointToCell method to scale the y value to land in the calculated cell.
/// </summary>
private float PointToCellScalingZ;
/// <summary>
/// Bool if initialized or not.
/// </summary>
protected bool Initialized = false;
/// <summary>
/// Array holding the dimensions of the resulting tensor
/// </summary>
private int[] m_Shape;
//
// Debug Parameters
//
/// <summary>
/// Array of Colors used for the grid gizmos.
/// </summary>
[Header("Debug Options")]
[Tooltip("Array of Colors used for the grid gizmos")]
public Color[] DebugColors;
/// <summary>
/// The height of the gizmos grid.
/// </summary>
[Tooltip("The height of the gizmos grid")]
public float GizmoYOffset = 0f;
/// <summary>
/// Whether to show gizmos or not.
/// </summary>
[Tooltip("Whether to show gizmos or not")]
public bool ShowGizmos = false;
/// <summary>
/// Array of colors displaying the DebugColors for each cell in OnDrawGizmos. Only updated if ShowGizmos.
/// </summary>
protected Color[] CellActivity;
/// <summary>
/// Array of positions where each position is the center of a cell.
/// </summary>
private Vector3[] CellPoints;
/// <summary>
/// List representing the multiple compressed images of all of the grids
/// </summary>
private List<byte[]> compressedImgs;
/// <summary>
/// List representing the sizes of the multiple images so they can be properly reconstructed on the python side
/// </summary>
private List<byte[]> byteSizesBytesList;
private Color DebugDefaultColor = new Color(1f, 1f, 1f, 0.25f);
/// <inheritdoc/>
public override ISensor CreateSensor()
{
return this;
}
/// <summary>
/// Sets the parameters of the grid sensor
/// </summary>
/// <param name="detectableObjects">array of strings representing the tags to be detected by the sensor</param>
/// <param name="channelDepth">array of ints representing the depth of each channel</param>
/// <param name="gridDepthType">enum representing the GridDepthType of the sensor</param>
/// <param name="cellScaleX">float representing the X scaling of each cell</param>
/// <param name="cellScaleZ">float representing the Z scaling of each cell</param>
/// <param name="gridWidth">int representing the number of cells in the X direction. Width of the Grid</param>
/// <param name="gridHeight">int representing the number of cells in the Z direction. Height of the Grid</param>
/// <param name="observeMaskInt">int representing the layer mask to observe</param>
/// <param name="rotateToAgent">bool if true then the grid is rotated to the rotation of the transform the rootReference</param>
/// <param name="debugColors">array of colors corresponding the the tags in the detectableObjects array</param>
public virtual void SetParameters(string[] detectableObjects, int[] channelDepth, GridDepthType gridDepthType,
float cellScaleX, float cellScaleZ, int gridWidth, int gridHeight, int observeMaskInt, bool rotateToAgent, Color[] debugColors)
{
this.ObserveMask = observeMaskInt;
this.DetectableObjects = detectableObjects;
this.ChannelDepth = channelDepth;
this.gridDepthType = gridDepthType;
this.CellScaleX = cellScaleX;
this.CellScaleZ = cellScaleZ;
this.GridNumSideX = gridWidth;
this.GridNumSideZ = gridHeight;
this.RotateToAgent = rotateToAgent;
this.DiffNumSideZX = (GridNumSideZ - GridNumSideX);
this.OffsetGridNumSide = (GridNumSideZ - 1f) / 2f;
this.DebugColors = debugColors;
}
/// <summary>
/// Initializes the constant parameters used within the perceive method call
/// </summary>
public void InitGridParameters()
{
NumCells = GridNumSideX * GridNumSideZ;
float sphereRadiusX = (CellScaleX * GridNumSideX) / Mathf.Sqrt(2);
float sphereRadiusZ = (CellScaleZ * GridNumSideZ) / Mathf.Sqrt(2);
SphereRadius = Mathf.Max(sphereRadiusX, sphereRadiusZ);
ChannelOffsets = new int[ChannelDepth.Length];
DiffNumSideZX = (GridNumSideZ - GridNumSideX);
OffsetGridNumSide = (GridNumSideZ - 1f) / 2f;
HalfOfGridX = CellScaleX * GridNumSideX / 2;
HalfOfGridZ = CellScaleZ * GridNumSideZ / 2;
PointToCellScalingX = GridNumSideX / (CellScaleX * GridNumSideX);
PointToCellScalingZ = GridNumSideZ / (CellScaleZ * GridNumSideZ);
}
/// <summary>
/// Initializes the constant parameters that are based on the Grid Depth Type
/// Sets the ObservationPerCell and the ChannelOffsets properties
/// </summary>
public virtual void InitDepthType()
{
switch (gridDepthType)
{
case GridDepthType.Channel:
ObservationPerCell = ChannelDepth.Length;
break;
case GridDepthType.ChannelHot:
ObservationPerCell = 0;
ChannelOffsets[ChannelOffsets.Length - 1] = 0;
for (int i = 1; i < ChannelDepth.Length; i++)
{
ChannelOffsets[i] = ChannelOffsets[i - 1] + ChannelDepth[i - 1];
}
for (int i = 0; i < ChannelDepth.Length; i++)
{
ObservationPerCell += ChannelDepth[i];
}
break;
}
// The maximum number of channels in the final output must be less than 255 * 3 because the "number of PNG images" to generate must fit in one byte
Assert.IsTrue(ObservationPerCell < (255 * 3), "The maximum number of channels per cell must be less than 255 * 3");
}
/// <summary>
/// Initializes the location of the CellPoints property
/// </summary>
private void InitCellPoints()
{
CellPoints = new Vector3[NumCells];
for (int i = 0; i < NumCells; i++)
{
CellPoints[i] = CellToPoint(i, false);
}
}
/// <summary>
/// Initializes the m_ChannelHotDefaultPerceptionBuffer with default data in the case that the grid depth type is ChannelHot
/// </summary>
public virtual void InitChannelHotDefaultPerceptionBuffer()
{
m_ChannelHotDefaultPerceptionBuffer = new float[ObservationPerCell];
for (int i = 0; i < ChannelDepth.Length; i++)
{
if (ChannelDepth[i] > 1)
{
m_ChannelHotDefaultPerceptionBuffer[ChannelOffsets[i]] = 1;
}
}
}
/// <summary>
/// Initializes the m_PerceptionBuffer as the main data storage property
/// Calculates the NumImages and NumChannelsOnLastImage that are used for serializing m_PerceptionBuffer
/// </summary>
public void InitPerceptionBuffer()
{
if (Application.isPlaying)
Initialized = true;
NumberOfObservations = ObservationPerCell * NumCells;
m_PerceptionBuffer = new float[NumberOfObservations];
if (gridDepthType == GridDepthType.ChannelHot)
{
InitChannelHotDefaultPerceptionBuffer();
}
m_PerceptionColors = new Color[NumCells];
NumImages = ObservationPerCell / 3;
NumChannelsOnLastImage = ObservationPerCell % 3;
if (NumChannelsOnLastImage == 0)
NumChannelsOnLastImage = 3;
else
NumImages += 1;
CellActivity = new Color[NumCells];
}
/// <summary>
/// Calls the initialization methods. Creates the data storing properties used to send the data
/// Establishes
/// </summary>
public virtual void Start()
{
InitGridParameters();
InitDepthType();
InitCellPoints();
InitPerceptionBuffer();
// Default root reference to current game object
if (rootReference == null)
rootReference = gameObject;
m_Shape = new[] { GridNumSideX, GridNumSideZ, ObservationPerCell };
compressedImgs = new List<byte[]>();
byteSizesBytesList = new List<byte[]>();
m_perceptionTexture2D = new Texture2D(GridNumSideX, GridNumSideZ, TextureFormat.RGB24, false);
}
/// <summary>
/// Clears the perception buffer before loading in new data. If the gridDepthType is ChannelHot, then it initializes the
/// Reset() also reinits the cell activity array (for debug)
/// </summary>
public void Reset()
{
if (m_PerceptionBuffer != null)
{
if (gridDepthType == GridDepthType.ChannelHot)
{
// Copy the default value to the array
for (int i = 0; i < NumCells; i++)
{
Array.Copy(m_ChannelHotDefaultPerceptionBuffer, 0, m_PerceptionBuffer, i * ObservationPerCell, ObservationPerCell);
}
}
else
{
Array.Clear(m_PerceptionBuffer, 0, m_PerceptionBuffer.Length);
}
}
else
{
m_PerceptionBuffer = new float[NumberOfObservations];
}
if (ShowGizmos)
{
// Ensure to init arrays if not yet assigned (for editor)
if (CellActivity == null)
CellActivity = new Color[NumCells];
// Assign the default color to the cell activities
for (int i = 0; i < NumCells; i++)
{
CellActivity[i] = DebugDefaultColor;
}
}
}
/// <summary>Gets the shape of the grid observation</summary>
/// <returns>integer array shape of the grid observation</returns>
public int[] GetFloatObservationShape()
{
m_Shape = new[] { GridNumSideX, GridNumSideZ, ObservationPerCell };
return m_Shape;
}
/// <inheritdoc/>
public string GetName()
{
return Name;
}
/// <inheritdoc/>
public virtual SensorCompressionType GetCompressionType()
{
return SensorCompressionType.PNG;
}
/// <summary>
/// GetCompressedObservation - Calls Perceive then puts the data stored on the perception buffer
/// onto the m_perceptionTexture2D to be converted to a byte array and returned
/// </summary>
/// <returns>byte[] containing the compressed observation of the grid observation</returns>
public byte[] GetCompressedObservation()
{
// Timer stack is in accessable due to its protection level
//using (TimerStack.Instance.Scoped("GridSensor.GetCompressedObservation"))
{
Perceive(); // Fill the perception buffer with observed data
var allBytes = new List<byte>();
for (int i = 0; i < NumImages - 1; i++)
{
ChannelsToTexture(3 * i, 3);
allBytes.AddRange(m_perceptionTexture2D.EncodeToPNG());
}
ChannelsToTexture(3 * (NumImages - 1), NumChannelsOnLastImage);
allBytes.AddRange(m_perceptionTexture2D.EncodeToPNG());
return allBytes.ToArray();
}
}
/// <summary>
/// ChannelsToTexture - Takes the channel index and the numChannelsToAdd.
/// For each cell and for each channel to add, sets it to a value of the color specified for that cell.
/// All colors are then set to the perceptionTexture via SetPixels.
/// m_perceptionTexture2D can then be read as an image as it now contains all of the information that was
/// stored in the channels
/// </summary>
/// <param name="channelIndex"></param>
/// <param name="numChannelsToAdd"></param>
protected void ChannelsToTexture(int channelIndex, int numChannelsToAdd)
{
for (int i = 0; i < NumCells; i++)
{
for (int j = 0; j < numChannelsToAdd; j++)
{
m_PerceptionColors[i][j] = m_PerceptionBuffer[i * ObservationPerCell + channelIndex + j];
}
}
m_perceptionTexture2D.SetPixels(m_PerceptionColors);
}
/// <summary>
/// Perceive - Clears the buffers, calls overlap box on the actual cell (the actual perception part)
/// for all found colliders, LoadObjectData is called
/// at the end, Perceive returns the float array of the perceptions
/// </summary>
/// <returns>A float[] containing all of the information collected from the gridsensor</returns>
public float[] Perceive()
{
Reset();
// TODO: make these part of the class
Collider[] foundColliders = null;
Vector3 cellCenter = Vector3.zero;
Vector3 halfCellScale = new Vector3(CellScaleX / 2f, CellScaleY, CellScaleZ / 2f);
for (int cellIndex = 0; cellIndex < NumCells; cellIndex++)
{
if (RotateToAgent)
{
cellCenter = transform.TransformPoint(CellPoints[cellIndex]);
foundColliders = Physics.OverlapBox(cellCenter, halfCellScale, transform.rotation, ObserveMask);
}
else
{
cellCenter = transform.position + CellPoints[cellIndex];
foundColliders = Physics.OverlapBox(cellCenter, halfCellScale, Quaternion.identity, ObserveMask);
}
if (foundColliders != null && foundColliders.Length > 0)
{
ParseColliders(foundColliders, cellIndex, cellCenter);
}
}
return m_PerceptionBuffer;
}
/// <summary>
/// Parses the array of colliders found within a cell. Finds the closest gameobject to the agent root reference within the cell
/// </summary>
/// <param name="foundColliders">Array of the colliders found within the cell</param>
/// <param name="cellIndex">The index of the cell</param>
/// <param name="cellCenter">The center position of the cell</param>
protected virtual void ParseColliders(Collider[] foundColliders, int cellIndex, Vector3 cellCenter)
{
GameObject currentColliderGo = null;
GameObject closestColliderGo = null;
Vector3 closestColliderPoint = Vector3.zero;
float distance = float.MaxValue;
float currentDistance = 0f;
for (int i = 0; i < foundColliders.Length; i++)
{
currentColliderGo = foundColliders[i].gameObject;
// Continue if the current collider go is the root reference
if (currentColliderGo == rootReference)
continue;
closestColliderPoint = foundColliders[i].ClosestPointOnBounds(cellCenter);
currentDistance = Vector3.Distance(closestColliderPoint, rootReference.transform.position);
// Checks if our colliders contain a detectable object
if ((Array.IndexOf(DetectableObjects, currentColliderGo.tag) > -1) && (currentDistance < distance))
{
distance = currentDistance;
closestColliderGo = currentColliderGo;
}
}
if (closestColliderGo != null)
LoadObjectData(closestColliderGo, cellIndex, distance / SphereRadius);
}
/// <summary>
/// GetObjectData - returns an array of values that represent the game object
/// This is one of the few methods that one may need to override to get their required functionality
/// For instance, if one wants specific information about the current gameobject, they can use this method
/// to extract it and then return it in an array format.
/// </summary>
/// <returns>
/// A float[] containing the data that holds the representative information of the passed in gameObject
/// </returns>
/// <param name="currentColliderGo">The game object that was found colliding with a certain cell</param>
/// <param name="typeIndex">The index of the type (tag) of the gameObject.
/// (e.g., if this GameObject had the 3rd tag out of 4, type_index would be 2.0f)</param>
/// <param name="normalizedDistance">A float between 0 and 1 describing the ratio of
/// the distance currentColliderGo is compared to the edge of the gridsensor</param>
/// <example>
/// Here is an example of extenind GetObjectData to include information about a potential Rigidbody:
/// <code>
/// protected override float[] GetObjectData(GameObject currentColliderGo,
/// float type_index, float normalized_distance)
/// {
/// float[] channelValues = new float[ChannelDepth.Length]; // ChannelDepth.Length = 4 in this example
/// channelValues[0] = type_index;
/// Rigidbody goRb = currentColliderGo.GetComponent&lt;Rigidbody&gt;();
/// if (goRb != null)
/// {
/// channelValues[1] = goRb.velocity.x;
/// channelValues[2] = goRb.velocity.y;
/// channelValues[3] = goRb.velocity.z;
/// }
/// return channelValues;
/// }
/// </code>
/// </example>
protected virtual float[] GetObjectData(GameObject currentColliderGo, float typeIndex, float normalizedDistance)
{
float[] channelValues = new float[ChannelDepth.Length];
channelValues[0] = typeIndex;
return channelValues;
}
/// <summary>
/// Runs basic validation assertions to check that the values can be normalized
/// </summary>
/// <param name="channelValues">The values to be validated</param>
/// <param name="currentColliderGo">The gameobject used for better error messages</param>
protected virtual void ValidateValues(float[] channelValues, GameObject currentColliderGo)
{
for (int j = 0; j < channelValues.Length; j++)
{
if (channelValues[j] < 0)
throw new UnityAgentsException("Expected ChannelValue[" + j + "] for " + currentColliderGo.name + " to be non-negative, was " + channelValues[j]);
if (channelValues[j] > ChannelDepth[j])
throw new UnityAgentsException("Expected ChannelValue[" + j + "] for " + currentColliderGo.name + " to be less than ChannelDepth[" + j + "] (" + ChannelDepth[j] + "), was " + channelValues[j]);
}
}
/// <summary>
/// LoadObjectData - If the GameObject matches a tag, GetObjectData is called to extract the data from the GameObject
/// then the data is transformed based on the GridDepthType of the gridsensor.
/// Further documetation on the GridDepthType can be found below
/// </summary>
/// <param name="currentColliderGo">The game object that was found colliding with a certain cell</param>
/// <param name="cellIndex">The index of the current cell</param>
/// <param name="normalized_distance">A float between 0 and 1 describing the ratio of
/// the distance currentColliderGo is compared to the edge of the gridsensor</param>
protected virtual void LoadObjectData(GameObject currentColliderGo, int cellIndex, float normalized_distance)
{
for (int i = 0; i < DetectableObjects.Length; i++)
{
if (currentColliderGo != null && currentColliderGo.CompareTag(DetectableObjects[i]))
{
// TODO: Create the array already then set the values using "out" in GetObjectData
// Using i+1 as the type index as "0" represents "empty"
float[] channelValues = GetObjectData(currentColliderGo, (float)i + 1, normalized_distance);
ValidateValues(channelValues, currentColliderGo);
if (ShowGizmos)
{
Color debugRayColor = Color.white;
if (DebugColors.Length > 0)
{
debugRayColor = DebugColors[i];
}
CellActivity[cellIndex] = new Color(debugRayColor.r, debugRayColor.g, debugRayColor.b, .5f);
}
switch (gridDepthType)
{
case GridDepthType.Channel:
/// <remarks>
/// The observations are "channel based" so each grid is WxHxC where C is the number of channels
/// This typically means that each channel value is normalized between 0 and 1
/// If channelDepth is 1, the value is assumed normalized, else the value is normalized by the channelDepth
/// The channels are then stored consecutively in PerceptionBuffer.
/// NOTE: This is the only grid type that uses floating point values
/// For example, if a cell contains the 3rd type of 5 possible on the 2nd team of 3 possible teams:
/// channelValues = {2, 1}
/// ObservationPerCell = channelValues.Length
/// channelValues = {2f/5f, 1f/3f} = {.4, .33..}
/// Array.Copy(channelValues, 0, PerceptionBuffer, cell_id*ObservationPerCell, ObservationPerCell);
/// </remarks>
for (int j = 0; j < channelValues.Length; j++)
{
channelValues[j] /= ChannelDepth[j];
}
Array.Copy(channelValues, 0, m_PerceptionBuffer, cellIndex * ObservationPerCell, ObservationPerCell);
break;
case GridDepthType.ChannelHot:
/// <remarks>
/// The observations are "channel hot" so each grid is WxHxD where D is the sum of all of the channel depths
/// The opposite of the "channel based" case, the channel values are represented as one hot vector per channel and then concatenated together
/// Thus channelDepth is assumed to be greater than 1.
/// For example, if a cell contains the 3rd type of 5 possible on the 2nd team of 3 possible teams,
/// channelValues = {2, 1}
/// channelOffsets = {5, 3}
/// ObservationPerCell = 5 + 3 = 8
/// channelHotVals = {0, 0, 1, 0, 0, 0, 1, 0}
/// Array.Copy(channelHotVals, 0, PerceptionBuffer, cell_id*ObservationPerCell, ObservationPerCell);
/// </remarks>
float[] channelHotVals = new float[ObservationPerCell];
for (int j = 0; j < channelValues.Length; j++)
{
if (ChannelDepth[j] > 1)
{
channelHotVals[(int)channelValues[j] + ChannelOffsets[j]] = 1f;
}
else
{
channelHotVals[ChannelOffsets[j]] = channelValues[j];
}
}
Array.Copy(channelHotVals, 0, m_PerceptionBuffer, cellIndex * ObservationPerCell, ObservationPerCell);
break;
}
break;
}
}
}
/// <summary>Converts the index of the cell to the 3D point (y is zero)</summary>
/// <returns>Vector3 of the position of the center of the cell</returns>
/// <param name="cell">The index of the cell</param>
/// <param name="shouldTransformPoint">Bool weather to transform the point to the current transform</param>
protected Vector3 CellToPoint(int cell, bool shouldTransformPoint = true)
{
float x = (cell % GridNumSideZ - OffsetGridNumSide) * CellScaleX;
float z = (cell / GridNumSideZ - OffsetGridNumSide) * CellScaleZ - DiffNumSideZX;
if (shouldTransformPoint)
return transform.TransformPoint(new Vector3(x, 0, z));
return new Vector3(x, 0, z);
}
/// <summary>Finds the cell in which the given global point falls</summary>
/// <returns>
/// The index of the cell in which the global point falls or -1 if the point does not fall into a cell
/// </returns>
/// <param name="globalPoint">The 3D point in global space</param>
public int PointToCell(Vector3 globalPoint)
{
Vector3 point = transform.InverseTransformPoint(globalPoint);
if (point.x < -HalfOfGridX || point.x > HalfOfGridX || point.z < -HalfOfGridZ || point.z > HalfOfGridZ)
return -1;
float x = point.x + HalfOfGridX;
float z = point.z + HalfOfGridZ;
int _x = (int)Mathf.Floor(x * PointToCellScalingX);
int _z = (int)Mathf.Floor(z * PointToCellScalingZ);
return GridNumSideX * _z + _x;
}
/// <summary>Copies the data from one cell to another</summary>
/// <param name="fromCellID">index of the cell to copy from</param>
/// <param name="toCellID">index of the cell to copy into</param>
protected void CopyCellData(int fromCellID, int toCellID)
{
Array.Copy(m_PerceptionBuffer,
fromCellID * ObservationPerCell,
m_PerceptionBuffer,
toCellID * ObservationPerCell,
ObservationPerCell);
if (ShowGizmos)
CellActivity[toCellID] = CellActivity[fromCellID];
}
/// <summary>Creates a copy of a float array</summary>
/// <returns>float[] of the original data</returns>
/// <param name="array">The array to copy from</parma>
private static float[] CreateCopy(float[] array)
{
float[] b = new float[array.Length];
System.Buffer.BlockCopy(array, 0, b, 0, array.Length * sizeof(float));
return b;
}
/// <summary>Utility method to find the index of a tag</summary>
/// <returns>Index of the tag in DetectableObjects, if it is in there</returns>
/// <param name="tag">The tag to search for</param>
public int IndexOfTag(string tag)
{
return Array.IndexOf(DetectableObjects, tag);
}
void OnDrawGizmos()
{
if (ShowGizmos)
{
if (Application.isEditor && !Application.isPlaying)
Start();
Perceive();
Vector3 scale = new Vector3(CellScaleX, 1, CellScaleZ);
Vector3 offset = new Vector3(0, GizmoYOffset, 0);
Matrix4x4 oldGizmoMatrix = Gizmos.matrix;
Matrix4x4 cubeTransform = Gizmos.matrix;
for (int i = 0; i < NumCells; i++)
{
if (RotateToAgent)
{
cubeTransform = Matrix4x4.TRS(CellToPoint(i) + offset, transform.rotation, scale);
}
else
{
cubeTransform = Matrix4x4.TRS(CellToPoint(i, false) + transform.position + offset, Quaternion.identity, scale);
}
Gizmos.matrix = oldGizmoMatrix * cubeTransform;
Gizmos.color = CellActivity[i];
Gizmos.DrawCube(Vector3.zero, Vector3.one);
}
Gizmos.matrix = oldGizmoMatrix;
if (Application.isEditor && !Application.isPlaying)
DestroyImmediate(m_perceptionTexture2D);
}
}
/// <inheritdoc/>
void ISensor.Update() { }
/// <summary>Gets the observation shape</summary>
/// <returns>int[] of the observation shape</returns>
public override int[] GetObservationShape()
{
m_Shape = new[] { GridNumSideX, GridNumSideZ, ObservationPerCell };
return m_Shape;
}
/// <inheritdoc/>
public int Write(ObservationWriter writer)
{
// Timer stack is in accessable due to its protection level
// using (TimerStack.Instance.Scoped("GridSensor.WriteToTensor"))
{
Perceive();
int index = 0;
for (var h = GridNumSideZ - 1; h >= 0; h--) // height
{
for (var w = 0; w < GridNumSideX; w++) // width
{
for (var d = 0; d < ObservationPerCell; d++) // depth
{
writer[h, w, d] = m_PerceptionBuffer[index];
index++;
}
}
}
return index;
}
}
}
}

11
com.unity.ml-agents.extensions/Runtime/Sensors/GridSensor.cs.meta


fileFormatVersion: 2
guid: 801669c0cdece6b40b2e741ad0b119ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

396
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotPerceiveTests.cs


using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.MLAgents.Extensions.Sensors;
using Unity.MLAgents.Extensions.TestUtils.Sensors;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class ChannelHotPerceiveTests
{
GameObject testGo;
GameObject boxGo;
SimpleTestGridSensor gridSensor;
GridSensorDummyData dummyData;
// Use built-in tags
const string k_Tag1 = "Player";
const string k_Tag2 = "Respawn";
[UnitySetUp]
public IEnumerator SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<SimpleTestGridSensor>();
boxGo = new GameObject("block");
boxGo.tag = k_Tag1;
boxGo.transform.position = new Vector3(3f, 0f, 3f);
boxGo.AddComponent<BoxCollider>();
dummyData = boxGo.AddComponent<GridSensorDummyData>();
yield return null;
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(boxGo);
Object.DestroyImmediate(testGo);
}
[UnityTest]
public IEnumerator OneChannelDepthOneBelowZeroException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
dummyData.Data = new float[] { -0.1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthOneAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
dummyData.Data = new float[] { 1.1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthOneGoodValue()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
dummyData.Data = new float[] { .2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .2f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator OneChannelDepthThreeBelowZeroException()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { -1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthThreeAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { 4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthThreeGoodValueInt()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { 2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 3, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 0, 1 }, 4);
float[] expectedDefault = new float[] { 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator OneChannelDepthThreeGoodValueFloat()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { 2.4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 3, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 0, 1 }, 4);
float[] expectedDefault = new float[] { 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthOneOneBelowZeroException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 1 };
dummyData.Data = new float[] { -1, 1 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator TwoChannelDepthOneOneAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 1 };
dummyData.Data = new float[] { 1, 3 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator TwoChannelDepthOneOneGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 1 };
dummyData.Data = new float[] { .4f, .3f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 2, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .4f, .3f }, 4);
float[] expectedDefault = new float[] { 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthOneThreeAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 3 };
dummyData.Data = new float[] { .4f, 4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator TwoChannelDepthOneThreeGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 3 };
dummyData.Data = new float[] { .4f, 1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 4, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .4f, 0, 1, 0 }, 4);
float[] expectedDefault = new float[] { 0, 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthThreeOneGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 3, 1 };
dummyData.Data = new float[] { 1f, .4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 4, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 1, 0, .4f }, 4);
float[] expectedDefault = new float[] { 1, 0, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthThreeThreeGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 3, 3 };
dummyData.Data = new float[] { 1f, 2.2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 6, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 1, 0, 0, 0, 1 }, 4);
float[] expectedDefault = new float[] { 1, 0, 0, 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator ThreeChannelDepthFiveOneThreeGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 5, 1, 3 };
dummyData.Data = new float[] { 3f, .6f, 2.2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 9, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 0, 0, 1, 0, .6f, 0, 0, 1 }, 4);
float[] expectedDefault = new float[] { 1, 0, 0, 0, 0, 0, 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator ProperReset()
{
string[] tags = { k_Tag1 };
int[] depths = { 5, 1, 3 };
dummyData.Data = new float[] { 3f, .6f, 2.2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 9, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 0, 0, 1, 0, .6f, 0, 0, 1 }, 4);
float[] expectedDefault = new float[] { 1, 0, 0, 0, 0, 0, 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
Object.DestroyImmediate(boxGo);
yield return null;
output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 9, output.Length);
subarrayIndicies = new int[0];
expectedSubarrays = new float[0][];
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotPerceiveTests.cs.meta


fileFormatVersion: 2
guid: a55ecbf349308fc42be9872f9894bafa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

93
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotShapeTests.cs


using NUnit.Framework;
using UnityEngine;
using Unity.MLAgents.Extensions.Sensors;
using NUnit.Framework.Internal;
using UnityEngine.TestTools;
using System.Collections;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class ChannelHotShapeTests
{
GameObject testGo;
GridSensor gridSensor;
[SetUp]
public void SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<GridSensor>();
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(testGo);
}
[Test]
public void OneChannelDepthOne()
{
string[] tags = { "Box", "Ball" };
int[] depths = { 1 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 1 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
[Test]
public void OneChannelDepthTwo()
{
string[] tags = { "Box", "Ball" };
int[] depths = { 2 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 2 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
[Test]
public void TwoChannelsDepthTwoOne()
{
string[] tags = { "Box", "Ball" };
int[] depths = { 2, 1 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 3 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
[Test]
public void TwoChannelsDepthThreeThree()
{
string[] tags = { "Box", "Ball" };
int[] depths = { 3, 3 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 6 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelHotShapeTests.cs.meta


fileFormatVersion: 2
guid: 056b95810b761d543a0dbbf7e7709e20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

395
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelPerceiveTests.cs


using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.MLAgents.Extensions.Sensors;
using Unity.MLAgents.Extensions.TestUtils.Sensors;
using System.Linq;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class ChannelPerceiveTests
{
GameObject testGo;
GameObject boxGo;
SimpleTestGridSensor gridSensor;
GridSensorDummyData dummyData;
// Use built-in tags
const string k_Tag1 = "Player";
[UnitySetUp]
public IEnumerator SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<SimpleTestGridSensor>();
boxGo = new GameObject("block");
boxGo.tag = k_Tag1;
boxGo.transform.position = new Vector3(3f, 0f, 3f);
boxGo.AddComponent<BoxCollider>();
dummyData = boxGo.AddComponent<GridSensorDummyData>();
yield return null;
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(boxGo);
Object.DestroyImmediate(testGo);
}
[UnityTest]
public IEnumerator OneChannelDepthOneBelowZeroException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
dummyData.Data = new float[] { -0.1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthOneAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
dummyData.Data = new float[] { 1.1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthOneGoodValue()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
dummyData.Data = new float[] { .2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .2f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator OneChannelDepthThreeBelowZeroException()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { -1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthThreeAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { 4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator OneChannelDepthThreeGoodValueInt()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { 2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 2f / 3f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator OneChannelDepthThreeGoodValueFloat()
{
string[] tags = { k_Tag1 };
int[] depths = { 3 };
dummyData.Data = new float[] { 2.4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 2.4f / 3f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthOneOneBelowZeroException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 1 };
dummyData.Data = new float[] { -1, 1 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator TwoChannelDepthOneOneAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 1 };
dummyData.Data = new float[] { 1, 3 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator TwoChannelDepthOneOneGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 1 };
dummyData.Data = new float[] { .4f, .3f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 2, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .4f, .3f }, 4);
float[] expectedDefault = new float[] { 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthOneThreeAboveDepthException()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 3 };
dummyData.Data = new float[] { .4f, 4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.Perceive();
});
}
[UnityTest]
public IEnumerator TwoChannelDepthOneThreeGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 1, 3 };
dummyData.Data = new float[] { .4f, 1f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 2, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .4f, 1f / 3f }, 4);
float[] expectedDefault = new float[] { 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthThreeOneGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 3, 1 };
dummyData.Data = new float[] { 1f, .4f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 2, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 1f / 3f, .4f }, 4);
float[] expectedDefault = new float[] { 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthThreeThreeGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 3, 3 };
dummyData.Data = new float[] { 1f, 2.2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 2, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 1f / 3f, 2.2f / 3 }, 4);
float[] expectedDefault = new float[] { 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator ThreeChannelDepthFiveOneThreeGoodValues()
{
string[] tags = { k_Tag1 };
int[] depths = { 5, 1, 3 };
dummyData.Data = new float[] { 3f, .6f, 2.2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 3, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 3f / 5f, .6f, 2.2f / 3f }, 4);
float[] expectedDefault = new float[] { 0, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator ProperReset()
{
string[] tags = { k_Tag1 };
int[] depths = { 5, 1, 3 };
dummyData.Data = new float[] { 3f, .6f, 2.2f };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 3, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 3f / 5f, .6f, 2.2f / 3f }, 4);
float[] expectedDefault = new float[] { 0, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
Object.DestroyImmediate(boxGo);
yield return null;
output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 3, output.Length);
subarrayIndicies = new int[0];
expectedSubarrays = new float[0][];
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelPerceiveTests.cs.meta


fileFormatVersion: 2
guid: 630b065522bc281409501355842d8af4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

70
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelShapeTests.cs


using NUnit.Framework;
using UnityEngine;
using Unity.MLAgents.Extensions.Sensors;
using NUnit.Framework.Internal;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class ChannelShapeTests
{
GameObject testGo;
GridSensor gridSensor;
[SetUp]
public void SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<GridSensor>();
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(testGo);
}
[Test]
public void OneChannel()
{
string[] tags = { "Box" };
int[] depths = { 1 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 1 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
[Test]
public void TwoChannel()
{
string[] tags = { "Box", "Ball" };
int[] depths = { 1, 1 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 2 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
[Test]
public void SevenChannel()
{
string[] tags = { "Box", "Ball" };
int[] depths = { 1, 1, 1, 1, 1, 1, 1 };
Color[] colors = { Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
int[] expectedShape = { 10, 10, 7 };
GridObsTestUtils.AssertArraysAreEqual(expectedShape, gridSensor.GetFloatObservationShape());
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/ChannelShapeTests.cs.meta


fileFormatVersion: 2
guid: e27c92ddd6b03714bbfcde95c7c2a376
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

147
com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorPerceiveTests.cs


using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.MLAgents.Extensions.Sensors;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class CountingGridSensorPerceiveTests
{
GameObject testGo;
GameObject boxGo;
GameObject boxGoTwo;
GameObject boxGoThree;
CountingGridSensor gridSensor;
// Use built-in tags
const string k_Tag1 = "Player";
const string k_Tag2 = "Respawn";
public GameObject CreateBlock(Vector3 postion, string tag, string name)
{
GameObject boxGo = new GameObject(name);
boxGo.tag = tag;
boxGo.transform.position = postion;
boxGo.AddComponent<BoxCollider>();
return boxGo;
}
[UnitySetUp]
public IEnumerator SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<CountingGridSensor>();
boxGo = CreateBlock(new Vector3(3f, 0f, 3f), k_Tag1, "box1");
yield return null;
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(boxGo);
Object.DestroyImmediate(boxGoTwo);
Object.DestroyImmediate(boxGoThree);
Object.DestroyImmediate(testGo);
}
[UnityTest]
public IEnumerator OneChannelDepthOneCount()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 1f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator OneChannelDepthOneCountMax()
{
string[] tags = { k_Tag1 };
int[] depths = { 1 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
boxGoTwo = CreateBlock(new Vector3(3.1f, 0f, 3.1f), k_Tag1, "box2");
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 1f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator OneChannelDepthFourCount()
{
string[] tags = { k_Tag1 };
int[] depths = { 4 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
boxGoTwo = CreateBlock(new Vector3(3.1f, 0f, 3.1f), k_Tag1, "box2");
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .5f }, 4);
float[] expectedDefault = new float[] { 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator TwoChannelDepthFourCount()
{
string[] tags = { k_Tag1, k_Tag2 };
int[] depths = { 4, 1 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
boxGoTwo = CreateBlock(new Vector3(3.1f, 0f, 3.1f), k_Tag1, "box2");
boxGoThree = CreateBlock(new Vector3(2.9f, 0f, 2.9f), k_Tag2, "box2");
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 2, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { .5f, 1 }, 4);
float[] expectedDefault = new float[] { 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorPerceiveTests.cs.meta


fileFormatVersion: 2
guid: e1ae8cd2fe32b0f4ca9b2dfd370fa1b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

41
com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorShapeTests.cs


using UnityEngine;
using NUnit.Framework;
using Unity.MLAgents.Extensions.Sensors;
using NUnit.Framework.Internal;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class CountingGridSensorShapeTests
{
GameObject testGo;
CountingGridSensor gridSensor;
[SetUp]
public void SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<CountingGridSensor>();
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(testGo);
}
[Test]
public void OneTagMoreDepthError()
{
string[] tags = { "block" };
int[] depths = { 1, 1 };
Color[] colors = { Color.magenta };
Assert.Throws<UnityAgentsException>(() =>
{
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel, 1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
});
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/CountingGridSensorShapeTests.cs.meta


fileFormatVersion: 2
guid: e838c056154f109449b7a90d12547208
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

113
com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridObservationPerceiveTests.cs


using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.MLAgents.Extensions.Sensors;
using System.Linq;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public class GridObservationPerceiveTests
{
GameObject testGo;
GameObject boxGo;
GridSensor gridSensor;
// Use built-in tags
const string k_Tag1 = "Player";
const string k_Tag2 = "Respawn";
[UnitySetUp]
public IEnumerator SetupScene()
{
testGo = new GameObject("test");
testGo.transform.position = Vector3.zero;
gridSensor = testGo.AddComponent<GridSensor>();
boxGo = new GameObject("block");
boxGo.tag = k_Tag1;
boxGo.transform.position = new Vector3(3f, 0f, 3f);
boxGo.AddComponent<BoxCollider>();
yield return null;
}
[TearDown]
public void ClearScene()
{
Object.DestroyImmediate(boxGo);
Object.DestroyImmediate(testGo);
}
[UnityTest]
public IEnumerator PerceiveNotSelfChannelHot()
{
testGo.tag = k_Tag1;
string[] tags = { k_Tag2, k_Tag1 };
int[] depths = { 3 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.ChannelHot,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 3, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 0, 0, 1 }, 4);
float[] expectedDefault = new float[] { 1, 0, 0 };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator PerceiveNotSelfChannel()
{
testGo.tag = k_Tag1;
string[] tags = { k_Tag2, k_Tag1 };
int[] depths = { 3 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 2f / 3f }, 4);
float[] expectedDefault = new float[] { 0f };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
[UnityTest]
public IEnumerator PerceiveNotSelfChannelCount()
{
testGo.tag = k_Tag1;
string[] tags = { k_Tag1 };
int[] depths = { 3 };
Color[] colors = { Color.red, Color.magenta };
gridSensor.SetParameters(tags, depths, GridSensor.GridDepthType.Channel,
1f, 1f, 10, 10, LayerMask.GetMask("Default"), false, colors);
gridSensor.Start();
yield return null;
float[] output = gridSensor.Perceive();
Assert.AreEqual(10 * 10 * 1, output.Length);
int[] subarrayIndicies = new int[] { 77, 78, 87, 88 };
float[][] expectedSubarrays = GridObsTestUtils.DuplicateArray(new float[] { 1f / 3f }, 4);
float[] expectedDefault = new float[] { 0f };
GridObsTestUtils.AssertSubarraysAtIndex(output, subarrayIndicies, expectedSubarrays, expectedDefault);
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridObservationPerceiveTests.cs.meta


fileFormatVersion: 2
guid: f73dca3e500abbb45bcbb5dcdb03dfd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

161
com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridSensorTestUtils.cs


using NUnit.Framework;
using System;
using System.Diagnostics;
using System.Linq;
namespace Unity.MLAgents.Extensions.Tests.Sensors
{
public static class GridObsTestUtils
{
/// <summary>
/// Returns a human readable string of an array. Optional arguments are the index to start from and the number of elements to add to the string
/// </summary>
/// <param name="arr">The array to convert to string</param>
/// <param name="initialIndex">The initial index. Default 0</param>
/// <param name="numOfElements">The number of elements to print</param>
/// <returns>Human readable string</returns>
public static string Array2Str<T>(T[] arr, int initialIndex = 0, int maxNumberOfElements = int.MaxValue)
{
return String.Join(", ", arr.Skip(initialIndex).Take(maxNumberOfElements));
}
/// <summary>
/// Given a flattened matrix and a shape, returns a string in human readable format
/// </summary>
/// <param name="arr">Flattened matrix array</param>
/// <param name="shape">Shape of matrix</param>
/// <returns>human readable string</returns>
public static string Matrix2Str(float[] arr, int[] shape)
{
string log = "[";
int t = 0;
for (int i = 0; i < shape[0]; i++)
{
log += "\n[";
for (int j = 0; j < shape[1]; j++)
{
log += "[";
for (int k = 0; k < shape[2]; k++)
{
log += arr[t] + ", ";
t++;
}
log += "],";
}
log += "]";
}
log += "]";
return log;
}
/// <summary>
/// Utility function to duplicate an array into an array of arrays
/// </summary>
/// <param name="array">array to duplicate</param>
/// <param name="numCopies">number of times to duplicate</param>
/// <returns>array of duplicated arrays</returns>
public static float[][] DuplicateArray(float[] array, int numCopies)
{
float[][] duplicated = new float[numCopies][];
for (int i = 0; i < numCopies; i++)
{
duplicated[i] = array;
}
return duplicated;
}
/// <summary>
/// Asserts that 2 int arrays are the same
/// </summary>
/// <param name="expected">The expected array</param>
/// <param name="actual">The actual array</param>
public static void AssertArraysAreEqual(int[] expected, int[] actual)
{
Assert.AreEqual(expected.Length, actual.Length, "Lengths are not the same");
for (int i = 0; i < actual.Length; i++)
{
Assert.AreEqual(expected[i], actual[i], "Got " + Array2Str(actual) + ", expected " + Array2Str(expected));
}
}
/// <summary>
/// Asserts that 2 float arrays are the same
/// </summary>
/// <param name="expected">The expected array</param>
/// <param name="actual">The actual array</param>
public static void AssertArraysAreEqual(float[] expected, float[] actual)
{
Assert.AreEqual(expected.Length, actual.Length, "Lengths are not the same");
for (int i = 0; i < actual.Length; i++)
{
Assert.AreEqual(expected[i], actual[i], "Got " + Array2Str(actual) + ", expected " + Array2Str(expected));
}
}
/// <summary>
/// Asserts that the sub-arrays of the total array are equal to specific subarrays at specific subarray indicies and equal to a default everywhere else.
/// </summary>
/// <param name="total">Array containing all data of the grid observation. Is a concatenation of N subarrays all of the same length</param>
/// <param name="indicies">The indicies to verify that differ from the default array</param>
/// <param name="expectedArrays">The sub arrays values that differ from the default array</param>
/// <param name="expectedDefaultArray">The default value of a sub array</param>
/// <example>
/// If the total array is data from a 4x4x2 grid observation, total will be an array of size 32 and each sub array will have a size of 2.
/// Let 3 cells at indicies (0, 1), (2, 2), and (3, 0) with values ([.1, .5]), ([.9, .7]), ([0, .2]), respectively.
/// If the default values of cells are ([0, 0]) then the grid observation will be as follows:
/// [ [0, 0], [.1, .5], [ 0, 0 ], [0, 0],
/// [0, 0], [ 0, 0 ], [ 0, 0 ], [0, 0],
/// [0, 0], [ 0, 0 ], [.9, .7], [0, 0],
/// [0, .2], [ 0, 0 ], [ 0, 0 ], [0, 0] ]
///
/// Which will make the total array will be the flattened array
/// total = [0, 0, .1, .5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .9, .7, 0, 0, 0, .2, 0, 0, 0, 0, 0]
///
/// The indicies of the activated cells in the flattened array will be 1, 10, and 12
///
/// So to verify that the total array is as expected, AssertSubarraysAtIndex should be called as
/// AssertSubarraysAtIndex(
/// total,
/// indicies = new int[] {1, 10, 12},
/// expectedArrays = new float[][] { new float[] {.1, .5}, new float[] {.9, .7}, new float[] {0, .2}},
/// expecedDefaultArray = new float[] {0, 0}
/// )
/// </example>
public static void AssertSubarraysAtIndex(float[] total, int[] indicies, float[][] expectedArrays, float[] expectedDefaultArray)
{
int totalIndex = 0;
int subIndex = 0;
int subarrayIndex = 0;
int lenOfData = expectedDefaultArray.Length;
int numArrays = total.Length / lenOfData;
for (int i = 0; i < numArrays; i++)
{
totalIndex = i * lenOfData;
if (indicies.Contains(i))
{
subarrayIndex = Array.IndexOf(indicies, i);
for (subIndex = 0; subIndex < lenOfData; subIndex++)
{
Assert.AreEqual(expectedArrays[subarrayIndex][subIndex], total[totalIndex],
"Expected " + expectedArrays[subarrayIndex][subIndex] + " at subarray index " + totalIndex + ", index = " + subIndex + " but was " + total[totalIndex]);
totalIndex++;
}
}
else
{
for (subIndex = 0; subIndex < lenOfData; subIndex++)
{
Assert.AreEqual(expectedDefaultArray[subIndex], total[totalIndex],
"Expected default value " + expectedDefaultArray[subIndex] + " at subarray index " + totalIndex + ", index = " + subIndex + " but was " + total[totalIndex]);
totalIndex++;
}
}
}
}
}
}

11
com.unity.ml-agents.extensions/Tests/Editor/Sensors/GridSensorTestUtils.cs.meta


fileFormatVersion: 2
guid: 53aa6ab552bca5d49a6d298f1e633717
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
com.unity.ml-agents.extensions/Tests/Utils.meta


fileFormatVersion: 2
guid: 8cb560e251b28433385e46b2b75d6dae
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

26
config/ppo/GridFoodCollector.yaml


behaviors:
GridFoodCollector:
trainer_type: ppo
hyperparameters:
batch_size: 1024
buffer_size: 10240
learning_rate: 0.0003
beta: 0.005
epsilon: 0.2
lambd: 0.95
num_epoch: 3
learning_rate_schedule: linear
network_settings:
normalize: false
hidden_units: 256
num_layers: 1
vis_encode_type: simple
reward_signals:
extrinsic:
gamma: 0.99
strength: 1.0
keep_checkpoints: 5
max_steps: 2000000
time_horizon: 64
summary_freq: 10000
threaded: true

28
config/sac/GridFoodCollector.yaml


behaviors:
GridFoodCollector:
trainer_type: sac
hyperparameters:
learning_rate: 0.0003
learning_rate_schedule: constant
batch_size: 256
buffer_size: 2048
buffer_init_steps: 0
tau: 0.005
steps_per_update: 10.0
save_replay_buffer: false
init_entcoef: 0.05
reward_signal_steps_per_update: 10.0
network_settings:
normalize: false
hidden_units: 256
num_layers: 1
vis_encode_type: simple
reward_signals:
extrinsic:
gamma: 0.99
strength: 1.0
keep_checkpoints: 5
max_steps: 2000000
time_horizon: 64
summary_freq: 60000
threaded: true

1001
com.unity.ml-agents.extensions/Documentation~/images/gridobs-vs-vectorobs.gif
文件差异内容过多而无法显示
查看文件

20
com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example-camera.png

之前 之后
宽度: 318  |  高度: 326  |  大小: 5.9 KiB

94
com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example-gridsensor.png

之前 之后
宽度: 738  |  高度: 327  |  大小: 20 KiB

67
com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example-raycast.png

之前 之后
宽度: 349  |  高度: 312  |  大小: 17 KiB

79
com.unity.ml-agents.extensions/Documentation~/images/gridsensor-example.png

之前 之后
宽度: 733  |  高度: 657  |  大小: 26 KiB

504
com.unity.ml-agents.extensions/Documentation~/images/persp_ortho_proj.png

之前 之后
宽度: 1000  |  高度: 482  |  大小: 151 KiB

8
com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents.meta


fileFormatVersion: 2
guid: 130c2db04b9e34f8ca1e6cdba5553eb7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

9
com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/GridSensorDummyData.cs


using UnityEngine;
namespace Unity.MLAgents.Extensions.TestUtils.Sensors
{
public class GridSensorDummyData : MonoBehaviour
{
public float[] Data;
}
}

11
com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/GridSensorDummyData.cs.meta


fileFormatVersion: 2
guid: 6a92f48383431bc4581da7651269e71b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

14
com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/SimpleTestGridSensor.cs


using UnityEngine;
using Unity.MLAgents.Extensions.Sensors;
namespace Unity.MLAgents.Extensions.TestUtils.Sensors
{
public class SimpleTestGridSensor : GridSensor
{
protected override float[] GetObjectData(GameObject currentColliderGo,
float type_index, float normalized_distance)
{
return (float[])currentColliderGo.GetComponent<GridSensorDummyData>().Data.Clone();
}
}
}

11
com.unity.ml-agents.extensions/Tests/Utils/GridObsTestComponents/SimpleTestGridSensor.cs.meta


fileFormatVersion: 2
guid: 06b1a7a5dba5ce345ad8dc4d97deef62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

15
com.unity.ml-agents.extensions/Tests/Utils/Unity.ML-Agents.Extensions.TestUtils.asmdef


{
"name": "Unity.ML-Agents.Extensions.TestUtils",
"references": [
"Unity.ML-Agents.Extensions",
"Unity.ML-Agents"
],
"optionalUnityReferences": [
"TestAssemblies"
],
"includePlatforms": [],
"excludePlatforms": [],
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
]
}

7
com.unity.ml-agents.extensions/Tests/Utils/Unity.ML-Agents.Extensions.TestUtils.asmdef.meta


fileFormatVersion: 2
guid: 0045c8e5c329e44109e998b5c8a63891
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存