0%

游戏里面的AI实现一般是状态机或者行为树。

状态机

状态机很好理解,就是AI会处于某一状态中,在不同条件下会进行切换,让AI处于正确的状态中。例如,一个怪物,有闲逛、战斗、逃跑三个状态。在没有遇到目标的时候,是处于【闲逛】状态,遇到了敌人,切换进入【战斗】状态;当血量低于5%或者远离出生点,进入【逃跑】状态。

行为树

行为树的执行就是从上到下,从左导游遍历整个树。每个节点执行之后都会返回true或者false,之后往上返回。不同节点遇到返回的结果执行方式不太一样。可能是往父节点返回,也可能是执行下一个子节点。具体结果要根据子节点的返回结果跟自身节点类型决定。
第一个节点是根节点,叶子是具体的操作行为,而中间节点是用来控制执行的顺序的。

组合节点

  • Sequence 节点
    • AlwaysSequence 顺序执行
    • RandomSequence 每次执行都以随机序列去执行
  • Select 节点
  • IfElse 节点,有三个子节点,当第一个节点返回true,执行第二个子节点并返回子节点的值,否则执行第三个节点并返回节点的值。
  • While 节点,有两个子节点,当第一个子节点返回true的时候,执行第二个子节点,然后重新开始执行流程;当第一个节点返回false的时候则执行完成,并且返回true。
  • Root 节点,根节点,只有一个子节点。
  • Probility 节点,随机执行节点

后缀

  • 静态库
    • .a
    • .lib
  • 动态库
    • .so
    • .dll

g++/gcc编译选项

  • -o 指定输出目标的文件名
  • -L 表示要链接的库所在目录
  • -l 指定链接时需要的动态库,编译器查找动态库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称。
  • -shared 指定生成动态链接库
  • -static 指定生成静态库
  • -fPIC 表示编译为位置独立的代码,用于编译共享库
  • -Wall 生成所有警告信息
  • -ggdb 此选项将尽可能的生成gdb的可以使用的调试信息
  • -g 编译器在编译的时候产生调试信息
  • -c 只激活预处理、编译和汇编,也就是把程序做成目标文件,后缀为.o

undefine

静态库

静态链接:链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。

  • 静态库对函数库的链接是放在编译时期完成的;
  • 程序在运行时与函数库再无瓜葛,移植方便;
  • 浪费空间和资源,因为所有相关的目标文件与牵扯到的函数库被链接合成一个可执行文件。

linux下静态库命名规范必须是lib[name].a,lib是前缀,是必须的,扩展名为.a

创建静态库

  • 首先将代码编译成目标文件.o
    • g++ -c Test.cpp
  • 通过ar工具将目标文件打包成.a静态文件
    • ar -crv libtest.a Test.o

undefine

动态库

动态库在程序编译时并不会被链接到目标代码中,而是程序运行时才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份被该共享库的实例,避免了内存上的浪费。

  • 动态库把一些库函数的链接载入推迟到程序运行的时期;
  • 抗原实现进程之间的资源共享;
  • 将一些程序升级变得简单;
  • 甚至可以真正做到链接载入完全由程序员在代码中控制。

undefine

调试帮助

nm 命令

nm命令可以打印出库中的涉及到的所有符合。

  • 在库中被调用,但是没有在库中定义的,用U表示
  • 在库中定义的函数,用T表示
  • 弱态符合,用W表示,虽然在库中被定义,但是可能被其他库中的同名符合覆盖。
1
nm libtest.h

ldd 命令

ldd命令可以查看一个可执行程序依赖的共享库。

1
ldd libtest.so

recastnavigation

背景

常见实现服务端地形的方式有使用数组保存地形的方式。例如使用一个二维数组,value为0表示不可行走,为1表示可以行走地板。也可以支持扩展其它地表,例如沙漠,水。此种方式优点是实现方式简单,缺点是寻路成本较大,只支持2d地形。

还有一种方式是使用著名的recastnavigation库,使用导航网格navmesh来实现。库的地址是 https://github.com/recastnavigation/recastnavigation 。此方式可以大幅度减少网格数量,在寻路上具有较大优势,而且支持3d地形。但是也不是所有3d地形也是支持的,如果是角度小于90度的墙,是生成不了导航网格的。

导航网格

导航网格生成的参数

  • cellSize 体素在XZ轴上的大小,越小越精细
  • cellHeight 体素在Y轴上的高度,越小越精细,但可能引起网格之间断裂
  • minTraversableHeight 最低可通过高度
  • maxTraversableStep 可跨越不同地形时的高度,多用于楼梯阈值
  • maxTraversableSlope 最大可通过的斜坡倾斜度
  • clipLedges 边缘突出部分是否可以行走
  • traversableAreaBorderSize 可行走区域与阻挡之间的距离大小,贴墙的程度,越小可能交叠越多
  • smoonthingThreshold 距离场
  • useComservativeExpansion
  • minUnconnectedRegionSize 最小的无法被链接的区域,可以避免孤岛的网格的产生
  • mergeRegionSize 合并区域尺寸,低于改数值,可能会被大区域吞并
  • maxEdgeLength 网格边界的多边形最大的边
  • edgeMaxDeviation 网格边界与原始集合图形的偏移量,越小产生的三角形越多,越贴近于原始图形
  • maxVertsPerPoly 每个多边形的最大定点数,通常情况下6个顶点能够平衡需求和性能
  • contourSampleDistance 采样距离
  • Voxelization 体素化,把原始几何转换成高度域
  • Generate Regions 生成各个多边形区域
  • Generate Contours 生成各个多边形的边界轮廓并标记起来
  • Generate Polygon Mesh 合并重复边界轮廓信息,整合到网格里面去
  • Generate Detailed Mesh

gamescene_map 方案

undefined

undefined
undefined

对象概念

  • navmesh
  • navmesh agent
  • navmesh obstacle
  • off-mesh link

undefined
这个图用于解释数据结构非常有用

  • tile - 片
  • poly - 多边形
  • vert - 顶点

使用

有了navmesh之后,就可以使用Detour进行寻路等操作。

  • 构建一个 dtNavMeshQuery 实例
  • findPath 查询到经过的多边形
  • findStraightPath 将多边形转换成寻路坐标

