Initial Combat Implementation
This commit is contained in:
@@ -131,6 +131,7 @@ GameObject:
|
||||
- component: {fileID: 330585545}
|
||||
- component: {fileID: 330585544}
|
||||
- component: {fileID: 330585547}
|
||||
- component: {fileID: 330585548}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
@@ -256,6 +257,27 @@ MonoBehaviour:
|
||||
m_VarianceClampScale: 0.9
|
||||
m_ContrastAdaptiveSharpening: 0
|
||||
m_Version: 2
|
||||
--- !u!114 &330585548
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 330585543}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3e5890693b64a429789bf3edfae0a6ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Client::ProjectM.Client.PrototypeCameraRig
|
||||
Pitch: 45
|
||||
Yaw: 0
|
||||
Distance: 16
|
||||
TargetHeight: 1
|
||||
Orthographic: 0
|
||||
FieldOfView: 55
|
||||
OrthoSize: 10
|
||||
FollowSharpness: 8
|
||||
FallbackTarget: {x: 3, y: 0, z: 4}
|
||||
--- !u!1 &410087039
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -432,6 +454,119 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &833091043
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 833091047}
|
||||
- component: {fileID: 833091046}
|
||||
- component: {fileID: 833091045}
|
||||
- component: {fileID: 833091044}
|
||||
m_Layer: 0
|
||||
m_Name: Ground
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!23 &833091044
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 833091043}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: f5ef5fb55f211414595517e5ed7857b9, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 1
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!64 &833091045
|
||||
MeshCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 833091043}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 5
|
||||
m_Convex: 0
|
||||
m_CookingOptions: 30
|
||||
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!33 &833091046
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 833091043}
|
||||
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &833091047
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 833091043}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 3, y: 0, z: 4}
|
||||
m_LocalScale: {x: 3, y: 1, z: 3}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1314640898
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -493,3 +628,4 @@ SceneRoots:
|
||||
- {fileID: 410087041}
|
||||
- {fileID: 832575519}
|
||||
- {fileID: 1314640900}
|
||||
- {fileID: 833091047}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06af691080cf54372ae1aac941c2589b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:33f3329aa1b6538d7c6ce29cf5ee32240ae9748448063cc3924e79b8cd720b1c
|
||||
size 167807
|
||||
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04f85d1ea79e74f48945ec9f95fb0f34
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,218 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Project M Input",
|
||||
"maps": [
|
||||
{
|
||||
"name": "Gameplay",
|
||||
"id": "69723709-3120-49dd-bbf0-91ac92ad8582",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Move",
|
||||
"type": "Value",
|
||||
"id": "2982f936-538e-4a37-a100-8eb7a2a88e4b",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "Aim",
|
||||
"type": "Value",
|
||||
"id": "a1b2c3d4-0a1m-4a1m-8a1m-000000000001",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "Fire",
|
||||
"type": "Button",
|
||||
"id": "a1b2c3d4-0f1r-4f1r-8f1r-000000000002",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"name": "",
|
||||
"id": "c3b1e700-eea3-426c-863d-36403d537af3",
|
||||
"path": "<Gamepad>/leftStick",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "WASD",
|
||||
"id": "cc5f8773-3f87-4e89-926e-2835221cc71b",
|
||||
"path": "Dpad",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "Move",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "cc98fd24-5e37-4b4a-8fdc-9a1c21bc1e09",
|
||||
"path": "<Keyboard>/w",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "59f74329-7d63-4bf5-b3bc-3af75462894f",
|
||||
"path": "<Keyboard>/upArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "624a58a3-f4ec-479f-8e1b-3b54ffaa7d8e",
|
||||
"path": "<Keyboard>/s",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "2d3501d8-5759-4d58-a909-a5ef3aa51842",
|
||||
"path": "<Keyboard>/downArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "14dbcdaf-402d-496b-9798-54b770c4a826",
|
||||
"path": "<Keyboard>/a",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "2f4809ce-93a5-44f7-802d-6ac61f1e03e2",
|
||||
"path": "<Keyboard>/leftArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "de79a6ae-cf9d-4536-b851-b630e3344d04",
|
||||
"path": "<Keyboard>/d",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "5bfaeff9-d16a-4fca-b43d-1d6d6b442bc8",
|
||||
"path": "<Keyboard>/rightArrow",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "401d3950-45d9-4839-b287-14c2b3902c67",
|
||||
"path": "<XRController>/{Primary2DAxis}",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "XR",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "b23bd67c-a1c3-44a5-a73b-c824416534fa",
|
||||
"path": "<Joystick>/stick",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "Joystick",
|
||||
"action": "Move",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "d1e2f3a4-aim0-4001-8001-000000000011",
|
||||
"path": "<Gamepad>/rightStick",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Aim",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "d1e2f3a4-fir0-4001-8001-000000000021",
|
||||
"path": "<Gamepad>/rightTrigger",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Gamepad",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "d1e2f3a4-fir0-4001-8001-000000000022",
|
||||
"path": "<Mouse>/leftButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "d1e2f3a4-fir0-4001-8001-000000000023",
|
||||
"path": "<Keyboard>/space",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": ";Keyboard&Mouse",
|
||||
"action": "Fire",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"controlSchemes": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5aa62f6ed584c43b791e76f2fd31820f
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
|
||||
generateWrapperCode: 1
|
||||
wrapperCodePath: Assets/_Project/Scripts/Client/Input/ProjectMInput.cs
|
||||
wrapperClassName:
|
||||
wrapperCodeNamespace:
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 130 B |
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42fbad828f6084cd7961237599a610fd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 84b20ca889a744e888c8c3b3b723ec69, type: 3}
|
||||
m_Name: Ability_FastLight
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.AbilityDefinition
|
||||
Id: 2
|
||||
DisplayName: Fast Light
|
||||
Damage: 8
|
||||
ProjectileSpeed: 40
|
||||
Range: 16
|
||||
AutoTargetRange: 12
|
||||
AutoTargetConeDegrees: 35
|
||||
CooldownTicks: 5
|
||||
ProjectilePrefab: {fileID: 8857056134628386430, guid: 700fb9c3f7cf94830a0ee78c4e4f1290, type: 3}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b594c2953f8304189b91ca8c96a9d0c4
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 84b20ca889a744e888c8c3b3b723ec69, type: 3}
|
||||
m_Name: Ability_Primary
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.AbilityDefinition
|
||||
Id: 1
|
||||
DisplayName: Primary
|
||||
Damage: 20
|
||||
ProjectileSpeed: 25
|
||||
Range: 20
|
||||
AutoTargetRange: 12
|
||||
AutoTargetConeDegrees: 35
|
||||
CooldownTicks: 12
|
||||
ProjectilePrefab: {fileID: 8857056134628386430, guid: 700fb9c3f7cf94830a0ee78c4e4f1290, type: 3}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 013954d16c1ff4c1dad82495e38b4657
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 84b20ca889a744e888c8c3b3b723ec69, type: 3}
|
||||
m_Name: Ability_SlowHeavy
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.AbilityDefinition
|
||||
Id: 3
|
||||
DisplayName: Slow Heavy
|
||||
Damage: 45
|
||||
ProjectileSpeed: 14
|
||||
Range: 24
|
||||
AutoTargetRange: 12
|
||||
AutoTargetConeDegrees: 35
|
||||
CooldownTicks: 28
|
||||
ProjectilePrefab: {fileID: 8857056134628386430, guid: 700fb9c3f7cf94830a0ee78c4e4f1290, type: 3}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 601a090742e5341cc9ee1f25d215136b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c49a4c4126cb24bd984381e2a48e9a99
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c63201f1fd9f24efd9ccae3e20cd2364, type: 3}
|
||||
m_Name: Character_Default
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.CharacterStatsDefinition
|
||||
Id: 1
|
||||
DisplayName: Default
|
||||
MoveSpeed: 6
|
||||
TurnRateDegreesPerSec: 720
|
||||
MaxHealth: 100
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e675b529048144a41a2054c729180bce
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9c772bb61e104e2e8f26dad62e3d686
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-8760768806978604505
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||
version: 10
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: M_Dummy
|
||||
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap:
|
||||
RenderType: Opaque
|
||||
disabledShaderPasses:
|
||||
- MOTIONVECTORS
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BaseMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _SpecGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _AddPrecomputedVelocity: 0
|
||||
- _AlphaClip: 0
|
||||
- _AlphaToMask: 0
|
||||
- _Blend: 0
|
||||
- _BlendModePreserveSpecular: 1
|
||||
- _BumpScale: 1
|
||||
- _ClearCoatMask: 0
|
||||
- _ClearCoatSmoothness: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DetailAlbedoMapScale: 1
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EnvironmentReflections: 1
|
||||
- _GlossMapScale: 0
|
||||
- _Glossiness: 0
|
||||
- _GlossyReflections: 0
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.005
|
||||
- _QueueOffset: 0
|
||||
- _ReceiveShadows: 1
|
||||
- _Smoothness: 0.5
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _WorkflowMode: 1
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 1, g: 0.15, b: 0.15, a: 1}
|
||||
- _Color: {r: 1, g: 0.14999998, b: 0.14999998, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8df9cd33fb974460a903e35a6fce3c9
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-3114125693251561139
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||
version: 10
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: M_Ground
|
||||
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap:
|
||||
RenderType: Opaque
|
||||
disabledShaderPasses:
|
||||
- MOTIONVECTORS
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BaseMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _SpecGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _AddPrecomputedVelocity: 0
|
||||
- _AlphaClip: 0
|
||||
- _AlphaToMask: 0
|
||||
- _Blend: 0
|
||||
- _BlendModePreserveSpecular: 1
|
||||
- _BumpScale: 1
|
||||
- _ClearCoatMask: 0
|
||||
- _ClearCoatSmoothness: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DetailAlbedoMapScale: 1
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EnvironmentReflections: 1
|
||||
- _GlossMapScale: 0
|
||||
- _Glossiness: 0
|
||||
- _GlossyReflections: 0
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.005
|
||||
- _QueueOffset: 0
|
||||
- _ReceiveShadows: 1
|
||||
- _Smoothness: 0.5
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _WorkflowMode: 1
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.16, g: 0.16, b: 0.2, a: 1}
|
||||
- _Color: {r: 0.15999997, g: 0.15999997, b: 0.19999996, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5ef5fb55f211414595517e5ed7857b9
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: M_Player
|
||||
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap:
|
||||
RenderType: Opaque
|
||||
disabledShaderPasses:
|
||||
- MOTIONVECTORS
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BaseMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _SpecGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _AddPrecomputedVelocity: 0
|
||||
- _AlphaClip: 0
|
||||
- _AlphaToMask: 0
|
||||
- _Blend: 0
|
||||
- _BlendModePreserveSpecular: 1
|
||||
- _BumpScale: 1
|
||||
- _ClearCoatMask: 0
|
||||
- _ClearCoatSmoothness: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DetailAlbedoMapScale: 1
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EnvironmentReflections: 1
|
||||
- _GlossMapScale: 0
|
||||
- _Glossiness: 0
|
||||
- _GlossyReflections: 0
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.005
|
||||
- _QueueOffset: 0
|
||||
- _ReceiveShadows: 1
|
||||
- _Smoothness: 0.5
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _WorkflowMode: 1
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.1, g: 0.55, b: 1, a: 1}
|
||||
- _Color: {r: 0.09999997, g: 0.54999995, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &6023047813083343898
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||
version: 10
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14cdbac7bb1304e0ea4716a764261457
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: M_Projectile
|
||||
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap:
|
||||
RenderType: Opaque
|
||||
disabledShaderPasses:
|
||||
- MOTIONVECTORS
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BaseMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _SpecGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _AddPrecomputedVelocity: 0
|
||||
- _AlphaClip: 0
|
||||
- _AlphaToMask: 0
|
||||
- _Blend: 0
|
||||
- _BlendModePreserveSpecular: 1
|
||||
- _BumpScale: 1
|
||||
- _ClearCoatMask: 0
|
||||
- _ClearCoatSmoothness: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DetailAlbedoMapScale: 1
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EnvironmentReflections: 1
|
||||
- _GlossMapScale: 0
|
||||
- _Glossiness: 0
|
||||
- _GlossyReflections: 0
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.005
|
||||
- _QueueOffset: 0
|
||||
- _ReceiveShadows: 1
|
||||
- _Smoothness: 0.5
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _WorkflowMode: 1
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 1, g: 0.85, b: 0.1, a: 1}
|
||||
- _Color: {r: 1, g: 0.85, b: 0.09999997, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &8800532420623758028
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||
version: 10
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba23fa98368bb4a4997bfd08547c83ee
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -69,7 +69,7 @@ MeshRenderer:
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
|
||||
- {fileID: 2100000, guid: 14cdbac7bb1304e0ea4716a764261457, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
@@ -105,8 +105,10 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 766c44362be2b4fcaa872e6fb44fc42f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.PlayerAuthoring
|
||||
MoveSpeed: 6
|
||||
TurnRateDegreesPerSec: 720
|
||||
Character: {fileID: 11400000, guid: e675b529048144a41a2054c729180bce, type: 2}
|
||||
PrimaryAbility: {fileID: 11400000, guid: 013954d16c1ff4c1dad82495e38b4657, type: 2}
|
||||
FallbackMaxHealth: 100
|
||||
HitRadius: 0.6
|
||||
--- !u!114 &304484164735584996
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -144,5 +146,4 @@ MonoBehaviour:
|
||||
OptimizationMode: 0
|
||||
Importance: 1
|
||||
MaxSendRate: 0
|
||||
SingleWorldHostInterpolationSmoothing: 1
|
||||
prefabId:
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &8857056134628386430
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8708023721942265622}
|
||||
- component: {fileID: 892549877506778438}
|
||||
- component: {fileID: 2964537246306930844}
|
||||
- component: {fileID: 866206118258644914}
|
||||
- component: {fileID: 398845119511860568}
|
||||
- component: {fileID: 4884824795720831881}
|
||||
m_Layer: 0
|
||||
m_Name: Projectile
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &8708023721942265622
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8857056134628386430}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.35, y: 0.35, z: 0.35}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &892549877506778438
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8857056134628386430}
|
||||
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &2964537246306930844
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8857056134628386430}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: ba23fa98368bb4a4997bfd08547c83ee, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 1
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!114 &866206118258644914
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8857056134628386430}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1429bc3b3a1da44e4a11065be0733a8f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ProjectileAuthoring
|
||||
Speed: 25
|
||||
Damage: 20
|
||||
Range: 20
|
||||
--- !u!114 &398845119511860568
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8857056134628386430}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
|
||||
--- !u!114 &4884824795720831881
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8857056134628386430}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
|
||||
HasOwner: 1
|
||||
SupportAutoCommandTarget: 0
|
||||
TrackInterpolationDelay: 0
|
||||
GhostGroup: 0
|
||||
UsePreSerialization: 0
|
||||
UseSingleBaseline: 0
|
||||
RollbackPredictedSpawnedGhostState: 0
|
||||
RollbackPredictionOnStructuralChanges: 1
|
||||
DefaultGhostMode: 2
|
||||
SupportedGhostModes: 3
|
||||
OptimizationMode: 0
|
||||
Importance: 1
|
||||
MaxSendRate: 0
|
||||
prefabId:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 700fb9c3f7cf94830a0ee78c4e4f1290
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,147 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &6681740481886397972
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3040602402374479956}
|
||||
- component: {fileID: 9111625462373738953}
|
||||
- component: {fileID: 6113974662432286332}
|
||||
- component: {fileID: 5080357267889081991}
|
||||
- component: {fileID: 3549493811897787778}
|
||||
- component: {fileID: 1198698002335808414}
|
||||
m_Layer: 0
|
||||
m_Name: TrainingDummy
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3040602402374479956
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6681740481886397972}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 2, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &9111625462373738953
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6681740481886397972}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &6113974662432286332
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6681740481886397972}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: f8df9cd33fb974460a903e35a6fce3c9, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 1
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!114 &5080357267889081991
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6681740481886397972}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c32aebeb7bfbb464898dfee8e6e87e6c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.TrainingDummyAuthoring
|
||||
MaxHealth: 60
|
||||
HitRadius: 0.8
|
||||
--- !u!114 &3549493811897787778
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6681740481886397972}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
|
||||
--- !u!114 &1198698002335808414
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6681740481886397972}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
|
||||
HasOwner: 0
|
||||
SupportAutoCommandTarget: 0
|
||||
TrackInterpolationDelay: 0
|
||||
GhostGroup: 0
|
||||
UsePreSerialization: 0
|
||||
UseSingleBaseline: 0
|
||||
RollbackPredictedSpawnedGhostState: 0
|
||||
RollbackPredictionOnStructuralChanges: 1
|
||||
DefaultGhostMode: 0
|
||||
SupportedGhostModes: 3
|
||||
OptimizationMode: 0
|
||||
Importance: 1
|
||||
MaxSendRate: 0
|
||||
prefabId:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ee6032b77c444d07a1dc9dfbf5abd68
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,149 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &3885353946372160549
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3572766465862231365}
|
||||
- component: {fileID: 3909651526955663392}
|
||||
- component: {fileID: 3320445911748035220}
|
||||
- component: {fileID: 6410754845414010269}
|
||||
- component: {fileID: 9053853372340598254}
|
||||
- component: {fileID: 6834786618115927220}
|
||||
m_Layer: 0
|
||||
m_Name: UpgradePickup
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3572766465862231365
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.8, y: 0.8, z: 0.8}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &3909651526955663392
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &3320445911748035220
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 1
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!114 &6410754845414010269
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 07e6d40378fcb43c5be706ef96cb4bb2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.UpgradePickupAuthoring
|
||||
Target: 0
|
||||
Op: 0
|
||||
Value: 10
|
||||
HitRadius: 1.2
|
||||
--- !u!114 &9053853372340598254
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
|
||||
--- !u!114 &6834786618115927220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
|
||||
HasOwner: 0
|
||||
SupportAutoCommandTarget: 1
|
||||
TrackInterpolationDelay: 0
|
||||
GhostGroup: 0
|
||||
UsePreSerialization: 0
|
||||
UseSingleBaseline: 0
|
||||
RollbackPredictedSpawnedGhostState: 0
|
||||
RollbackPredictionOnStructuralChanges: 1
|
||||
DefaultGhostMode: 0
|
||||
SupportedGhostModes: 3
|
||||
OptimizationMode: 0
|
||||
Importance: 1
|
||||
MaxSendRate: 0
|
||||
prefabId:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7dd0078c7b1f4cf995ca3a4b1155569
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7329bc1c607064c54a087b67292b8ff5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Generic;
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Bakes the designer-authored ability + character definitions into a single AbilityDatabase blob
|
||||
/// singleton (immutable, shared, Burst-fast) plus a companion AbilityPrefabElement buffer holding the
|
||||
/// per-ability projectile ghost prefab entity refs (entity refs cannot live in a blob). Place ONE of
|
||||
/// these in the gameplay subscene; it streams identically into the client and server worlds (config,
|
||||
/// not replicated). DependsOn each definition so a value change in an SO re-bakes the blob.
|
||||
/// </summary>
|
||||
public class AbilityDatabaseAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("All ability definitions available in the game. Indexed at runtime by AbilityId.")]
|
||||
public List<AbilityDefinition> Abilities = new List<AbilityDefinition>();
|
||||
|
||||
[Tooltip("All character-stats definitions. Indexed at runtime by CharacterId.")]
|
||||
public List<CharacterStatsDefinition> Characters = new List<CharacterStatsDefinition>();
|
||||
|
||||
private class DatabaseBaker : Baker<AbilityDatabaseAuthoring>
|
||||
{
|
||||
public override void Bake(AbilityDatabaseAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(TransformUsageFlags.None);
|
||||
|
||||
int abilityCount = authoring.Abilities != null ? authoring.Abilities.Count : 0;
|
||||
int charCount = authoring.Characters != null ? authoring.Characters.Count : 0;
|
||||
|
||||
var builder = new BlobBuilder(Allocator.Temp);
|
||||
ref var root = ref builder.ConstructRoot<AbilityDatabaseBlob>();
|
||||
|
||||
var abilityArray = builder.Allocate(ref root.Abilities, abilityCount);
|
||||
for (int i = 0; i < abilityCount; i++)
|
||||
{
|
||||
var def = authoring.Abilities[i];
|
||||
if (def == null) { abilityArray[i] = default; continue; }
|
||||
DependsOn(def);
|
||||
abilityArray[i] = new AbilityDefBlob
|
||||
{
|
||||
Id = (byte)def.Id,
|
||||
Damage = def.Damage,
|
||||
ProjectileSpeed = def.ProjectileSpeed,
|
||||
Range = def.Range,
|
||||
AutoTargetRange = def.AutoTargetRange,
|
||||
AutoTargetConeRadians = math.radians(def.AutoTargetConeDegrees),
|
||||
CooldownTicks = def.CooldownTicks,
|
||||
Name = def.DisplayName,
|
||||
};
|
||||
}
|
||||
|
||||
var charArray = builder.Allocate(ref root.Characters, charCount);
|
||||
for (int i = 0; i < charCount; i++)
|
||||
{
|
||||
var def = authoring.Characters[i];
|
||||
if (def == null) { charArray[i] = default; continue; }
|
||||
DependsOn(def);
|
||||
charArray[i] = new CharacterStatsBlob
|
||||
{
|
||||
Id = (byte)def.Id,
|
||||
MoveSpeed = def.MoveSpeed,
|
||||
TurnRateRadiansPerSec = math.radians(def.TurnRateDegreesPerSec),
|
||||
MaxHealth = def.MaxHealth,
|
||||
Name = def.DisplayName,
|
||||
};
|
||||
}
|
||||
|
||||
var blob = builder.CreateBlobAssetReference<AbilityDatabaseBlob>(Allocator.Persistent);
|
||||
builder.Dispose();
|
||||
AddBlobAsset(ref blob, out _);
|
||||
AddComponent(entity, new AbilityDatabase { Value = blob });
|
||||
|
||||
// Companion entity-ref buffer: per-ability projectile ghost prefab (resolved via GetEntity).
|
||||
var prefabBuffer = AddBuffer<AbilityPrefabElement>(entity);
|
||||
for (int i = 0; i < abilityCount; i++)
|
||||
{
|
||||
var def = authoring.Abilities[i];
|
||||
if (def == null || def.ProjectilePrefab == null) continue;
|
||||
prefabBuffer.Add(new AbilityPrefabElement
|
||||
{
|
||||
Id = (byte)def.Id,
|
||||
Prefab = GetEntity(def.ProjectilePrefab, TransformUsageFlags.Dynamic),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f60b41d8ec8d4c24b4d4f54af919080
|
||||
@@ -0,0 +1,33 @@
|
||||
using ProjectM.Simulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Designer-facing definition of one ability. Numeric fields are baked into the AbilityDatabase blob
|
||||
/// (immutable, Burst-fast runtime config); the projectile prefab is baked into the companion
|
||||
/// AbilityPrefabElement buffer (entity refs cannot live inside a blob). UI fields (icon/description)
|
||||
/// are deliberately deferred to a later managed lookup keyed by id.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Project M/Ability Definition", fileName = "Ability_")]
|
||||
public class AbilityDefinition : ScriptableObject
|
||||
{
|
||||
public AbilityId Id = AbilityId.Primary;
|
||||
public string DisplayName = "Ability";
|
||||
|
||||
[Header("Combat")]
|
||||
[Min(0f)] public float Damage = 20f;
|
||||
[Min(0f)] public float ProjectileSpeed = 25f;
|
||||
[Min(0f)] public float Range = 20f;
|
||||
|
||||
[Header("Auto-target assist")]
|
||||
[Min(0f)] public float AutoTargetRange = 12f;
|
||||
[Min(0f)] public float AutoTargetConeDegrees = 35f;
|
||||
|
||||
[Header("Timing")]
|
||||
[Min(1)] public int CooldownTicks = 12;
|
||||
|
||||
[Header("Prefab (baked into the prefab buffer, not the blob)")]
|
||||
public GameObject ProjectilePrefab;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b20ca889a744e888c8c3b3b723ec69
|
||||
@@ -0,0 +1,21 @@
|
||||
using ProjectM.Simulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Designer-facing definition of a character's base stats (movement + survivability), baked into the
|
||||
/// AbilityDatabase blob and looked up at runtime by CharacterStatsRef. The single source of these
|
||||
/// values - PlayerAuthoring also seeds the player's starting Health from MaxHealth.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Project M/Character Stats Definition", fileName = "Character_")]
|
||||
public class CharacterStatsDefinition : ScriptableObject
|
||||
{
|
||||
public CharacterId Id = CharacterId.Default;
|
||||
public string DisplayName = "Character";
|
||||
|
||||
[Min(0f)] public float MoveSpeed = 6f;
|
||||
[Min(0f)] public float TurnRateDegreesPerSec = 720f;
|
||||
[Min(0f)] public float MaxHealth = 100f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c63201f1fd9f24efd9ccae3e20cd2364
|
||||
@@ -0,0 +1,38 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the projectile ghost prefab fired by the player's primary ability. Bakes the
|
||||
/// baked-once tunables (<see cref="Projectile.Speed"/>, <see cref="Projectile.Damage"/>,
|
||||
/// <see cref="Projectile.Range"/>) onto the entity; the replicated <c>Direction</c>/<c>SpawnId</c>
|
||||
/// and the integrated <c>DistanceTravelled</c> are left at their default 0 and written at spawn
|
||||
/// time by AbilityFireSystem. Ghost replication and <c>GhostOwner</c> are supplied by the
|
||||
/// GhostAuthoringComponent added on the same prefab GameObject (not added here, nor is Health).
|
||||
/// <c>GetEntity(TransformUsageFlags.Dynamic)</c> ensures a runtime-mutable LocalTransform exists.
|
||||
/// </summary>
|
||||
public class ProjectileAuthoring : MonoBehaviour
|
||||
{
|
||||
[Min(0f)] public float Speed = 25f;
|
||||
[Min(0f)] public float Damage = 20f;
|
||||
[Min(0f)] public float Range = 20f;
|
||||
|
||||
private class ProjectileBaker : Baker<ProjectileAuthoring>
|
||||
{
|
||||
public override void Bake(ProjectileAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
|
||||
// Direction / SpawnId / DistanceTravelled default to 0 — set at spawn by AbilityFireSystem.
|
||||
AddComponent(entity, new Projectile
|
||||
{
|
||||
Speed = authoring.Speed,
|
||||
Damage = authoring.Damage,
|
||||
Range = authoring.Range
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1429bc3b3a1da44e4a11065be0733a8f
|
||||
@@ -0,0 +1,34 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring placed once in the gameplay subscene. Bakes a <see cref="ProjectileSpawner"/>
|
||||
/// singleton holding the projectile ghost prefab entity, which the predicted AbilityFireSystem
|
||||
/// instantiates whenever a player fires. The spawner itself carries no transform (it is a pure
|
||||
/// data singleton) so it is baked with <c>TransformUsageFlags.None</c>, while the referenced
|
||||
/// prefab is baked with <c>TransformUsageFlags.Dynamic</c> so the spawned projectile has a
|
||||
/// runtime-mutable LocalTransform.
|
||||
/// </summary>
|
||||
public class ProjectileSpawnerAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("The projectile ghost prefab spawned when a player fires.")]
|
||||
public GameObject ProjectilePrefab;
|
||||
|
||||
private class ProjectileSpawnerBaker : Baker<ProjectileSpawnerAuthoring>
|
||||
{
|
||||
public override void Bake(ProjectileSpawnerAuthoring authoring)
|
||||
{
|
||||
// The spawner itself needs no transform; it is a data singleton.
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.None);
|
||||
|
||||
AddComponent(entity, new ProjectileSpawner
|
||||
{
|
||||
Prefab = GetEntity(authoring.ProjectilePrefab, TransformUsageFlags.Dynamic)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa1b7c054d5a043f2801cddf90567acf
|
||||
@@ -0,0 +1,41 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the training-dummy enemy prefab. Bakes a stationary, damageable auto-target
|
||||
/// candidate: <see cref="TrainingDummyTag"/> marks it for the ability auto-target cone,
|
||||
/// <see cref="Health"/> and <see cref="HitRadius"/> make it a valid projectile hit target, and a
|
||||
/// <see cref="DamageEvent"/> buffer receives server-authoritative hits. Dummies are NOT ghosts and
|
||||
/// carry no <c>GhostOwner</c>, so projectiles never treat them as the firing owner.
|
||||
/// <c>GetEntity(TransformUsageFlags.Dynamic)</c> ensures a runtime LocalTransform for hit tests
|
||||
/// and spawn placement.
|
||||
/// </summary>
|
||||
public class TrainingDummyAuthoring : MonoBehaviour
|
||||
{
|
||||
[Min(0f), Tooltip("Starting and maximum health for the dummy.")]
|
||||
public float MaxHealth = 60f;
|
||||
|
||||
[Min(0f), Tooltip("World-unit radius used by the projectile hit test.")]
|
||||
public float HitRadius = 0.8f;
|
||||
|
||||
private class TrainingDummyBaker : Baker<TrainingDummyAuthoring>
|
||||
{
|
||||
public override void Bake(TrainingDummyAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
|
||||
AddComponent<TrainingDummyTag>(entity);
|
||||
AddComponent(entity, new Health
|
||||
{
|
||||
Current = authoring.MaxHealth,
|
||||
Max = authoring.MaxHealth
|
||||
});
|
||||
AddComponent(entity, new HitRadius { Value = authoring.HitRadius });
|
||||
AddBuffer<DamageEvent>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c32aebeb7bfbb464898dfee8e6e87e6c
|
||||
@@ -0,0 +1,48 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the baked <see cref="TrainingDummySpawner"/> singleton. Place this on a single
|
||||
/// GameObject in the gameplay subscene; at runtime the server-only
|
||||
/// <c>TrainingDummySpawnSystem</c> reads the singleton, instantiates <see cref="Count"/> dummies
|
||||
/// laid out along +X from <see cref="Origin"/> at <see cref="Spacing"/> intervals, then destroys
|
||||
/// the singleton so it fires exactly once. The entity itself carries no transform
|
||||
/// (<c>TransformUsageFlags.None</c>); only the referenced <see cref="DummyPrefab"/> needs a
|
||||
/// runtime-mutable LocalTransform (<c>TransformUsageFlags.Dynamic</c>).
|
||||
/// </summary>
|
||||
public class TrainingDummySpawnerAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Training dummy prefab to instantiate. Must carry TrainingDummyAuthoring.")]
|
||||
public GameObject DummyPrefab;
|
||||
|
||||
[Min(0)]
|
||||
[Tooltip("How many dummies to spawn.")]
|
||||
public int Count = 3;
|
||||
|
||||
[Min(0f)]
|
||||
[Tooltip("World-unit spacing between consecutive dummies along +X.")]
|
||||
public float Spacing = 3f;
|
||||
|
||||
[Tooltip("World-space position of the first dummy.")]
|
||||
public Vector3 Origin = new Vector3(0, 0, 8);
|
||||
|
||||
private class TrainingDummySpawnerBaker : Baker<TrainingDummySpawnerAuthoring>
|
||||
{
|
||||
public override void Bake(TrainingDummySpawnerAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.None);
|
||||
|
||||
AddComponent(entity, new TrainingDummySpawner
|
||||
{
|
||||
Prefab = GetEntity(authoring.DummyPrefab, TransformUsageFlags.Dynamic),
|
||||
Count = authoring.Count,
|
||||
Spacing = authoring.Spacing,
|
||||
Origin = authoring.Origin
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51cc543c146d84239ba6dc219221df18
|
||||
@@ -0,0 +1,43 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for an upgrade pickup ghost prefab: a world object that grants one stat modifier to the
|
||||
/// first player that overlaps it (server-authoritative, applied by <c>UpgradePickupSystem</c>) and
|
||||
/// then despawns. Bake the prefab as an interpolated ghost (add a GhostAuthoringComponent) so clients
|
||||
/// see it appear and despawn. <c>GetEntity(TransformUsageFlags.Dynamic)</c> gives it a world transform.
|
||||
/// </summary>
|
||||
public class UpgradePickupAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Which stat the granted modifier targets.")]
|
||||
public StatTarget Target = StatTarget.Damage;
|
||||
|
||||
[Tooltip("How the granted modifier combines.")]
|
||||
public ModOp Op = ModOp.Flat;
|
||||
|
||||
[Tooltip("Modifier magnitude: flat amount, or fractional percent (0.1 = +10%).")]
|
||||
public float Value = 10f;
|
||||
|
||||
[Tooltip("Overlap radius (world units) for the player pickup test.")]
|
||||
[Min(0f)] public float HitRadius = 1f;
|
||||
|
||||
private class UpgradePickupBaker : Baker<UpgradePickupAuthoring>
|
||||
{
|
||||
public override void Bake(UpgradePickupAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
AddComponent(entity, new UpgradePickup
|
||||
{
|
||||
Target = (byte)authoring.Target,
|
||||
Op = (byte)authoring.Op,
|
||||
Value = authoring.Value,
|
||||
SourceId = 0u,
|
||||
});
|
||||
AddComponent(entity, new HitRadius { Value = authoring.HitRadius });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07e6d40378fcb43c5be706ef96cb4bb2
|
||||
@@ -0,0 +1,48 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the baked <see cref="UpgradePickupSpawner"/> singleton (mirrors
|
||||
/// TrainingDummySpawnerAuthoring). Place on a single GameObject in the gameplay subscene; the
|
||||
/// server-only <c>UpgradePickupSpawnSystem</c> reads it, spawns <see cref="Count"/> pickups along +X
|
||||
/// from <see cref="Origin"/> at <see cref="Spacing"/> intervals, then destroys the singleton so it
|
||||
/// fires exactly once. The entity carries no transform; only the prefab needs a runtime transform.
|
||||
/// </summary>
|
||||
public class UpgradePickupSpawnerAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Upgrade pickup prefab to instantiate. Must carry UpgradePickupAuthoring.")]
|
||||
public GameObject PickupPrefab;
|
||||
|
||||
[Min(0)]
|
||||
[Tooltip("How many pickups to spawn.")]
|
||||
public int Count = 2;
|
||||
|
||||
[Min(0f)]
|
||||
[Tooltip("World-unit spacing between consecutive pickups along +X.")]
|
||||
public float Spacing = 3f;
|
||||
|
||||
[Tooltip("World-space position of the first pickup.")]
|
||||
public Vector3 Origin = new Vector3(-4f, 0f, 6f);
|
||||
|
||||
private class UpgradePickupSpawnerBaker : Baker<UpgradePickupSpawnerAuthoring>
|
||||
{
|
||||
public override void Bake(UpgradePickupSpawnerAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.None);
|
||||
|
||||
AddComponent(entity, new UpgradePickupSpawner
|
||||
{
|
||||
Prefab = authoring.PickupPrefab != null
|
||||
? GetEntity(authoring.PickupPrefab, TransformUsageFlags.Dynamic)
|
||||
: Entity.Null,
|
||||
Count = authoring.Count,
|
||||
Spacing = authoring.Spacing,
|
||||
Origin = authoring.Origin,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e367eb55e2c2248f18be10d6c3c9ad67
|
||||
@@ -1,21 +1,32 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the player ghost prefab. Bakes the gameplay components onto the entity and
|
||||
/// exposes movement tunables for designers. Ghost replication, <c>GhostOwner</c> and
|
||||
/// AutoCommandTarget are supplied by the GhostAuthoringComponent added on the same prefab
|
||||
/// GameObject. <c>GetEntity(TransformUsageFlags.Dynamic)</c> ensures a runtime-mutable
|
||||
/// LocalTransform exists.
|
||||
/// Authoring for the player ghost prefab. As of M3 the numeric tunables live in data
|
||||
/// (<see cref="CharacterStatsDefinition"/> / <see cref="AbilityDefinition"/> ScriptableObjects);
|
||||
/// this authoring only selects which definitions the player uses and bakes the light id refs, the
|
||||
/// (empty) replicated modifier buffer, and the zeroed effective-stat components that
|
||||
/// StatRecomputeSystem fills each predicted tick. Health is seeded from the character definition's
|
||||
/// MaxHealth (single source). Ghost replication, <c>GhostOwner</c> and AutoCommandTarget come from
|
||||
/// the GhostAuthoringComponent on the same prefab GameObject; <c>GetEntity(TransformUsageFlags.Dynamic)</c>
|
||||
/// ensures a runtime-mutable LocalTransform exists.
|
||||
/// </summary>
|
||||
public class PlayerAuthoring : MonoBehaviour
|
||||
{
|
||||
[Min(0f)] public float MoveSpeed = 6f;
|
||||
[Min(0f)] public float TurnRateDegreesPerSec = 720f;
|
||||
[Tooltip("Character-stats definition (move speed, turn rate, max health). Single source of those values.")]
|
||||
public CharacterStatsDefinition Character;
|
||||
|
||||
[Tooltip("Ability definition occupying the player's primary slot.")]
|
||||
public AbilityDefinition PrimaryAbility;
|
||||
|
||||
[Header("Fallbacks (used only if a definition above is unassigned)")]
|
||||
[Min(0f)] public float FallbackMaxHealth = 100f;
|
||||
|
||||
/// <summary>Projectile hit-test radius for the player as a damageable target, in world units.</summary>
|
||||
[Min(0f)] public float HitRadius = 0.6f;
|
||||
|
||||
private class PlayerBaker : Baker<PlayerAuthoring>
|
||||
{
|
||||
@@ -23,14 +34,38 @@ namespace ProjectM.Authoring
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
|
||||
// Re-bake when a referenced definition's serialized values change.
|
||||
if (authoring.Character != null) DependsOn(authoring.Character);
|
||||
if (authoring.PrimaryAbility != null) DependsOn(authoring.PrimaryAbility);
|
||||
|
||||
byte characterId = authoring.Character != null
|
||||
? (byte)authoring.Character.Id : (byte)CharacterId.Default;
|
||||
byte abilityId = authoring.PrimaryAbility != null
|
||||
? (byte)authoring.PrimaryAbility.Id : (byte)AbilityId.Primary;
|
||||
float maxHealth = authoring.Character != null
|
||||
? authoring.Character.MaxHealth : authoring.FallbackMaxHealth;
|
||||
|
||||
AddComponent<PlayerTag>(entity);
|
||||
AddComponent(entity, new PlayerMoveStats
|
||||
{
|
||||
MoveSpeed = authoring.MoveSpeed,
|
||||
TurnRateRadiansPerSec = math.radians(authoring.TurnRateDegreesPerSec)
|
||||
});
|
||||
AddComponent<PlayerFacing>(entity);
|
||||
AddComponent<PlayerInput>(entity);
|
||||
|
||||
// Data-driven stat refs (replace M2's inlined PlayerMoveStats / AbilityStats values).
|
||||
AddComponent(entity, new CharacterStatsRef { Id = characterId });
|
||||
AddComponent(entity, new AbilityRef { Id = abilityId });
|
||||
|
||||
// Effective stats: zeroed at bake, recomputed every predicted tick by StatRecomputeSystem.
|
||||
AddComponent(entity, new EffectiveAbilityStats());
|
||||
AddComponent(entity, new EffectiveCharacterStats());
|
||||
|
||||
// Empty replicated modifier stack (grown by upgrades/pickups/debug hook, server-authoritative).
|
||||
AddBuffer<StatModifier>(entity);
|
||||
|
||||
// Combat: server-authoritative health (Current replicated for display), the player's
|
||||
// damageable hit radius, predicted cooldown state, and the per-tick damage inbox.
|
||||
AddComponent(entity, new Health { Current = maxHealth, Max = maxHealth });
|
||||
AddComponent(entity, new HitRadius { Value = authoring.HitRadius });
|
||||
AddComponent<AbilityCooldown>(entity);
|
||||
AddBuffer<DamageEvent>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 626396bc0d4204603abfd345e6e4f21d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
using Unity.NetCode.LowLevel;
|
||||
|
||||
namespace ProjectM.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-only predicted-spawn classifier for projectiles. When a predicted client fires, it
|
||||
/// locally spawns a predicted projectile ghost; later the server's authoritative spawn arrives in
|
||||
/// the <see cref="GhostSpawnQueue"/>. This system pairs the incoming server ghost with the matching
|
||||
/// locally predicted entity so netcode reconciles them instead of double-spawning. The match key is
|
||||
/// <see cref="Projectile.SpawnId"/> — a deterministic <c>(ownerNetId << 16) | absoluteFireCount</c>
|
||||
/// value computed identically on client and server, replicated as a <c>[GhostField]</c> so it is
|
||||
/// present in snapshot history and readable here via <see cref="SnapshotDataBufferComponentLookup"/>.
|
||||
/// Mirrors the official Netcode HelloNetcode 02_PredictedSpawning GrenadeClassificationSystem,
|
||||
/// with GrenadeData→<see cref="Projectile"/> and GrenadeSpawner→<see cref="ProjectileSpawner"/>.
|
||||
/// Runs after the built-in <see cref="GhostSpawnClassificationSystem"/> (so any owner-predicted
|
||||
/// default classification has already had a pass) and before the OrderLast
|
||||
/// <c>DefaultGhostSpawnClassificationSystem</c> (so entries this system does NOT match still fall
|
||||
/// through to the spawn-tick-window fallback).
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||
[UpdateInGroup(typeof(GhostSpawnClassificationSystemGroup))]
|
||||
[UpdateAfter(typeof(GhostSpawnClassificationSystem))]
|
||||
[CreateAfter(typeof(GhostCollectionSystem))]
|
||||
[CreateAfter(typeof(GhostReceiveSystem))]
|
||||
// NOTE: intentionally NOT [BurstCompile]d. The cross-assembly generic
|
||||
// SnapshotDataBufferComponentLookup.TryGetComponentDataFromSnapshotHistory<T>() trips a Burst
|
||||
// internal compiler error (type-hash resolution) on Netcode 1.13.2. Classification only runs when
|
||||
// ghost spawns are received (a cold path, not the prediction loop), so a managed job is fine here.
|
||||
public partial struct ProjectileClassificationSystem : ISystem
|
||||
{
|
||||
SnapshotDataLookupHelper m_SnapshotDataLookupHelper;
|
||||
BufferLookup<PredictedGhostSpawn> m_PredictedGhostSpawnLookup;
|
||||
ComponentLookup<Projectile> m_ProjectileLookup;
|
||||
|
||||
/// <summary>
|
||||
/// Resolved once in <see cref="OnUpdate"/>: the ghost-collection index of our projectile prefab.
|
||||
/// -1 until the <see cref="GhostCollectionPrefab"/> buffer has been populated and scanned.
|
||||
/// </summary>
|
||||
int m_GhostType;
|
||||
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
// Match the built-in GhostSpawnClassificationSystem / DefaultGhostSpawnClassificationSystem:
|
||||
// in a single-world host (a world flagged GameClient AND GameServer) there is no real client
|
||||
// snapshot history to classify against, so the package's classification systems disable
|
||||
// themselves. We do the same so we never run alone in that scenario after the system we
|
||||
// UpdateAfter has switched itself off. (For Project M's standard separate ClientWorld +
|
||||
// ServerWorld over IPC, IsHost() is false and this guard is a no-op.)
|
||||
if (state.WorldUnmanaged.IsHost())
|
||||
{
|
||||
state.Enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the snapshot lookup helper from the two collection singletons. CreateAfter on
|
||||
// GhostCollectionSystem + GhostReceiveSystem guarantees both singletons exist by now.
|
||||
m_SnapshotDataLookupHelper = new SnapshotDataLookupHelper(
|
||||
ref state,
|
||||
SystemAPI.GetSingletonEntity<GhostCollection>(),
|
||||
SystemAPI.GetSingletonEntity<SpawnedGhostEntityMap>());
|
||||
|
||||
m_PredictedGhostSpawnLookup = state.GetBufferLookup<PredictedGhostSpawn>(true);
|
||||
m_ProjectileLookup = state.GetComponentLookup<Projectile>(true);
|
||||
|
||||
state.RequireForUpdate<GhostSpawnQueue>();
|
||||
state.RequireForUpdate<PredictedGhostSpawnList>();
|
||||
state.RequireForUpdate<NetworkId>();
|
||||
state.RequireForUpdate<ProjectileSpawner>();
|
||||
|
||||
m_GhostType = -1;
|
||||
}
|
||||
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Resolve our projectile ghost-type index once by scanning the ghost-collection prefab
|
||||
// buffer for the spawner's prefab entity. The collection is populated only after the ghost
|
||||
// prefabs have loaded, so retry each tick until found.
|
||||
if (m_GhostType == -1)
|
||||
{
|
||||
var projectilePrefab = SystemAPI.GetSingleton<ProjectileSpawner>().Prefab;
|
||||
var ghostCollection = SystemAPI.GetSingletonEntity<GhostCollection>();
|
||||
var prefabs = SystemAPI.GetBuffer<GhostCollectionPrefab>(ghostCollection);
|
||||
for (int i = 0; i < prefabs.Length; ++i)
|
||||
{
|
||||
if (prefabs[i].GhostPrefab == projectilePrefab)
|
||||
{
|
||||
m_GhostType = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_GhostType == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
m_SnapshotDataLookupHelper.Update(ref state);
|
||||
m_PredictedGhostSpawnLookup.Update(ref state);
|
||||
m_ProjectileLookup.Update(ref state);
|
||||
|
||||
// SystemAPI is a system-context-only facade and cannot be used inside the IJobEntity; resolve
|
||||
// the predicted-spawn-list singleton entity here and pass it into the job (this mirrors the
|
||||
// built-in DefaultGhostSpawnClassificationJob.spawnListEntity pattern).
|
||||
var classificationJob = new ProjectileClassificationJob
|
||||
{
|
||||
GhostType = m_GhostType,
|
||||
SnapshotDataLookup = m_SnapshotDataLookupHelper.CreateSnapshotBufferLookup(),
|
||||
PredictedSpawnListEntity = SystemAPI.GetSingletonEntity<PredictedGhostSpawnList>(),
|
||||
PredictedGhostSpawnLookup = m_PredictedGhostSpawnLookup,
|
||||
ProjectileLookup = m_ProjectileLookup,
|
||||
};
|
||||
state.Dependency = classificationJob.Schedule(state.Dependency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For each newly received server spawn in a <see cref="GhostSpawnQueue"/>, attempts to find a
|
||||
/// locally predicted projectile with the same <see cref="Projectile.SpawnId"/> read out of
|
||||
/// snapshot history. On a match it points the queued spawn at the predicted entity (so netcode
|
||||
/// adopts it instead of instantiating a duplicate), marks the entry classified, and removes that
|
||||
/// predicted entry from the list. Entries this system does NOT match are left untouched so the
|
||||
/// OrderLast <c>DefaultGhostSpawnClassificationSystem</c> fallback can still try the spawn-tick
|
||||
/// window match.
|
||||
/// </summary>
|
||||
[WithAll(typeof(GhostSpawnQueue))]
|
||||
partial struct ProjectileClassificationJob : IJobEntity
|
||||
{
|
||||
public int GhostType;
|
||||
public SnapshotDataBufferComponentLookup SnapshotDataLookup;
|
||||
|
||||
// Resolved in OnUpdate (SystemAPI is unavailable inside a job). A single Entity field, so it
|
||||
// is NOT marked [ReadOnly].
|
||||
public Entity PredictedSpawnListEntity;
|
||||
|
||||
[ReadOnly] public BufferLookup<PredictedGhostSpawn> PredictedGhostSpawnLookup;
|
||||
[ReadOnly] public ComponentLookup<Projectile> ProjectileLookup;
|
||||
|
||||
// 'data' is taken by value (NOT 'in') because TryGetComponentDataFromSnapshotHistory needs a
|
||||
// mutable 'ref DynamicBuffer<SnapshotDataBuffer>'. The built-in GhostSpawnClassification uses
|
||||
// 'in' only because it never calls that ref overload — do not copy that here.
|
||||
public void Execute(DynamicBuffer<GhostSpawnBuffer> newSpawns, DynamicBuffer<SnapshotDataBuffer> data)
|
||||
{
|
||||
var predictedSpawnList = PredictedGhostSpawnLookup[PredictedSpawnListEntity];
|
||||
|
||||
for (int i = 0; i < newSpawns.Length; ++i)
|
||||
{
|
||||
ref var newSpawn = ref newSpawns.ElementAt(i);
|
||||
|
||||
// Only classify our own ghost type, and only predicted spawns that have not already
|
||||
// been matched/claimed (PredictedSpawnEntity == Null && !HasClassifiedPredictedSpawn)
|
||||
// — leave everything else to the defaults.
|
||||
if (newSpawn.GhostType != GhostType)
|
||||
continue;
|
||||
if (newSpawn.SpawnType != GhostSpawnBuffer.Type.Predicted ||
|
||||
newSpawn.HasClassifiedPredictedSpawn ||
|
||||
newSpawn.PredictedSpawnEntity != Entity.Null)
|
||||
continue;
|
||||
|
||||
if (!SnapshotDataLookup.TryGetComponentDataFromSnapshotHistory(
|
||||
newSpawn.GhostType, data, out Projectile incoming, i))
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < predictedSpawnList.Length; ++j)
|
||||
{
|
||||
if (predictedSpawnList[j].ghostType != GhostType)
|
||||
continue;
|
||||
|
||||
var predictedEntity = predictedSpawnList[j].entity;
|
||||
if (incoming.SpawnId == ProjectileLookup[predictedEntity].SpawnId)
|
||||
{
|
||||
// Claim the decision ONLY on a real match, so non-matches still fall through
|
||||
// to the OrderLast default classifier (matches the official sample).
|
||||
newSpawn.PredictedSpawnEntity = predictedEntity;
|
||||
newSpawn.HasClassifiedPredictedSpawn = true;
|
||||
predictedSpawnList.RemoveAtSwapBack(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97e6942342fcf4de5b0ede9f2a33ed5b
|
||||
@@ -0,0 +1,85 @@
|
||||
#if UNITY_EDITOR
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// EDITOR-ONLY validation hook for driving the local player's <see cref="PlayerInput"/> without a
|
||||
/// real input device or a focused Game view. The Unity Input System ignores injected/real device
|
||||
/// input while the Game view is unfocused, which makes headless (MCP <c>execute_code</c>) or
|
||||
/// automated fire/move validation impossible through <see cref="PlayerInputGatherSystem"/> alone.
|
||||
/// <para>
|
||||
/// This system runs in <see cref="GhostInputSystemGroup"/> immediately AFTER the real gather and,
|
||||
/// when <see cref="Active"/> is set, overwrites the locally-owned player's input from static fields
|
||||
/// you can poke from a debugger / <c>execute_code</c> / an editor button. Because it writes the same
|
||||
/// <see cref="PlayerInput"/> the gather does, it drives the authentic command → prediction →
|
||||
/// AbilityFireSystem pipeline (not a shortcut), so it validates the real fire/move/auto-target path.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Entirely wrapped in <c>#if UNITY_EDITOR</c>: it does not exist in player builds. Pair with
|
||||
/// <c>Application.runInBackground = true</c> so the unfocused editor keeps ticking. Usage from
|
||||
/// <c>execute_code</c>: <c>ProjectM.Client.DebugInputInjectionSystem.Fire();</c> (one shot),
|
||||
/// <c>...SetMove(0f, 1f);</c> (hold a move heading), <c>...SetAim(1f, 0f);</c>, <c>...Stop();</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[UpdateInGroup(typeof(GhostInputSystemGroup))]
|
||||
[UpdateAfter(typeof(PlayerInputGatherSystem))]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||
public partial class DebugInputInjectionSystem : SystemBase
|
||||
{
|
||||
/// <summary>While true, this system overrides the local player's gathered input each frame.</summary>
|
||||
public static bool Active;
|
||||
|
||||
/// <summary>Movement heading applied to PlayerInput.Move while <see cref="Active"/>.</summary>
|
||||
public static float2 Move;
|
||||
|
||||
/// <summary>Aim vector applied to PlayerInput.Aim while <see cref="Active"/> (zero = face movement).</summary>
|
||||
public static float2 Aim;
|
||||
|
||||
/// <summary>Frames remaining to hold the Fire event. Each held frame raises Fire.Set(); holding
|
||||
/// across several frames spans multiple network ticks so the one-shot event reliably reaches the
|
||||
/// command buffer (a single-frame pulse can be lost across the frame→tick boundary). 0 = idle.</summary>
|
||||
public static int FireFrames;
|
||||
|
||||
/// <summary>Convenience: hold Fire for the next <paramref name="frames"/> frames (also enables
|
||||
/// override). The ability cooldown still gates how many shots actually result.</summary>
|
||||
public static void Fire(int frames = 10) { Active = true; FireFrames = math.max(FireFrames, frames); }
|
||||
|
||||
/// <summary>Convenience: hold a planar move heading (also enables override).</summary>
|
||||
public static void SetMove(float x, float z) { Active = true; Move = new float2(x, z); }
|
||||
|
||||
/// <summary>Convenience: hold an aim direction (also enables override).</summary>
|
||||
public static void SetAim(float x, float z) { Active = true; Aim = new float2(x, z); }
|
||||
|
||||
/// <summary>Convenience: stop overriding and clear all injected input.</summary>
|
||||
public static void Stop() { Active = false; Move = default; Aim = default; FireFrames = 0; }
|
||||
|
||||
protected override void OnCreate()
|
||||
{
|
||||
RequireForUpdate<PlayerInput>();
|
||||
}
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
if (!Active)
|
||||
return;
|
||||
|
||||
bool fire = FireFrames > 0;
|
||||
if (FireFrames > 0) FireFrames--;
|
||||
float2 move = Move;
|
||||
float2 aim = Aim;
|
||||
|
||||
foreach (var input in SystemAPI.Query<RefRW<PlayerInput>>().WithAll<GhostOwnerIsLocal>())
|
||||
{
|
||||
input.ValueRW.Move = move;
|
||||
input.ValueRW.Aim = aim;
|
||||
if (fire)
|
||||
input.ValueRW.Fire.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51213eca1bfe84a16837d5755c334101
|
||||
@@ -6,62 +6,73 @@ using Unity.NetCode;
|
||||
namespace ProjectM.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-only twin-stick input gather. Samples the Input System once per frame (WASD /
|
||||
/// left-stick -> Move, right-stick -> Aim) and writes <see cref="PlayerInput"/> on the
|
||||
/// locally-owned player ghost (filtered to <see cref="GhostOwnerIsLocal"/>). Runs in
|
||||
/// <see cref="GhostInputSystemGroup"/> — NOT the prediction loop — so devices are read once per
|
||||
/// frame, never re-read during rollback. Implemented as a non-Burst <see cref="ISystem"/>
|
||||
/// because it reads the managed Input System.
|
||||
/// Client-only twin-stick input gather. Samples the new Input System action map (the generated
|
||||
/// <c>ProjectMInput</c> wrapper over <c>Assets/Settings/Project M Input.inputactions</c>) once per
|
||||
/// frame and writes <see cref="PlayerInput"/> on the locally-owned player ghost (filtered to
|
||||
/// <see cref="GhostOwnerIsLocal"/>). Runs in <see cref="GhostInputSystemGroup"/> — NOT the
|
||||
/// prediction loop — so devices are read once per frame, never re-read during rollback.
|
||||
/// <para>
|
||||
/// NOTE: the Input System device types are fully qualified rather than imported via
|
||||
/// <c>using UnityEngine.InputSystem;</c> on purpose — that namespace also defines a
|
||||
/// <c>PlayerInput</c> type which would collide with <see cref="ProjectM.Simulation.PlayerInput"/>
|
||||
/// and make the Entities source generator bind <c>RefRW<PlayerInput></c> to the managed
|
||||
/// class (a spurious CS8377 "must be unmanaged").
|
||||
/// Implemented as a managed <see cref="SystemBase"/> (not a Burst <c>ISystem</c>) because it holds
|
||||
/// and reads the managed Input System wrapper. Fire is an <see cref="InputEvent"/>: the event field
|
||||
/// is reset each frame and raised via <c>Set()</c> on the press edge, so a single click fires
|
||||
/// exactly once; netcode accumulates the absolute <c>Count</c> into the command buffer across the
|
||||
/// frame→tick boundary (read back in <c>AbilityFireSystem</c> as the predicted-spawn key).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// NOTE: Input System types are fully qualified (e.g. <c>UnityEngine.Vector2</c>) and
|
||||
/// <c>using UnityEngine.InputSystem;</c> is intentionally omitted — that namespace defines a
|
||||
/// <c>PlayerInput</c> type that collides with <see cref="ProjectM.Simulation.PlayerInput"/> and
|
||||
/// makes the Entities generator bind <c>RefRW<PlayerInput></c> to the managed class (a
|
||||
/// spurious CS8377). The generated <c>ProjectMInput</c> wrapper lives in this assembly.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[UpdateInGroup(typeof(GhostInputSystemGroup))]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||
public partial struct PlayerInputGatherSystem : ISystem
|
||||
public partial class PlayerInputGatherSystem : SystemBase
|
||||
{
|
||||
public void OnCreate(ref SystemState state)
|
||||
private ProjectMInput _controls;
|
||||
|
||||
protected override void OnCreate()
|
||||
{
|
||||
state.RequireForUpdate<PlayerInput>();
|
||||
RequireForUpdate<PlayerInput>();
|
||||
_controls = new ProjectMInput();
|
||||
_controls.Gameplay.Enable();
|
||||
}
|
||||
|
||||
public void OnUpdate(ref SystemState state)
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
float2 move = float2.zero;
|
||||
float2 aim = float2.zero;
|
||||
|
||||
var keyboard = UnityEngine.InputSystem.Keyboard.current;
|
||||
if (keyboard != null)
|
||||
if (_controls != null)
|
||||
{
|
||||
if (keyboard.wKey.isPressed) move.y += 1f;
|
||||
if (keyboard.sKey.isPressed) move.y -= 1f;
|
||||
if (keyboard.dKey.isPressed) move.x += 1f;
|
||||
if (keyboard.aKey.isPressed) move.x -= 1f;
|
||||
_controls.Gameplay.Disable();
|
||||
_controls.Dispose();
|
||||
_controls = null;
|
||||
}
|
||||
}
|
||||
|
||||
var gamepad = UnityEngine.InputSystem.Gamepad.current;
|
||||
if (gamepad != null)
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
float2 leftStick = gamepad.leftStick.ReadValue();
|
||||
if (math.lengthsq(leftStick) > math.lengthsq(move))
|
||||
move = leftStick;
|
||||
var gameplay = _controls.Gameplay;
|
||||
|
||||
aim = gamepad.rightStick.ReadValue();
|
||||
}
|
||||
float2 move = (float2)gameplay.Move.ReadValue<UnityEngine.Vector2>();
|
||||
float2 aim = (float2)gameplay.Aim.ReadValue<UnityEngine.Vector2>();
|
||||
|
||||
// Right-stick deadzone: a resting stick yields zero Aim so PlayerAimSystem falls back to
|
||||
// the movement heading (controller-first directional aim).
|
||||
// Right-stick deadzone: a resting stick yields zero Aim so PlayerAimSystem falls back to the
|
||||
// movement heading (controller-first directional aim).
|
||||
if (math.lengthsq(aim) < 0.04f)
|
||||
aim = float2.zero;
|
||||
|
||||
bool firePressed = gameplay.Fire.WasPressedThisFrame();
|
||||
|
||||
foreach (var input in SystemAPI.Query<RefRW<PlayerInput>>().WithAll<GhostOwnerIsLocal>())
|
||||
{
|
||||
input.ValueRW.Move = move;
|
||||
input.ValueRW.Aim = aim;
|
||||
|
||||
// Reset the per-frame event, then raise it on the press edge. Netcode latches the
|
||||
// absolute Count into the command buffer; AbilityFireSystem reads it as the SpawnId key.
|
||||
input.ValueRW.Fire = default;
|
||||
if (firePressed)
|
||||
input.ValueRW.Fire.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator
|
||||
// version 1.19.0
|
||||
// from Assets/Settings/Project M Input.inputactions
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides programmatic access to <see cref="InputActionAsset" />, <see cref="InputActionMap" />, <see cref="InputAction" /> and <see cref="InputControlScheme" /> instances defined in asset "Assets/Settings/Project M Input.inputactions".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is source generated and any manual edits will be discarded if the associated asset is reimported or modified.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using namespace UnityEngine;
|
||||
/// using UnityEngine.InputSystem;
|
||||
///
|
||||
/// // Example of using an InputActionMap named "Player" from a UnityEngine.MonoBehaviour implementing callback interface.
|
||||
/// public class Example : MonoBehaviour, MyActions.IPlayerActions
|
||||
/// {
|
||||
/// private MyActions_Actions m_Actions; // Source code representation of asset.
|
||||
/// private MyActions_Actions.PlayerActions m_Player; // Source code representation of action map.
|
||||
///
|
||||
/// void Awake()
|
||||
/// {
|
||||
/// m_Actions = new MyActions_Actions(); // Create asset object.
|
||||
/// m_Player = m_Actions.Player; // Extract action map object.
|
||||
/// m_Player.AddCallbacks(this); // Register callback interface IPlayerActions.
|
||||
/// }
|
||||
///
|
||||
/// void OnDestroy()
|
||||
/// {
|
||||
/// m_Actions.Dispose(); // Destroy asset object.
|
||||
/// }
|
||||
///
|
||||
/// void OnEnable()
|
||||
/// {
|
||||
/// m_Player.Enable(); // Enable all actions within map.
|
||||
/// }
|
||||
///
|
||||
/// void OnDisable()
|
||||
/// {
|
||||
/// m_Player.Disable(); // Disable all actions within map.
|
||||
/// }
|
||||
///
|
||||
/// #region Interface implementation of MyActions.IPlayerActions
|
||||
///
|
||||
/// // Invoked when "Move" action is either started, performed or canceled.
|
||||
/// public void OnMove(InputAction.CallbackContext context)
|
||||
/// {
|
||||
/// Debug.Log($"OnMove: {context.ReadValue<Vector2>()}");
|
||||
/// }
|
||||
///
|
||||
/// // Invoked when "Attack" action is either started, performed or canceled.
|
||||
/// public void OnAttack(InputAction.CallbackContext context)
|
||||
/// {
|
||||
/// Debug.Log($"OnAttack: {context.ReadValue<float>()}");
|
||||
/// }
|
||||
///
|
||||
/// #endregion
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public partial class @ProjectMInput: IInputActionCollection2, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the underlying asset instance.
|
||||
/// </summary>
|
||||
public InputActionAsset asset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance.
|
||||
/// </summary>
|
||||
public @ProjectMInput()
|
||||
{
|
||||
asset = InputActionAsset.FromJson(@"{
|
||||
""version"": 1,
|
||||
""name"": ""Project M Input"",
|
||||
""maps"": [
|
||||
{
|
||||
""name"": ""Gameplay"",
|
||||
""id"": ""69723709-3120-49dd-bbf0-91ac92ad8582"",
|
||||
""actions"": [
|
||||
{
|
||||
""name"": ""Move"",
|
||||
""type"": ""Value"",
|
||||
""id"": ""2982f936-538e-4a37-a100-8eb7a2a88e4b"",
|
||||
""expectedControlType"": ""Vector2"",
|
||||
""processors"": """",
|
||||
""interactions"": """",
|
||||
""initialStateCheck"": true
|
||||
},
|
||||
{
|
||||
""name"": ""Aim"",
|
||||
""type"": ""Value"",
|
||||
""id"": ""a1b2c3d4-0a1m-4a1m-8a1m-000000000001"",
|
||||
""expectedControlType"": ""Vector2"",
|
||||
""processors"": """",
|
||||
""interactions"": """",
|
||||
""initialStateCheck"": true
|
||||
},
|
||||
{
|
||||
""name"": ""Fire"",
|
||||
""type"": ""Button"",
|
||||
""id"": ""a1b2c3d4-0f1r-4f1r-8f1r-000000000002"",
|
||||
""expectedControlType"": ""Button"",
|
||||
""processors"": """",
|
||||
""interactions"": """",
|
||||
""initialStateCheck"": false
|
||||
}
|
||||
],
|
||||
""bindings"": [
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""c3b1e700-eea3-426c-863d-36403d537af3"",
|
||||
""path"": ""<Gamepad>/leftStick"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Gamepad"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": ""WASD"",
|
||||
""id"": ""cc5f8773-3f87-4e89-926e-2835221cc71b"",
|
||||
""path"": ""Dpad"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": """",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": true,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": ""up"",
|
||||
""id"": ""cc98fd24-5e37-4b4a-8fdc-9a1c21bc1e09"",
|
||||
""path"": ""<Keyboard>/w"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""up"",
|
||||
""id"": ""59f74329-7d63-4bf5-b3bc-3af75462894f"",
|
||||
""path"": ""<Keyboard>/upArrow"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""down"",
|
||||
""id"": ""624a58a3-f4ec-479f-8e1b-3b54ffaa7d8e"",
|
||||
""path"": ""<Keyboard>/s"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""down"",
|
||||
""id"": ""2d3501d8-5759-4d58-a909-a5ef3aa51842"",
|
||||
""path"": ""<Keyboard>/downArrow"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""left"",
|
||||
""id"": ""14dbcdaf-402d-496b-9798-54b770c4a826"",
|
||||
""path"": ""<Keyboard>/a"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""left"",
|
||||
""id"": ""2f4809ce-93a5-44f7-802d-6ac61f1e03e2"",
|
||||
""path"": ""<Keyboard>/leftArrow"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""right"",
|
||||
""id"": ""de79a6ae-cf9d-4536-b851-b630e3344d04"",
|
||||
""path"": ""<Keyboard>/d"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": ""right"",
|
||||
""id"": ""5bfaeff9-d16a-4fca-b43d-1d6d6b442bc8"",
|
||||
""path"": ""<Keyboard>/rightArrow"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": true
|
||||
},
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""401d3950-45d9-4839-b287-14c2b3902c67"",
|
||||
""path"": ""<XRController>/{Primary2DAxis}"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": ""XR"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""b23bd67c-a1c3-44a5-a73b-c824416534fa"",
|
||||
""path"": ""<Joystick>/stick"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": ""Joystick"",
|
||||
""action"": ""Move"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""d1e2f3a4-aim0-4001-8001-000000000011"",
|
||||
""path"": ""<Gamepad>/rightStick"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Gamepad"",
|
||||
""action"": ""Aim"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""d1e2f3a4-fir0-4001-8001-000000000021"",
|
||||
""path"": ""<Gamepad>/rightTrigger"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Gamepad"",
|
||||
""action"": ""Fire"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""d1e2f3a4-fir0-4001-8001-000000000022"",
|
||||
""path"": ""<Mouse>/leftButton"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Fire"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
},
|
||||
{
|
||||
""name"": """",
|
||||
""id"": ""d1e2f3a4-fir0-4001-8001-000000000023"",
|
||||
""path"": ""<Keyboard>/space"",
|
||||
""interactions"": """",
|
||||
""processors"": """",
|
||||
""groups"": "";Keyboard&Mouse"",
|
||||
""action"": ""Fire"",
|
||||
""isComposite"": false,
|
||||
""isPartOfComposite"": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
""controlSchemes"": []
|
||||
}");
|
||||
// Gameplay
|
||||
m_Gameplay = asset.FindActionMap("Gameplay", throwIfNotFound: true);
|
||||
m_Gameplay_Move = m_Gameplay.FindAction("Move", throwIfNotFound: true);
|
||||
m_Gameplay_Aim = m_Gameplay.FindAction("Aim", throwIfNotFound: true);
|
||||
m_Gameplay_Fire = m_Gameplay.FindAction("Fire", throwIfNotFound: true);
|
||||
}
|
||||
|
||||
~@ProjectMInput()
|
||||
{
|
||||
UnityEngine.Debug.Assert(!m_Gameplay.enabled, "This will cause a leak and performance issues, ProjectMInput.Gameplay.Disable() has not been called.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys this asset and all associated <see cref="InputAction"/> instances.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
UnityEngine.Object.Destroy(asset);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.bindingMask" />
|
||||
public InputBinding? bindingMask
|
||||
{
|
||||
get => asset.bindingMask;
|
||||
set => asset.bindingMask = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.devices" />
|
||||
public ReadOnlyArray<InputDevice>? devices
|
||||
{
|
||||
get => asset.devices;
|
||||
set => asset.devices = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.controlSchemes" />
|
||||
public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.Contains(InputAction)" />
|
||||
public bool Contains(InputAction action)
|
||||
{
|
||||
return asset.Contains(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.GetEnumerator()" />
|
||||
public IEnumerator<InputAction> GetEnumerator()
|
||||
{
|
||||
return asset.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEnumerable.GetEnumerator()" />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.Enable()" />
|
||||
public void Enable()
|
||||
{
|
||||
asset.Enable();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.Disable()" />
|
||||
public void Disable()
|
||||
{
|
||||
asset.Disable();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.bindings" />
|
||||
public IEnumerable<InputBinding> bindings => asset.bindings;
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.FindAction(string, bool)" />
|
||||
public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
|
||||
{
|
||||
return asset.FindAction(actionNameOrId, throwIfNotFound);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionAsset.FindBinding(InputBinding, out InputAction)" />
|
||||
public int FindBinding(InputBinding bindingMask, out InputAction action)
|
||||
{
|
||||
return asset.FindBinding(bindingMask, out action);
|
||||
}
|
||||
|
||||
// Gameplay
|
||||
private readonly InputActionMap m_Gameplay;
|
||||
private List<IGameplayActions> m_GameplayActionsCallbackInterfaces = new List<IGameplayActions>();
|
||||
private readonly InputAction m_Gameplay_Move;
|
||||
private readonly InputAction m_Gameplay_Aim;
|
||||
private readonly InputAction m_Gameplay_Fire;
|
||||
/// <summary>
|
||||
/// Provides access to input actions defined in input action map "Gameplay".
|
||||
/// </summary>
|
||||
public struct GameplayActions
|
||||
{
|
||||
private @ProjectMInput m_Wrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the input action map wrapper class.
|
||||
/// </summary>
|
||||
public GameplayActions(@ProjectMInput wrapper) { m_Wrapper = wrapper; }
|
||||
/// <summary>
|
||||
/// Provides access to the underlying input action "Gameplay/Move".
|
||||
/// </summary>
|
||||
public InputAction @Move => m_Wrapper.m_Gameplay_Move;
|
||||
/// <summary>
|
||||
/// Provides access to the underlying input action "Gameplay/Aim".
|
||||
/// </summary>
|
||||
public InputAction @Aim => m_Wrapper.m_Gameplay_Aim;
|
||||
/// <summary>
|
||||
/// Provides access to the underlying input action "Gameplay/Fire".
|
||||
/// </summary>
|
||||
public InputAction @Fire => m_Wrapper.m_Gameplay_Fire;
|
||||
/// <summary>
|
||||
/// Provides access to the underlying input action map instance.
|
||||
/// </summary>
|
||||
public InputActionMap Get() { return m_Wrapper.m_Gameplay; }
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionMap.Enable()" />
|
||||
public void Enable() { Get().Enable(); }
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionMap.Disable()" />
|
||||
public void Disable() { Get().Disable(); }
|
||||
/// <inheritdoc cref="UnityEngine.InputSystem.InputActionMap.enabled" />
|
||||
public bool enabled => Get().enabled;
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see ref="GameplayActions" /> to an <see ref="InputActionMap" /> instance.
|
||||
/// </summary>
|
||||
public static implicit operator InputActionMap(GameplayActions set) { return set.Get(); }
|
||||
/// <summary>
|
||||
/// Adds <see cref="InputAction.started"/>, <see cref="InputAction.performed"/> and <see cref="InputAction.canceled"/> callbacks provided via <param cref="instance" /> on all input actions contained in this map.
|
||||
/// </summary>
|
||||
/// <param name="instance">Callback instance.</param>
|
||||
/// <remarks>
|
||||
/// If <paramref name="instance" /> is <c>null</c> or <paramref name="instance"/> have already been added this method does nothing.
|
||||
/// </remarks>
|
||||
/// <seealso cref="GameplayActions" />
|
||||
public void AddCallbacks(IGameplayActions instance)
|
||||
{
|
||||
if (instance == null || m_Wrapper.m_GameplayActionsCallbackInterfaces.Contains(instance)) return;
|
||||
m_Wrapper.m_GameplayActionsCallbackInterfaces.Add(instance);
|
||||
@Move.started += instance.OnMove;
|
||||
@Move.performed += instance.OnMove;
|
||||
@Move.canceled += instance.OnMove;
|
||||
@Aim.started += instance.OnAim;
|
||||
@Aim.performed += instance.OnAim;
|
||||
@Aim.canceled += instance.OnAim;
|
||||
@Fire.started += instance.OnFire;
|
||||
@Fire.performed += instance.OnFire;
|
||||
@Fire.canceled += instance.OnFire;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes <see cref="InputAction.started"/>, <see cref="InputAction.performed"/> and <see cref="InputAction.canceled"/> callbacks provided via <param cref="instance" /> on all input actions contained in this map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling this method when <paramref name="instance" /> have not previously been registered has no side-effects.
|
||||
/// </remarks>
|
||||
/// <seealso cref="GameplayActions" />
|
||||
private void UnregisterCallbacks(IGameplayActions instance)
|
||||
{
|
||||
@Move.started -= instance.OnMove;
|
||||
@Move.performed -= instance.OnMove;
|
||||
@Move.canceled -= instance.OnMove;
|
||||
@Aim.started -= instance.OnAim;
|
||||
@Aim.performed -= instance.OnAim;
|
||||
@Aim.canceled -= instance.OnAim;
|
||||
@Fire.started -= instance.OnFire;
|
||||
@Fire.performed -= instance.OnFire;
|
||||
@Fire.canceled -= instance.OnFire;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters <param cref="instance" /> and unregisters all input action callbacks via <see cref="GameplayActions.UnregisterCallbacks(IGameplayActions)" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="GameplayActions.UnregisterCallbacks(IGameplayActions)" />
|
||||
public void RemoveCallbacks(IGameplayActions instance)
|
||||
{
|
||||
if (m_Wrapper.m_GameplayActionsCallbackInterfaces.Remove(instance))
|
||||
UnregisterCallbacks(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all existing callback instances and previously registered input action callbacks associated with them with callbacks provided via <param cref="instance" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <paramref name="instance" /> is <c>null</c>, calling this method will only unregister all existing callbacks but not register any new callbacks.
|
||||
/// </remarks>
|
||||
/// <seealso cref="GameplayActions.AddCallbacks(IGameplayActions)" />
|
||||
/// <seealso cref="GameplayActions.RemoveCallbacks(IGameplayActions)" />
|
||||
/// <seealso cref="GameplayActions.UnregisterCallbacks(IGameplayActions)" />
|
||||
public void SetCallbacks(IGameplayActions instance)
|
||||
{
|
||||
foreach (var item in m_Wrapper.m_GameplayActionsCallbackInterfaces)
|
||||
UnregisterCallbacks(item);
|
||||
m_Wrapper.m_GameplayActionsCallbackInterfaces.Clear();
|
||||
AddCallbacks(instance);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Provides a new <see cref="GameplayActions" /> instance referencing this action map.
|
||||
/// </summary>
|
||||
public GameplayActions @Gameplay => new GameplayActions(this);
|
||||
/// <summary>
|
||||
/// Interface to implement callback methods for all input action callbacks associated with input actions defined by "Gameplay" which allows adding and removing callbacks.
|
||||
/// </summary>
|
||||
/// <seealso cref="GameplayActions.AddCallbacks(IGameplayActions)" />
|
||||
/// <seealso cref="GameplayActions.RemoveCallbacks(IGameplayActions)" />
|
||||
public interface IGameplayActions
|
||||
{
|
||||
/// <summary>
|
||||
/// Method invoked when associated input action "Move" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||
void OnMove(InputAction.CallbackContext context);
|
||||
/// <summary>
|
||||
/// Method invoked when associated input action "Aim" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||
void OnAim(InputAction.CallbackContext context);
|
||||
/// <summary>
|
||||
/// Method invoked when associated input action "Fire" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||
void OnFire(InputAction.CallbackContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d0226495dbc844da9ddfb554d46aa02
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d9e7bf5afc824f079f0a869c9948ad9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Prototype ARPG follow camera. Attach to the Main Camera (a plain MonoBehaviour): each LateUpdate
|
||||
/// it frames the local player at a fixed pitch/yaw and distance — the classic top-down/isometric ARPG
|
||||
/// framing (V Rising / Diablo / PoE2). Every lever is exposed so the feel can be dialled in live in
|
||||
/// Play Mode. The player's world position is published by <see cref="PrototypeCameraTargetSystem"/>
|
||||
/// (an ECS system that observes job dependencies safely); the MonoBehaviour never touches the
|
||||
/// EntityManager directly — doing so from LateUpdate raced the subscene async-load job and threw a
|
||||
/// job-safety exception at startup. Before a player spawns it frames <see cref="FallbackTarget"/>.
|
||||
/// <para>
|
||||
/// Presentation only — uses wall-clock <c>Time.deltaTime</c> for framerate-independent smoothing,
|
||||
/// which is correct here (not deterministic simulation). Default: mid 3/4 ~45°, perspective.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Camera))]
|
||||
[DisallowMultipleComponent]
|
||||
public class PrototypeCameraRig : MonoBehaviour
|
||||
{
|
||||
/// <summary>Local player world position, published each client tick by <see cref="PrototypeCameraTargetSystem"/>.</summary>
|
||||
public static float3 TargetWorldPos;
|
||||
|
||||
/// <summary>True while a locally-owned player exists to follow.</summary>
|
||||
public static bool HasTarget;
|
||||
|
||||
[Header("Angle (degrees)")]
|
||||
[Range(10f, 89f)] public float Pitch = 45f;
|
||||
[Range(-180f, 180f)] public float Yaw = 0f;
|
||||
|
||||
[Header("Framing")]
|
||||
[Min(1f)] public float Distance = 16f;
|
||||
[Tooltip("Raise the look-at point off the ground toward the character's centre of mass.")]
|
||||
public float TargetHeight = 1f;
|
||||
|
||||
[Header("Lens")]
|
||||
public bool Orthographic = false;
|
||||
[Range(20f, 90f)] public float FieldOfView = 55f;
|
||||
[Min(1f)] public float OrthoSize = 10f;
|
||||
|
||||
[Header("Follow")]
|
||||
[Tooltip("Higher = snappier follow. 0 = instant. Framerate-independent.")]
|
||||
[Min(0f)] public float FollowSharpness = 8f;
|
||||
[Tooltip("What to frame before a local player exists (edit mode / pre-spawn).")]
|
||||
public Vector3 FallbackTarget = new Vector3(3f, 0f, 4f);
|
||||
|
||||
Camera _cam;
|
||||
|
||||
void Awake() => _cam = GetComponent<Camera>();
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (_cam == null) _cam = GetComponent<Camera>();
|
||||
|
||||
_cam.orthographic = Orthographic;
|
||||
_cam.fieldOfView = FieldOfView;
|
||||
_cam.orthographicSize = OrthoSize;
|
||||
|
||||
Vector3 target = HasTarget ? (Vector3)TargetWorldPos : FallbackTarget;
|
||||
target.y += TargetHeight;
|
||||
|
||||
var rot = Quaternion.Euler(Pitch, Yaw, 0f);
|
||||
Vector3 desired = target - (rot * Vector3.forward) * Distance;
|
||||
|
||||
float k = FollowSharpness <= 0f ? 1f : 1f - Mathf.Exp(-FollowSharpness * Time.deltaTime);
|
||||
transform.SetPositionAndRotation(Vector3.Lerp(transform.position, desired, k), rot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes the locally-owned player's world position to <see cref="PrototypeCameraRig"/> statics
|
||||
/// each client tick. Lives in ECS (not the camera MonoBehaviour) so reading <see cref="LocalTransform"/>
|
||||
/// respects job dependencies — avoiding the subscene-load job-safety exception that direct
|
||||
/// EntityManager access from LateUpdate caused.
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||
public partial class PrototypeCameraTargetSystem : SystemBase
|
||||
{
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
bool found = false;
|
||||
foreach (var transform in SystemAPI.Query<RefRO<LocalTransform>>()
|
||||
.WithAll<GhostOwnerIsLocal, PlayerTag>())
|
||||
{
|
||||
PrototypeCameraRig.TargetWorldPos = transform.ValueRO.Position;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
PrototypeCameraRig.HasTarget = found;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e5890693b64a429789bf3edfae0a6ff
|
||||
@@ -4,6 +4,7 @@
|
||||
"references": [
|
||||
"ProjectM.Simulation",
|
||||
"Unity.Entities",
|
||||
"Unity.Transforms",
|
||||
"Unity.Collections",
|
||||
"Unity.Mathematics",
|
||||
"Unity.Burst",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8dcd28017b9e4abead6e2dc32ef9383
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative damage application. Drains each damageable entity's
|
||||
/// <see cref="DamageEvent"/> buffer (appended by <see cref="ProjectileDamageSystem"/> earlier
|
||||
/// this tick), subtracts the summed amount from <see cref="Health"/>, then clears the buffer so
|
||||
/// each hit is applied exactly once. Entities that carry character stats (players) clamp to their
|
||||
/// data-driven <see cref="EffectiveCharacterStats.MaxHealth"/> ceiling; others (training dummies)
|
||||
/// clamp at zero. A dead <see cref="TrainingDummyTag"/> is destroyed; player death is deferred.
|
||||
/// Health.Current is a <c>[GhostField]</c>, so the new value replicates to clients for display.
|
||||
///
|
||||
/// Runs server-only (<see cref="WorldSystemFilterFlags.ServerSimulation"/>) inside the prediction
|
||||
/// group so it shares tick timing with movement/damage, where it executes once per tick. The
|
||||
/// single structural change (destroying a dead dummy) is batched through a frame-allocator
|
||||
/// <see cref="EntityCommandBuffer"/> that is played back immediately to the entity manager — so a
|
||||
/// plain-world EditMode test needs no separate ECB system.
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
|
||||
[UpdateAfter(typeof(ProjectileDamageSystem))]
|
||||
[BurstCompile]
|
||||
public partial struct HealthApplyDamageSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (health, dmg, entity) in
|
||||
SystemAPI.Query<RefRW<Health>, DynamicBuffer<DamageEvent>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
if (dmg.Length == 0)
|
||||
continue;
|
||||
|
||||
float total = 0f;
|
||||
for (int i = 0; i < dmg.Length; i++)
|
||||
total += dmg[i].Amount;
|
||||
dmg.Clear();
|
||||
|
||||
float newHp = health.ValueRO.Current - total;
|
||||
|
||||
// Effective max health (base + modifiers) is the runtime ceiling for entities that carry
|
||||
// character stats (players); others just clamp at zero. No auto-heal on a max increase.
|
||||
if (SystemAPI.HasComponent<EffectiveCharacterStats>(entity))
|
||||
newHp = math.clamp(newHp, 0f, SystemAPI.GetComponent<EffectiveCharacterStats>(entity).MaxHealth);
|
||||
else
|
||||
newHp = math.max(0f, newHp);
|
||||
|
||||
health.ValueRW.Current = newHp;
|
||||
|
||||
// Server-authoritative death: training dummies despawn; player death is deferred (clamp only).
|
||||
if (health.ValueRO.Current <= 0f && SystemAPI.HasComponent<TrainingDummyTag>(entity))
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c1729291515a4966b83ef050554a772
|
||||
@@ -0,0 +1,145 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative projectile resolution: applies hits to damageable entities and expires
|
||||
/// projectiles past their range. Runs in the server world only
|
||||
/// (<see cref="WorldSystemFilterFlags.ServerSimulation"/>) inside the
|
||||
/// <see cref="PredictedSimulationSystemGroup"/>, ordered <see cref="ProjectileMoveSystem"/> so the
|
||||
/// projectile's <see cref="LocalTransform"/> is the post-move position for this tick.
|
||||
///
|
||||
/// Hit detection is a <b>swept</b> planar (XZ) test: rather than checking the projectile's point
|
||||
/// position (which tunnels straight through a target when the per-tick step exceeds the target's
|
||||
/// radius — e.g. a fast projectile, or any projectile while the server is tick-batching under load),
|
||||
/// it reconstructs the segment the projectile traversed this tick
|
||||
/// (<c>[curPos - dir*speed*dt, curPos]</c>) and tests each target's hit radius against the closest
|
||||
/// point on that segment. The target hit earliest along the path (smallest segment parameter) wins.
|
||||
/// A target whose <see cref="GhostOwner"/> matches the projectile's owner is skipped (no self-hits);
|
||||
/// dummies carry no <see cref="GhostOwner"/> and are therefore always valid targets.
|
||||
///
|
||||
/// On a hit the system appends a <see cref="DamageEvent"/> to the target (consumed by
|
||||
/// <c>HealthApplyDamageSystem</c>) and destroys the projectile. Deferring damage to a buffer lets a
|
||||
/// single tick stack hits from multiple projectiles. All structural changes go through an
|
||||
/// <see cref="EntityCommandBuffer"/> that plays back immediately to the
|
||||
/// <see cref="EntityManager"/> (Temp allocator) — keeping this server-only, once-per-tick system
|
||||
/// self-contained and plain-world testable without a separate ECB system.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
|
||||
[UpdateAfter(typeof(ProjectileMoveSystem))]
|
||||
public partial struct ProjectileDamageSystem : ISystem
|
||||
{
|
||||
/// <summary>Lookup used to read a target's owner so a projectile never hits its own caster.</summary>
|
||||
ComponentLookup<GhostOwner> m_GhostOwnerLookup;
|
||||
|
||||
/// <summary>Extra forgiveness added to a target's hit radius to approximate the projectile's own size.</summary>
|
||||
const float k_ProjectileRadius = 0.2f;
|
||||
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
m_GhostOwnerLookup = state.GetComponentLookup<GhostOwner>(isReadOnly: true);
|
||||
|
||||
// No projectiles → nothing to expire or hit-test; skip the tick (and its allocations) entirely.
|
||||
state.RequireForUpdate<Projectile>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
m_GhostOwnerLookup.Update(ref state);
|
||||
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
// Snapshot all damageable targets once for this tick. Stable iteration order (query order).
|
||||
var targetEntities = new NativeList<Entity>(Allocator.Temp);
|
||||
var targetPositions = new NativeList<float3>(Allocator.Temp);
|
||||
var targetRadii = new NativeList<float>(Allocator.Temp);
|
||||
|
||||
foreach (var (xform, hitRadius, targetEntity) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<HitRadius>>()
|
||||
.WithAll<Health>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
targetEntities.Add(targetEntity);
|
||||
targetPositions.Add(xform.ValueRO.Position);
|
||||
targetRadii.Add(hitRadius.ValueRO.Value);
|
||||
}
|
||||
|
||||
foreach (var (xform, proj, owner, projectileEntity) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<Projectile>, RefRO<GhostOwner>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
int projOwnerId = owner.ValueRO.NetworkId;
|
||||
|
||||
// This tick's planar travel segment: [segStart -> segEnd]. Sweeping the segment (rather
|
||||
// than testing only segEnd) is what prevents fast projectiles from tunnelling targets.
|
||||
float3 cur = xform.ValueRO.Position;
|
||||
float2 segEnd = new float2(cur.x, cur.z);
|
||||
float2 segStart = segEnd - proj.ValueRO.Direction * (proj.ValueRO.Speed * dt);
|
||||
float2 seg = segEnd - segStart;
|
||||
float segLenSq = math.lengthsq(seg);
|
||||
|
||||
int bestIdx = -1;
|
||||
float bestT = float.MaxValue;
|
||||
for (int i = 0; i < targetEntities.Length; i++)
|
||||
{
|
||||
var target = targetEntities[i];
|
||||
|
||||
// Skip the caster: a target whose GhostOwner matches the projectile owner is the
|
||||
// shooter (or another ghost they own). Dummies have no GhostOwner, so never skipped.
|
||||
if (m_GhostOwnerLookup.HasComponent(target) &&
|
||||
m_GhostOwnerLookup[target].NetworkId == projOwnerId)
|
||||
continue;
|
||||
|
||||
float2 tp = new float2(targetPositions[i].x, targetPositions[i].z);
|
||||
|
||||
// Closest point on the travel segment to the target centre.
|
||||
float t = segLenSq > 1e-8f
|
||||
? math.saturate(math.dot(tp - segStart, seg) / segLenSq)
|
||||
: 0f;
|
||||
float2 closest = segStart + t * seg;
|
||||
|
||||
float hitDist = targetRadii[i] + k_ProjectileRadius;
|
||||
if (math.distancesq(tp, closest) <= hitDist * hitDist && t < bestT)
|
||||
{
|
||||
bestT = t;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIdx >= 0)
|
||||
{
|
||||
// Earliest target along the travel path: deal damage and consume the projectile.
|
||||
ecb.AppendToBuffer(targetEntities[bestIdx], new DamageEvent
|
||||
{
|
||||
Amount = proj.ValueRO.Damage,
|
||||
SourceNetworkId = projOwnerId,
|
||||
});
|
||||
ecb.DestroyEntity(projectileEntity);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nothing hit this tick: expire the projectile once it has travelled its full range.
|
||||
if (proj.ValueRO.DistanceTravelled >= proj.ValueRO.Range)
|
||||
ecb.DestroyEntity(projectileEntity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
|
||||
ecb.Dispose();
|
||||
targetEntities.Dispose();
|
||||
targetPositions.Dispose();
|
||||
targetRadii.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f19fa77609fa04c2ca4e293f19c052a4
|
||||
@@ -0,0 +1,52 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only, one-shot dummy spawner. On its first update it reads the baked
|
||||
/// <see cref="TrainingDummySpawner"/> singleton and instantiates <c>Count</c> training-dummy
|
||||
/// ghosts in a row, spaced <c>Spacing</c> world-units apart along +X starting at <c>Origin</c>.
|
||||
/// It then destroys the spawner singleton entity so <c>RequireForUpdate<TrainingDummySpawner></c>
|
||||
/// is no longer satisfied and the system stops running (idempotent: dummies are spawned exactly once).
|
||||
/// Runs in the default <see cref="SimulationSystemGroup"/> (NOT the prediction loop) since spawning is
|
||||
/// a non-predicted, server-authoritative event; the dummies replicate to clients as interpolated ghosts.
|
||||
/// All structural changes are batched through an <see cref="EntityCommandBuffer"/>.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct TrainingDummySpawnSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<TrainingDummySpawner>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Grab both the singleton entity (to destroy when done) and its baked config.
|
||||
var spawnerEntity = SystemAPI.GetSingletonEntity<TrainingDummySpawner>();
|
||||
var spawner = SystemAPI.GetComponent<TrainingDummySpawner>(spawnerEntity);
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
for (int i = 0; i < spawner.Count; i++)
|
||||
{
|
||||
var dummy = ecb.Instantiate(spawner.Prefab);
|
||||
var position = spawner.Origin + new float3(i * spawner.Spacing, 0f, 0f);
|
||||
ecb.SetComponent(dummy, LocalTransform.FromPosition(position));
|
||||
}
|
||||
|
||||
// One-shot: remove the spawner so RequireForUpdate fails and the system idles.
|
||||
ecb.DestroyEntity(spawnerEntity);
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7673acbc74f7a4bddbca0679122511ea
|
||||
@@ -0,0 +1,52 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only, one-shot upgrade-pickup spawner (mirrors TrainingDummySpawnSystem). On its first
|
||||
/// update it reads the baked <see cref="UpgradePickupSpawner"/> singleton and instantiates
|
||||
/// <c>Count</c> pickup ghosts in a row, spaced <c>Spacing</c> world-units apart along +X starting at
|
||||
/// <c>Origin</c>, then destroys the singleton so the system idles (spawned exactly once). Runs in the
|
||||
/// default <see cref="SimulationSystemGroup"/> (NOT the prediction loop); pickups replicate to clients
|
||||
/// as interpolated ghosts. Structural changes are batched through an <see cref="EntityCommandBuffer"/>.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct UpgradePickupSpawnSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<UpgradePickupSpawner>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var spawnerEntity = SystemAPI.GetSingletonEntity<UpgradePickupSpawner>();
|
||||
var spawner = SystemAPI.GetComponent<UpgradePickupSpawner>(spawnerEntity);
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
if (spawner.Prefab != Entity.Null)
|
||||
{
|
||||
for (int i = 0; i < spawner.Count; i++)
|
||||
{
|
||||
var pickup = ecb.Instantiate(spawner.Prefab);
|
||||
var position = spawner.Origin + new float3(i * spawner.Spacing, 0f, 0f);
|
||||
ecb.SetComponent(pickup, LocalTransform.FromPosition(position));
|
||||
}
|
||||
}
|
||||
|
||||
// One-shot: remove the spawner so RequireForUpdate fails and the system idles.
|
||||
ecb.DestroyEntity(spawnerEntity);
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 602544c80ca5649258a9d6ca9ad74f79
|
||||
@@ -0,0 +1,78 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative upgrade pickup grant. When a player overlaps an <see cref="UpgradePickup"/>
|
||||
/// (planar XZ distance within the pickup's <see cref="HitRadius"/>), appends the pickup's modifier to
|
||||
/// the player's replicated <see cref="StatModifier"/> buffer (which replicates to the predicting
|
||||
/// owner, so StatRecomputeSystem folds identical effective stats on both worlds) and destroys the
|
||||
/// pickup. Runs in the default <see cref="SimulationSystemGroup"/> (NOT the prediction loop) since the
|
||||
/// grant is a non-predicted server event. The buffer append + pickup destroy are batched through an
|
||||
/// <see cref="EntityCommandBuffer"/> played back immediately — so a plain-world EditMode test needs no
|
||||
/// separate ECB system.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct UpgradePickupSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<UpgradePickup>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Snapshot modifiable players (carrying the modifier buffer + a transform) once this tick.
|
||||
var playerEntities = new NativeList<Entity>(Allocator.Temp);
|
||||
var playerPositions = new NativeList<float3>(Allocator.Temp);
|
||||
foreach (var (xform, e) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>>()
|
||||
.WithAll<PlayerTag, StatModifier>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
playerEntities.Add(e);
|
||||
playerPositions.Add(xform.ValueRO.Position);
|
||||
}
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (xform, radius, pickup, pickupEntity) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<HitRadius>, RefRO<UpgradePickup>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
float2 pp = new float2(xform.ValueRO.Position.x, xform.ValueRO.Position.z);
|
||||
float r = radius.ValueRO.Value;
|
||||
|
||||
for (int i = 0; i < playerEntities.Length; i++)
|
||||
{
|
||||
float2 cp = new float2(playerPositions[i].x, playerPositions[i].z);
|
||||
if (math.distancesq(pp, cp) > r * r)
|
||||
continue;
|
||||
|
||||
ecb.AppendToBuffer(playerEntities[i], new StatModifier
|
||||
{
|
||||
Target = pickup.ValueRO.Target,
|
||||
Op = pickup.ValueRO.Op,
|
||||
Value = pickup.ValueRO.Value,
|
||||
SourceId = pickup.ValueRO.SourceId,
|
||||
});
|
||||
ecb.DestroyEntity(pickupEntity);
|
||||
break; // granted to the first overlapping player, then despawns
|
||||
}
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
playerEntities.Dispose();
|
||||
playerPositions.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3d9ef25fbc464e52aa342b531d6f35e
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94ea01b14c1384c9f96d8f7bd6ac1a14
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor-only debug hook (mirrors ProjectM.Client.DebugInputInjectionSystem's static-poke pattern)
|
||||
/// for driving the server-authoritative modifier stack from MCP execute_code. Because modifiers are
|
||||
/// server-authoritative, a client-side append would be stomped by the next snapshot, so this runs in
|
||||
/// the SERVER world: the change flows back through the snapshot and is prediction-correct on the
|
||||
/// client. In-editor single-process only (client + server worlds in one process). Poke from execute_code:
|
||||
/// DebugModifierInjectionSystem.AddModifier((byte)StatTarget.Damage, (byte)ModOp.Flat, 50f);
|
||||
/// DebugModifierInjectionSystem.AddModifier((byte)StatTarget.MoveSpeed, (byte)ModOp.PercentAdd, 0.5f);
|
||||
/// DebugModifierInjectionSystem.CycleAbility(); // Primary -> FastLight -> SlowHeavy -> Primary
|
||||
/// DebugModifierInjectionSystem.ClearModifiers();
|
||||
/// All applied to the first player on the next server tick.
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial class DebugModifierInjectionSystem : SystemBase
|
||||
{
|
||||
struct PendingModifier { public byte Target; public byte Op; public float Value; }
|
||||
|
||||
static readonly List<PendingModifier> s_Pending = new List<PendingModifier>();
|
||||
static bool s_Clear;
|
||||
static bool s_Cycle;
|
||||
|
||||
/// <summary>Queue a modifier to append to the first player on the next server tick.</summary>
|
||||
public static void AddModifier(byte target, byte op, float value)
|
||||
{
|
||||
s_Pending.Add(new PendingModifier { Target = target, Op = op, Value = value });
|
||||
}
|
||||
|
||||
/// <summary>Clear the first player's whole modifier stack on the next server tick.</summary>
|
||||
public static void ClearModifiers() => s_Clear = true;
|
||||
|
||||
/// <summary>Cycle the first player's primary ability id on the next server tick.</summary>
|
||||
public static void CycleAbility() => s_Cycle = true;
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
if (s_Pending.Count == 0 && !s_Clear && !s_Cycle)
|
||||
return;
|
||||
|
||||
Entity player = Entity.Null;
|
||||
foreach (var (abilityRef, e) in
|
||||
SystemAPI.Query<RefRO<AbilityRef>>().WithAll<PlayerTag, StatModifier>().WithEntityAccess())
|
||||
{
|
||||
player = e;
|
||||
break;
|
||||
}
|
||||
if (player == Entity.Null)
|
||||
return;
|
||||
|
||||
if (s_Clear)
|
||||
{
|
||||
EntityManager.GetBuffer<StatModifier>(player).Clear();
|
||||
s_Clear = false;
|
||||
}
|
||||
|
||||
if (s_Pending.Count > 0)
|
||||
{
|
||||
var buffer = EntityManager.GetBuffer<StatModifier>(player);
|
||||
for (int i = 0; i < s_Pending.Count; i++)
|
||||
{
|
||||
var m = s_Pending[i];
|
||||
buffer.Add(new StatModifier { Target = m.Target, Op = m.Op, Value = m.Value, SourceId = 0u });
|
||||
}
|
||||
s_Pending.Clear();
|
||||
}
|
||||
|
||||
if (s_Cycle)
|
||||
{
|
||||
var abilityRef = EntityManager.GetComponentData<AbilityRef>(player);
|
||||
abilityRef.Id = abilityRef.Id switch
|
||||
{
|
||||
(byte)AbilityId.Primary => (byte)AbilityId.FastLight,
|
||||
(byte)AbilityId.FastLight => (byte)AbilityId.SlowHeavy,
|
||||
_ => (byte)AbilityId.Primary,
|
||||
};
|
||||
EntityManager.SetComponentData(player, abilityRef);
|
||||
s_Cycle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f8906df777734bbcb3f21100d6e664e
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a7ea79ccc9c74c62a94c777999859c6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Predicted per-player ability cooldown gate. Holds the earliest server tick at which the
|
||||
/// owning player may fire again, so <see cref="AbilityFireSystem"/> can throttle shots
|
||||
/// deterministically across client prediction and server simulation.
|
||||
/// <para>
|
||||
/// Replicated as a <see cref="GhostField"/> so the cooldown survives the frame→tick→rollback
|
||||
/// boundary: when the client re-predicts ticks after a snapshot, it sees the same authoritative
|
||||
/// gate the server applied and converges without double-firing. Stored as a raw <c>uint</c>
|
||||
/// rather than a <see cref="NetworkTick"/> for simple, quantization-free serialization; compare
|
||||
/// by wrapping it back into a <see cref="NetworkTick"/> and using
|
||||
/// <see cref="NetworkTick.IsNewerThan"/> (raw subtraction is unsafe across tick wraparound).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public struct AbilityCooldown : IComponentData
|
||||
{
|
||||
/// <summary>
|
||||
/// Raw tick value of the earliest tick the player may fire again. <c>0</c> = ready (no
|
||||
/// cooldown pending). Set by <see cref="AbilityFireSystem"/> to
|
||||
/// <c>serverTick + max(1, CooldownTicks)</c> on fire; treat as "still cooling down" only
|
||||
/// while a valid <see cref="NetworkTick"/> built from it is newer than the current
|
||||
/// <c>ServerTick</c>.
|
||||
/// </summary>
|
||||
[GhostField] public uint NextFireTick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7a2b67b22b2a4abaa8efd84759445c0
|
||||
@@ -0,0 +1,14 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton handle to the baked ability/character definition database (config, not replicated -
|
||||
/// baked identically into both worlds from the gameplay subscene). The companion AbilityPrefabElement
|
||||
/// buffer on the same entity carries the per-ability projectile prefab entity refs.
|
||||
/// </summary>
|
||||
public struct AbilityDatabase : IComponentData
|
||||
{
|
||||
public BlobAssetReference<AbilityDatabaseBlob> Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b297f7d451084342af25012ccb3a3e8
|
||||
@@ -0,0 +1,74 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>One authored ability definition, baked immutable into the AbilityDatabase blob.</summary>
|
||||
public struct AbilityDefBlob
|
||||
{
|
||||
public byte Id; // AbilityId
|
||||
public float Damage;
|
||||
public float ProjectileSpeed;
|
||||
public float Range;
|
||||
public float AutoTargetRange;
|
||||
public float AutoTargetConeRadians;
|
||||
public int CooldownTicks;
|
||||
public FixedString64Bytes Name;
|
||||
}
|
||||
|
||||
/// <summary>One authored character-stats definition, baked immutable into the AbilityDatabase blob.</summary>
|
||||
public struct CharacterStatsBlob
|
||||
{
|
||||
public byte Id; // CharacterId
|
||||
public float MoveSpeed;
|
||||
public float TurnRateRadiansPerSec;
|
||||
public float MaxHealth;
|
||||
public FixedString64Bytes Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immutable designer-authored definition database, baked from ScriptableObjects to a blob asset and
|
||||
/// shared by every entity (Burst-fast, zero per-instance cost). Looked up by stable id. Entity/prefab
|
||||
/// references are NOT stored here (blobs don't remap entity refs) - see AbilityPrefabElement.
|
||||
///
|
||||
/// NOTE: the lookups are intentionally NOT 'readonly' methods. A readonly struct method forces a
|
||||
/// defensive copy of a field when calling a non-readonly member on it; copying a BlobArray breaks its
|
||||
/// relative-offset pointer, so the array would read as empty. Plain (non-readonly) methods access the
|
||||
/// BlobArray in place. Always reach these through 'ref blob.Value'.
|
||||
/// </summary>
|
||||
public struct AbilityDatabaseBlob
|
||||
{
|
||||
public BlobArray<AbilityDefBlob> Abilities;
|
||||
public BlobArray<CharacterStatsBlob> Characters;
|
||||
|
||||
/// <summary>Linear lookup by ability id (the array is tiny). Returns false if not present.</summary>
|
||||
public bool TryGetAbility(byte id, out AbilityDefBlob def)
|
||||
{
|
||||
for (int i = 0; i < Abilities.Length; i++)
|
||||
{
|
||||
if (Abilities[i].Id == id)
|
||||
{
|
||||
def = Abilities[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
def = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Linear lookup by character id (the array is tiny). Returns false if not present.</summary>
|
||||
public bool TryGetCharacter(byte id, out CharacterStatsBlob def)
|
||||
{
|
||||
for (int i = 0; i < Characters.Length; i++)
|
||||
{
|
||||
if (Characters[i].Id == id)
|
||||
{
|
||||
def = Characters[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
def = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 974be1ee95bef486ea49a4d42ecc9796
|
||||
@@ -0,0 +1,173 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Predicted "fire" ability: on the single fully-predicting pass of each tick, spawns a
|
||||
/// Projectile ghost for every player whose PlayerInput.Fire event is set this tick and whose
|
||||
/// AbilityCooldown has elapsed. Runs in both worlds: the owning client predict-spawns the
|
||||
/// projectile (classified into the authoritative ghost by ProjectileClassificationSystem via the
|
||||
/// Projectile.SpawnId key), and the server spawns the replicated truth.
|
||||
///
|
||||
/// M3 data-driven: ability stats are read from the per-entity EffectiveAbilityStats (authored base
|
||||
/// from the AbilityDatabase blob keyed by AbilityRef, folded with the replicated StatModifier buffer
|
||||
/// by StatRecomputeSystem earlier this tick). The projectile ghost prefab is resolved per ability via
|
||||
/// the AbilityPrefabElement buffer on the AbilityDatabase singleton. Effective Speed/Damage/Range are
|
||||
/// snapshotted into the spawned Projectile, so the downstream move/damage systems are unchanged and
|
||||
/// predicted + server projectiles match (both folded the same replicated modifiers).
|
||||
///
|
||||
/// Determinism / idempotency: the prediction loop re-runs this system on rollback, so all
|
||||
/// non-idempotent effects (spawning, cooldown advance) are gated behind
|
||||
/// NetworkTime.IsFirstTimeFullyPredictingTick so they happen exactly once per tick. The absolute
|
||||
/// fire count comes from the replicated input command buffer at NetworkTime.ServerTick (not a
|
||||
/// local counter) so the SpawnId matches on client and server. No wall-clock, no System.Random,
|
||||
/// no UnityEngine.Time.
|
||||
///
|
||||
/// Auto-target is intentionally server-only: the client fires along raw aim, and the server's
|
||||
/// authoritative Projectile.Direction GhostField reconciles the predicted projectile to the
|
||||
/// assisted heading.
|
||||
/// </summary>
|
||||
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
|
||||
[UpdateAfter(typeof(PlayerAimSystem))]
|
||||
[BurstCompile]
|
||||
public partial struct AbilityFireSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<AbilityDatabase>();
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Spawning is a one-off effect: only run on the unique fully-predicting pass of this tick
|
||||
// so a rollback re-simulation does not double-spawn.
|
||||
var networkTime = SystemAPI.GetSingleton<NetworkTime>();
|
||||
if (!networkTime.IsFirstTimeFullyPredictingTick)
|
||||
return;
|
||||
|
||||
var serverTick = networkTime.ServerTick;
|
||||
if (!serverTick.IsValid)
|
||||
return;
|
||||
|
||||
// Per-ability projectile ghost prefabs live on the AbilityDatabase singleton's companion buffer.
|
||||
var dbEntity = SystemAPI.GetSingletonEntity<AbilityDatabase>();
|
||||
var abilityPrefabs = SystemAPI.GetBuffer<AbilityPrefabElement>(dbEntity);
|
||||
|
||||
bool isServer = state.WorldUnmanaged.IsServer();
|
||||
|
||||
// Server-only auto-target candidate set: training-dummy world XZ positions, collected once.
|
||||
var candidatePositions = new NativeList<float3>(Allocator.Temp);
|
||||
if (isServer)
|
||||
{
|
||||
foreach (var dummyTransform in
|
||||
SystemAPI.Query<RefRO<LocalTransform>>().WithAll<TrainingDummyTag>())
|
||||
{
|
||||
candidatePositions.Add(dummyTransform.ValueRO.Position);
|
||||
}
|
||||
}
|
||||
var candidates = candidatePositions.AsArray();
|
||||
|
||||
var ecb = new EntityCommandBuffer(state.WorldUpdateAllocator);
|
||||
|
||||
foreach (var (input, facing, xform, eff, abilityRef, cd, owner, entity) in
|
||||
SystemAPI.Query<RefRO<PlayerInput>, RefRO<PlayerFacing>, RefRO<LocalTransform>,
|
||||
RefRO<EffectiveAbilityStats>, RefRO<AbilityRef>, RefRW<AbilityCooldown>,
|
||||
RefRO<GhostOwner>>()
|
||||
.WithAll<Simulate>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
// The InputEvent on the component carries the per-tick delta: set => fired this tick.
|
||||
if (!input.ValueRO.Fire.IsSet)
|
||||
continue;
|
||||
|
||||
// Cooldown gate. NextFireTick == 0 means "ready". Otherwise the player may fire only
|
||||
// once serverTick is at-or-newer than the stored tick (i.e. the stored tick is not
|
||||
// strictly newer than now).
|
||||
uint nextFireRaw = cd.ValueRO.NextFireTick;
|
||||
if (nextFireRaw != 0)
|
||||
{
|
||||
var nextTick = new NetworkTick(nextFireRaw);
|
||||
if (nextTick.IsValid && nextTick.IsNewerThan(serverTick))
|
||||
continue; // still cooling down
|
||||
}
|
||||
|
||||
// Resolve the projectile ghost prefab for this player's selected ability id.
|
||||
Entity prefab = Entity.Null;
|
||||
for (int i = 0; i < abilityPrefabs.Length; i++)
|
||||
{
|
||||
if (abilityPrefabs[i].Id == abilityRef.ValueRO.Id)
|
||||
{
|
||||
prefab = abilityPrefabs[i].Prefab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prefab == Entity.Null)
|
||||
continue; // ability has no projectile prefab wired
|
||||
|
||||
// Absolute (monotonic) fire count from the replicated command buffer at this tick.
|
||||
// This is the classification key shared by client prediction and server truth.
|
||||
var inputBuffer = SystemAPI.GetBuffer<InputBufferData<PlayerInput>>(entity);
|
||||
if (!inputBuffer.GetDataAtTick(serverTick, out var applied))
|
||||
continue;
|
||||
uint absoluteFireCount = applied.InternalInput.Fire.Count;
|
||||
|
||||
float2 rawAim = facing.ValueRO.Direction;
|
||||
if (math.lengthsq(rawAim) < 1e-6f)
|
||||
rawAim = new float2(0f, 1f);
|
||||
else
|
||||
rawAim = math.normalize(rawAim);
|
||||
|
||||
// Client fires along raw aim; only the server applies the auto-target assist cone.
|
||||
float2 dir = rawAim;
|
||||
if (isServer && eff.ValueRO.AutoTargetRange > 0f)
|
||||
{
|
||||
dir = AutoTarget.Resolve(
|
||||
xform.ValueRO.Position,
|
||||
rawAim,
|
||||
eff.ValueRO.AutoTargetRange,
|
||||
eff.ValueRO.AutoTargetConeRadians,
|
||||
candidates);
|
||||
}
|
||||
|
||||
uint spawnId = (uint)owner.ValueRO.NetworkId << 16 | absoluteFireCount;
|
||||
|
||||
var projectile = ecb.Instantiate(prefab);
|
||||
|
||||
float3 planarDir = new float3(dir.x, 0f, dir.y);
|
||||
float3 spawnPos = xform.ValueRO.Position + planarDir * 0.6f;
|
||||
spawnPos.y = xform.ValueRO.Position.y;
|
||||
quaternion rot = quaternion.LookRotationSafe(planarDir, math.up());
|
||||
|
||||
ecb.SetComponent(projectile, LocalTransform.FromPositionRotation(spawnPos, rot));
|
||||
ecb.SetComponent(projectile, new GhostOwner { NetworkId = owner.ValueRO.NetworkId });
|
||||
// Snapshot the effective ability stats into the projectile (base + modifiers, computed
|
||||
// identically on both worlds), so the move/damage systems need no modifier lookup.
|
||||
ecb.SetComponent(projectile, new Projectile
|
||||
{
|
||||
Direction = math.normalize(dir),
|
||||
SpawnId = spawnId,
|
||||
Speed = eff.ValueRO.ProjectileSpeed,
|
||||
Damage = eff.ValueRO.Damage,
|
||||
Range = eff.ValueRO.Range,
|
||||
DistanceTravelled = 0f,
|
||||
});
|
||||
|
||||
// Earliest raw tick the player may fire again. Clamp cooldown to >= 1 tick.
|
||||
uint cooldownTicks = (uint)math.max(1, eff.ValueRO.CooldownTicks);
|
||||
cd.ValueRW.NextFireTick = serverTick.TickIndexForValidTick + cooldownTicks;
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
candidatePositions.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 473b7521bce4d4d1abc794bcd4e8e6fe
|
||||
@@ -0,0 +1,15 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Companion buffer on the AbilityDatabase singleton mapping an ability id to its projectile ghost
|
||||
/// prefab entity. Prefab/entity references are kept OUT of the blob (blob assets don't remap entity
|
||||
/// references); they are baked here via GetEntity, which the entity serializer patches correctly.
|
||||
/// </summary>
|
||||
public struct AbilityPrefabElement : IBufferElementData
|
||||
{
|
||||
public byte Id; // AbilityId
|
||||
public Entity Prefab;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91844bb3b4d7843318fc0cdbfe68d43e
|
||||
@@ -0,0 +1,15 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Which authored ability definition occupies this entity's primary slot - a light replicated key
|
||||
/// into the AbilityDatabase blob, replacing M2's inlined AbilityStats values. Replicated so an
|
||||
/// ability swap is server-authoritative and prediction-correct. <c>Id</c> stores an <see cref="AbilityId"/>.
|
||||
/// </summary>
|
||||
public struct AbilityRef : IComponentData
|
||||
{
|
||||
[GhostField] public byte Id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6ea08a11ef3d4afdb722b735ca3ed03
|
||||
@@ -0,0 +1,92 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure, deterministic auto-target assist helper for player abilities (M2 combat). Given a shooter
|
||||
/// position, a raw aim direction, and a set of candidate target positions, picks the best target
|
||||
/// inside an assist cone and snaps the shot toward it; otherwise returns the raw aim unchanged.
|
||||
/// <para>
|
||||
/// Authored as a <see langword="static"/> class (no state) so it is Burst-safe and allocation-free,
|
||||
/// callable from inside predicted/jobified systems. It is intended to run server-side only (see
|
||||
/// <c>AbilityFireSystem</c>) — the server's authoritative <c>Projectile.Direction</c> GhostField then
|
||||
/// reconciles the client's raw-aim predicted projectile. Determinism: no wall-clock, no randomness,
|
||||
/// pure math; ties are broken by smallest candidate index so identical inputs always yield identical
|
||||
/// output across the prediction loop and across machines.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class AutoTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the planar (XZ) direction a shot should take. Returns the normalized direction toward
|
||||
/// the nearest candidate within <paramref name="autoTargetRange"/> whose bearing from
|
||||
/// <paramref name="from"/> lies within <paramref name="coneHalfAngleRadians"/> of
|
||||
/// <paramref name="rawAimDir"/>; if no candidate qualifies, returns <paramref name="rawAimDir"/>
|
||||
/// unchanged.
|
||||
/// </summary>
|
||||
/// <param name="from">Shooter world position; only the XZ plane is considered.</param>
|
||||
/// <param name="rawAimDir">
|
||||
/// Caller-normalized planar aim direction (world XZ mapped to <c>float2(x, y)</c>). If it is
|
||||
/// effectively zero-length, it is returned unchanged (no valid heading to assist).
|
||||
/// </param>
|
||||
/// <param name="autoTargetRange">Max planar distance to consider a candidate; <c>0</c> (or less) disables assist.</param>
|
||||
/// <param name="coneHalfAngleRadians">Half-angle of the assist cone, measured from <paramref name="rawAimDir"/>.</param>
|
||||
/// <param name="candidatePositions">Candidate target world positions (XZ used). Read-only; not modified.</param>
|
||||
/// <returns>
|
||||
/// The normalized direction toward the chosen candidate, or <paramref name="rawAimDir"/> when no
|
||||
/// candidate qualifies. Ties on distance are broken by the smallest candidate index for determinism.
|
||||
/// </returns>
|
||||
public static float2 Resolve(float3 from, float2 rawAimDir, float autoTargetRange, float coneHalfAngleRadians,
|
||||
in NativeArray<float3> candidatePositions)
|
||||
{
|
||||
// No valid heading to assist along — caller guarantees normalization, but guard zero-length.
|
||||
if (math.lengthsq(rawAimDir) < 1e-6f)
|
||||
return rawAimDir;
|
||||
|
||||
// Disabled / nothing to consider.
|
||||
if (autoTargetRange <= 0f || candidatePositions.Length == 0)
|
||||
return rawAimDir;
|
||||
|
||||
float rangeSq = autoTargetRange * autoTargetRange;
|
||||
float cosCone = math.cos(coneHalfAngleRadians);
|
||||
|
||||
int bestIndex = -1;
|
||||
float bestDistSq = float.MaxValue;
|
||||
float2 bestDir = rawAimDir;
|
||||
|
||||
for (int i = 0; i < candidatePositions.Length; i++)
|
||||
{
|
||||
// Planar (XZ) offset from shooter to candidate.
|
||||
float3 offset = candidatePositions[i] - from;
|
||||
float2 planar = new float2(offset.x, offset.z);
|
||||
|
||||
float distSq = math.lengthsq(planar);
|
||||
|
||||
// Skip self / coincident candidates (effectively zero distance → undefined bearing).
|
||||
if (distSq < 1e-6f)
|
||||
continue;
|
||||
|
||||
// Out of range.
|
||||
if (distSq > rangeSq)
|
||||
continue;
|
||||
|
||||
// Bearing test: dot of unit bearing with the (unit) raw aim vs cos(half-angle).
|
||||
float2 dir = planar * math.rsqrt(distSq); // normalized planar bearing
|
||||
float dot = math.dot(dir, rawAimDir);
|
||||
if (dot < cosCone)
|
||||
continue; // outside the assist cone
|
||||
|
||||
// Nearest wins; strict less-than keeps the first (smallest-index) candidate on ties.
|
||||
if (distSq < bestDistSq)
|
||||
{
|
||||
bestDistSq = distSq;
|
||||
bestIndex = i;
|
||||
bestDir = dir;
|
||||
}
|
||||
}
|
||||
|
||||
return bestIndex >= 0 ? bestDir : rawAimDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac02264d177dd426e8f6972c7c3ceaae
|
||||
@@ -0,0 +1,20 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// One pending hit against a damageable entity, queued as a per-entity buffer element. The server
|
||||
/// appends a DamageEvent when a projectile hits (ProjectileDamageSystem), then HealthApplyDamageSystem
|
||||
/// drains the buffer once per tick to subtract from Health. Buffering decouples hit detection from
|
||||
/// health resolution and lets multiple simultaneous hits accumulate before being applied. Not
|
||||
/// replicated — only Health.Current is a GhostField; the buffer is server-side and cleared each tick.
|
||||
/// </summary>
|
||||
public struct DamageEvent : IBufferElementData
|
||||
{
|
||||
/// <summary>Damage to subtract from the target's Health.Current (world health units).</summary>
|
||||
public float Amount;
|
||||
|
||||
/// <summary>NetworkId of the firing player that caused this hit (attribution / self-hit filtering upstream).</summary>
|
||||
public int SourceNetworkId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 498738282d585418893b23454a4b88a0
|
||||
@@ -0,0 +1,20 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Per-entity effective ability stats: the authored base (from the AbilityDatabase blob keyed by
|
||||
/// AbilityRef) folded with the entity's StatModifier buffer by StatRecomputeSystem each predicted
|
||||
/// tick. Derived/local, NOT replicated - both worlds recompute it deterministically from the
|
||||
/// replicated modifier buffer, so it matches under prediction without being in the snapshot.
|
||||
/// </summary>
|
||||
public struct EffectiveAbilityStats : IComponentData
|
||||
{
|
||||
public float Damage;
|
||||
public float ProjectileSpeed;
|
||||
public float Range;
|
||||
public float AutoTargetRange;
|
||||
public float AutoTargetConeRadians;
|
||||
public int CooldownTicks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8bb3a5c343e74e7fb249e96c0c55fdc
|
||||
@@ -0,0 +1,16 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Per-entity effective character stats (movement + survivability): authored base (from the
|
||||
/// CharacterStats blob keyed by CharacterStatsRef) folded with the StatModifier buffer by
|
||||
/// StatRecomputeSystem each predicted tick. Derived/local, NOT replicated (see EffectiveAbilityStats).
|
||||
/// </summary>
|
||||
public struct EffectiveCharacterStats : IComponentData
|
||||
{
|
||||
public float MoveSpeed;
|
||||
public float TurnRateRadiansPerSec;
|
||||
public float MaxHealth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d8413eebbeda4761b33430128e7a437
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user