血量显示在玩家头顶

This commit is contained in:
2026-09-22 17:51:52 +08:00
parent b11da48e23
commit 414fcafe1d
5 changed files with 61 additions and 35 deletions
+37 -18
View File
@@ -93,19 +93,29 @@ func _ready() -> void:
**1a. 在 `player.tscn` 里加血条节点**
1. 打开 `res://player/player.tscn`。
2. 选中根节点 `Player`,添加子节点,类型 **`CanvasLayer`**,改名 `Hud`。
> `CanvasLayer` 让节点脱离 3D 世界、按屏幕坐标渲染。`Net` 那个菜单也是这么做的。
3. 选中 `Hud`,添加子节点,类型 `Label`,改名 `HealthLabel`。
4. 在 Inspector 里设 `HealthLabel` 的 `Position` = `(16, 56)`,`Text` = `HP 100 / 100`。
5. `Ctrl+S` 保存。
2. 选中根节点 `Player`,添加子节点,类型 **`Label3D`**,改名 `HealthLabel`。
> 用 `Label3D` 而不是 `CanvasLayer` + `Label`:`Label3D` 是**3D 世界里的文字**,
> 挂在哪就跟着哪走,所以会自动待在玩家头顶,不需要每帧做屏幕投影。
3. 在 Inspector 里设:
| 属性 | 值 | 为什么 |
|---|---|---|
| `Transform → Position` | `(0, 2.05, 0)` | 头顶上方。胶囊高 1.8、中心在 y=0.9,头顶约在 1.8 |
| `Text` | `HP 100 / 100` | 运行时被脚本覆盖,这里只是编辑器预览 |
| `Billboard` | `Enabled` | 永远面朝相机。**不开的话文字会跟着角色转,从背后看是镜像的** |
| `No Depth Test` | 开 | 被别的物体挡住时也能看见 |
| `Font Size` | `64` | |
| `Pixel Size` | `0.005` | 字号→世界单位:64 × 0.005 ≈ 0.32 米高 |
| `Outline Size` | `8` | 描边,远看和杂乱背景上也能读 |
4. `Ctrl+S` 保存。
层级应该是:
```
Player (CharacterBody3D)
├─ Collision / Body / CameraRig / Sync
└─ Hud (CanvasLayer)
└─ HealthLabel (Label)
└─ HealthLabel (Label3D) ← 头顶血条
```
**1b. 在 `player.gd` 里加属性和节点引用**
@@ -118,34 +128,40 @@ Player (CharacterBody3D)
health = clampi(value, 0, 100)
_refresh_health_label()
@onready var _hud: CanvasLayer = $Hud
@onready var _health_label: Label = $Hud/HealthLabel
## 头顶血条是 player.tscn 里的 Label3D 节点(3D 世界里的文字,会跟着角色走)
@onready var _health_label: Label3D = $HealthLabel
```
`clampi(value, 0, 100)` 把值夹在 0~100,省得到处写边界判断。
**1c. 处理远端玩家**
**1c. 远端玩家的血条要不要显示?**
血条节点现在**每个玩家实例上都有**(因为它是场景的一部分)。远端玩家不需要显示,所以在 `_ready()` 的远端分支里关掉:
因为血条挂在**每个玩家节点**上,而 `health` 是同步过来的,所以默认行为是
**每个人的血量都显示在他自己头顶**,你能看到别人还剩多少血。这通常正是想要的效果,不用额外写代码。
> 对比一下两种做法:如果血条是**屏幕坐标**的 `CanvasLayer` + `Label`(像左上角那种),
> 就必须在远端分支里 `hide()` 掉,否则 N 份血条会叠在同一个屏幕位置、显示别人的血量。
> 换成 `Label3D` 之后这个坑自然消失了 —— 这也是「让 UI 待在它该待的世界位置」的好处。
如果你**只想看自己的**,在 `_ready()` 的远端分支里加一行:
```gdscript
func _ready() -> void:
_is_local = is_multiplayer_authority()
if not _is_local:
camera_rig.queue_free()
_hud.visible = false # ← 血条同理:N 份叠在同一位置会显示错的血量
_health_label.visible = false # ← 只在需要「只看自己」时加
set_physics_process(false)
set_process_unhandled_input(false)
return
```
本地分支末尾记得补刷一次:
```gdscript
# ...相机初始化
set_mouse_captured(true)
_refresh_health_label() # 补刷一次(原因见下面的提示)
```
> **这一步千万别漏。** 血条节点的坐标是屏幕坐标,如果没有 `_hud.visible = false`,
> 那么每个玩家(包括别人)都会在左上角画一份血条,**全部重叠在同一位置**,
> 你看到的是最后生成的那个玩家的血量。现象是:主机按 `H` 扣自己的血,屏幕上的数字**纹丝不动**。
**1d. 刷新函数**
放在文件底部:
@@ -163,6 +179,9 @@ func _refresh_health_label() -> void:
> (节点刚进场景树时)。那一次 setter 会因为 `_health_label == null` 被跳过,
> 所以本地分支末尾要补一次 `_refresh_health_label()`,把当前值显示出来。
> **和关卡 3 的名牌不冲突**:关卡 3 会在 `(0, 2.3, 0)` 加一个 `Label3D` 名牌,
> 血量放在 `2.05`,名牌放它上面,两个叠不到一起。
### 第 2 步:把 health 加进同步配置
打开 `player/player_sync.tres`,在末尾追加三行:
+1 -2
View File
@@ -90,8 +90,7 @@ E:\GD_Projects\agame\
│ │ ├─ CameraRig → SpringArm → Camera ← 远端玩家会被删掉这一支
│ │ ├─ Body → Mesh ← 视觉层,换角色模型挂这里
│ │ ├─ Sync (MultiplayerSynchronizer) ← 负责同步位置和朝向
│ │ └─ Hud (CanvasLayer) ← 本地玩家的血条,远端玩家会被隐藏
│ │ └─ HealthLabel (Label)
│ │ └─ HealthLabel (Label3D) ← 头顶血条(3D 世界里的文字,跟着角色走)
│ ├── player.gd ← 加了「本地 / 远端」分支
│ └── player_sync.tres ← 同步配置:到底同步哪几个属性
└── tests\