接口

todo

导出lua接口

todo

局限性

  • 无法对角度小于90度的墙生成导航网格,如塞尔达等游戏爬墙
  • 要小心掉落到两片网格夹缝中
  • 不适合开放世界,因为开放世界可能没有加载整个地图
  • agent不能飞,不能跳

skynet.call 在设计的时候,是没有考虑超时的问题的。云风的解释是,加入之后会使得在其构建的系统增加不必要的复杂度。这个解释也是合理的,但是还是免不了有需求要使用到超时的机制。

举个简单例子,skynet实现的web服务,一些http请求的时候,如果没有超时机制,http请求就会一直占用。

云风给出了一种解决方案,就是增加一个代理服务,让其负责转发,然后代理服务增加超时的检查。也是一种不错的方式。

skynet 是支持协程的,应该说snlua服务,都是基于协程实现的。基于协程的特性,本身就能实现超时检测,逻辑也很简单。

执行函数的时候,调用 do_func_or_timeout 传入 msecfunc 。函数就会启用(fork)两个协程,并且让当前线程wait:

  • 第一个协程,sleep msec,之后检测 is_wait 如果为false,结束;否则wakeup原本协程;
  • 第二个线程则是执行 func函数,正常返回后,检查is_wait 如果为false,结束;否则wakeup原本协程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
local Skynet = require "skynet"
local M = {}

local STATUS_SUCC = 0
local STATUS_TIMEOUT = 1

function M.do_func_or_timeout(msec, func, ...)
local args = ...
local ctx = {
is_wait = true,
corout = coroutine.running(),
}

Skynet.fork(function()
Skynet.sleep(msec) -- 超时等待msec 1/00秒
if not ctx.is_wait then
return
end

ctx.is_wait = false
ctx.status = STATUS_TIMEOUT
Skynet.wakeup(ctx.corout)
end)

Skynet.fork(function()
local status = STATUS_SUCC
local ret = func(args)

if not ctx.is_wait then
return
end

ctx.is_wait = false
ctx.status = status
ctx.ret = ret

Skynet.wakeup(ctx.corout)
end)
Skynet.wait()
return ctx.status, ctx.ret
end

print("== start test ==", Skynet.time())
local status, ret = M.do_func_or_timeout(200, function()
Skynet.sleep(300)
return "ok"
end)
print("== status ret 1 ==", Skynet.time(), status, ret)

local status, ret = M.do_func_or_timeout(600, function()
Skynet.sleep(300)
return "ok"
end)
print("== status ret 2 ==", Skynet.time(), status, ret)

return M

输出:

1
2
3
== start test ==        1631807810.97
== status ret 1 == 1631807812.97 1 nil
== status ret 2 == 1631807815.97 0 ok

基本原理

将场景区域划分为小格子,然后将玩家的视野统一设定为玩家所在的格子和周边的八个格子。这样在同步的时候就只需要同步九宫格内的数据。为此需要维护玩家进入和离开格子的数据。

基本接口

主要有四个接口,其中进入场景(enter)和场景内移动(move)在这里合并为set接口。

  • new_area: 新建aoi场景区域
  • set: 进入场景或者场景内移动
  • leave: 离开场景
  • get_ids_by_grid: 根据格子id获取格子内对象id

实现

数据结构

所有数据都挂在场景内,此处称为area。area内包含了场景范围,对象列表,格子列表。new_area 接口也就是创建场景数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local tb_area = {
min_x = args.min_x, -- 场景范围
min_y = args.min_y,
max_x = args.max_x,
max_y = args.max_y,

grid_x = grid_x, -- x轴有几段
grid_y = grid_y, -- y轴有几段

grid_max = grid_max, -- 总格子数量
grid_size = grid_size, -- 格子长度

map_actor = {}, -- 对象列表
lst_grid = {}, -- 格子
}

进入场景和移动

进入场景非常简单,根据坐标计算出目标格子,然后分别加入 map_actor 列表和 lst_grid 列表即可。

场景内移动分2种情况:

第一种在格子内移动,这种情况,aoi没有变化。

第二种跨格子移动,出现了离开旧格子加入新格子的情况。这里稍微注意的是,有可能旧的九宫格和新的九宫格存在重叠的区域,这样的情况,对于重叠的区域的对象视野来说,目标对象并没有离开过视野。

离开场景

离开也非常简单,从 map_actor 列表和 lst_grid 列表删除即可。

完整代码

GitHub - rondsny/lua_aoi

数据结构

lua的内存结构最主要有三大块,lua_State、 CallInfo、 lua_TValue。

  • lua_State里面的 stack (栈)是主要的内存结构,类型是 lua_TValue;
  • lua_TValue 主要是Value,是一个 uion,存的内容根据 lua_TValue.tt_ 标记;
  • CallInfo 用于记录函数调用信息:作用的栈区间,返回数量,调用链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

typedef union lua_Value {
void * p; // LUA_TLIGHTUSERDATA
int b; // bool
lua_Integer i; // integer
lua_Number n; // number
lua_CFunction f; // function
} Value;

typedef struct lua_TValue {
Value value_;
int tt_;
} TValue;

typedef TValue* StkId;

struct CallInfo {
StkId func; // 被调用函数在栈中的位置
StkId top; // 被调用函数的栈顶位置
int nresult; // 又多少个返回值
int callstatus; // 调用状态
struct CallInfo* next; // 下一个调用
struct CallInfo* previous; // 上一个调用
};

typedef struct lua_State {
StkId stack; // 栈
StkId stack_last; // 从这里开始,栈不能被使用
StkId top; // 栈顶
int stack_size; // 大小
struct lua_longjmp * errorjmp;
int status; // 状态
struct lua_State * next; // 下一个lua_State,通常创建协程时会产生
struct lua_State * previous;
struct CallInfo base_ci; // 和lua_State生命周期一致的函数调用信息
struct CallInfo* ci; // 当前运作的CallInfo
struct global_State* l_G; // global_state指针
ptrdiff_t errorfunc; // 错误函数位于栈的那个位置
int ncalls; // 进行了多少次函数调用
} lua_State;

