Files
ngzz-mc/internal/chunk/chunk.go
NianGao Dev 9e2f5a6186 fix(game): 崩溃修复(卸载区块实体冻结)+ 立面光照、掉落可见、打击感、水下效果、空手拳头、血条左/饥饿右
- 崩溃(index out of range):实体所在区块卸载后失去碰撞支撑无限下坠到
  负坐标,光查询越界 panic——WorldView 增 ChunkLoaded,未加载区块实体冻结;
  chunk 光查询加越界防御(双保险)
- 立面光照:mesh 每面取**邻格**光照(原版光照模型)——此前用方块自身光照
  (sky=0)导致悬崖/矿洞立面全黑(泥土竖向黑色根因)
- 掉落可见:生成后 1 秒拾取延迟 + 图标加大到 0.45
- 打击感:攻击命中音(block.hit)+ 实体受击白闪(uTint)+ 手持挥动动画
- 水下效果:相机入水蓝色叠加(渲染.md §8 水体)
- 空手显示肤色拳头(白羊毛 × 肤色 tint),持方块正常显示
- HUD 布局:血条左下、饥饿右下(经典布局)
2026-08-16 20:59:28 +08:00

161 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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()
}