Android Region类碰撞检测问题

前言

众所周知,Region是android graphics一族中比较低调的工具类,主要原因还是在碰撞检测方面存在一些不足,甚至可以说成事不足败事有余,以至于难以用于2D游戏开发。

用法

既然这么失败的工具为什么要介绍呢,主要我们介绍一下它能成事的部份,以及正确的用法。

  • Region类能成事的部份主要还是Op布尔操作和矩阵操作,但是这个似乎又和Path的作用重合,另外一部分contain包含关系判断,contain能准确的判断点和矩形是不是被包含了,但是其他形状那就没办法了。
  • 成事不足败事有余的部份,是quickXXX 快速检测方法,返回值true-能确保物体没有碰撞,但false无法确保是不是已经碰撞了,换句话说true是100%没碰撞,但是false还需要你自己进一步确认,不过这点可以作为减少判断的优化方法,但不是判定方法。

学习的意义 对于一些粒子,我们不太关注大小,这个时候是可以利用中心点去检测的,另外quickXXX其实用处不大,不过可以减少一部分代码,但是我们仍然还需要了解它的用法。

非Path用法

对于非Path用法,Region还是相当简单的,直接使用set方法即可

java 复制代码
mainRegion.set((int) -radius, (int) -radius, (int) radius, (int) radius);

Path方法

这个用法比较奇怪,需要2个参数,最后一个是Region类,弄不好就是鸡生蛋蛋生鸡一样令人迷惑,第二个可以看作被裁剪的区域,如下操作,求并集区域。不过话说回来,这个意义在哪里?

java 复制代码
circlePath.reset();
circlePath.addCircle(x- width/2f,y - height/2f,10, Path.Direction.CCW);
circleRegion.setPath(circlePath,mainRegion);

小试一下

实现开头的图片效果

定义一些变量

java 复制代码
 private float x; //x事件坐标
 private float y; //y事件坐标

 //所以形状
 Path[] objectPaths = new Path[5];
 //形状区域检测
 Region objectRegion = new Region();
 
 //小圆球区域
 Region circleRegion = new Region();
//小圆
 Path circlePath = new Path();
 //绘制区域
 Region mainRegion = new Region();

构建物体

三角形、圆等物体

java 复制代码
for (int i = 0; i < objectPaths.length; i++) {
    Path path = objectPaths[i];
    if (path == null) {
        path = new Path();
        objectPaths[i] = path;
    } else {
        path.reset();
    }
}

Path path = objectPaths[0];
path.moveTo(radius / 2, -radius / 2);
path.lineTo(0, -radius);
path.lineTo(radius / 2, -radius);
path.close();

path = objectPaths[1];
path.moveTo(-radius / 2, radius / 2);
path.lineTo(-radius / 2 - 100, radius / 2);
path.arcTo(-radius / 2 - 100, radius / 2, -radius / 2, radius / 2 + 100, 0, 180, false);
path.lineTo(-radius / 2, radius / 2);
path.close();

path = objectPaths[2];
path.addCircle(-radius + 200f, -radius + 100f, 50f, Path.Direction.CCW);

path = objectPaths[3];
path.addRoundRect(-radius / 2, -radius / 2, -radius / 2 + 20, 0, 10, 10, Path.Direction.CCW);

path = objectPaths[4];
path.addRect(120, 120, 200, 200, Path.Direction.CCW);

区域检测

检测是否发生了碰撞,准确度不高,但还能凑合

ini 复制代码
circlePath.reset();
circlePath.addCircle(x- width/2f,y - height/2f,10, Path.Direction.CCW);
circleRegion.setPath(circlePath,mainRegion);

mCommonPaint.setColor(Color.CYAN);
for (int i = 0; i < objectPaths.length; i++) {
    objectRegion.setPath(objectPaths[i],mainRegion);
    if(!objectRegion.quickReject(circleRegion)){
        Log.d("RegionView"," 可能发生了碰撞");
        mCommonPaint.setColor(Color.YELLOW);
    }else{
        mCommonPaint.setColor(Color.CYAN);
    }
    canvas.drawPath(objectPaths[i], mCommonPaint);
}

总结

到这里结束了,对于Region类,我们其实最有用的还是contain类方法,contain(x,y)准确度很高,便于我们检测粒子是不是在几何内部,本篇没有涉及到,但是有些绘制地图类的应用会使用到这个,主要原因是地图纵横交错,不是矩形也不是圆形,甚至还有飞地,因此使用containXXX是最好的方案之一。

全部代码