主要操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// lua_State的创建和销毁
struct lua_State* lua_newstate(lua_Alloc alloc, void* ud);
void lua_close(struct lua_State* L);
// 入栈
void increase_top(struct lua_State* L);
void lua_pushinteger(struct lua_State* L, int integer);

// 出栈
void lua_settop(struct lua_State* L, int idx);
int lua_gettop(struct lua_State* L);
void lua_pop(struct lua_State* L);

// 取栈上的值
lua_Integer lua_tointegerx(struct lua_State* L, int idx, int* isnum);

// 创建新的函数对象CallInfo
static struct CallInfo* next_ci(struct lua_State* L, StkId func, int nresult);

背景

在项目使用中,出现了以下报错:

1
2
3
Error Code: 1118 - Row size too large (> 8126).
Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help.
In current row format, BLOB prefix of 768 bytes is stored inline.

上面报错,这就涉及到了row size的上限,也有可能会涉及到file format的设置。

一些上限

  • 创建表报错:maximum row size > 65535
  • 创建表报错:row size too large > 8126
  • 插入数据报错:row size too larget > 8126

这里主要讲第三种报错,插入数据的时候触发的报错。先要理清file format和row format两个概念。
个人理解修改这两个参数都有可能解决掉问题。为啥是有可能,因为file format只是部分长字段类型的设置。

File format

先解释下file forma。这是用于varchat或者text字段存储数据的方案。file format有两种,分别是Antelope和Barracuda。

Antelope模式

在MySQL 5.6之前的版本,默认file format是Antelope。
意思是,对于varchar和text字段,会将内容的前768字段放置在index record中,后面再接20个字节的指针。
剩下内容会放置在BLOB page中。

假设有11个text字段,每个字段都是1000字节。那么在插入数据的时候,row size = (768+20)* 11 = 8668 > 8126,将会触发row size too larget > 8126报错。
如果使用Antelope模式,不建议使用超过10个text或varchar字段。

Barracuda模式

Barracude会将所有数据放在BLOB page中,在index record里面只放20个字节的指针。

设置切换

查询File format设置:

1
show variables like "%innodb_file%";

my.cnf 设置

1
2
3
innodb_file_format = Barracuda #
innodb_file_per_table = 1
innodb_large_prefix = 1

Row format

innodb引擎的行格式(row format)有两种。分别是compact和dynamic/compressed。

1
2
3
ALTER TABLE test ROW_FORMAT=COMPRESSED;

SHOW TABLE STATUS IN test_db;

这里还是之说Antelope模式下使用text的问题。普通的数值类型,其实很难超8126的长度限制,就不说了。在Antelope模式下,text字段的前768会存储在index record中,会占用row size的768+2个字节。所以text/varchar字段不能太多。除非确定不会太长。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

# 验证测试的sql

create table text2
(
text1 longtext,
text2 longtext,
text3 longtext,
text4 longtext,
text5 longtext,
text6 longtext,
text7 longtext,
text8 longtext,
text9 longtext,
text10 longtext,
text11 longtext
) ENGINE=InnoDB DEFAULT CHARSET=ascii;



insert into text2 values(
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000)
);

insert into text2 values(
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',197) # repeat('y',198) error
);


--------


create table text3
(
text1 longtext,
text2 longtext,
text3 longtext,
text4 longtext,
text5 longtext,
text6 longtext,
text7 longtext,
text8 longtext,
text9 longtext,
text10 longtext,
text11 longtext
) ENGINE=INNODB DEFAULT CHARSET=ascii ROW_FORMAT=COMPRESSED;


insert into text3 values(
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000)
);

1. 插件扩展

1.1. 命名空间

1
2
using UnityEditor;
using UnityEngine; //非必需,常用到

1.2. 使用语法

1
2
3
4
5
[MenuItem("Assets/My Test")]
private static void Test()
{
Debug.Log("Hello World!")
}

语法说明

1
2
3
4
5
6
7
[MenuItem("path $t", Is, Priority)]
private static void Test1()
{}

[MenuItem("path _t", Is, Priority)]
private static void Test2()
{}
  • path 是菜单路径;
    • 一级菜单名称不支持中文
  • $t 是一个快捷键实例,在路径后面接空格,然后加上快捷键表示,单纯的一个按键快捷键按键字符前带下划线。该项非必需
    • % 表示 ctrl
    • # 表示 shift
    • & 表示 alt
  • Is 设置为true的时候,如果没有选中游戏对象,会显示不可用状态,该选项非必需
  • Priority 是优先级,数值越小优先级越高,非必需,其默认值为1000。

下面表示快捷键为”ctrl+h” 的实例。

1
2
3
4
5
[MenuItem("Assets/My Test %h")]
private static void Test()
{
Debug.Log("Hello World!")
}

1.3. Selection类

https://docs.unity3d.com/ScriptReference/Selection.html

1.3.1. 获取选中物体

Selection.Objects可以获得选中的物品。

1.3.2. 获取选中目录

1
2
3
4
5
6
7
8
9
10
string[] guids = Selection.assetGUIDs;
foreach ( var id in guids )
{
string path = AssetDatabase.GUIDToAssetPath(id);
if(Directory.Exists(path))
{
Debug.Log(string.format("Directory {0} exist", path))
}
}

1.4. 给控件添加右上角齿轮菜单增加功能

1
2
3
4
5
[MenuItem("CONTEXT/Rigidbody/wyg_test")]
private static TestRightButton()
{
Debug.Log("右键测试")
}
  • CONTEXT 为固定写法;
  • Rigidbody 是控件名称,可以修改为其他控件;
  • 我使用中文的时候不知道为什么没有显示出来。

1.5. 弹窗

编辑器的弹窗类需要继承EditorWindow

使用方法与GUI的使用方法基本一致,有Init,Awake,OnGUI等函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class TestWindow : EditorWindow
{
public void Init()
{
}

publick void OnGUI()
{
GUILayout.Label("Test Window");
if (GUILayout.Button("Button"))
{
Debug.Log("On Button")
}
}
}

2. 组件属性展示

以下效果都是组件的显示,所以是属于using UnityEngine;的。

2.1. Range

