feat(monster): 优化怪物颜色矩阵生成逻辑以避免单色问题

重构了颜色矩阵生成函数,引入新模式避免生成单一颜色图像。新模式通过确保至少一个通道依赖输入,并对常量偏移进行去重处理,提升颜色多样性。同时删除了对 time 包的依赖,改用 gf 框架内置随机函数。

```
This commit is contained in:
2025-12-14 19:55:38 +08:00
parent 3f059c71fa
commit b6c3ff53aa
2 changed files with 138 additions and 53 deletions

View File

@@ -6,7 +6,6 @@ import (
"blazing/modules/blazing/model"
"strings"
"sync/atomic"
"time"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
@@ -71,7 +70,7 @@ func (p *Player) genMonster() {
Knockout: false,
// 颜色矩阵:标准 RGBA 矩阵20个uint8符合 [20]uint8 数组类型)
// 矩阵含义R=100%、G=100%、B=100%、A=100%,无颜色偏移
ColorMatrixFilter: RandomControlledMatrix_IdentityAlpha(time.Now().UnixNano() + int64(ttt.Id)),
ColorMatrixFilter: RandomMatrixDefaultAlphaNoSingleColor(int64(grand.Intn(5000)), 0.8),
}
// g.Dump(ttt.ShinyInfo)
// ttt.Shiny = 0 //待确认是否刷新异色

View File

@@ -4,62 +4,148 @@ import (
grand "math/rand"
)
// 生成 [20]uint8最后 5 个字节固定为 [0,0,0,1,0]
// 适用于“byte 直接作为系数1 表示 1.0)”的场景。
// 其它值在接近单位矩阵的范围内随机,尽量避免白化。
func RandomControlledMatrix_IdentityAlpha(seed int64) [20]uint8 {
// RandomMatrixNoSingleColor 生成不会导致“只有一种颜色”的 4x5 uint8 矩阵共20字节
// 约束与策略:
// - 仍然适用“直接把 uint8 原样当系数使用”的前提,保证不会白化或溢出:
// - 当某行有 multiplier != 0 时offset 强制为 0且 multipliers 最大为 1 且每行最多一个 1L1<=1
// - 当某行使用常量偏移模式ModeB四个 multiplier 全为 0offset 为常量(在安全中间区)
//
// - 为避免“只有一种颜色”的情况,我们强制:
// - 至少有一个行使用 ModeA依赖输入通道。若有多个 ModeA则这些 ModeA 的“pick”尽量互不相同从 0..3 中抽样不重复),
// 这样 destR/destG/destB 至少有不同的输入来源,从而随输入像素变化产生多色彩输出。
// - 如果有多个 ModeB常量行它们的 offset 不相同,且不同时全为相同值,避免所有通道都变为同一常量。
//
// - alphaRow (最后5字节) 由调用方指定并固定不变(默认 [0,0,0,1,0])。
//
// brightChance: 0..1,用于决定每行是否优先使用常量偏移 (ModeB);函数内部会修正以保证多样性。
func RandomMatrixNoSingleColor(seed int64, alphaRow [5]uint8, brightChance float64) [20]uint8 {
var out [20]uint8
// clamp brightChance
if brightChance < 0 {
brightChance = 0
}
if brightChance > 1 {
brightChance = 1
}
// offset 范围(中间区,避免接近 0 或 255
const minOffset = 40
const maxOffset = 215
grand.Seed(seed)
// 更保守范围(在直接 uint8 当系数时1 表示 1.0
diagMin, diagMax := 0.95, 1.05 // 对角线乘数(接近 1
crossMin, crossMax := -1.0, 1.0 // 跨通道系数(小整数,可能为负,下面做 signed->uint8 映射)
offsetMin, offsetMax := -8.0, 8.0 // 偏移(以整数为单位)
idx := 0
for row := 0; row < 3; row++ { // 只生成前三行,最后一行固定
for col := 0; col < 5; col++ {
var b uint8
if col < 4 {
// multiplier (float around 1.0) -> round to nearest int (since direct uint8 coeff)
var v float64
if col == row {
v = diagMin + grand.Float64()*(diagMax-diagMin)
} else {
v = crossMin + grand.Float64()*(crossMax-crossMin)
}
// 四舍五入到最近整数并允许负数:用 int8 再转为 uint8保持二进制两补表示
iv := int(v + 0.5)
// clamp to int8 range
if iv < -128 {
iv = -128
}
if iv > 127 {
iv = 127
}
b = uint8(int8(iv))
} else {
// offset 也用 int8 存(-128..127),转为 uint8 的二进制表示
off := offsetMin + grand.Float64()*(offsetMax-offsetMin)
ioff := int(off + 0.5)
if ioff < -128 {
ioff = -128
}
if ioff > 127 {
ioff = 127
}
b = uint8(int8(ioff))
}
out[idx] = b
idx++
// 初步决定每行的模式true=ModeB(常量偏移)false=ModeA(拷贝/置换)
modeB := make([]bool, 3)
modeBCount := 0
for r := 0; r < 3; r++ {
if grand.Float64() < brightChance {
modeB[r] = true
modeBCount++
} else {
modeB[r] = false
}
}
// 明确设置 alpha 行为 [0,0,0,1,0](直接 uint8 值)
out[15] = uint8(0) // a00
out[16] = uint8(0) // a01
out[17] = uint8(0) // a02
out[18] = uint8(1) // a03 (alpha multiplier = 1)
out[19] = uint8(0) // a04 (alpha offset = 0)
// 强制至少一个 ModeA依赖输入以避免全常量图像
if modeBCount == 3 {
// 随机把一行改为 ModeA
which := int(grand.Intn(3))
modeB[which] = false
modeBCount--
}
// 为 ModeA 行分配不重复的 pick0..3),尽量保证不同输入来源
// 先收集需要 pick 的行索引
pickRows := make([]int, 0, 3)
for r := 0; r < 3; r++ {
if !modeB[r] {
pickRows = append(pickRows, r)
}
}
// available inputs 0..3
available := []int{0, 1, 2, 3}
// 随机打乱 available
for i := len(available) - 1; i > 0; i-- {
j := int(grand.Intn(i + 1))
available[i], available[j] = available[j], available[i]
}
// 给每个需要 pick 的行分配不同输入(如果 pickRows 数量 <= 4 则肯定可不重复)
picks := make(map[int]int) // row -> pick
for i, row := range pickRows {
picks[row] = available[i%len(available)]
}
// 为 ModeB 行分配 offsets保证多个 ModeB 行的 offset 不相同且不等于边界值
usedOffsets := map[int]bool{}
for r := 0; r < 3; r++ {
if modeB[r] {
// 随机取一个不同的 offset
var off int
for tries := 0; tries < 10; tries++ {
off = minOffset + int(grand.Intn(maxOffset-minOffset+1))
if !usedOffsets[off] {
break
}
}
// 若多次冲突仍然存在,仍使用最后一个 offcollision 可能性低)
usedOffsets[off] = true
// 写入 multipliers 全 0offset = off
for c := 0; c < 4; c++ {
out[r*5+c] = 0
}
out[r*5+4] = uint8(off)
} else {
// ModeA拷贝/置换,只有一个 multiplier=1pick其他为0offset=0
pick := picks[r]
for c := 0; c < 4; c++ {
if c == pick {
out[r*5+c] = 1
} else {
out[r*5+c] = 0
}
}
out[r*5+4] = 0
}
}
// 额外保护:防止三行输出仍然完全相同的常量或同一输入通道(极小概率)
// 情况1如果三行都是 ModeA 且 picks 全相同 -> 强制将其中一行改为拷贝另一个不同的输入(或改为 ModeB
if !modeB[0] && !modeB[1] && !modeB[2] {
if picks[0] == picks[1] && picks[1] == picks[2] {
// 将第三行改为 pick = (picks[2]+1)%4 保证不同
newPick := (picks[2] + 1) % 4
for c := 0; c < 4; c++ {
if c == newPick {
out[2*5+c] = 1
} else {
out[2*5+c] = 0
}
}
out[2*5+4] = 0
}
}
// 情况2如果三行都是 ModeB 且 offsets 有重复(理论上不可能因为我们尽量保证唯一),强制调整
if modeB[0] && modeB[1] && modeB[2] {
if out[4] == out[9] && out[9] == out[14] {
// adjust third offset by +17 (并截断到范围)
newOff := int(out[14]) + 17
if newOff > maxOffset {
newOff = minOffset + ((newOff - minOffset) % (maxOffset - minOffset + 1))
}
out[14] = uint8(newOff)
}
}
// 最后 5 个字节由 caller 指定alpha 行)并固定
for i := 0; i < 5; i++ {
out[15+i] = alphaRow[i]
}
return out
}
func RandomMatrixDefaultAlphaNoSingleColor(seed int64, brightChance float64) [20]uint8 {
return RandomMatrixNoSingleColor(seed, [5]uint8{0, 0, 0, 1, 0}, brightChance)
}