gms2 视觉系统实现
gamemaker吧
全部回复
仅看楼主
level 4
最近想给npc加个带障碍阻挡的视觉功能,于是乎花了半天时间各种找,最后喜闻乐见的没有收获,看来又得自己写了,又是伸手失败的一天。(话说最近想要实现的功能就没几个找到的,我都要怀疑自己上网冲浪的水平了。。。)
回归正题,废话不多说,先上效果图,代码放二楼
2024年04月16日 00点04分 1
level 4
先上图,方便查看。
以下文字代码,方便复制
============================分隔符==============================
// 角色视觉
function check_sight(_x, _y, _r, _dir, _range, _sight_h = 0) {
// 半径R内的对象列表
var _res_list = ds_list_create();
// 视野范围内的对象列表
var _sight_list = ds_list_create();
// 视野范围内的对象遮挡范围列表
var _blind_list = ds_list_create();
// 视野范围,逆时针第一条边为_sight_a1
var _sight_a1 = angle_dif(_dir, _range / 2);
var _sight_a2 = (_dir + _range / 2) % 360;
collision_ellipse_list(_x - _r, _y - _r, _x + _r, _y + _r, o_collision, false, true, _res_list, true);
for(var _i = 0; _i < ds_list_size(_res_list); _i++;) {
var _res = _res_list[| _i];
//draw_rectangle(_res.bbox_left, _res.bbox_top, _res.bbox_right, _res.bbox_bottom, 1)
// 计算物品四角与对象的角度
var _lt = point_direction(_x, _y, _res.bbox_left, _res.bbox_top);
var _lb = point_direction(_x, _y, _res.bbox_left, _res.bbox_bottom);
var _rt = point_direction(_x, _y, _res.bbox_right, _res.bbox_top);
var _rb = point_direction(_x, _y, _res.bbox_right, _res.bbox_bottom);
// 计算物品遮挡角度范围
var _dot_list = [[_lt, _res.bbox_left, _res.bbox_top]
, [_lb, _res.bbox_left, _res.bbox_bottom]
, [_rt, _res.bbox_right, _res.bbox_top]
, [_rb, _res.bbox_right, _res.bbox_bottom]]
// 升序排序 (引擎自带的这个排序函数有bug,排序不准)
//array_sort(_dot_list, function(_var1, _var2){ return _var1[0] - _var2[0];});
// 升序排序
for (var _k = 0; _k < array_length(_dot_list); _k++) {
for (var _j = _k + 1; _j < array_length(_dot_list); _j++) {
if _dot_list[_k][0] <= _dot_list[_j][0] continue;
var _tmp = _dot_list[_k][0];
_dot_list[_k][0] = _dot_list[_j][0];
_dot_list[_j][0] = _tmp;
}
}
var _angle1 = 0;
var _angle2 = 0;
// 如果物体跨越了第四第一象限,特殊处理
var _is_special = _dot_list[0][0] < 90 && _dot_list[3][0] > 270;
if _is_special {
_angle1 = _dot_list[2];
_angle2 = _dot_list[1];
} else {
_angle1 = _dot_list[0];
_angle2 = _dot_list[3];
}
// 能否看见物体
var _is_sight = false;
var _dif_angle1_sight1 = angle_dif(_angle1[0], _sight_a1);
var _dif_angle2_sight1 = angle_dif(_angle2[0], _sight_a1);
var _dif_angle2_sight2 = angle_dif(_angle2[0], _sight_a2);
// 如果物体超过视野横截宽度
if ((_dif_angle2_sight2 < 90 - _range / 2 && (_dif_angle1_sight1 > _dif_angle2_sight2 + 180 + _range / 2))
|| (_dif_angle1_sight1 > 270 + _range / 2 && _dif_angle2_sight2 < _dif_angle1_sight1 - 180 && _dif_angle2_sight1 > _range))
&& _dif_angle2_sight2 > 0 {
_angle1[0] = _sight_a1;
_angle2[0] = _sight_a2;
_is_sight = true;
}
// 如果_angle1在视野内, 且该点距离小于半径
else if (_dif_angle1_sight1 < _range && point_distance(_x, _y, _angle1[1], _angle1[2]) < _r) {
// 如果_angle2超过视野边界
if _dif_angle2_sight1 > _range _angle2[0] = _sight_a2;
_is_sight = true;
}
// 如果_angle2在视野内, 且该点距离小于半径
else if (_dif_angle2_sight1 < _range && point_distance(_x, _y, _angle2[1], _angle2[2]) < _r) {
// 如果_angle1超过视野边界
if _dif_angle1_sight1 > (_dif_angle2_sight1 + 180) _angle1[0] = _sight_a1;
_is_sight = true;
}
// 如果_angle1、_angle2均在视野内
else if (_dif_angle2_sight1 < _range && _dif_angle1_sight1 < _range) {
_is_sight = true;
}
// 如果_angle1或_angle2在视野内, 且与视野边缘碰撞
else if (_dif_angle1_sight1 < _range) || (_dif_angle2_sight1 < _range) {
if collision_line(_x, _y, _x + lengthdir_x(_r, _sight_a1), _y + lengthdir_y(_r, _sight_a1), _res, false, true) {
_angle1[0] = _sight_a1;
_is_sight = true;
}
else if collision_line(_x, _y, _x + lengthdir_x(_r, _sight_a2), _y + lengthdir_y(_r, _sight_a2), _res, false, true) {
_angle2[0] = _sight_a2;
_is_sight = true;
}
}
// 判断物品是否在视野内
if _is_sight {
// 是否被遮挡
var _is_blind = false;
// 计算物品高度
var _res_h = is_empty(_res[$ "obj_height"] != noone) ? _res[$ "obj_height"] : max(0, sprite_get_height(_res.sprite_index) - (_res.bbox_bottom - _res.bbox_top));
// 合并计算遮挡范围
var _blind_gth = blind_merge_gth(_x, _y, _sight_h, _res.x, _res.y, _res_h, _blind_list);
// 判断是否被遮挡
for(var _j = 0; _j < array_length(_blind_gth); _j++;) {
var _blind_a1 = _blind_gth[_j][0];
var _blind_range = _blind_gth[_j][1];
if angle_dif(_angle1[0], _blind_a1) <= _blind_range && angle_dif(_angle2[0], _blind_a1) <= _blind_range {
_is_blind = true;
break;
}
}
// 如果物品被阻挡,跳过
if _is_blind continue;
// 计算物品遮挡角度大小
var _a_range = angle_dif(_angle2[0], _angle1[0]);
// 保存物品遮挡角度及范围
ds_list_add(_blind_list, [_angle1[0], _angle1[1], _angle1[2], _a_range, _res_h, _res]);
// 保存物品
ds_list_add(_sight_list, _res);
}
}
ds_list_destroy(_res_list);
ds_list_destroy(_blind_list);
return _sight_list;
}
// 筛选出高度足够的遮挡物;合并有重叠的遮挡区域
function blind_merge_gth(_x, _y, _sight_h, _res_x, _res_y, _res_h, _blind_list) {
// 筛选出的遮挡物
var _blind_gth = [];
//draw_line(_x, _y, _res_x, _res_y)
for(var _i = 0; _i < ds_list_size(_blind_list); _i++;) {
var _i_a1 = _blind_list[| _i][0];
var _i_x = _blind_list[| _i][1];
var _i_y = _blind_list[| _i][2];
var _i_range = _blind_list[| _i][3];
var _i_h = _blind_list[| _i][4];
var _i_res = _blind_list[| _i][5];
var _dif_angle = angle_difference(point_direction(_x, _y, _i_res.x, _i_res.y), point_direction(_x, _y, _res_x, _res_y))
// 阻挡物的距离 在 目标物方向上的分量
//var _x_map = abs(point_distance(_x, _y, _i_res.x, _i_res.y) / cos(degtorad(_dif_angle)))
// 对象距离障碍物碰撞框的最近距离
var _i_d = point_to_rect_distance(_x, _y, _i_res)
// 目标物体被遮挡的高度
var _target_h = ((_i_h - _sight_h) / _i_d) * point_distance(_x, _y, _res_x, _res_y) + _sight_h
// 如果高度足够阻挡_res
if _target_h >= _res_h {
var if_merge = false;
for(var _j = 0; _j < array_length(_blind_gth); _j++;) {
var _j_a1 = _blind_gth[_j][0];
var _j_range = _blind_gth[_j][1];
// 如果遮挡区域有重叠,则将区域相加
var _angle_dif_a1 = angle_dif(_i_a1, _j_a1);
var _angle_dif_a2 = angle_dif((_i_a1 + _i_range) % 360, _j_a1);
if _angle_dif_a1 <= _j_range {
if _j_range < _angle_dif_a1 + _i_range {
_blind_gth[_j][1] = _angle_dif_a1 + _i_range;
if_merge = true;
break;
}
}
else if _angle_dif_a2 <= _j_range {
_blind_gth[_j][0] = _i_a1;
_blind_gth[_j][1] = angle_dif((_j_a1 + _j_range) % 360, _i_a1);
if_merge = true;
break;
}
}
// 如果遮挡区域无重叠,则添加该区域至列表
if !if_merge {
_blind_gth[array_length(_blind_gth)] = [_i_a1, _i_range];
}
}
}
return _blind_gth;
}
// 计算点到框的最短距离
function point_to_rect_distance(_x, _y, _rect) {
var _dx = max(_rect.bbox_left - _x, 0, _x - _rect.bbox_right);
var _dy = max(_rect.bbox_top - _y, 0, _y - _rect.bbox_bottom);
return sqrt(_dx * _dx + _dy * _dy);
}
// 计算两个角度的差值(不可用系统函数angle_difference()替代!)
function angle_dif(_angle1, _angle2) {
return (_angle1 - _angle2 + 360) % 360;
}
=============================分隔符============================
2024年04月16日 00点04分 2
level 4
实现逻辑大概就是先利用collision_ellipse_list()方法,找出半径_r内的物体(实在不知道怎么做扇形碰撞,只好先这样了),再根据物体的碰撞盒的四个角算角度,将角度在视野范围内的物体依次做遮挡判定,遮挡判断将会根据视野高度_sight_h、遮挡物高度、遮挡物距离以及被遮挡物距离算出最终遮挡高度,如果最终判定为未遮挡,则加入_sight_list ,并最后返回。
2024年04月16日 00点04分 3
level 7
那人物是棘刺吧[呵呵]
2024年04月16日 02点04分 4
没错,难得画素材,随便用的[滑稽]
2024年04月16日 08点04分
吧务
level 13
想了想,还是放在“研究成果”分类了,教程类的话说明会再详细一些(大概)。
咱之前好像写过类似东西,大概就只是用方块遮挡扇形视野,扇形按距离从近到远有行动/警戒/无视3档,分别全涂色/半涂色/不涂色。整体分了两部分,一部分是用draw triangle画遮挡区再配合shader绘制视野范围,另一部分检测某个目标是否在视野内就简单的coll line判定是否和遮挡物碰撞了(相当于两个目标都是“点状”)。当时就没做可以阻挡视野的大型方块障碍是否可见的判定,反正只要draw出
正确的
“不可见区域”就够了。而且当时想到的“判定障碍的4角是否可见”这种简易方法也会与实际会遇到的“如果透过一个较小缝隙只能看到一个障碍的中间一段”这样的实际问题冲突,就干脆放弃掉了。
2024年04月18日 04点04分 5
透过缝隙看障碍物这个问题我通过合并遮挡区域解决了。其他还有一些个让人头疼的特殊情况,我实在没办法,只能一一判定了
2024年04月18日 12点04分
1