可以将数值的展示效果变成滑动条效果。

1
2
[Range(1, 12)]
public int month;

2.2. Multiline/TextArea

控制占用多行显示。

1
2
3
4
5
[Multiline(5)]
public string msg1;

[TextArea(3,5)]
public string msg2; // 最少显示三行,最多显示五行

2.3. ContextMenuItem/ContextMenu

添加右键小菜单,添加一些小功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
[ContextMenuItem("Random", "RandomNumber")]
[ContextMenuItem("Reset", "ResetNumber")]
public int number;

void RandomNumber()
{
number = Random.Range(0, 100);
}

void ResetNumber()
{
number = 0;
}

还有一个ContextMenu的特性,用法类似。

1
2
3
4
5
6
7
public string Name;

[ContextMenu("Reset Name")]
private void ResetName()
{
Name = "";
}

2.4. ColorUsage

设置颜色选择器。

1
2
3
4
5
6
7
public Color color1;

[ColorUsage(false)]
public Color color2; // 没有alpha

[ColorUsage(true, true, 0, 8, 0.125f, 3)]
public Color color3; // HDR

Unity常见API

本文主要为了方便查阅

1. MonoBehaviour 生命周期

  • Awake 对象创建的时候调用,类似构造函数
  • Start 在Awake之后执行,区别在于,如果组件不可用(在Inspector没有勾选该组件),是不会执行Start
  • Update 主函数循环每帧调用
  • FixedUpdate 每次固定帧调用,在物理计算的时候,应该使用该方法,而不是Update
  • OnDestroy 对象销毁时调用

2. MonoBehaviour 常见回调函数

  • OnMouseEnter 鼠标移入GUI控件或者碰撞体时调用
  • OnMouseOver 鼠标停留在GUI控件或者碰撞体时调用
  • OnMouseExit 鼠标移出GUI控件或者碰撞体时调用
  • OnMouseDown 鼠标在GUI控件或者碰撞体上按下时调用
  • OnMouseUp 鼠标在GUI控件或者碰撞体上释放时调用
  • OnTriggerEnter 当其他碰撞体进入触发器时调用
  • OnTriggerExit 当其他碰撞体离开触发器时调用
  • OnCollisionEnter 当碰撞体或者刚体与其他碰撞体或刚体接触时调用
    • OnCollisionEnter2D 其它2D函数类似
  • OnCollisionExit 当碰撞体或者刚体与其他碰撞体或刚体停止接触时调用
  • OnCollisionStay 当碰撞体或者刚体与其他碰撞体或刚体保持接触时调用
  • OnContollerColliderHit 当控制器移动时与碰撞体发生碰撞时调用
  • OnBecameVisible 对于任意一个相机可见时调用
  • OnBecameInVisible 对于任意一个相机不可见时调用
  • OnEnable 对象启用或者激活时调用
  • OnDisable 对象禁用或者取消激活时调用
  • OnDestroy 脚本销毁时调用
  • OnGUI 渲染GUI和处理GUI消息时调用

3. 访问游戏对象

  • GameObject.Find 多个时返回第一个
  • GameObject.FindWithTag 多个时返回第一个
  • GameObject.FindGameObjectsWithTag 返回数组

以上函数比较耗时,尽量避免在update函数中使用。

4. 访问组件

4.1. 常见组件

  • Transform 位置、旋转、缩放
  • Rigidbody 刚体
  • Renderer 渲染物体模型
  • Light 灯光属性
  • Camera 相机属性
  • Collider 碰撞体
  • Animation 动画
  • Audio 声音
  • Mesh
    • Mesh Filter 网格过滤器
    • Text Mesh 文本
    • Mesh Renderer 网格渲染器
    • Skinned Mesh Renderer 蒙皮渲染器,用于骨骼动画
  • Particle Sysyem 粒子系统
  • Physics 物理
  • Image Effects
  • Scripts 自定义组件

4.2. 获取组件

  • GetComponent<>() 获取组件
  • GetComponents<>()
  • GetComponentInChildren<>() 得到对象或者对象子物体上的组件
  • GetComponentsInChildren<>()

以上函数比较耗时,尽量避免在update函数中使用。

4.3. Transform 组件

4.3.1. 成员变量
  • position 世界坐标系
  • localPosition 相对坐标系(父对象)
  • eulerAngles 世界坐标系中以欧拉角表示的旋转
  • localEulerAngles 相对旋转
  • right 右方向
  • up 上方向
  • forward 前方向
  • rotation 旋转四元数
  • localRotation 相对旋转四元数
  • localScale 相对缩放比例
  • parent 父对象的Transform组件
  • worldToLocalMatrix 世界坐标系到局部坐标系的变换矩阵(只读)
  • localToWorldMatrix 局部坐标系到世界坐标系的变换矩阵(只读)
  • root 根对象的Transform组件
  • childCount 子孙对象的数量
  • lossyScale 全局缩放比例(只读)
4.3.2. 成员函数
  • Translate 按指定方向和距离平移
  • Rotate 按指定的欧拉角旋转
  • RotateAround 按给定旋转轴和旋转角度进行旋转
  • LookAt 旋转使得自身的前方向指向目标的位置
  • TransformDirection 将一个方向从局部坐标系变换到世界坐标系
  • InverseTansformDirection 将一个方向从世界坐标系变化到局部坐标系
  • DetachChildren 与所有子物体接触父子关系
  • Find 按名称查找子对象
  • IsChildOf 判断是否是指定对象的子对象

5. Time 时间类

5.1. 成员变量

  • time 游戏从开始到现在经历的时间(秒)(只读)
  • timeSinceLevelLoad 此帧开始时间(秒)(只读),从关卡加载完成开始计算
  • dateTime 上一帧耗费的时间(秒)(只读)
  • fixedTime 最近FixedUpdate的时间。该时间从游戏开始计算
  • fixedDateTime 物理引擎和FixedUpdate的更新时间间隔
  • maximunDateTime 一帧的最大耗时时间
  • smoothDeltaTime Time.deltaTime的平滑淡出
  • timeScale 时间流逝速度的比例。可以制作慢动作特效
  • frameCount 已渲染的帧的总数(只读)
  • realtimeSinceStartup 游戏从开始到现在的真实时间(秒),该事件不受timeScale影响
  • captureFramerate 固定帧率设置

