Add MrGameEng.AI utility-AI module; format codebase with CSharpier New MrGameEng.AI module (ResponseCurve, Consideration, UtilityAction, UtilityAi selector, Blackboard) plus CSharpier formatting applied across the whole engine. Documents the CSharpier convention in CLAUDE.md. @
This commit is contained in:
@@ -52,23 +52,35 @@ public readonly struct CameraState
|
||||
public static class CameraMath
|
||||
{
|
||||
/// <summary>Computes the full camera state for a frame.</summary>
|
||||
public static CameraState Compute(in Camera camera, int virtualWidth, int virtualHeight, ViewportMapping mapping)
|
||||
public static CameraState Compute(
|
||||
in Camera camera,
|
||||
int virtualWidth,
|
||||
int virtualHeight,
|
||||
ViewportMapping mapping
|
||||
)
|
||||
{
|
||||
var zoom = camera.Zoom <= 0f ? 1f : camera.Zoom;
|
||||
var position = ClampToBounds(camera, virtualWidth, virtualHeight, zoom);
|
||||
|
||||
var view =
|
||||
Matrix.CreateTranslation(-position.X, -position.Y, 0f) *
|
||||
Matrix.CreateRotationZ(-camera.Rotation) *
|
||||
Matrix.CreateScale(zoom, zoom, 1f) *
|
||||
Matrix.CreateTranslation(virtualWidth / 2f, virtualHeight / 2f, 0f);
|
||||
Matrix.CreateTranslation(-position.X, -position.Y, 0f)
|
||||
* Matrix.CreateRotationZ(-camera.Rotation)
|
||||
* Matrix.CreateScale(zoom, zoom, 1f)
|
||||
* Matrix.CreateTranslation(virtualWidth / 2f, virtualHeight / 2f, 0f);
|
||||
|
||||
var inverseView = Matrix.Invert(view);
|
||||
|
||||
return new CameraState
|
||||
{
|
||||
View = view,
|
||||
Projection = Matrix.CreateOrthographicOffCenter(0f, virtualWidth, virtualHeight, 0f, 0f, 1f),
|
||||
Projection = Matrix.CreateOrthographicOffCenter(
|
||||
0f,
|
||||
virtualWidth,
|
||||
virtualHeight,
|
||||
0f,
|
||||
0f,
|
||||
1f
|
||||
),
|
||||
InverseView = inverseView,
|
||||
CullRect = ComputeCullRect(inverseView, virtualWidth, virtualHeight),
|
||||
VirtualWidth = virtualWidth,
|
||||
@@ -81,14 +93,29 @@ public static class CameraMath
|
||||
/// Computes the letterbox mapping that fits the virtual resolution into a physical
|
||||
/// viewport, preserving aspect ratio and centering.
|
||||
/// </summary>
|
||||
public static ViewportMapping ComputeMapping(int screenWidth, int screenHeight, int virtualWidth, int virtualHeight)
|
||||
public static ViewportMapping ComputeMapping(
|
||||
int screenWidth,
|
||||
int screenHeight,
|
||||
int virtualWidth,
|
||||
int virtualHeight
|
||||
)
|
||||
{
|
||||
var scale = MathF.Min((float)screenWidth / virtualWidth, (float)screenHeight / virtualHeight);
|
||||
var offset = new Vector2(screenWidth - virtualWidth * scale, screenHeight - virtualHeight * scale) / 2f;
|
||||
var scale = MathF.Min(
|
||||
(float)screenWidth / virtualWidth,
|
||||
(float)screenHeight / virtualHeight
|
||||
);
|
||||
var offset =
|
||||
new Vector2(screenWidth - virtualWidth * scale, screenHeight - virtualHeight * scale)
|
||||
/ 2f;
|
||||
return new ViewportMapping(offset, scale);
|
||||
}
|
||||
|
||||
private static Vector2 ClampToBounds(in Camera camera, int virtualWidth, int virtualHeight, float zoom)
|
||||
private static Vector2 ClampToBounds(
|
||||
in Camera camera,
|
||||
int virtualWidth,
|
||||
int virtualHeight,
|
||||
float zoom
|
||||
)
|
||||
{
|
||||
if (camera.Bounds is not { } bounds)
|
||||
{
|
||||
@@ -100,7 +127,8 @@ public static class CameraMath
|
||||
var halfH = virtualHeight / (2f * zoom);
|
||||
return new Vector2(
|
||||
ClampAxis(camera.Position.X, bounds.Left + halfW, bounds.Right - halfW),
|
||||
ClampAxis(camera.Position.Y, bounds.Top + halfH, bounds.Bottom - halfH));
|
||||
ClampAxis(camera.Position.Y, bounds.Top + halfH, bounds.Bottom - halfH)
|
||||
);
|
||||
}
|
||||
|
||||
private static float ClampAxis(float value, float min, float max) =>
|
||||
|
||||
@@ -10,7 +10,11 @@ public static class CullingMath
|
||||
/// (valid for any rotation), given its transform, region size in pixels and origin.
|
||||
/// </summary>
|
||||
public static (Vector2 Center, float Radius) SpriteBoundingCircle(
|
||||
in Transform2D transform, float regionWidth, float regionHeight, Vector2 origin)
|
||||
in Transform2D transform,
|
||||
float regionWidth,
|
||||
float regionHeight,
|
||||
Vector2 origin
|
||||
)
|
||||
{
|
||||
var scaledW = regionWidth * transform.Scale.X;
|
||||
var scaledH = regionHeight * transform.Scale.Y;
|
||||
@@ -24,20 +28,33 @@ public static class CullingMath
|
||||
/// diagonal (no square root per sprite; conservative for non-uniform scale, exact for uniform).
|
||||
/// </summary>
|
||||
public static (Vector2 Center, float Radius) SpriteBoundingCircle(
|
||||
in Transform2D transform, Texture2DRegion region, Vector2 origin)
|
||||
in Transform2D transform,
|
||||
Texture2DRegion region,
|
||||
Vector2 origin
|
||||
)
|
||||
{
|
||||
var center = SpriteCenter(
|
||||
in transform, region.Width * transform.Scale.X, region.Height * transform.Scale.Y, origin);
|
||||
in transform,
|
||||
region.Width * transform.Scale.X,
|
||||
region.Height * transform.Scale.Y,
|
||||
origin
|
||||
);
|
||||
var maxScale = MathF.Max(MathF.Abs(transform.Scale.X), MathF.Abs(transform.Scale.Y));
|
||||
return (center, 0.5f * region.Diagonal * maxScale);
|
||||
}
|
||||
|
||||
private static Vector2 SpriteCenter(in Transform2D transform, float scaledW, float scaledH, Vector2 origin)
|
||||
private static Vector2 SpriteCenter(
|
||||
in Transform2D transform,
|
||||
float scaledW,
|
||||
float scaledH,
|
||||
Vector2 origin
|
||||
)
|
||||
{
|
||||
// Offset from the pivot (= transform.Position) to the sprite's geometric center.
|
||||
var toCenter = new Vector2(
|
||||
scaledW / 2f - origin.X * transform.Scale.X,
|
||||
scaledH / 2f - origin.Y * transform.Scale.Y);
|
||||
scaledH / 2f - origin.Y * transform.Scale.Y
|
||||
);
|
||||
|
||||
if (transform.Rotation == 0f)
|
||||
{
|
||||
@@ -45,9 +62,8 @@ public static class CullingMath
|
||||
}
|
||||
|
||||
var (sin, cos) = MathF.SinCos(transform.Rotation);
|
||||
return transform.Position + new Vector2(
|
||||
toCenter.X * cos - toCenter.Y * sin,
|
||||
toCenter.X * sin + toCenter.Y * cos);
|
||||
return transform.Position
|
||||
+ new Vector2(toCenter.X * cos - toCenter.Y * sin, toCenter.X * sin + toCenter.Y * cos);
|
||||
}
|
||||
|
||||
/// <summary>True when the circle overlaps the rectangle.</summary>
|
||||
|
||||
@@ -52,14 +52,20 @@ public sealed class LayerRegistry
|
||||
public int Count => _layers.Length;
|
||||
|
||||
/// <summary>Registers a layer drawn after all previously registered ones.</summary>
|
||||
public LayerId Register(string name, LayerSpace space = LayerSpace.World, LayerSortMode sortMode = LayerSortMode.Depth)
|
||||
public LayerId Register(
|
||||
string name,
|
||||
LayerSpace space = LayerSpace.World,
|
||||
LayerSortMode sortMode = LayerSortMode.Depth
|
||||
)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
var layers = _layers;
|
||||
if (layers.Length == 256)
|
||||
{
|
||||
throw new InvalidOperationException("Maximum number of render layers (256) reached.");
|
||||
throw new InvalidOperationException(
|
||||
"Maximum number of render layers (256) reached."
|
||||
);
|
||||
}
|
||||
|
||||
var id = new LayerId((byte)layers.Length);
|
||||
@@ -80,7 +86,9 @@ public sealed class LayerRegistry
|
||||
return id.Value < layers.Length
|
||||
? layers[id.Value]
|
||||
: throw new ArgumentOutOfRangeException(
|
||||
nameof(id), $"Render layer {id.Value} is not registered (registered: {layers.Length}).");
|
||||
nameof(id),
|
||||
$"Render layer {id.Value} is not registered (registered: {layers.Length})."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
@@ -7,5 +6,4 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MrGameEng.Core\MrGameEng.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -96,20 +96,24 @@ public sealed class SpriteRenderSystem : QuerySystem<Sprite, Transform2D>
|
||||
}
|
||||
|
||||
_renderer.BeginChunkedSubmit(_segmentLengths.AsSpan(0, _segments.Count));
|
||||
Parallel.For(0, _segments.Count, segmentIndex =>
|
||||
{
|
||||
var (chunk, start, length) = _segments[segmentIndex];
|
||||
var (sprites, transforms) = _chunks[chunk];
|
||||
var writer = _renderer.GetChunkWriter(segmentIndex);
|
||||
var s = sprites.Span.Slice(start, length);
|
||||
var t = transforms.Span.Slice(start, length);
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
Parallel.For(
|
||||
0,
|
||||
_segments.Count,
|
||||
segmentIndex =>
|
||||
{
|
||||
_renderer.SubmitInto(ref writer, in t[i], in s[i]);
|
||||
}
|
||||
var (chunk, start, length) = _segments[segmentIndex];
|
||||
var (sprites, transforms) = _chunks[chunk];
|
||||
var writer = _renderer.GetChunkWriter(segmentIndex);
|
||||
var s = sprites.Span.Slice(start, length);
|
||||
var t = transforms.Span.Slice(start, length);
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
{
|
||||
_renderer.SubmitInto(ref writer, in t[i], in s[i]);
|
||||
}
|
||||
|
||||
_renderer.EndChunk(segmentIndex, in writer);
|
||||
});
|
||||
_renderer.EndChunk(segmentIndex, in writer);
|
||||
}
|
||||
);
|
||||
_renderer.CommitChunkedSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ public sealed class Renderer2D : IDisposable
|
||||
private const int MaxQuadsPerDraw = 8192;
|
||||
private const int ParallelBlock = 4096;
|
||||
|
||||
private static readonly int VertexStride = VertexPositionColorTexture.VertexDeclaration.VertexStride;
|
||||
private static readonly int VertexStride = VertexPositionColorTexture
|
||||
.VertexDeclaration
|
||||
.VertexStride;
|
||||
|
||||
/// <summary>Render layer registry. Register layers before the first frame.</summary>
|
||||
public LayerRegistry Layers { get; } = new();
|
||||
@@ -71,7 +73,11 @@ public sealed class Renderer2D : IDisposable
|
||||
_batcher = new SpriteBatcher(_options.InitialCapacity);
|
||||
_vertices = new VertexPositionColorTexture[_options.InitialCapacity * 4];
|
||||
_vertexBuffer = new DynamicVertexBuffer(
|
||||
device, VertexPositionColorTexture.VertexDeclaration, _vertices.Length * 2, BufferUsage.WriteOnly);
|
||||
device,
|
||||
VertexPositionColorTexture.VertexDeclaration,
|
||||
_vertices.Length * 2,
|
||||
BufferUsage.WriteOnly
|
||||
);
|
||||
|
||||
_effect = new BasicEffect(device)
|
||||
{
|
||||
@@ -91,7 +97,11 @@ public sealed class Renderer2D : IDisposable
|
||||
var (virtualW, virtualH, mapping) = ResolveVirtualResolution();
|
||||
Camera = CameraMath.Compute(camera, virtualW, virtualH, mapping);
|
||||
_screenCamera = CameraMath.Compute(
|
||||
new Camera(new Vector2(virtualW / 2f, virtualH / 2f)), virtualW, virtualH, mapping);
|
||||
new Camera(new Vector2(virtualW / 2f, virtualH / 2f)),
|
||||
virtualW,
|
||||
virtualH,
|
||||
mapping
|
||||
);
|
||||
|
||||
_batcher.Clear();
|
||||
SubmittedSprites = 0;
|
||||
@@ -155,14 +165,18 @@ public sealed class Renderer2D : IDisposable
|
||||
if (count >= _options.ParallelThreshold)
|
||||
{
|
||||
var blocks = (count + ParallelBlock - 1) / ParallelBlock;
|
||||
Parallel.For(0, blocks, block =>
|
||||
{
|
||||
var end = Math.Min((block + 1) * ParallelBlock, count);
|
||||
for (var i = block * ParallelBlock; i < end; i++)
|
||||
Parallel.For(
|
||||
0,
|
||||
blocks,
|
||||
block =>
|
||||
{
|
||||
BuildVertex(order, i);
|
||||
var end = Math.Min((block + 1) * ParallelBlock, count);
|
||||
for (var i = block * ParallelBlock; i < end; i++)
|
||||
{
|
||||
BuildVertex(order, i);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -190,7 +204,14 @@ public sealed class Renderer2D : IDisposable
|
||||
hint = SetDataOptions.Discard;
|
||||
}
|
||||
|
||||
_vertexBuffer.SetData(_ringBaseVertex * VertexStride, _vertices, 0, vertexCount, VertexStride, hint);
|
||||
_vertexBuffer.SetData(
|
||||
_ringBaseVertex * VertexStride,
|
||||
_vertices,
|
||||
0,
|
||||
vertexCount,
|
||||
VertexStride,
|
||||
hint
|
||||
);
|
||||
_ringCursor = _ringBaseVertex + vertexCount;
|
||||
var uploadEnd = Stopwatch.GetTimestamp();
|
||||
UploadMs = ToMs(uploadEnd - buildEnd);
|
||||
@@ -213,7 +234,8 @@ public sealed class Renderer2D : IDisposable
|
||||
(int)MathF.Round(mapping.Offset.X),
|
||||
(int)MathF.Round(mapping.Offset.Y),
|
||||
(int)MathF.Round(Camera.VirtualWidth * mapping.Scale),
|
||||
(int)MathF.Round(Camera.VirtualHeight * mapping.Scale));
|
||||
(int)MathF.Round(Camera.VirtualHeight * mapping.Scale)
|
||||
);
|
||||
}
|
||||
|
||||
DrawBatches(order, count);
|
||||
@@ -247,9 +269,14 @@ public sealed class Renderer2D : IDisposable
|
||||
_batcher.BeginChunks(chunkLengths);
|
||||
}
|
||||
|
||||
internal SpriteChunkWriter GetChunkWriter(int chunkIndex) => _batcher.GetChunkWriter(chunkIndex);
|
||||
internal SpriteChunkWriter GetChunkWriter(int chunkIndex) =>
|
||||
_batcher.GetChunkWriter(chunkIndex);
|
||||
|
||||
internal void SubmitInto(ref SpriteChunkWriter writer, in Transform2D transform, in Sprite sprite)
|
||||
internal void SubmitInto(
|
||||
ref SpriteChunkWriter writer,
|
||||
in Transform2D transform,
|
||||
in Sprite sprite
|
||||
)
|
||||
{
|
||||
switch (TryBuildInstance(in transform, in sprite, out var instance, out var key))
|
||||
{
|
||||
@@ -262,7 +289,8 @@ public sealed class Renderer2D : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal void EndChunk(int chunkIndex, in SpriteChunkWriter writer) => _batcher.EndChunk(chunkIndex, in writer);
|
||||
internal void EndChunk(int chunkIndex, in SpriteChunkWriter writer) =>
|
||||
_batcher.EndChunk(chunkIndex, in writer);
|
||||
|
||||
internal void CommitChunkedSubmit()
|
||||
{
|
||||
@@ -278,7 +306,11 @@ public sealed class Renderer2D : IDisposable
|
||||
}
|
||||
|
||||
private SubmitResult TryBuildInstance(
|
||||
in Transform2D transform, in Sprite sprite, out SpriteInstance instance, out ulong key)
|
||||
in Transform2D transform,
|
||||
in Sprite sprite,
|
||||
out SpriteInstance instance,
|
||||
out ulong key
|
||||
)
|
||||
{
|
||||
instance = default;
|
||||
key = 0;
|
||||
@@ -288,10 +320,16 @@ public sealed class Renderer2D : IDisposable
|
||||
}
|
||||
|
||||
var layer = Layers[sprite.Layer];
|
||||
var (center, radius) = CullingMath.SpriteBoundingCircle(in transform, region, sprite.Origin);
|
||||
var (center, radius) = CullingMath.SpriteBoundingCircle(
|
||||
in transform,
|
||||
region,
|
||||
sprite.Origin
|
||||
);
|
||||
|
||||
if (layer.Space == LayerSpace.World &&
|
||||
!CullingMath.CircleIntersectsRect(center, radius, Camera.CullRect))
|
||||
if (
|
||||
layer.Space == LayerSpace.World
|
||||
&& !CullingMath.CircleIntersectsRect(center, radius, Camera.CullRect)
|
||||
)
|
||||
{
|
||||
return SubmitResult.Culled;
|
||||
}
|
||||
@@ -304,7 +342,9 @@ public sealed class Renderer2D : IDisposable
|
||||
{
|
||||
Region = region,
|
||||
Center = center,
|
||||
HalfSize = new Vector2(region.Width * transform.Scale.X, region.Height * transform.Scale.Y) / 2f,
|
||||
HalfSize =
|
||||
new Vector2(region.Width * transform.Scale.X, region.Height * transform.Scale.Y)
|
||||
/ 2f,
|
||||
Rotation = transform.Rotation,
|
||||
Color = sprite.Color,
|
||||
Flip = sprite.Flip,
|
||||
@@ -319,7 +359,8 @@ public sealed class Renderer2D : IDisposable
|
||||
if (!_begun)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Renderer used outside BeginFrame/EndFrame (is CameraSystem registered first?).");
|
||||
"Renderer used outside BeginFrame/EndFrame (is CameraSystem registered first?)."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,8 +372,11 @@ public sealed class Renderer2D : IDisposable
|
||||
return (viewport.Width, viewport.Height, ViewportMapping.Identity);
|
||||
}
|
||||
|
||||
return (virtualSize.X, virtualSize.Y,
|
||||
CameraMath.ComputeMapping(viewport.Width, viewport.Height, virtualSize.X, virtualSize.Y));
|
||||
return (
|
||||
virtualSize.X,
|
||||
virtualSize.Y,
|
||||
CameraMath.ComputeMapping(viewport.Width, viewport.Height, virtualSize.X, virtualSize.Y)
|
||||
);
|
||||
}
|
||||
|
||||
private void BuildVertex(int[] order, int i)
|
||||
@@ -355,7 +399,8 @@ public sealed class Renderer2D : IDisposable
|
||||
(v0, v1) = (v1, v0);
|
||||
}
|
||||
|
||||
Vector2 rx, ry;
|
||||
Vector2 rx,
|
||||
ry;
|
||||
if (instance.Rotation == 0f)
|
||||
{
|
||||
rx = new Vector2(instance.HalfSize.X, 0f);
|
||||
@@ -432,7 +477,11 @@ public sealed class Renderer2D : IDisposable
|
||||
{
|
||||
pass.Apply();
|
||||
_device.DrawIndexedPrimitives(
|
||||
PrimitiveType.TriangleList, _ringBaseVertex + firstQuad * 4, 0, quads * 2);
|
||||
PrimitiveType.TriangleList,
|
||||
_ringBaseVertex + firstQuad * 4,
|
||||
0,
|
||||
quads * 2
|
||||
);
|
||||
DrawCalls++;
|
||||
}
|
||||
|
||||
@@ -461,15 +510,24 @@ public sealed class Renderer2D : IDisposable
|
||||
{
|
||||
_vertexBuffer.Dispose();
|
||||
_vertexBuffer = new DynamicVertexBuffer(
|
||||
_device, VertexPositionColorTexture.VertexDeclaration, wantedBuffer, BufferUsage.WriteOnly);
|
||||
_device,
|
||||
VertexPositionColorTexture.VertexDeclaration,
|
||||
wantedBuffer,
|
||||
BufferUsage.WriteOnly
|
||||
);
|
||||
_ringCursor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static float ToMs(long timestampDelta) => (float)timestampDelta * 1000f / Stopwatch.Frequency;
|
||||
private static float ToMs(long timestampDelta) =>
|
||||
(float)timestampDelta * 1000f / Stopwatch.Frequency;
|
||||
|
||||
private static VertexPositionColorTexture Vertex(Vector2 position, Color color, float u, float v) =>
|
||||
new(new Vector3(position, 0f), color, new Vector2(u, v));
|
||||
private static VertexPositionColorTexture Vertex(
|
||||
Vector2 position,
|
||||
Color color,
|
||||
float u,
|
||||
float v
|
||||
) => new(new Vector3(position, 0f), color, new Vector2(u, v));
|
||||
|
||||
private static IndexBuffer CreateQuadIndexBuffer(GraphicsDevice device)
|
||||
{
|
||||
@@ -486,7 +544,12 @@ public sealed class Renderer2D : IDisposable
|
||||
indices[index + 5] = (ushort)(vertex + 3);
|
||||
}
|
||||
|
||||
var buffer = new IndexBuffer(device, IndexElementSize.SixteenBits, indices.Length, BufferUsage.WriteOnly);
|
||||
var buffer = new IndexBuffer(
|
||||
device,
|
||||
IndexElementSize.SixteenBits,
|
||||
indices.Length,
|
||||
BufferUsage.WriteOnly
|
||||
);
|
||||
buffer.SetData(indices);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ public static class SceneGraphicsExtensions
|
||||
/// is created on first use and shared between scenes. Call from <c>OnLoad</c>.
|
||||
/// </summary>
|
||||
public static Renderer2D UseRenderer2D(
|
||||
this Scene scene, Renderer2DOptions? options = null, params BaseSystem[] extraDrawSystems)
|
||||
this Scene scene,
|
||||
Renderer2DOptions? options = null,
|
||||
params BaseSystem[] extraDrawSystems
|
||||
)
|
||||
{
|
||||
var services = scene.Context.Services;
|
||||
var renderer = services.GetOrDefault<Renderer2D>();
|
||||
@@ -25,8 +28,9 @@ public static class SceneGraphicsExtensions
|
||||
else if (options is not null)
|
||||
{
|
||||
Log.Warning(
|
||||
"UseRenderer2D: the renderer already exists, the passed options are ignored " +
|
||||
"(Renderer2D is a shared service configured by its first user).");
|
||||
"UseRenderer2D: the renderer already exists, the passed options are ignored "
|
||||
+ "(Renderer2D is a shared service configured by its first user)."
|
||||
);
|
||||
}
|
||||
|
||||
scene.DrawSystems.Add(new CameraSystem(renderer));
|
||||
|
||||
@@ -19,11 +19,18 @@ public sealed class SpriteAnimationClip
|
||||
public float Duration => Frames.Count / FramesPerSecond;
|
||||
|
||||
/// <summary>Creates a clip.</summary>
|
||||
public SpriteAnimationClip(IReadOnlyList<Texture2DRegion> frames, float framesPerSecond = 12f, bool loop = true)
|
||||
public SpriteAnimationClip(
|
||||
IReadOnlyList<Texture2DRegion> frames,
|
||||
float framesPerSecond = 12f,
|
||||
bool loop = true
|
||||
)
|
||||
{
|
||||
if (frames.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("An animation clip needs at least one frame.", nameof(frames));
|
||||
throw new ArgumentException(
|
||||
"An animation clip needs at least one frame.",
|
||||
nameof(frames)
|
||||
);
|
||||
}
|
||||
|
||||
Frames = frames;
|
||||
|
||||
@@ -130,7 +130,8 @@ public sealed class SpriteBatcher
|
||||
|
||||
// Биты, различающиеся хотя бы у одной пары ключей: проходы по одинаковым
|
||||
// разрядам (один слой, одна глубина) пропускаются целиком.
|
||||
ulong orBits = 0, andBits = ~0UL;
|
||||
ulong orBits = 0,
|
||||
andBits = ~0UL;
|
||||
for (var i = 0; i < n; i++)
|
||||
{
|
||||
orBits |= _keys[i];
|
||||
|
||||
@@ -9,7 +9,9 @@ public static class SpriteSortKey
|
||||
{
|
||||
/// <summary>Composes a sort key from layer, depth and texture grouping key.</summary>
|
||||
public static ulong Make(byte layer, float depth, int textureKey) =>
|
||||
((ulong)layer << 56) | ((ulong)DepthToSortableBits(depth) << 24) | ((uint)textureKey & 0xFF_FFFF);
|
||||
((ulong)layer << 56)
|
||||
| ((ulong)DepthToSortableBits(depth) << 24)
|
||||
| ((uint)textureKey & 0xFF_FFFF);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a float to bits whose unsigned order matches the float order
|
||||
|
||||
@@ -35,7 +35,9 @@ public sealed class Texture2DRegion
|
||||
Texture = texture;
|
||||
Bounds = bounds;
|
||||
TextureSortKey = System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(texture);
|
||||
Diagonal = MathF.Sqrt((float)bounds.Width * bounds.Width + (float)bounds.Height * bounds.Height);
|
||||
Diagonal = MathF.Sqrt(
|
||||
(float)bounds.Width * bounds.Width + (float)bounds.Height * bounds.Height
|
||||
);
|
||||
|
||||
// UV предрассчитаны один раз — в кадре на каждый спрайт экономятся 4 деления.
|
||||
// texture может быть null только в headless-тестах.
|
||||
@@ -50,7 +52,5 @@ public sealed class Texture2DRegion
|
||||
|
||||
/// <summary>Creates a region covering the whole <paramref name="texture"/>.</summary>
|
||||
public Texture2DRegion(Texture2D texture)
|
||||
: this(texture, new Rectangle(0, 0, texture.Width, texture.Height))
|
||||
{
|
||||
}
|
||||
: this(texture, new Rectangle(0, 0, texture.Width, texture.Height)) { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user