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

基本 HUD(React)

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

创建 React 应用

信息

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

信息

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

在你选择的文件夹中,创建一个新的 React 应用,用于构建我们的 UI。 为此,请在终端中运行以下命令:

Terminal
npx create-react-app basic-hud

完成后,你应该会在终端中看到以下消息:

Terminal
Success! Created basic-hud at F:\Nanos\Documentation\basic-hud
Inside that directory, you can run several commands:

npm start
Starts the development server.

npm run build
Bundles the app into static files for production.

npm test
Starts the test runner.

npm run eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

cd basic-hud
npm start

Happy hacking!

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

应用的基本配置

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

  • 修改应用的主页路径

如果不正确设置主页路径,编译后的应用可能无法正常工作。 为了避免任何与路径相关的问题,请编辑你的 package.json 文件并添加以下行:

./package.json
"homepage": "./"
  • 添加 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/App.test.js
  • ./src/logo.svg
  • ./src/reportWebVitals.js
  • ./src/setupTests.js
  • ./public/favicon.ico
  • ./public/logo192.png
  • ./public/logo512.png
  • ./public/manifest.json
  • ./public/robots.txt
信息

请别忘记在 ./src/index.js./src/App.js 文件中移除对已删除文件和函数的引用。

清理完成后,你的应用结构应该如下所示:

创建 UI

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

./basic-hud/src/App.js
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 start
信息

你可以在以下地址查看运行中的应用:http://localhost:3000

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

在应用中添加事件处理

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

./src/App.js
import './App.css';
import Events from './Events.js';
import { useState } 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);

// 订阅与弹药及弹药容器显示状态相关的事件
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

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

创建 UI 包

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

之后,将 React 应用 ./build 文件夹中的所有文件(除了 assets-manifest.json)复制到 nanos world 包的 /UI 文件夹中。

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

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

-- 当 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(例如当 GetControllerCharacter() 未返回角色时)
if (character == nil) then
return
end

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

-- 在角色上设置一个事件,以便在受到伤害后更新生命值 UI
character:Subscribe("TakeDamage", function(charac, damage, type, bone, from_direction, instigator, causer)
UpdateHealth(math.max(charac:GetHealth() - damage, 0))
end)

-- 在角色上设置一个事件,以便在死亡后更新生命值 UI
character:Subscribe("Death", function(charac)
UpdateHealth(0)
end)

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

-- 如果手持武器,则更新 UI
if (current_picked_item and current_picked_item:GetClass():GetName() == "Weapon") then
UpdateAmmo(true, current_picked_item:GetAmmoClip(), current_picked_item:GetAmmoBag())
end

-- 在角色上设置一个事件,以便在拾取武器时进行更新(在 UI 上显示弹药)
character:Subscribe("PickUp", function(charac, object)
if (object.Super:GetClass():GetName() == "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 中,随时可以使用。 欢迎在你自己的项目中使用此示例 :)