6. Random 随机数类

6.1. 成员变量

  • seed 随机数生成器种子
  • value 0~1随机数,包含0和1
  • insideUnitSphere 半径为1的球体内的一个随机点
  • insideUnitCircle 半径为1的圆内的一个随机点
  • onUnitSphere 半径为1的球面上的一个随机点
  • rotation 随机旋转
  • rotationUnitform 均匀分布的随机旋转

6.2. 成员函数

  • Range 返回(min,max)直接的随机浮点数,包含min和max

7. Mathf 数学类

7.1. 成员变量

  • PI
  • Infinity 正无穷大
  • NegativeInfinity 负无穷大
  • Deg2Rad 度到弧度的转换系数
  • Rad2Deg 弧度到度的转换系数
  • Epsilon 一个很小的浮点数

7.2. 成员函数

  • Sin 弧度
  • Cos 弧度
  • Tan 弧度
  • Asin 角度
  • ACos 角度
  • Atan 角度
  • Sqrt 计算平方根
  • Abs 绝对值
  • Min
  • Max
  • Pow Pow(f,p) f的p次方
  • Exp Exp(p) e的p次方
  • Log 计算对数
  • Log10 计算基为10的对数
  • Ceil 向上取整
  • Floor 向下取整
  • Round 四舍五入
  • Clamp 将数值限制在min和max之间
  • Clamp01 将数值限制在0和1之间

8. Coroutine 协同程序函数

  • StartCoroutine 启动一个协同程序
  • StopCoroutine 终止一个协同程序
  • StopAllCoroutines 终止所有协同程序
  • WaitForSeconds 等到若干秒
  • WaitForFixedUpdates 等待直到下一次FiexedUpdate调用

官方文档:http://bulletphysics.org
开源代码:https://github.com/bulletphysics/bullet3/releases
API文档:http://bulletphysics.org/Bullet/BulletFull/annotated.html

封装bullet碰撞检测

该碰撞检测包含场景创建、场景物体添加、射线击中检测、胶囊体碰撞检测。

0. 几个文件说明

  1. 将所有场景管理碰撞检测封装到ColScene类中;
  2. Erlang的入口为 t_bullet.erl, C++的入口为 t_bullet.cpp;
  3. data_scene.erl 为场景地形存储文件;
  4. t_btest.erl 为测试场景。

1. ColScene类

1.1. ColScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#ifndef COL_SCENE
#define COL_SCENE

#include "btBulletDynamicsCommon.h"

class ColScene
{
private:
btDefaultCollisionConfiguration* m_colConfig;
btCollisionDispatcher* m_dispatcher;
btBroadphaseInterface* m_broadInterface;
btSequentialImpulseConstraintSolver* m_solver;
btDynamicsWorld* m_world; // 场景信息,退出的时候需要delete

// add collision object
btRigidBody* createAddRigidBody(btScalar mass, const btTransform& startTransform, btCollisionShape* shape);
void deleteColObj(btCollisionObject* obj);
void setColPos(btVector3 p);

public:
btVector3* m_colPos; // 用于记录每次碰撞位置

ColScene();
~ColScene();

// for add 增加长方体、球体、胶囊体、三角网格到场景中
btRigidBody* addBox(btVector3 boxHalf, btVector3 bpos, btVector4 rota, btScalar mass);
btRigidBody* addSphere(btScalar radius, btVector3 bpos, btVector4 rota, btScalar mass);
btRigidBody* addCupsule(
btScalar radius,
btScalar height,
btVector3 bpos, btVector4 rota, btScalar mass);
btRigidBody* addTriMesh(
int vtxCount,
int idxCount,
btScalar vtx[],
unsigned short idx[],
btVector3 bpos, btVector4 rota, btScalar mass);

// for check 射线检查、碰撞检测
bool rayHit(btVector3 from, btVector3 to);
bool checkPos(btCollisionObject* obj);
bool checkFirstCupsule();

// for debug
btCollisionObject* createCapsule(btVector3 posA, btVector3 posB, double radius);
};
#endif

1.2. ColScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#include <list>
#include <math.h>
#include "ColScene.h"
#include "btBulletDynamicsCommon.h"

using namespace std;

// 碰撞检测回调
struct MyColCallBack : btCollisionWorld::ContactResultCallback
{
btVector3 m_colPos;
bool is_col = false;

btScalar addSingleResult(
btManifoldPoint & cp,
const btCollisionObjectWrapper * colObj0Wrap,
int partId0,
int index0,
const btCollisionObjectWrapper * colObj1Wrap,
int partId1,
int index1)
{
m_colPos = cp.getPositionWorldOnB();
is_col = true;
printf("col pos {%f, %f, %f}\n", m_colPos.getX(), m_colPos.getY(), m_colPos.getZ());
return btScalar(0.f);
};
};

// -------------------------------
// 构造函数和析构函数
// -------------------------------
ColScene::ColScene()
{
m_colPos = new btVector3();
m_colConfig = new btDefaultCollisionConfiguration();
m_dispatcher = new btCollisionDispatcher(m_colConfig);
m_broadInterface = new btDbvtBroadphase();

m_solver = new btSequentialImpulseConstraintSolver;
m_world = new btDiscreteDynamicsWorld(m_dispatcher, m_broadInterface, m_solver, m_colConfig);
}

//
ColScene::~ColScene()
{
for (int i = 0; i < m_world->getNumCollisionObjects(); ++i)
{
btCollisionObject* obj = m_world->getCollisionObjectArray()[i];
deleteColObj(obj);
}
delete m_world;
delete m_solver;
delete m_broadInterface;
delete m_dispatcher;
delete m_colConfig;
delete m_colPos;
}

// -------------------------------
// 私有函数
// -------------------------------
void ColScene::deleteColObj(btCollisionObject* obj)
{
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState())
{
delete body->getMotionState();
delete body->getCollisionShape();
}
m_world->removeCollisionObject(obj);
delete obj;
}

