- 崩溃(index out of range):实体所在区块卸载后失去碰撞支撑无限下坠到 负坐标,光查询越界 panic——WorldView 增 ChunkLoaded,未加载区块实体冻结; chunk 光查询加越界防御(双保险) - 立面光照:mesh 每面取**邻格**光照(原版光照模型)——此前用方块自身光照 (sky=0)导致悬崖/矿洞立面全黑(泥土竖向黑色根因) - 掉落可见:生成后 1 秒拾取延迟 + 图标加大到 0.45 - 打击感:攻击命中音(block.hit)+ 实体受击白闪(uTint)+ 手持挥动动画 - 水下效果:相机入水蓝色叠加(渲染.md §8 水体) - 空手显示肤色拳头(白羊毛 × 肤色 tint),持方块正常显示 - HUD 布局:血条左下、饥饿右下(经典布局)
161 lines
4.6 KiB
Go
161 lines
4.6 KiB
Go
// Package chunk 区块数据结构:一维数组 + 每区块独立锁(场景/区块管理.md §2)。
|
||
//
|
||
// 布局:16×16×256 一维 []block.State(索引 (y*16+z)*16+x),
|
||
// 天空光/方块光各 4-bit 打包存储(每字节 2 个方块)。
|
||
package chunk
|
||
|
||
import (
|
||
"sync"
|
||
"sync/atomic"
|
||
|
||
"mc/internal/block"
|
||
)
|
||
|
||
// 区块尺寸常量(设计.md §4.1)。
|
||
const (
|
||
SizeX = 16 // 水平 X 大小
|
||
SizeZ = 16 // 水平 Z 大小
|
||
Height = 256 // 垂直高度
|
||
SectionY = Height / 16 // section 数量(16×16×16)
|
||
)
|
||
|
||
// Chunk 一个区块。锁粒度:每 Chunk 一个 RWMutex(禁止全局锁,性能与内存.md §3)。
|
||
type Chunk struct {
|
||
CX, CZ int32
|
||
|
||
mu sync.RWMutex
|
||
blocks []block.State // 方块状态(一维数组,cache 友好)
|
||
sky []uint8 // 天空光 4-bit 打包
|
||
blockLt []uint8 // 方块光 4-bit 打包
|
||
|
||
Dirty atomic.Bool // mesh 需重建(渲染.md §3.3)
|
||
LightDirty atomic.Bool // 光照需重算(光照.md)
|
||
SaveDirty atomic.Bool // 需写盘(存档与持久化.md §2)
|
||
}
|
||
|
||
// New 创建全空气区块。
|
||
func New(cx, cz int32) *Chunk {
|
||
return &Chunk{
|
||
CX: cx,
|
||
CZ: cz,
|
||
blocks: make([]block.State, SizeX*SizeZ*Height),
|
||
sky: make([]uint8, SizeX*SizeZ*Height/2),
|
||
blockLt: make([]uint8, SizeX*SizeZ*Height/2),
|
||
}
|
||
}
|
||
|
||
// Index 计算方块数组索引:(y*16+z)*16+x。
|
||
func Index(x, y, z int) int { return (y*SizeZ+z)*SizeX + x }
|
||
|
||
// Block 读取方块(读锁;越界返回空气,防 mesh/光照边界查询崩溃)。
|
||
func (c *Chunk) Block(x, y, z int) block.State {
|
||
if x < 0 || x >= SizeX || y < 0 || y >= Height || z < 0 || z >= SizeZ {
|
||
return block.Air
|
||
}
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
return c.blocks[Index(x, y, z)]
|
||
}
|
||
|
||
// SetBlock 写入方块并标脏(mesh + 光照 + 存档)。
|
||
func (c *Chunk) SetBlock(x, y, z int, s block.State) {
|
||
c.mu.Lock()
|
||
c.blocks[Index(x, y, z)] = s
|
||
c.mu.Unlock()
|
||
c.Dirty.Store(true)
|
||
c.LightDirty.Store(true)
|
||
c.SaveDirty.Store(true)
|
||
}
|
||
|
||
// Fill 初始化填充(生成器专用,不标存档脏:生成结果属纯函数可重算)。
|
||
func (c *Chunk) Fill(x, y, z int, s block.State) {
|
||
c.blocks[Index(x, y, z)] = s
|
||
}
|
||
|
||
// FillAll 整块填充(生成器初始化)。
|
||
func (c *Chunk) FillAll(s block.State) {
|
||
for i := range c.blocks {
|
||
c.blocks[i] = s
|
||
}
|
||
}
|
||
|
||
// SkyLight 读取天空光(4-bit,0–15;越界返回 0——实体在未加载区块
|
||
// 下落时查询负数坐标不应崩溃,区块管理.md §6 防御)。
|
||
func (c *Chunk) SkyLight(x, y, z int) uint8 {
|
||
if x < 0 || x >= SizeX || y < 0 || y >= Height || z < 0 || z >= SizeZ {
|
||
return 0
|
||
}
|
||
return nibble(c.sky, Index(x, y, z))
|
||
}
|
||
|
||
// SetSkyLight 写入天空光(越界忽略)。
|
||
func (c *Chunk) SetSkyLight(x, y, z int, v uint8) {
|
||
if x < 0 || x >= SizeX || y < 0 || y >= Height || z < 0 || z >= SizeZ {
|
||
return
|
||
}
|
||
setNibble(c.sky, Index(x, y, z), v)
|
||
}
|
||
|
||
// BlockLight 读取方块光(4-bit,0–15;越界返回 0)。
|
||
func (c *Chunk) BlockLight(x, y, z int) uint8 {
|
||
if x < 0 || x >= SizeX || y < 0 || y >= Height || z < 0 || z >= SizeZ {
|
||
return 0
|
||
}
|
||
return nibble(c.blockLt, Index(x, y, z))
|
||
}
|
||
|
||
// SetBlockLight 写入方块光(越界忽略)。
|
||
func (c *Chunk) SetBlockLight(x, y, z int, v uint8) {
|
||
if x < 0 || x >= SizeX || y < 0 || y >= Height || z < 0 || z >= SizeZ {
|
||
return
|
||
}
|
||
setNibble(c.blockLt, Index(x, y, z), v)
|
||
}
|
||
|
||
// nibble 读取半字节(偶数索引 → 低 4 位,奇数索引 → 高 4 位)。
|
||
func nibble(arr []uint8, idx int) uint8 {
|
||
if idx&1 == 0 {
|
||
return arr[idx/2] & 0xF
|
||
}
|
||
return arr[idx/2] >> 4
|
||
}
|
||
|
||
// setNibble 写入半字节。
|
||
func setNibble(arr []uint8, idx int, v uint8) {
|
||
v &= 0xF
|
||
if idx&1 == 0 {
|
||
arr[idx/2] = (arr[idx/2] & 0xF0) | v
|
||
} else {
|
||
arr[idx/2] = (arr[idx/2] & 0x0F) | (v << 4)
|
||
}
|
||
}
|
||
|
||
// Snapshot 无锁快照(mesh 构建 goroutine 读取用;调用方需保证期间无写入)。
|
||
func (c *Chunk) Snapshot() []block.State {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
out := make([]block.State, len(c.blocks))
|
||
copy(out, c.blocks)
|
||
return out
|
||
}
|
||
|
||
// LightBytes 复制天空光/方块光原始字节(存档序列化用)。
|
||
func (c *Chunk) LightBytes() (sky, blockLt []byte) {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
sky = make([]byte, len(c.sky))
|
||
bl := make([]byte, len(c.blockLt))
|
||
copy(sky, c.sky)
|
||
copy(bl, c.blockLt)
|
||
return sky, bl
|
||
}
|
||
|
||
// SetAll 整体覆盖方块与光照数据(存档载入用;不标脏——载入即当前状态)。
|
||
func (c *Chunk) SetAll(blocks []block.State, sky, blockLt []byte) {
|
||
c.mu.Lock()
|
||
copy(c.blocks, blocks)
|
||
copy(c.sky, sky)
|
||
copy(c.blockLt, blockLt)
|
||
c.mu.Unlock()
|
||
}
|