跳至正文
版本:最新版 - a1.169.2 ⚖️

基本 HUD(React)

如何使用 React 添加基本 HUD 以显示角色的生命值和弹药。

创建 React 应用​

信息

在本教程中,我们将使用 React,请下载并安装 NodeJs 以便使用它。

信息

本教程不会教授如何使用 React。理解本教程需要对该框架有基础了解。你可以在此处找到一些文档和教程。

在你选择的文件夹中,创建一个新的 React 应用,用于构建我们的 UI。我们将使用官方推荐的方式 Vite 来启动一个新的 React 项目。在终端中执行以下命令(需要安装 Node.js):

Terminal
npm create vite@latest basic-hud -- --template react
cd basic-hud
npm install

你的应用文件夹结构应如下所示:

basic-hud/
├── public/
├── src/
│ ├── assets/
│ ├── App.css
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx
├── index.html
├── package.json
└── vite.config.js

应用的基本配置​

在开始添加代码之前,需要配置一些内容以确保你的应用与 nanos world 兼容:

  • 在构建中使用相对路径​

WebUI 从包文件(file://)中加载你的应用,因此构建产物必须使用相对路径引用其文件。编辑你的 vite.config.js 并添加 base 选项:

./vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()],
base: './',
})
  • 添加 nanos world 事件处理​

默认情况下,你的 React 应用无法处理原生 nanos world 事件。要解决此问题,请在应用的 src 目录中新建名为 Events.js 的文件,并添加以下代码:

./src/Events.js
const Events = {};

Events.Call = function (sEventName, ...args) {
if (typeof (window.Events) == "undefined") return;
window.Events.Call(sEventName, ...args);
}

Events.Subscribe = function (sEventName, callback) {
if (typeof (window.Events) == "undefined") return;
window.Events.Subscribe(sEventName, callback);
}

export default Events;
信息

不要忘记在需要调用/订阅 nanos world 事件的组件中导入此文件

  • 清理应用中未使用的文件​

应用中默认包含一些示例文件。为了保持项目整洁,我们将移除它们:

  • ./src/assets/react.svg
  • ./public/vite.svg

同时清空 ./src/index.css 的内容,因为我们将直接在 App.css 中编写我们自己的样式。

信息

不要忘记移除 ./index.html(网站图标链接)以及 ./src/App.jsx 中对已删除文件的引用。

创建 UI​

现在我们的应用已经就绪且与 nanos world 兼容,可以开始编写代码了。由于此 UI 非常简单,我们将直接使用 src 文件夹中默认提供的 App.jsx 组件

./src/App.jsx
import './App.css';

function App() {
return (
<div >
{/* 生命值容器(黑色背景) */}
<div id="health_container">
<img src="./img/health.png" alt="health-cross"/>
<span id="health_current">100</span> {/* 生命值 */}
</div>

{/* 武器弹药容器(黑色背景) */}
<div id="weapon_ammo_container">
<span id="weapon_ammo_clip">30</span> {/* 弹夹数值 */}
<span id="weapon_ammo_bag">/ 1000</span> {/* 弹药袋数值 */}
</div>
</div>
);
}

export default App;
信息

如果你注意到我们使用了一张图片 health.png,可以在此处下载 https://i.imgur.com/0BmQJVZ.png 并将其放置在应用的 ./public/img 文件夹中。

提示

当你引用存储在 public 文件夹中的文件时,无需在路径中添加 public。构建完成后,public 文件夹中的所有内容都会被复制到你应用的根目录下。

现在让我们为其添加样式。请编辑你的 App.css 文件并添加以下 CSS 代码:

./src/App.css
body {
font-family: Tahoma, sans-serif;
font-size: 14px;
margin: 0px;
padding: 0px;
margin-bottom: 20px;
}

#weapon_ammo_container {
position: absolute;
right: 0px;
bottom: 0px;
width: 200px;
height: 50px;
background-image: linear-gradient(to right, #00000000, #00000080);
text-align: right;
line-height: 47px;
}

#weapon_ammo_bag {
color: #ededed;
font-weight: bold;
font-size: 16px;
margin-right: 30px;
position: relative;
top: -4px;
}

#weapon_ammo_clip {
color: white;
font-weight: bold;
font-size: 32px;
}

#health_container {
position: absolute;
bottom: 0px;
width: 200px;
height: 50px;
background-image: linear-gradient(to left, #00000000, #00000080);
}

#health_container img {
height: 23px;
margin: 13px;
}

#health_current {
color: white;
font-size: 32px;
font-weight: bold;
position: absolute;
margin-top: 4px;
}

要确保你的应用正常运行,只需启动应用并在网页浏览器中查看最终效果即可。为此,请使用以下命令:

Terminal
npm run dev
信息