void ColScene::setColPos(btVector3 p)
{
printf("pos = {%f, %f, %f}\n", p.getX(), p.getY(), p.getZ());
m_colPos->setX(p.getX());
m_colPos->setY(p.getY());
m_colPos->setZ(p.getZ());
}

btRigidBody* ColScene::createAddRigidBody(float mass, const btTransform& startTransform, btCollisionShape* shape)
{
bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* body = new btRigidBody(cInfo);

m_world->addRigidBody(body);
return body;
};

// -----------------------
// 射线检测、碰撞检测
// -----------------------
bool ColScene::rayHit(btVector3 from, btVector3 to)
{
btCollisionWorld::ClosestRayResultCallback callback(from, to);
m_world->rayTest(from, to, callback);
if(callback.hasHit())
{
btVector3 p = callback.m_hitPointWorld;
setColPos(p);
return true;
}
return false;
}

bool ColScene::checkPos(btCollisionObject* obj)
{
MyColCallBack cb = MyColCallBack();
m_world->contactTest(obj, cb);
btVector3 pos = cb.m_colPos;
setColPos(pos);
return cb.is_col;
}

bool ColScene::checkFirstCupsule()
{
bool is_col = false;

m_world->performDiscreteCollisionDetection();
// m_world->stepSimulation(1.f/60.f);

list<btCollisionObject*> m_collisionObjects;
int numManifolds = m_world->getDispatcher()->getNumManifolds();

for(int i=0; i<numManifolds; i++)
{
btPersistentManifold* contactManifold = m_world->getDispatcher()->getManifoldByIndexInternal(i);
btCollisionObject* obA = (btCollisionObject*)(contactManifold->getBody0());
btCollisionObject* obB = (btCollisionObject*)(contactManifold->getBody1());

int numContacts = contactManifold->getNumContacts();
for(int j=0; j<numContacts; j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if(pt.getDistance()<0.f)
{
m_collisionObjects.push_back(obA);
m_collisionObjects.push_back(obB);
btVector3 posA = pt.getPositionWorldOnA();
btVector3 posB = pt.getPositionWorldOnB();
}
}
}

if(m_collisionObjects.size()>0)
{
m_collisionObjects.sort();
m_collisionObjects.unique();
for(list<btCollisionObject*>::iterator itr = m_collisionObjects.begin(); itr != m_collisionObjects.end(); ++itr) {
btCollisionObject* colObj = *itr;

if(colObj->getCollisionShape()->getShapeType()==CAPSULE_SHAPE_PROXYTYPE) // 如果是胶囊体刚体
{
btTransform trans = colObj->getWorldTransform();
setColPos(trans.getOrigin());
is_col = true;
break;
}
}
m_collisionObjects.clear();
}
return is_col;
}

// -------------------------------
// 增加长方体、球体、胶囊体、三角网格(注意,这里会添加到场景中)
// -------------------------------
btRigidBody* ColScene::addBox(btVector3 boxHalf, btVector3 bpos, btVector4 rota, btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btBoxShape(boxHalf);
return createAddRigidBody(mass, trans, shape);
};

btRigidBody* ColScene::addSphere(btScalar radius, btVector3 bpos, btVector4 rota, btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btSphereShape(radius);
return createAddRigidBody(mass, trans, shape);
};

btRigidBody* ColScene::addCupsule(
btScalar radius,
btScalar height,
btVector3 bpos, btVector4 rota, btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btCapsuleShape((btScalar)radius, height);
return createAddRigidBody(mass, trans, shape);
};

btRigidBody* ColScene::addTriMesh(
int vtxCount,
int idxCount,
btScalar vtx[],
unsigned short idx[],
btVector3 bpos,
btVector4 rota,
btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setOrigin(btVector3(0,-25,0));
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));

btTriangleIndexVertexArray* meshInterface = new btTriangleIndexVertexArray();
btIndexedMesh part;

part.m_vertexBase = (const unsigned char*)vtx;
part.m_vertexStride = sizeof(btScalar) * 3;
part.m_numVertices = vtxCount;
part.m_triangleIndexBase = (const unsigned char*)idx;
part.m_triangleIndexStride = sizeof(short) * 3;
part.m_numTriangles = idxCount/3;
part.m_indexType = PHY_SHORT;

meshInterface->addIndexedMesh(part, PHY_SHORT);

bool useQuantizedAabbCompression = true;
btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression);

return createAddRigidBody(mass, trans, shape);
};

// for debug
// -------------------------
// 创建一个胶囊体(但是不添加到场景)
btCollisionObject* ColScene::createCapsule(btVector3 posA, btVector3 posB, double radius)
{
btScalar lenX = posB.getX() - posA.getX();
btScalar lenY = posB.getY() - posA.getY();
btScalar lenZ = posB.getZ() - posA.getZ();
btScalar height = sqrt(lenX*lenX + lenY*lenY + lenZ*lenZ);

btScalar posX = (posA.getX()+posB.getX())/2.f;
btScalar posY = (posA.getY()+posB.getY())/2.f;
btScalar posZ = (posA.getZ()+posB.getZ())/2.f;

printf("lenX -> %f\n", lenX );
printf("lenY -> %f\n", lenY );
printf("lenZ -> %f\n", lenZ );
printf("heigh -> %f\n", height);

printf("posX -> %f\n", posX );
printf("posY -> %f\n", posY );
printf("posZ -> %f\n", posZ );

btVector3 bpos(posX, posY, posZ);
btVector4 rota(lenX, lenY, lenZ, 1.f);
btScalar mass(1.f);

btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btCapsuleShape((btScalar)radius, height);

bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(trans);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* body = new btRigidBody(cInfo);
return body;
}

2. 入口

2.1. Erlang项目rebar配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

{deps,[
{bullet3, ".*", {git, "git@github.com:bulletphysics/bullet3.git", {tag, "2.85"}}, [raw]}
]}.

{port_env, [{"linux|darwin", "CFLAGS", "$CFLAGS "},
{"win32", "CFLAGS", "/Ox /fp:fast"}]}.

