gamemaker吧 关注:13,593贴子:94,254
  • 6回复贴,共1

gms2 视觉系统实现

只看楼主收藏回复

最近想给npc加个带障碍阻挡的视觉功能,于是乎花了半天时间各种找,最后喜闻乐见的没有收获,看来又得自己写了,又是伸手失败的一天。(话说最近想要实现的功能就没几个找到的,我都要怀疑自己上网冲浪的水平了。。。)
回归正题,废话不多说,先上效果图,代码放二楼




IP属地:四川1楼2024-04-16 08:14回复
    先上图,方便查看。

    以下文字代码,方便复制
    ============================分隔符==============================
    // 角色视觉
    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;
    }
    =============================分隔符============================


    IP属地:四川2楼2024-04-16 08:18
    回复
      实现逻辑大概就是先利用collision_ellipse_list()方法,找出半径_r内的物体(实在不知道怎么做扇形碰撞,只好先这样了),再根据物体的碰撞盒的四个角算角度,将角度在视野范围内的物体依次做遮挡判定,遮挡判断将会根据视野高度_sight_h、遮挡物高度、遮挡物距离以及被遮挡物距离算出最终遮挡高度,如果最终判定为未遮挡,则加入_sight_list ,并最后返回。


      IP属地:四川3楼2024-04-16 08:27
      回复
        那人物是棘刺吧


        IP属地:河南来自Android客户端4楼2024-04-16 10:08
        收起回复
          想了想,还是放在“研究成果”分类了,教程类的话说明会再详细一些(大概)。
          咱之前好像写过类似东西,大概就只是用方块遮挡扇形视野,扇形按距离从近到远有行动/警戒/无视3档,分别全涂色/半涂色/不涂色。整体分了两部分,一部分是用draw triangle画遮挡区再配合shader绘制视野范围,另一部分检测某个目标是否在视野内就简单的coll line判定是否和遮挡物碰撞了(相当于两个目标都是“点状”)。当时就没做可以阻挡视野的大型方块障碍是否可见的判定,反正只要draw出正确的“不可见区域”就够了。而且当时想到的“判定障碍的4角是否可见”这种简易方法也会与实际会遇到的“如果透过一个较小缝隙只能看到一个障碍的中间一段”这样的实际问题冲突,就干脆放弃掉了。


          IP属地:北京5楼2024-04-18 12:34
          收起回复