Files
LittleSim/spikes/KniWeb/SpikeScene.cs
T
Leonid PershinandClaude Fable 5 e8273f9ffa Multiplayer: networked dedicated server + desktop client over MrGameEng.Net
Engine bump brings MrGameEng.Net (WebSocket transport, RFC 6455 server,
delta component replication) — this commit is its showcase.

LittleSim.Server --listen runs the world online: 24 pawns wander, tire
and rest on the engine's fixed-tick HeadlessHost (the same Wander/
Needs/Decision systems the windowed world uses), a WebSocketServer
accepts clients and a ReplicationServer ships Transform2D + PawnNeeds
deltas at 10 snapshots/s per the shared NetSchema. --probe is the CLI
check: it connects, listens for a second and prints replicated pawns
with positions sampled twice to show the world is alive.

The game gains MultiplayerScene (dotnet run --project src/LittleSim --
--connect [ws://host:port]): simulation stays on the server, the client
applies snapshots into its scene store, decorates spawned entities with
sprites and reuses PawnAppearanceSystem so replicated fatigue darkens
pawns locally. HUD strings go through ru/en localization; the `net`
console command reports connection state and entity count. Esc returns
to the main menu.

Verified end to end: probe sees 24 pawns moving between samples; the
windowed client connects and runs against a live local server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:45:35 +03:00

150 lines
4.8 KiB
C#

using System;
using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MrGameEng.Core;
namespace KniWebSpike
{
// Компоненты симуляции — платформо-независимые, как в LittleSim.
public struct DotPosition : IComponent
{
public float X;
public float Y;
}
public struct DotVelocity : IComponent
{
public float X;
public float Y;
}
public struct DotTint : IComponent
{
public byte R;
public byte G;
public byte B;
}
/// <summary>
/// Сцена спайка: 300 «спрайтов» в Friflo EntityStore, движение в Update-фазе
/// (детерминированный seed, отскок от краёв), отрисовка в Draw-фазе через
/// KNI SpriteBatch (WebGL). Сцена и системные корни — из MrGameEng.Core.
/// </summary>
public sealed class SpikeScene : Scene
{
private readonly Func<SpriteBatch> _spriteBatch;
private readonly Func<Texture2D> _pixel;
private readonly float _width;
private readonly float _height;
public SpikeScene(
Func<SpriteBatch> spriteBatch,
Func<Texture2D> pixel,
float width,
float height
)
{
_spriteBatch = spriteBatch;
_pixel = pixel;
_width = width;
_height = height;
}
protected override void OnLoad()
{
var random = new Random(42);
for (var i = 0; i < 300; i++)
{
var angle = (float)(random.NextDouble() * Math.Tau);
var speed = 40f + (float)random.NextDouble() * 160f;
Store.CreateEntity(
new DotPosition
{
X = (float)random.NextDouble() * _width,
Y = (float)random.NextDouble() * _height,
},
new DotVelocity { X = MathF.Cos(angle) * speed, Y = MathF.Sin(angle) * speed },
new DotTint
{
R = (byte)random.Next(64, 256),
G = (byte)random.Next(64, 256),
B = (byte)random.Next(64, 256),
}
);
}
UpdateSystems.Add(new BounceSystem(_width, _height));
DrawSystems.Add(new DotDrawSystem(_spriteBatch, _pixel));
}
private sealed class BounceSystem : QuerySystem<DotPosition, DotVelocity>
{
private readonly float _width;
private readonly float _height;
public BounceSystem(float width, float height)
{
_width = width;
_height = height;
}
protected override void OnUpdate()
{
var delta = Tick.deltaTime;
var width = _width;
var height = _height;
Query.ForEachEntity(
(ref DotPosition pos, ref DotVelocity vel, Entity _) =>
{
pos.X += vel.X * delta;
pos.Y += vel.Y * delta;
if (pos.X < 0f || pos.X > width)
{
vel.X = -vel.X;
pos.X = Math.Clamp(pos.X, 0f, width);
}
if (pos.Y < 0f || pos.Y > height)
{
vel.Y = -vel.Y;
pos.Y = Math.Clamp(pos.Y, 0f, height);
}
}
);
}
}
private sealed class DotDrawSystem : QuerySystem<DotPosition, DotTint>
{
private readonly Func<SpriteBatch> _spriteBatch;
private readonly Func<Texture2D> _pixel;
public DotDrawSystem(Func<SpriteBatch> spriteBatch, Func<Texture2D> pixel)
{
_spriteBatch = spriteBatch;
_pixel = pixel;
}
protected override void OnUpdate()
{
var batch = _spriteBatch();
var pixel = _pixel();
batch.Begin();
Query.ForEachEntity(
(ref DotPosition pos, ref DotTint tint, Entity _) =>
{
batch.Draw(
pixel,
new Rectangle((int)pos.X, (int)pos.Y, 6, 6),
new Color(tint.R, tint.G, tint.B)
);
}
);
batch.End();
}
}
}
}