Player/BotAvatar.cs
using System;
using System.Collections.Generic;
/// <summary>
/// bot 几何图案头像(M3):按 bot 名字 + 色号哈希**确定性生成**——同一 bot 在 host 与
/// 各客户端算出完全相同的图案,零同步成本。纯数学图样(同心环/辐射条/点阵/波纹),
/// 经 Texture.Create 程序化生成,颜色取霓虹调色板;背景透明(透出球体的霓虹底色)。
/// </summary>
public static class BotAvatar
{
const int Size = 128;
static readonly Dictionary<string, Texture> _cache = new();
/// <summary> 取 bot 头像:同 key 必得同一张;缓存超限整体清空(bot 数有限,实际到不了) </summary>
public static Texture GetOrCreate( string botName, int colorIndex )
{
var key = $"{botName}#{colorIndex}";
if ( _cache.TryGetValue( key, out var tex ) && tex.IsValid() ) return tex;
if ( _cache.Count > 64 ) _cache.Clear();
tex = Generate( botName ?? "", colorIndex );
_cache[key] = tex;
return tex;
}
static Texture Generate( string botName, int colorIndex )
{
// 稳定种子:FNV-1a 哈希(跨机器、跨进程一致)
ulong hash = 14695981039346656037UL;
foreach ( var ch in botName )
{
hash ^= ch;
hash *= 1099511628211UL;
}
var rng = new System.Random( (int)( hash & 0x7fffffff ) );
int pattern = rng.Next( 4 );
int variant = rng.Next( 3 ); // 频率/相位微调,同款图样也不重样
var baseColor = GameConfig.Palette[Math.Clamp( colorIndex, 0, GameConfig.Palette.Length - 1 )];
var px = new byte[Size * Size * 4];
int p = 0;
for ( int y = 0; y < Size; y++ )
{
for ( int x = 0; x < Size; x++ )
{
float dx = ( x - Size * 0.5f ) / ( Size * 0.5f );
float dy = ( y - Size * 0.5f ) / ( Size * 0.5f );
float r = MathF.Sqrt( dx * dx + dy * dy );
float a = MathF.Atan2( dy, dx );
float v = pattern switch
{
0 => Rings( r, variant ),
1 => Spokes( r, a, variant ),
2 => Dots( dx, dy, variant ),
_ => Waves( dx, dy, variant ),
};
// 圆形裁切 + 边缘 2px 抗锯齿;图样覆盖率 → 不透明度
float alpha = Math.Clamp( ( 0.96f - r ) / 0.05f, 0f, 1f );
float on = Math.Clamp( v, 0f, 1f ) * alpha;
float shade = 1.15f - r * 0.5f; // 中心亮边缘暗,带点立体感
px[p++] = (byte)( Math.Clamp( baseColor.r * shade, 0f, 1f ) * on * 255f );
px[p++] = (byte)( Math.Clamp( baseColor.g * shade, 0f, 1f ) * on * 255f );
px[p++] = (byte)( Math.Clamp( baseColor.b * shade, 0f, 1f ) * on * 255f );
px[p++] = (byte)( on * 255f );
}
}
return Texture.Create( Size, Size, ImageFormat.RGBA8888 )
.WithData( px )
.WithName( $"botavatar_{(uint)hash:X8}" )
.Finish();
}
/// <summary> 同心环:实心核心 + N 道环 </summary>
static float Rings( float r, int variant )
{
if ( r < 0.18f ) return 1f;
var freq = 2.6f + variant * 1.3f;
var f = r * freq - variant * 0.33f;
return Fract( f ) < 0.45f ? 1f : 0f;
}
/// <summary> 辐射条:从中心射出的辐条 + 实心核心 </summary>
static float Spokes( float r, float a, int variant )
{
if ( r < 0.2f ) return 1f;
var spokes = 6 + variant * 2;
var f = ( a / ( MathF.PI * 2f ) + 1f ) * spokes;
return Fract( f ) < 0.4f ? 1f : 0f;
}
/// <summary> 点阵:正弦调制的圆点网格 </summary>
static float Dots( float dx, float dy, int variant )
{
var freq = 7f + variant * 3f;
var phase = variant * 2.1f;
var v = MathF.Sin( dx * freq + phase ) * MathF.Sin( dy * freq - phase );
return v > 0.35f ? 1f : 0f;
}
/// <summary> 波纹:水平正弦波带 </summary>
static float Waves( float dx, float dy, int variant )
{
var freq = 5f + variant * 2.5f;
var phase = variant * 1.7f;
var band = dy + 0.32f * MathF.Sin( dx * freq + phase );
return MathF.Abs( band ) < 0.16f ? 1f : 0f;
}
static float Fract( float f ) => f - MathF.Floor( f );
}