血量显示在玩家头顶
This commit is contained in:
@@ -77,6 +77,18 @@
|
||||
- 文档同步:`联机实战教程-由浅入深.md` 关卡 1 改成「先在 player.tscn 里加 Hud/HealthLabel 节点」;
|
||||
`联机框架使用指南.md` 的 player.tscn 结构图。
|
||||
|
||||
### 血条改到玩家头顶(Label3D)
|
||||
|
||||
- `player.tscn`:删掉 `Hud (CanvasLayer)`,改为 `HealthLabel (Label3D)` 直接挂在 `Player` 下,
|
||||
position `(0, 2.05, 0)`、`billboard=1`、`no_depth_test=true`、`font_size=64`、`pixel_size=0.005`(默认值)、`outline_size=8`。
|
||||
- `player.gd`:`@onready var _health_label: Label3D = $HealthLabel`,删掉 `_hud` 引用与 `_hud.visible = false`。
|
||||
- **顺带消掉一个坑**:用 `Label3D` 后,血条天然待在各自头顶,不再需要「远端玩家隐藏血条」那一步
|
||||
(屏幕坐标的 CanvasLayer 才需要,否则 N 份叠在同一屏幕位置)。
|
||||
现在默认人人可见别人的血量 —— 因为 health 是同步字段。
|
||||
- 尺寸换算:`Label3D` 的文字世界高度 ≈ `font_size × pixel_size`。64 × 0.005 ≈ 0.32 米。
|
||||
- 若要与关卡 3 的头顶名牌共存:血量放 `y=2.05`,名牌放 `y=2.3`,不重叠。
|
||||
- 验证:双实例 headless 联调,两个日志里 `ERROR|Invalid|null` 一条都没有。
|
||||
|
||||
### 反复出现的坑:编辑器把外部写盘的内容覆盖回去
|
||||
|
||||
`net.gd` 用 MCP `script_create` 写成功后,磁盘上又变回了旧版(`extends Node` + `_build_ui` 全在)。
|
||||
|
||||
+37
-18
@@ -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
@@ -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\
|
||||
|
||||
+5
-8
@@ -10,8 +10,7 @@ extends CharacterBody3D
|
||||
## │ └─ SpringArm (SpringArm3D) ← 承担俯仰 pitch,自动避障
|
||||
## │ └─ Camera (Camera3D)
|
||||
## ├─ Sync (MultiplayerSynchronizer) ← 同步 position 与 Body.rotation
|
||||
## └─ Hud (CanvasLayer) ← 本地玩家的血条(节点配置,不是代码建的)
|
||||
## └─ HealthLabel (Label)
|
||||
## └─ HealthLabel (Label3D) ← 头顶血条(节点配置,不是代码建的)
|
||||
##
|
||||
## 操作:WASD/方向键移动,鼠标转视角,Shift 疾跑,空格跳跃,Esc 释放/锁定鼠标
|
||||
|
||||
@@ -50,9 +49,8 @@ const JUMP_BUFFER_FRAMES: int = 6
|
||||
@onready var body: Node3D = $Body
|
||||
@onready var camera_rig: Node3D = $CameraRig
|
||||
@onready var spring_arm: SpringArm3D = $CameraRig/SpringArm
|
||||
## 血条是 player.tscn 里的节点,不是代码建的 UI
|
||||
@onready var _hud: CanvasLayer = $Hud
|
||||
@onready var _health_label: Label = $Hud/HealthLabel
|
||||
## 头顶血条是 player.tscn 里的 Label3D 节点(3D 世界里的文字,会跟着角色走)
|
||||
@onready var _health_label: Label3D = $HealthLabel
|
||||
|
||||
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8))
|
||||
var _yaw: float = 0.0
|
||||
@@ -68,11 +66,10 @@ func _ready() -> void:
|
||||
if not _is_local:
|
||||
# 远端玩家只是一具「回放身体」:
|
||||
# 拆掉相机 —— 否则每多一个玩家就多一台 Camera3D 互相抢渲染;
|
||||
# 关掉血条 —— 否则 N 份血条叠在同一位置,显示的血量可能是别人的;
|
||||
# 关掉输入与物理 —— 否则与权威端双重模拟,表现为抖动、漂移、穿地。
|
||||
# 它的 position 与 Body.rotation 由子节点 Sync 同步过来。
|
||||
# 它的 position 与 Body.rotation 由子节点 Sync 同步过来;
|
||||
# 头顶血条保留 —— health 同样是同步过来的,这样谁都能看到别人还剩多少血。
|
||||
camera_rig.queue_free()
|
||||
_hud.visible = false
|
||||
set_physics_process(false)
|
||||
set_process_unhandled_input(false)
|
||||
return
|
||||
|
||||
+6
-7
@@ -44,11 +44,10 @@ surface_material_override/0 = SubResource("StandardMaterial3D_yw30f")
|
||||
[node name="Sync" type="MultiplayerSynchronizer" parent="." unique_id=1979575210]
|
||||
replication_config = ExtResource("2_rkbax")
|
||||
|
||||
[node name="Hud" type="CanvasLayer" parent="." unique_id=425326662]
|
||||
|
||||
[node name="HealthLabel" type="Label" parent="Hud" unique_id=348125801]
|
||||
offset_left = 16.0
|
||||
offset_top = 56.0
|
||||
offset_right = 17.0
|
||||
offset_bottom = 79.0
|
||||
[node name="HealthLabel" type="Label3D" parent="." unique_id=300336561]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.05, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
text = "HP 100 / 100"
|
||||
font_size = 64
|
||||
outline_size = 8
|
||||
|
||||
Reference in New Issue
Block a user