Files
LittleSim/spikes/KniWeb/SpikeScene.cs
T
Leonid PershinandClaude Fable 5 ce8f8ba2ed KNI spike: engine core + Friflo render via WebGL in the browser
spikes/KniWeb (outside LittleSim.sln): a kni-blazor-gl template project
(KNI 4.2.9001, net8.0) referencing MrGameEng.Core directly. A mini-host
in the GameHost mold drives EngineContext/GameClock/Scene phases over
KNI's Game; the scene moves 300 Friflo entities in the update phase and
draws them with SpriteBatch (WebGL). Verified in a real browser: sprites
render and animate, browser console is clean.

Decision (docs/web-client.md): path A — KNI — is the primary route for
the web client; the core runs in Blazor WASM unchanged thanks to the
Core/Host split. Known follow-ups: per-platform compilation of the
graphics libraries against nkast.* packages, shader compatibility for
Renderer2D, HTTP-served content instead of the filesystem.

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

149 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();
}
}
}
}