feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,32 @@
using System;
using System.Threading.Tasks;
namespace PCL.Core.UI.Animation.Clock;
public interface IClock
{
/// <summary>
/// 当前是否在运行。
/// </summary>
bool IsRunning { get; }
/// <summary>
/// 时钟频率 (FPS),如果为 <see cref="int.MaxValue"/> 则表示每次循环都触发 Tick 事件。
/// </summary>
int Fps { get; set; }
/// <summary>
/// 启动时钟。
/// </summary>
void Start();
/// <summary>
/// 停止时钟。
/// </summary>
void Stop();
/// <summary>
/// 每一帧触发的事件。
/// </summary>
event EventHandler<long>? Tick;
}
@@ -0,0 +1,9 @@
namespace PCL.Core.UI.Animation.Clock;
/// <summary>
///
/// </summary>
public interface IUIClock : IClock
{
}
@@ -0,0 +1,73 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using PCL.Core.Utils;
namespace PCL.Core.UI.Animation.Clock;
/// <summary>
/// 一个基于 Stopwatch 的时钟实现。
/// </summary>
/// <param name="fps">帧率。</param>
public class StopwatchClock(int fps = 60) : IClock, IDisposable
{
private CancellationTokenSource? _cts;
private long _lastStamp;
private long _lastFrame;
public event EventHandler<long>? Tick;
public int Fps { get; set; } = fps;
public bool IsRunning => _cts is not null && !_cts.IsCancellationRequested;
~StopwatchClock()
{
Dispose();
}
public void Start()
{
if (IsRunning) return;
_cts = new CancellationTokenSource();
_ = Task.Run(() =>
{
_lastStamp = FrameUtils.NowStamp();
while (!_cts.IsCancellationRequested)
{
if (Fps == int.MaxValue)
{
_lastFrame++;
Tick?.Invoke(this, _lastFrame);
}
else
{
var frame = FrameUtils.StampToFrameIndex(_lastStamp, Fps);
if (frame == _lastFrame) continue;
_lastFrame = frame;
Tick?.Invoke(this, frame);
}
}
}, _cts.Token);
}
public void Stop()
{
if (_cts is null) return;
_cts.Cancel();
_cts = null;
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
GC.SuppressFinalize(this);
}
}
@@ -0,0 +1,80 @@
using System;
using System.Runtime.InteropServices;
namespace PCL.Core.UI.Animation.Clock;
public sealed partial class WinMMClock(int fps = 60) : IClock, IDisposable
{
private uint _timerId;
private long _frameIndex;
private TimeProc? _callback;
public event EventHandler<long>? Tick;
public int Fps { get; set; } = fps;
public bool IsRunning { get; private set; }
~WinMMClock()
{
Dispose();
}
public void Start()
{
if (IsRunning) return;
IsRunning = true;
_frameIndex = 0;
// 计算帧间隔(毫秒)
var delay = (uint)Math.Max(1, 1000.0 / Fps);
// 定义回调函数
_callback = (_, _, _, _, _) =>
{
_frameIndex++;
Tick?.Invoke(this, _frameIndex);
};
// 设置定时器
_timerId = _TimeSetEvent(
delay,
0,
_callback,
IntPtr.Zero,
TimePeriodic | TimeCallbackFunction
);
}
public void Stop()
{
if (!IsRunning) return;
IsRunning = false;
// 停止定时器
if (_timerId != 0)
{
_TimeKillEvent(_timerId);
_timerId = 0;
}
_callback = null;
}
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
[LibraryImport("winmm.dll", EntryPoint = "timeSetEvent", SetLastError = true)]
private static partial uint _TimeSetEvent(uint uDelay, uint uResolution, TimeProc lpTimeProc, IntPtr dwUser, uint fuEvent);
[LibraryImport("winmm.dll", EntryPoint = "timeKillEvent", SetLastError = true)]
private static partial void _TimeKillEvent(uint uTimerId);
private delegate void TimeProc(uint id, uint msg, IntPtr user, IntPtr dw1, IntPtr dw2);
private const uint TimePeriodic = 0x0001;
private const uint TimeCallbackFunction = 0x0000;
}
@@ -0,0 +1,91 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using PCL.Core.Utils;
namespace PCL.Core.UI.Animation.Clock;
/// <summary>
/// 一个基于 WPF CompositionTarget.Rendering 事件的时钟实现。
/// 该时钟引发的所有事件均在 UI 线程上执行。
/// </summary>
public class WpfCompositionTargetRenderingClock(int fps = 60) : IUIClock, IDisposable
{
private CancellationTokenSource? _cts;
private TimeSpan _lastTime = TimeSpan.Zero;
private long _lastFrame;
public event EventHandler<long>? Tick;
public int Fps { get; set; } = fps;
public bool IsRunning => _cts is not null && !_cts.IsCancellationRequested;
~WpfCompositionTargetRenderingClock()
{
Dispose();
}
public void Start()
{
if (IsRunning) return;
_cts = new CancellationTokenSource();
CompositionTarget.Rendering += _OnCompositionTargetOnRendering;
}
private void _OnCompositionTargetOnRendering(object? _, EventArgs args)
{
if (args is not RenderingEventArgs renderingEventArgs) return;
if (_cts!.IsCancellationRequested)
{
CompositionTarget.Rendering -= _OnCompositionTargetOnRendering;
return;
}
if (Fps == int.MaxValue)
{
_lastFrame++;
Tick?.Invoke(this, _lastFrame);
}
else
{
if (_lastTime == TimeSpan.Zero)
{
_lastTime = renderingEventArgs.RenderingTime;
}
var frame = FrameUtils.TimeSpanToFrameIndex(_lastTime, renderingEventArgs.RenderingTime, Fps);
if (frame == _lastFrame) return;
_lastFrame = frame;
_lastTime = renderingEventArgs.RenderingTime;
Tick?.Invoke(this, frame);
}
}
public void Stop()
{
CompositionTarget.Rendering -= _OnCompositionTargetOnRendering;
if (_cts is null) return;
_cts.Cancel();
_cts = null;
}
public void Dispose()
{
CompositionTarget.Rendering -= _OnCompositionTargetOnRendering;
_cts?.Cancel();
_cts?.Dispose();
GC.SuppressFinalize(this);
}
}