ini 复制代码
public class RegionView extends View {
    private final DisplayMetrics mDM;
    private TextPaint mCommonPaint;

    public RegionView(Context context) {
        this(context, null);
    }

    public RegionView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mDM = getResources().getDisplayMetrics();
        initPaint();
        setClickable(true); //触发hotspot
    }

    private void initPaint() {
        //否则提供给外部纹理绘制
        mCommonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mCommonPaint.setAntiAlias(true);
        mCommonPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mCommonPaint.setStrokeCap(Paint.Cap.ROUND);
        mCommonPaint.setFilterBitmap(true);
        mCommonPaint.setDither(true);
        mCommonPaint.setStrokeWidth(dp2px(20));

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mDM.widthPixels / 2;
        }
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = widthSize / 2;
        }
        setMeasuredDimension(widthSize, heightSize);

    }

    private float x;
    private float y;

    //所以形状
    Path[] objectPaths = new Path[5];
    //形状区域检测
    Region objectRegion = new Region();

    //小圆球区域
    Region circleRegion = new Region();
   //小圆
    Path circlePath = new Path();
    //绘制区域
    Region mainRegion = new Region();

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        if (width < 1 || height < 1) {
            return;
        }

        int save = canvas.save();
        canvas.translate(width / 2f, height / 2f);
        float radius = Math.min(width / 2f, height / 2f);

        mainRegion.set((int) -radius, (int) -radius, (int) radius, (int) radius);

        for (int i = 0; i < objectPaths.length; i++) {
            Path path = objectPaths[i];
            if (path == null) {
                path = new Path();
                objectPaths[i] = path;
            } else {
                path.reset();
            }
        }

        Path path = objectPaths[0];
        path.moveTo(radius / 2, -radius / 2);
        path.lineTo(0, -radius);
        path.lineTo(radius / 2, -radius);
        path.close();

        path = objectPaths[1];
        path.moveTo(-radius / 2, radius / 2);
        path.lineTo(-radius / 2 - 100, radius / 2);
        path.arcTo(-radius / 2 - 100, radius / 2, -radius / 2, radius / 2 + 100, 0, 180, false);
        path.lineTo(-radius / 2, radius / 2);
        path.close();

        path = objectPaths[2];
        path.addCircle(-radius + 200f, -radius + 100f, 50f, Path.Direction.CCW);

        path = objectPaths[3];
        path.addRoundRect(-radius / 2, -radius / 2, -radius / 2 + 20, 0, 10, 10, Path.Direction.CCW);

        path = objectPaths[4];
        path.addRect(120, 120, 200, 200, Path.Direction.CCW);


        circlePath.reset();
        circlePath.addCircle(x- width/2f,y - height/2f,10, Path.Direction.CCW);
        circleRegion.setPath(circlePath,mainRegion);

        mCommonPaint.setColor(Color.CYAN);
        for (int i = 0; i < objectPaths.length; i++) {
            objectRegion.setPath(objectPaths[i],mainRegion);
            if(!objectRegion.quickReject(circleRegion)){
                Log.d("RegionView"," 可能发生了碰撞");
                mCommonPaint.setColor(Color.YELLOW);
            }else{
                mCommonPaint.setColor(Color.CYAN);
            }
            canvas.drawPath(objectPaths[i], mCommonPaint);
        }

        mCommonPaint.setColor(Color.WHITE);
        canvas.drawPath(circlePath,mCommonPaint);
        canvas.restoreToCount(save);
    }

    @Override
    public void dispatchDrawableHotspotChanged(float x, float y) {
        super.dispatchDrawableHotspotChanged(x, y);
        this.x = x;
        this.y = y;
        postInvalidate();
    }

    @Override
    protected void dispatchSetPressed(boolean pressed) {
        super.dispatchSetPressed(pressed);
        postInvalidate();
    }

    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);
    }

    public static int argb(float red, float green, float blue) {
        return ((int) (1 * 255.0f + 0.5f) << 24) |
                ((int) (red * 255.0f + 0.5f) << 16) |
                ((int) (green * 255.0f + 0.5f) << 8) |
                (int) (blue * 255.0f + 0.5f);
    }


}
相关推荐
Jiaberrr6 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
网络研究院11 分钟前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
杨哥带你写代码16 分钟前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
凉亭下17 分钟前
android navigation 用法详细使用
android
郭二哈42 分钟前
C++——模板进阶、继承
java·服务器·c++
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
A尘埃1 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23071 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
沉登c1 小时前
幂等性接口实现
java·rpc
代码之光_19801 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端