ODRP开发日记-靠近NPC触发交互(二)

书接上文,我们已经搭建好了服务器和开发环境,也初始化了JavaScript项目,接下来我们将开始正式编码

引入全局声明

由于@citizenfx/client@citizenfx/server并不是一个模块,我们需要使用一种新的方式来引入全局声明,即在代码文件顶部添加这样一行

/// <reference types="@citizenfx/client" />

添加后,我们在编写代码时即可使用代码补全,编写时会舒服很多。

声明全局常量

我们希望当NPC离角色只有2.5M的时候才可以触发交互,因此我们事先声明好一个常量用于存储此值

const INTERACT_DISTANCE = 2.5;

检测附近的NPC

方法一:遍历GamePool

GmaePool是FiveM提供的一个实体缓存池,可以通过调用GetGamePool来获取。

GamePool有以下几种类型:

  • CPed: Ped池包含已加载的行人、动物和玩家
  • CObject:Object池包含已加载的对象,比如门、发射物等
  • CNetObject:NetObject池是网络同步对象池
  • CVehicle:Vehicle池包含场景中已加载的载具
  • CPickup:Pickup池包含可拾取的物品

GamePool本质上是一个一维数组,元素类型为int,其元素代表的是对象句柄,在js中,我们可以用number[]类型的变量来承接。

// 此值没有修改的必要,使用const声明为常量以符合代码规范
const pedPool: number[] = GetGamePool('CPed');
const objectPool: number[] = GetGamePool('CObject');
const netObjectPool: number[] = GetGamePool('CNetObject');
const vehiclePool: number[] = GetGamePool('CVehicle');
const pickupPool: number[] = GetGamePool('CPickup');

在C#中,GetGamePool的返回类型为dynamic,实际类型为ushort数组,不可直接使用,必须在for loop中强制转换为int才可使用。

获取CPed

本文要做的是靠近NPC触发交互提示,因此我们获取CPed池即可。

const pedPool: number[] = GetGamePool('CPed');

缓存距离计算结果

对于每个Ped,我们都需要计算其与玩家之间的距离,取距离最近且满足我们要求的Ped进行交互,因此我们需要创建一个变量用于缓存我们计算到的最近距离,以及其对应的ped

let result:number | undefined = undefined;
let minimalDist = INTERACT_DISTANCE;

获取玩家PedId及坐标

在客户端侧的代码中,可以直接使用PlayerPedId()来获取指向玩家Ped的句柄,获取到句柄后,使用GetEntityCoords来获取实体坐标

const playerPed = PlayerPedId();// 玩家句柄

const playerCoords = GetEntityCoords(playerPed);// 玩家坐标

遍历

  • 玩家判断

使用IsPedAPlayer来判断此句柄是否指向一名玩家,如果是玩家,则不满足我们的要求,跳过循环。

  • 坐标获取

与获取玩家坐标一样,使用GetEntityCoords获取实体坐标即可

  • 距离计算

FiveM提供了Vdist方法来快速计算坐标之间的距离,我们可以非常方便地调用它

我们只需要分别传入两个位置的x、y、z坐标即可,其函数声明为

/**
 * Calculates the distance between two points in 3D space. For performance reasons, consider using direct mathematical calculations for distance, as they can be more efficient than calling this native function.
 * ```
 * NativeDB Introduced: v323
 * ```
 * @param x1 X coordinate of the first point.
 * @param y1 Y coordinate of the first point.
 * @param z1 Z coordinate of the first point. Represents the height or elevation at the first point.
 * @param x2 X coordinate of the second point.
 * @param y2 Y coordinate of the second point.
 * @param z2 Z coordinate of the second point. Represents the height or elevation at the second point.
 * @return Returns the distance as a float between the two points (`x1`, `y1`, `z1`) and (`x2`, `y2`, `z2`) in the game world.
 */
declare function Vdist(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number): number;

for (const ped of pedPool) {
    if (IsPedAPlayer(ped)) continue;
    const pedCoords = GetEntityCoords(ped);
    const dist = Vdist(playerCoords[0]!, playerCoords[1]!, playerCoords[2]!, pedCoords[0]!, pedCoords[1]!, pedCoords[2]!);
    if (dist < minimalDist) {
      minimalDist = dist;
      result = ped;
    }
  }

返回结果

遍历完成后,result变量即我们所需的结果,如果存在符合要求的NPC,则result是离玩家最近的NPC,否则result为undefined

方法二:射线检测

方法二我没跑通,等会的吧

绘制提示GUI

这一部分没什么细节,总共就三行

BeginTextCommandDisplayHelp('STRING');
AddTextComponentString('按 ~INPUT_CONTEXT~ 与 AIC 交谈');
EndTextCommandDisplayHelp(0, false, false, -1);

第一行:创建了一个Help Display绘制事件
第二行:向绘制事件添加要绘制的文本,其中INPUT_CONTEXT是占位符,可以显示按键图标
第三行:结束绘制事件,并渲染绘制结果

这三个函数的文档在这里可以查看

posted @ 2026-05-14 12:28  梨猫先森  阅读(0)  评论(0)    收藏  举报