你可以在终端显示的地址(通常为 http://localhost:5173)中查看运行中的应用。

提示

在开发过程中,你可以将 WebUI 指向此地址(WebUI("Main HUD", "http://localhost:5173"))以在游戏内获得热重载支持。发布前请切记切换回构建好的文件!

启动后,你应该会在网页浏览器中看到类似这样的内容:

在应用中添加事件处理​

现在应用的基础工作已准备就绪,我们可以开始实现事件处理功能。为此,请编辑你的 App.jsx:

./src/App.jsx
import './App.css';
import Events from './Events.js';
import { useState, useEffect } from 'react';


function App() {

// 使用 React State 存储生命值和弹药数值
// 这些数值由来自服务端的事件进行更新
const [health, setHealth] = useState(100);
const [weaponAmmo, setWeaponAmmo] = useState(30);
const [weaponBag, setWeaponBag] = useState(1000);
const [displayAmmo, setDisplayAmmo] = useState(false);

// 仅在组件挂载时订阅一次事件
// (直接在组件主体中订阅会导致每次渲染时都添加新订阅)
useEffect(() => {
// 订阅与弹药及弹药容器可见性相关的事件
Events.Subscribe("UpdateWeaponAmmo", (enable, clip, bag) => {
setDisplayAmmo(enable);
setWeaponAmmo(clip);
setWeaponBag(bag);
});

// 订阅与生命值相关的事件
Events.Subscribe("UpdateHealth", (health) => {
setHealth(health);
});
}, []);

return (
<div >
{/* 生命值容器(黑色背景) */}
<div id="health_container">
<img src="./img/health.png" alt="health-cross"/>
<span id="health_current">{health}</span> {/* 生命值数值 */}
</div>

{/* 武器弹药容器(黑色背景) */}
{displayAmmo &&
<div id="weapon_ammo_container">
<span id="weapon_ammo_clip">{weaponAmmo}</span> {/* 弹夹弹药数值 */}
<span id="weapon_ammo_bag">/ {weaponBag}</span> {/* 备用弹药数值 */}
</div>
}
</div>
);
}

export default App;

UI 编码部分完成后,你可以对它进行 build,以便将其添加到你的 nanos world 包中!

构建你的 React 应用​

完成 Web 部分的开发后,你需要对应用进行构建。在应用的根目录下,运行以下命令:

Terminal
npm run build

完成后,你将在 ./dist 文件夹中找到你应用的所有文件。

创建 UI 包​

在包的 Client 文件夹内,新建一个名为 UI/ 的文件夹(可选),以便将 UI 文件与脚本(lua)文件区分开来:

之后,将 React 应用中 ./dist 文件夹里的所有文件复制到 nanos world 包 的 UI/ 文件夹中。

最后,在包的 Index.lua 中,让我们添加生成和更新 UI 所需的所有代码:

Client/Index.lua
-- 使用你刚刚创建的 HTML 文件生成一个 WebUI
main_hud = WebUI("Main HUD", "file://UI/index.html", WidgetVisibility.VisibleNotHitTestable)

-- 当 LocalPlayer 生成时,在其上设置一个事件,以便在我们控制新角色时触发。此事件仅调用一次,重新加载包时的机制请参考 Package.Subscribe("Load")
Client.Subscribe("SpawnLocalPlayer", function(local_player)
local_player:Subscribe("Possess", function(player, character)
UpdateLocalCharacter(character)
end)
end)

-- 当包加载时,验证 LocalPlayer 是否已经存在(例如在重新加载包时),然后尝试获取并存储其控制的角色
Package.Subscribe("Load", function()
local local_player = Client.GetLocalPlayer()
if (local_player ~= nil) then
UpdateLocalCharacter(local_player:GetControlledCharacter())
end
end)

-- 为本地角色设置所有必要事件的函数(以便在其受到伤害或死亡时更新 UI)
function UpdateLocalCharacter(character)
-- 验证角色是否不为 nil(例如当 GetControlledCharacter() 未返回角色时)
if (character == nil) then
return
end

-- 使用当前角色的生命值更新 UI
UpdateHealth(character:GetHealth())

-- 每当生命值发生变化(受到伤害、受到治疗、死亡或复活)时更新生命值 UI
character:Subscribe("HealthChange", function(charac, old_health, new_health)
UpdateHealth(new_health)
end)

-- 尝试获取角色是否持有任何武器
local current_picked_item = character:GetPicked()

-- 如果持有,则更新 UI(IsA 也匹配继承自武器的类)
if (current_picked_item and current_picked_item:IsA(Weapon)) then
UpdateAmmo(true, current_picked_item:GetAmmoClip(), current_picked_item:GetAmmoBag())
end

-- 在角色上设置事件,以更新其抓取的武器(在 UI 上显示弹药)
character:Subscribe("PickUp", function(charac, object)
if (object:IsA(Weapon)) then
UpdateAmmo(true, object:GetAmmoClip(), object:GetAmmoBag())
end
end)

-- 在角色上设置事件,以在其丢弃武器时移除弹药 UI
character:Subscribe("Drop", function(charac, object)
UpdateAmmo(false)
end)

-- 在角色上设置事件,以在其开火时更新 UI
character:Subscribe("Fire", function(charac, weapon)
UpdateAmmo(true, weapon:GetAmmoClip(), weapon:GetAmmoBag())
end)

-- 在角色上设置事件,以在其重新装填武器时更新 UI
character:Subscribe("Reload", function(charac, weapon, ammo_to_reload)
UpdateAmmo(true, weapon:GetAmmoClip(), weapon:GetAmmoBag())
end)
end

-- 用于更新弹药 UI 的函数
function UpdateAmmo(enable_ui, ammo, ammo_bag)
main_hud:CallEvent("UpdateWeaponAmmo", enable_ui, ammo, ammo_bag)
end

-- 用于更新生命值 UI 的函数
function UpdateHealth(health)
main_hud:CallEvent("UpdateHealth", health)
end

大功告成!你的应用现在应该已经准备好在 nanos world 中使用了。要进行测试,只需运行你的服务器并欣赏你的劳动成果

就这些!你的 React Web UI 现已成功集成到 nanos world 中,随时可以使用。欢迎在你自己的项目中使用此示例 :)