Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Theriapolis.Core;
|
||||
using Theriapolis.Core.Entities;
|
||||
|
||||
namespace Theriapolis.Game.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Renders the player actor in either view. Both world-map and tactical use
|
||||
/// the same camera (world-pixel space), but the sprite is counter-scaled by
|
||||
/// 1/camera.Zoom so it stays a constant on-screen size at every zoom level.
|
||||
/// Otherwise the marker would become tiny when zoomed out and screen-filling
|
||||
/// at CAMERA_MAX_ZOOM.
|
||||
/// </summary>
|
||||
public sealed class PlayerSprite : IDisposable
|
||||
{
|
||||
private readonly GraphicsDevice _gd;
|
||||
private Texture2D _arrow = null!;
|
||||
private Texture2D _ring = null!;
|
||||
private bool _disposed;
|
||||
|
||||
// Texture size = target on-screen size. Convenient because the
|
||||
// counter-scale formula reduces to (1 / camera.Zoom) at draw time.
|
||||
private const int MarkerPx = C.PLAYER_MARKER_SCREEN_PX;
|
||||
|
||||
public PlayerSprite(GraphicsDevice gd)
|
||||
{
|
||||
_gd = gd;
|
||||
BuildTextures();
|
||||
}
|
||||
|
||||
private void BuildTextures()
|
||||
{
|
||||
// Filled arrow — orientation comes from sprite rotation at draw time.
|
||||
int s = MarkerPx;
|
||||
var arrow = new Color[s * s];
|
||||
var ring = new Color[s * s];
|
||||
float cx = (s - 1) * 0.5f, cy = (s - 1) * 0.5f, r = s * 0.5f - 1f;
|
||||
for (int y = 0; y < s; y++)
|
||||
for (int x = 0; x < s; x++)
|
||||
{
|
||||
float nx = x - cx, ny = y - cy;
|
||||
float dist = MathF.Sqrt(nx * nx + ny * ny);
|
||||
// Filled disc (body) plus a small notch on the +X side to indicate facing.
|
||||
bool body = dist <= r * 0.85f;
|
||||
bool notch = nx > 0 && MathF.Abs(ny) < (r - nx) * 0.6f;
|
||||
arrow[y * s + x] = body
|
||||
? (notch ? new Color(255, 230, 180) : new Color(220, 80, 60))
|
||||
: Color.Transparent;
|
||||
// Outer ring drawn beneath the body so it remains visible against
|
||||
// similarly-coloured terrain at low zoom (settlement icons sit
|
||||
// close to the marker on the world map).
|
||||
ring[y * s + x] = (dist > r * 0.85f && dist <= r) ? new Color(0, 0, 0, 200) : Color.Transparent;
|
||||
}
|
||||
_arrow = new Texture2D(_gd, s, s); _arrow.SetData(arrow);
|
||||
_ring = new Texture2D(_gd, s, s); _ring.SetData(ring);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch sb, Camera2D camera, Actor a)
|
||||
{
|
||||
sb.Begin(
|
||||
transformMatrix: camera.TransformMatrix,
|
||||
samplerState: SamplerState.PointClamp,
|
||||
sortMode: SpriteSortMode.Deferred,
|
||||
blendState: BlendState.AlphaBlend);
|
||||
|
||||
int s = MarkerPx;
|
||||
var origin = new Vector2(s * 0.5f, s * 0.5f);
|
||||
var pos = new Vector2(a.Position.X, a.Position.Y);
|
||||
|
||||
// Counter-scale by 1/Zoom so the camera transform's Zoom multiplier
|
||||
// cancels out, leaving a constant MarkerPx-pixel on-screen size.
|
||||
// Guard against zero just in case (shouldn't happen — clamped by C.CAMERA_MIN_ZOOM).
|
||||
float screenScale = camera.Zoom > 1e-6f ? 1f / camera.Zoom : 1f;
|
||||
|
||||
sb.Draw(_ring, pos, null, Color.White, 0f, origin, screenScale, SpriteEffects.None, 0f);
|
||||
sb.Draw(_arrow, pos, null, Color.White, a.FacingAngleRad, origin, screenScale, SpriteEffects.None, 0f);
|
||||
|
||||
sb.End();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_arrow?.Dispose();
|
||||
_ring?.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user