{port_specs, [
{"win32", "priv/t_bullet.dll", [
"c_src/t_bullet.cpp",
"deps/bullet3/src/BulletDynamics/Character/*.cpp",
"deps/bullet3/src/BulletDynamics/ConstraintSolver/*.cpp",
"deps/bullet3/src/BulletDynamics/Dynamics/*.cpp",
"deps/bullet3/src/BulletDynamics/Featherstone/*.cpp",
"deps/bullet3/src/BulletDynamics/MLCPSolvers/*.cpp",
"deps/bullet3/src/BulletDynamics/Vehicle/*.cpp",
"deps/bullet3/src/BulletCollision/BroadphaseCollision/*.cpp",
"deps/bullet3/src/BulletCollision/CollisionDispatch/*.cpp",
"deps/bullet3/src/BulletCollision/CollisionShapes/*.cpp",
"deps/bullet3/src/BulletCollision/Gimpact/*.cpp",
"deps/bullet3/src/BulletCollision/NarrowPhaseCollision/*.cpp",
"deps/bullet3/src/LinearMath/*.cpp"
],
[{env, [{"CXXFLAGS", "/Ox /fp:fast -I\"deps\\bullet3\\src\" "}]}]}

]}.

2.2. Erlang代码入口t_bullet.erl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
% @Author: weiyanguang
% @Email: rondsny@gmail.com
% @Date: 2017-01-22 18:04:28
% @Last Modified by: weiyanguang
% @Last Modified time: 2017-02-10 11:55:43
% @Desc:

-module(t_bullet).
-export([
open_scene/0
,close_scene/0

,add_box/3
,add_sphere/3
,add_mesh/6

,col_check_capsule/3
,ray_hit/2
]).

-on_load(init/0).

init() ->
Code = filename:join("./priv", atom_to_list(?MODULE)),
erlang:load_nif(Code, 0).

%% 初始化场景
open_scene() ->
erlang:nif_error(undef).

%% 删除场景内资源,回收内存
close_scene() ->
erlang:nif_error(undef).

add_box(_BoxHalf, _Bpos, _Rotation) ->
erlang:nif_error(undef).

add_sphere(_Radius, _Bpos, _Rotation) ->
erlang:nif_error(undef).

add_cupsule(_Radius, _Height, _Bpos, _Rotation) ->
erlang:nif_error(undef).

add_mesh(_VtxCount, _IdxCount, _VtxList, _IdxList, _Bpos, _Rotation) ->
erlang:nif_error(undef).

%% 检查胶囊体与场景内是否发生碰撞
%% 胶囊体两端坐标,半径
%% @return {1, {x,y,z}}| {0,{A.x, A.y, A.z}}
col_check_capsule(_PosA, _PosB, _Raduis) ->
erlang:nif_error(undef).

%% 射线击中检测
ray_hit(_From, _To) ->
erlang:nif_error(undef).

2.3. C++代码入口t_bullet.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#include <stdio.h>
#include "erl_nif.h"
#include "btBulletDynamicsCommon.h"
#include "ColScene.h"

ColScene* g_scene;

// ---------------------------
static int get_number_f(ErlNifEnv *env, ERL_NIF_TERM eterm, double *f)
{
if (enif_get_double(env, eterm, f)) {
return 1;
}
else {
long n;
if (enif_get_long(env, eterm, &n)){
*f = (double)n;
return 1;
}
else {
return 0;
}
}
}

static int get_vector3(ErlNifEnv *env, ERL_NIF_TERM eterm, btVector3 *vector)
{
double x,y,z;
int arity;
const ERL_NIF_TERM *array;
if (enif_get_tuple(env, eterm, &arity, &array)
&& 3 == arity
&& get_number_f(env, array[0], &x)
&& get_number_f(env, array[1], &y)
&& get_number_f(env, array[2], &z)) {

vector->setX((btScalar)x);
vector->setY((btScalar)y);
vector->setZ((btScalar)z);

return 1;
}
else {
return 0;
}
}

static int get_vector4(ErlNifEnv *env, ERL_NIF_TERM eterm, btVector4 *vector)
{
double x,y,z,w;
int arity;
const ERL_NIF_TERM *array;
if (enif_get_tuple(env, eterm, &arity, &array)
&& 4 == arity
&& get_number_f(env, array[0], &x)
&& get_number_f(env, array[1], &y)
&& get_number_f(env, array[2], &z)
&& get_number_f(env, array[3], &w)) {

vector->setX((btScalar)x);
vector->setY((btScalar)y);
vector->setZ((btScalar)z);
vector->setW((btScalar)w);

return 1;
}
else {
return 0;
}
}

// ---------------------------

static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
return 0;
}

static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
{
return 0;
}

static ERL_NIF_TERM open_scene(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (!g_scene)
{
g_scene = new ColScene();
printf("init scene done\n");
}
return enif_make_atom(env, "ok");
}

static ERL_NIF_TERM close_scene(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
delete g_scene;
return enif_make_atom(env, "ok");
}

// ---------------------------------------------------------

