feat(client): 手持物品渲染 + 动物方块模型 + 实体贴图 alpha test
- 手持非放置物品(工具/食物):实体图集物品图标小四边形(原版倾斜角, 双面、alpha test),随挥动动画平移;可放置方块仍为六面立方体 - 动物(牛/猪/羊/鸡)改为方块组合模型(身体+头+腿),按原版皮肤布局 取子矩形贴图,水平面向相机;修复动物 VAO 未捕获 EBO 导致的驱动崩溃 - 僵尸/村民/掉落物广告牌开启 alpha test(透明区域不再显黑块)
This commit is contained in:
@@ -118,14 +118,16 @@ type clientApp struct {
|
||||
cursorReq atomic.Int32
|
||||
|
||||
// 手持方块(tick 计算 UV → 渲染线程上传,渲染.md §5.2)
|
||||
handInit bool
|
||||
handSlot int
|
||||
handHas bool
|
||||
handDirty bool
|
||||
handUVs [6][4]float32
|
||||
handTint [3]float32 // 手持颜色乘数(空手肤色拳 / 持方块 1,1,1)
|
||||
swing float64 // 挥动动画剩余量(左/右键触发,渲染线程衰减)
|
||||
swingTimer float64 // 按住挖掘时周期性挥动的节拍器
|
||||
handInit bool
|
||||
handSlot int
|
||||
handHas bool
|
||||
handDirty bool
|
||||
handUVs [6][4]float32
|
||||
handTint [3]float32 // 手持颜色乘数(空手肤色拳 / 持方块 1,1,1)
|
||||
handItemHas bool // 手持非放置物品(图标四边形,实体图集 UV)
|
||||
handItemUV [4]float32
|
||||
swing float64 // 挥动动画剩余量(左/右键触发,渲染线程衰减)
|
||||
swingTimer float64 // 按住挖掘时周期性挥动的节拍器
|
||||
|
||||
// 水下状态(渲染线程读;tick 更新)
|
||||
underwater bool
|
||||
@@ -747,12 +749,30 @@ func (a *clientApp) computeHand() {
|
||||
return
|
||||
}
|
||||
d, ok := a.sess.Items.Get(sel.Name)
|
||||
if !ok || d.Place == "" {
|
||||
// 非可放置物品:同样显示肤色拳(原版按物品模型渲染,后置)
|
||||
if !ok {
|
||||
a.computeFistHand()
|
||||
return
|
||||
}
|
||||
bid, ok := a.reg.ID(d.Place)
|
||||
if d.Place != "" {
|
||||
a.computeBlockHand(d.Place)
|
||||
return
|
||||
}
|
||||
// 非放置物品(工具/食物等):实体图集中含全部物品贴图,手持图标四边形
|
||||
if uv, found := a.rend.EntityUV(d.Texture); found {
|
||||
a.mu.Lock()
|
||||
a.handItemHas = true
|
||||
a.handItemUV = uv
|
||||
a.handHas = false
|
||||
a.handDirty = true
|
||||
a.mu.Unlock()
|
||||
return
|
||||
}
|
||||
a.computeFistHand()
|
||||
}
|
||||
|
||||
// computeBlockHand 手持可放置方块:六面贴图立方体(渲染.md §5.2)。
|
||||
func (a *clientApp) computeBlockHand(place string) {
|
||||
bid, ok := a.reg.ID(place)
|
||||
if !ok {
|
||||
a.computeFistHand()
|
||||
return
|
||||
@@ -770,6 +790,7 @@ func (a *clientApp) computeHand() {
|
||||
a.mu.Lock()
|
||||
a.handUVs = uvs
|
||||
a.handHas = true
|
||||
a.handItemHas = false
|
||||
a.handTint = [3]float32{1, 1, 1}
|
||||
a.handDirty = true
|
||||
a.mu.Unlock()
|
||||
@@ -795,6 +816,7 @@ func (a *clientApp) computeFistHand() {
|
||||
a.mu.Lock()
|
||||
a.handUVs = [6][4]float32{uvs, uvs, uvs, uvs, uvs, uvs}
|
||||
a.handHas = true
|
||||
a.handItemHas = false
|
||||
a.handTint = [3]float32{0.85, 0.65, 0.5} // 肤色
|
||||
a.handDirty = true
|
||||
a.mu.Unlock()
|
||||
@@ -850,17 +872,21 @@ func (a *clientApp) Render() {
|
||||
chunks := a.sess.World.ActiveChunks()
|
||||
a.rend.DrawChunks(func(i int) (int32, int32) { return chunks[i].CX, chunks[i].CZ }, len(chunks))
|
||||
|
||||
// 实体广告牌(渲染.md §5.3:僵尸/村民/动物/掉落物)
|
||||
// 实体绘制(渲染.md §5.3:动物方块模型;僵尸/村民/掉落物广告牌)
|
||||
for _, e := range a.sess.Entities.List() {
|
||||
if e.Dead {
|
||||
continue
|
||||
}
|
||||
key, h := entityVisual(e, a.sess.Items)
|
||||
key, h, variant := entityVisual(e, a.sess.Items)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if uv, ok := a.rend.EntityUV(key); ok {
|
||||
if e.Kind == entity.KindItemDrop {
|
||||
if variant >= 0 {
|
||||
// 动物:方块组合模型,始终面朝相机(原版观感)
|
||||
yaw := float32(math.Atan2(a.sess.Cam.Pos[0]-e.Pos[0], a.sess.Cam.Pos[2]-e.Pos[2]))
|
||||
a.rend.DrawAnimalModel(float32(e.Pos[0]), float32(e.Pos[1]), float32(e.Pos[2]), yaw, animalModel(variant), uv, e.HitFlash > 0)
|
||||
} else if e.Kind == entity.KindItemDrop {
|
||||
a.rend.DrawBillboard(float32(e.Pos[0]), float32(e.Pos[1]), float32(e.Pos[2]), h, h, uv, false)
|
||||
} else {
|
||||
a.rend.DrawBillboard(float32(e.Pos[0]), float32(e.Pos[1]), float32(e.Pos[2]), h, h*0.6, uv, e.HitFlash > 0)
|
||||
@@ -888,6 +914,7 @@ func (a *clientApp) Render() {
|
||||
}
|
||||
a.mu.Lock()
|
||||
handDirty, handHas := a.handDirty, a.handHas
|
||||
handItemHas, handItemUV := a.handItemHas, a.handItemUV
|
||||
a.handDirty = false
|
||||
var handUVs [6][4]float32
|
||||
var handTint [3]float32
|
||||
@@ -902,7 +929,7 @@ func (a *clientApp) Render() {
|
||||
swingPos[1] -= float32(a.swing) * 0.10
|
||||
handDirty = true // 挥动期间逐帧重传位置
|
||||
}
|
||||
if handDirty {
|
||||
if handDirty && !handItemHas {
|
||||
if handHas {
|
||||
a.rend.SetHandCube(handUVs, swingPos, 0.16)
|
||||
} else {
|
||||
@@ -910,7 +937,12 @@ func (a *clientApp) Render() {
|
||||
}
|
||||
}
|
||||
if a.screen == 1 {
|
||||
a.rend.DrawHand(handTint)
|
||||
if handItemHas {
|
||||
// 手持非放置物品:物品图标四边形(随挥动位置移动)
|
||||
a.rend.DrawHandItem(handItemUV, swingPos, 0.22)
|
||||
} else {
|
||||
a.rend.DrawHand(handTint)
|
||||
}
|
||||
}
|
||||
|
||||
// 准星指向方块名上传(tick 光栅化 → 渲染线程上传,UI还原.md §3.4)
|
||||
@@ -1196,6 +1228,17 @@ func main() {
|
||||
}
|
||||
defer rend.Shutdown()
|
||||
rend.SetAtlas(atlasImg)
|
||||
// 窗口图标:素材包 pack.png(原版 logo,构建与发布.md §8)
|
||||
for _, p := range am.Packs() {
|
||||
iconPath := paths.Join(p.Root, "pack.png")
|
||||
if f, err := os.Open(iconPath); err == nil {
|
||||
if img, err := png.Decode(f); err == nil {
|
||||
rend.SetWindowIcon(img)
|
||||
}
|
||||
f.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
// 主菜单状态:鼠标可见(仅游戏内捕获,UI与输入.md §6)
|
||||
rend.SetCursorMode(false)
|
||||
// 裂纹图集上传(方块交互与动画.md §4)
|
||||
|
||||
@@ -1,43 +1,126 @@
|
||||
//go:build gl
|
||||
|
||||
// 实体可视映射:实体类型 → 广告牌贴图键与高度(渲染.md §5.3)。
|
||||
// 实体可视映射:实体类型 → 贴图键/身高/动物模型(渲染.md §5.3)。
|
||||
// 动物用方块组合模型(身体+头+四腿)近似原版外观;僵尸/村民用交叉广告牌;
|
||||
// 掉落物用物品图标小广告牌。
|
||||
package main
|
||||
|
||||
import (
|
||||
"mc/internal/entity"
|
||||
"mc/internal/item"
|
||||
"mc/internal/render"
|
||||
)
|
||||
|
||||
// entityVisual 返回实体的贴图键与身高(米)。
|
||||
// 动物按 ID 轮换四种外观(cow/pig/sheep/chicken);掉落物用物品图标(0.3 米小广告牌)。
|
||||
func entityVisual(e *entity.Entity, items *item.Registry) (key string, height float32) {
|
||||
// 动物额外返回外观变体(0 牛 / 1 猪 / 2 羊 / 3 鸡),供方块模型选择。
|
||||
func entityVisual(e *entity.Entity, items *item.Registry) (key string, height float32, variant int) {
|
||||
switch e.Kind {
|
||||
case entity.KindZombie:
|
||||
return "entity/zombie/zombie", 1.95
|
||||
return "entity/zombie/zombie", 1.95, -1
|
||||
case entity.KindVillager:
|
||||
return "entity/villager/villager", 1.95
|
||||
return "entity/villager/villager", 1.95, -1
|
||||
case entity.KindAnimal:
|
||||
switch e.ID % 4 {
|
||||
case 0:
|
||||
return "entity/cow/cow_temperate", 1.4
|
||||
return "entity/cow/cow_temperate", 1.4, 0
|
||||
case 1:
|
||||
return "entity/pig/pig_temperate", 0.9
|
||||
return "entity/pig/pig_temperate", 0.9, 1
|
||||
case 2:
|
||||
return "entity/sheep/sheep", 1.3
|
||||
return "entity/sheep/sheep", 1.3, 2
|
||||
default:
|
||||
return "entity/chicken/chicken_temperate", 0.7
|
||||
return "entity/chicken/chicken_temperate", 0.7, 3
|
||||
}
|
||||
case entity.KindItemDrop:
|
||||
d, ok := e.Data.(*entity.Drop)
|
||||
if !ok || items == nil {
|
||||
return "", 0
|
||||
return "", 0, -1
|
||||
}
|
||||
def, ok := items.Get(d.Item)
|
||||
if !ok {
|
||||
return "", 0
|
||||
return "", 0, -1
|
||||
}
|
||||
return def.Texture, 0.45
|
||||
return def.Texture, 0.45, -1
|
||||
default:
|
||||
return "", 0
|
||||
return "", 0, -1
|
||||
}
|
||||
}
|
||||
|
||||
// rect 构造图集 tile 内归一化子矩形。
|
||||
func rect(u0, v0, u1, v1 float32) [4]float32 { return [4]float32{u0, v0, u1, v1} }
|
||||
|
||||
// boxPart 构造模型部件:六面统一用 side 矩形,前/后取中间条带,顶取上条带,底取下条带。
|
||||
func boxPart(cx, cy, cz, w, h, d float32, side [4]float32) render.AnimalPart {
|
||||
u0, v0, u1, v1 := side[0], side[1], side[2], side[3]
|
||||
uw, vh := u1-u0, v1-v0
|
||||
front := rect(u0+uw*0.35, v0, u0+uw*0.65, v1) // 前/后面:中间三分之一
|
||||
top := rect(u0, v0, u1, v0+vh*0.3)
|
||||
bottom := rect(u0, v1-vh*0.3, u1, v1)
|
||||
var uv [6][4]float32
|
||||
// 顺序同 cubeFaces:顶/底/东/西/南(前)/北(后)
|
||||
uv[0], uv[1] = top, bottom
|
||||
uv[2], uv[3] = side, side
|
||||
uv[4], uv[5] = front, front
|
||||
return render.AnimalPart{CX: cx, CY: cy, CZ: cz, W: w, H: h, D: d, UV: uv}
|
||||
}
|
||||
|
||||
// animalModel 返回动物方块模型(variant:0 牛 / 1 猪 / 2 羊 / 3 鸡)。
|
||||
// 贴图子矩形按原版皮肤布局:头部在纹理左侧、身体在右侧、腿在下部。
|
||||
// 图集 tile 内纹理被等比拉伸成 64×64(64×32 原图纵向拉长一倍,矩形已按拉伸后比例折算)。
|
||||
func animalModel(variant int) render.AnimalModel {
|
||||
switch variant {
|
||||
case 0: // 牛:纹理 64×64,内容在上半(v 0–0.5)
|
||||
head := rect(0.02, 0.02, 0.48, 0.48)
|
||||
body := rect(0.52, 0.02, 0.98, 0.48)
|
||||
legs := rect(0.55, 0.30, 0.98, 0.50)
|
||||
var m render.AnimalModel
|
||||
m.Parts = append(m.Parts,
|
||||
boxPart(0, 0.72, 0, 0.92, 0.62, 1.28, body),
|
||||
boxPart(0, 1.26, 0.60, 0.56, 0.50, 0.56, head),
|
||||
boxPart(-0.34, 0.24, 0.50, 0.22, 0.48, 0.22, legs),
|
||||
boxPart(0.34, 0.24, 0.50, 0.22, 0.48, 0.22, legs),
|
||||
boxPart(-0.34, 0.24, -0.50, 0.22, 0.48, 0.22, legs),
|
||||
boxPart(0.34, 0.24, -0.50, 0.22, 0.48, 0.22, legs),
|
||||
)
|
||||
return m
|
||||
case 1: // 猪:纹理 64×64,内容在上半
|
||||
head := rect(0.14, 0.03, 0.48, 0.45)
|
||||
body := rect(0.52, 0.03, 0.98, 0.45)
|
||||
legs := rect(0.60, 0.30, 0.98, 0.50)
|
||||
var m render.AnimalModel
|
||||
m.Parts = append(m.Parts,
|
||||
boxPart(0, 0.50, 0, 0.76, 0.44, 1.02, body),
|
||||
boxPart(0, 0.86, 0.50, 0.50, 0.42, 0.50, head),
|
||||
boxPart(-0.28, 0.15, 0.42, 0.18, 0.30, 0.18, legs),
|
||||
boxPart(0.28, 0.15, 0.42, 0.18, 0.30, 0.18, legs),
|
||||
boxPart(-0.28, 0.15, -0.42, 0.18, 0.30, 0.18, legs),
|
||||
boxPart(0.28, 0.15, -0.42, 0.18, 0.30, 0.18, legs),
|
||||
)
|
||||
return m
|
||||
case 2: // 羊:纹理 64×32(tile 内 v 拉长一倍)
|
||||
head := rect(0.02, 0.02, 0.26, 0.40)
|
||||
body := rect(0.50, 0.15, 1.00, 0.72)
|
||||
legs := rect(0.62, 0.50, 0.95, 0.85)
|
||||
var m render.AnimalModel
|
||||
m.Parts = append(m.Parts,
|
||||
boxPart(0, 0.72, 0, 0.92, 0.60, 1.26, body),
|
||||
boxPart(0, 1.20, 0.60, 0.50, 0.46, 0.50, head),
|
||||
boxPart(-0.33, 0.21, 0.50, 0.20, 0.42, 0.20, legs),
|
||||
boxPart(0.33, 0.21, 0.50, 0.20, 0.42, 0.20, legs),
|
||||
boxPart(-0.33, 0.21, -0.50, 0.20, 0.42, 0.20, legs),
|
||||
boxPart(0.33, 0.21, -0.50, 0.20, 0.42, 0.20, legs),
|
||||
)
|
||||
return m
|
||||
default: // 鸡:纹理 64×32
|
||||
head := rect(0.02, 0.05, 0.28, 0.55)
|
||||
body := rect(0.28, 0.10, 0.70, 0.75)
|
||||
legs := rect(0.30, 0.55, 0.60, 0.80)
|
||||
var m render.AnimalModel
|
||||
m.Parts = append(m.Parts,
|
||||
boxPart(0, 0.36, 0, 0.44, 0.40, 0.52, body),
|
||||
boxPart(0, 0.66, 0.24, 0.32, 0.30, 0.32, head),
|
||||
boxPart(-0.14, 0.10, 0.18, 0.09, 0.20, 0.09, legs),
|
||||
boxPart(0.14, 0.10, 0.18, 0.09, 0.20, 0.09, legs),
|
||||
)
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user