static ERL_NIF_TERM add_box(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
btVector3 boxHalf;
btVector3 bpos;
btVector4 rota;

if(
get_vector3(env, argv[0], &boxHalf) &&
get_vector3(env, argv[1], &bpos) &&
get_vector4(env, argv[2], &rota)
){
g_scene->addBox(boxHalf, bpos, rota, 0.);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

static ERL_NIF_TERM add_sphere(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
double radius;
btVector3 bpos;
btVector4 rota;

if(
enif_get_double(env, argv[0], &radius) &&
get_vector3(env, argv[1], &bpos) &&
get_vector4(env, argv[2], &rota)
){
g_scene->addSphere(radius, bpos, rota, 0.);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

static ERL_NIF_TERM add_cupsule(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
double radius;
double height;
btVector3 bpos;
btVector4 rota;

if(
enif_get_double(env, argv[0], &radius) &&
enif_get_double(env, argv[1], &height) &&
get_vector3(env, argv[2], &bpos) &&
get_vector4(env, argv[3], &rota)
){
g_scene->addCupsule(radius, height, bpos, rota, 0.);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

static ERL_NIF_TERM add_mesh(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
int vtxCount;
int idxCount;
btVector3 bpos;
btVector4 rota;

double vt;
int id;
btScalar vtx[10240];
unsigned short idx[20480];

ERL_NIF_TERM head;
ERL_NIF_TERM tail;

tail = argv[2];
int i = 0;
while(enif_get_list_cell(env, tail, &head, &tail))
{
enif_get_double(env, head, &vt);
vtx[i] = (float)vt;
i++;
}

tail = argv[3];
i = 0;
while(enif_get_list_cell(env, tail, &head, &tail)){
enif_get_int(env, head, &id);
idx[i] = (unsigned short)id;
i++;
}

if(
enif_get_int(env, argv[0], &vtxCount) &&
enif_get_int(env, argv[1], &idxCount) &&
get_vector3(env, argv[4], &bpos) &&
get_vector4(env, argv[5], &rota)
){
g_scene->addTriMesh(vtxCount, idxCount,
vtx, idx,
bpos, rota, 0.f);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

// ---------------------------------------------------------

// @return {1, {x,y,z}}| {0, {A.x, A.y, A.z}}
static ERL_NIF_TERM col_check_capsule(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
btVector3 posA;
btVector3 posB;
if (!get_vector3(env, argv[0], &posA))
return enif_make_badarg(env);

else
{
if (!get_vector3(env, argv[1], &posB))
return enif_make_badarg(env);

else
{
double radius;
enif_get_double(env, argv[3], &radius);
btCollisionObject* obj = g_scene->createCapsule(posA, posB, radius);
int shapType = g_scene->checkPos(obj);
if(shapType>0)
{
ERL_NIF_TERM termPos = enif_make_tuple3(env,
enif_make_double(env, g_scene->m_colPos->getX()),
enif_make_double(env, g_scene->m_colPos->getY()),
enif_make_double(env, g_scene->m_colPos->getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env,
enif_make_int(env, 1),
termPos);
return terms;
}

ERL_NIF_TERM termA = enif_make_tuple3(env,
enif_make_double(env, posA.getX()),
enif_make_double(env, posA.getY()),
enif_make_double(env, posA.getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env, enif_make_int(env, 0), termA);
return terms;
}
}
}


// check raycast hit
static ERL_NIF_TERM ray_hit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
btVector3 from;
btVector3 to;
if (!get_vector3(env, argv[0], &from))
return enif_make_badarg(env);

else
{
if (!get_vector3(env, argv[1], &to))
return enif_make_badarg(env);

else
{
if(g_scene->rayHit(from, to))
{
ERL_NIF_TERM termPos = enif_make_tuple3(env,
enif_make_double(env, g_scene->m_colPos->getX()),
enif_make_double(env, g_scene->m_colPos->getY()),
enif_make_double(env, g_scene->m_colPos->getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env, enif_make_int(env, 1), termPos);
return terms;
}

ERL_NIF_TERM termA = enif_make_tuple3(env,
enif_make_double(env, from.getX()),
enif_make_double(env, from.getY()),
enif_make_double(env, from.getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env, enif_make_int(env, 0), termA);
return terms;
}
}
}


static ErlNifFunc nif_funcs[] = {
{"open_scene", 0, open_scene},
{"close_scene", 0, close_scene},

{"add_box", 3, add_box},
{"add_sphere", 3, add_sphere},
{"add_cupsule", 4, add_cupsule},
{"add_mesh", 6, add_mesh},

{"col_check_capsule", 3, col_check_capsule},
{"ray_hit", 2, ray_hit}
};


ERL_NIF_INIT(t_bullet, nif_funcs, load, NULL, upgrade, NULL)

3. 地形数据的erlang处理

3.1. 定义数据格式

数据record定义

1
2
3
4
5
6
7
8
9
10
11
-record(data_scene, {
id = 0,
type = undef, % box|sphere|mesh
position = {0,0,0}, % 位置
rotation = {0,0,0,1}, % 旋转
% 以下玩为box|sphere的内容
scale = {0,0,0}, % 形状,当为sphere的时候取第一个数为半径
% 以下为mesh的内容
vtx = [], % 顶点坐标
idx = [] % 三角形坐标索引
}).

一个测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-module(data_scene).
-include("data.hrl").
-compile([export_all, {no_auto_import, [{get, 1}]}]).
gets() ->
[
1,
2,
3].

get(1) -> #data_scene{
id = 1,
type = box,
position = {1,1,1},
rotation = {0,0,0,1},
scale = {1,1,1}
};

get(2) -> #data_scene{
id = 2,
type = sphere,
position = {1,1,1},
rotation = {0,0,0,1},
scale = {1,1,1}
};

get(3) -> #data_scene{
id = 3,
type = mesh,
position = {0,-25,0},
rotation = {0,0,0,1},
vtx = [
-250.0,2.99192,113.281,
-250.0,2.18397,117.188,
-246.094,1.62262,113.281,
-246.094,1.51628,117.188,
-242.188,0.847411,113.281,
...],
idx = [
0,1,2,
3,2,1,
2,3,4,
...]

...

3.2. 初始化场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-module(t_btest).
-include("ts_global.hrl").
-include("data.hrl").
-compile([export_all]).

init() ->
t_bullet:open_scene(),

Ids = data_scene:gets(),
Cfsg = [data_scene:get(Id)||Id<-Ids],
lists:foreach(fun add_obj/1, Cfsg),
ok.

add_obj(#data_scene{
type = box,
position = Bpos,
rotation = Rota,
scale = Scal
}) ->
t_bullet:add_box(Scal, Bpos, Rota);

add_obj(#data_scene{
type = sphere,
position = Bpos,
rotation = Rota,
scale = {Radius, _, _}
}) ->
t_bullet:add_sphere(Radius, Bpos, Rota);
add_obj(#data_scene{
type = sphere,
position = Bpos,
rotation = Rota,
scale = Radius
}) when is_number(Radius) ->
t_bullet:add_sphere(Radius, Bpos, Rota);
add_obj(#data_scene{
type = mesh,
position = Bpos,
rotation = Rota,
vtx = Vts,
idx = Idx
}) ->
t_bullet:add_mesh(length(Vts)/3, length(Idx), Vts, Idx, Bpos, Rota);
add_obj(_Unk) ->
skip.

3.3. 测试结果