如果用户点击圆圈附近,将圆圈按钮移动到随机位置的应用程序

App that moves circle button to random location if user clicks near circle

我目前有一个 Android 应用程序,如果我点击屏幕上 不是 圆圈的某个位置,它会将绘制的圆圈移动到屏幕上的随机位置本身。


我的问题是,我怎样才能实现它,以便仅在点击相对靠近圆圈时才执行此逻辑?距离有多近没有严格定义,但是,如果圆圈位于屏幕的左下角,则单击屏幕右上角不会触发圆圈移动到新位置。我想象的是一个比圆形稍大的正方形,它可以作为碰撞框(但仍然不包括圆圈内的点击)

我假设更改必须在 isInsideCircle 函数中发生,但我缺乏必要的知识来理解我应该 add/subtract 哪些变量来限制区域这触发了逻辑。 这是我目前拥有的代码:

    public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Circle(this));
    }
    public class Circle extends View {
        private float x = 300;
        private float y = 300;
        private int r = 150;
        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private Random random = new Random();

        // draws circle
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.RED);
            canvas.drawCircle(x, y, r, mPaint);
        }

        // constructor
        public Circle(Context context) {
            super(context);
        }

        // gets random coordinates
        void generateRandom() {

            int w = getWidth() - r - 50;
            int h = getHeight()- r - 50;

            int border = r + 50;

            this.x = border + random.nextInt(w-border);
            this.y = border + random.nextInt(h-border);
        }

        // when screen is tapped, old circle removed, new circle drawn
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (!isInsideCircle(event.getX(), event.getY())) { // only register clicks outside of circle
                generateRandom();
                invalidate();
            }
            return super.onTouchEvent(event);
        }


        boolean isInsideCircle(float xPoint, float yPoint) {
            float dx = (x - xPoint);
            float dxPow = (float) Math.pow(dx, 2);
            float dy = (y - yPoint);
            float dyPow = (float) Math.pow(dy, 2);
            float radPow = (float) Math.pow(r, 2);
            return ((dxPow + dyPow) <= radPow));
        }
    }
}

任何帮助将不胜感激,谢谢!

这个问题有几个部分,我想确保在讨论之前已经很清楚了。

  1. 我们希望将命中区域修改为正方形,而不是圆形
  2. 我们想颠倒我们的逻辑,所以我们不想在命中区域外部时做出反应,而是在命中区域内部时做出反应面积。

实际上我要先从第二点开始,然后再转到第一点。

测试内部,而不是外部

这个相当简单,因为您已经在测试点击是否在圆圈内,至少 Circle [=111] 是这样假设的=]' isInsideCircle 方法。碰巧的是,可以相当安全地假设,如果您 不是 (!) 在圈内,那么您在圈外。将 if (!isInsideCircle(...)) 修改为 if(isInsideCircle(...))(删除 not [!]) 运算符)应该在这方面对我们有所帮助,不是吗?好吧,那是相当轻松的,希望测试在正方形内也一样容易!

测试正方形,而不是圆形

...不会像检查我们是否在命中区域内部而不是外部那样容易。
我的意思是它确实需要 isInsideCircle 执行的逻辑完全转变。另外,我不确定更改 isInsideCircle 的正文以测试它是否在 正方形 内是否是个好主意。这可能有点令人困惑,不是吗?

因此,制作一种新方法来测试给定的 (x, y) 坐标是否在正方形内可能是一个更好的主意。也许 isInsideSquare?等一会儿! Square 肯定不是 Circle 那么,我们需要什么信息来表示 Square?那么现在我们需要一种方法来表示 Square 和一种判断坐标是否在 正方形的方法。正方形是一种特殊的矩形,您实际上只需要一个特殊的坐标位置(一个顶点或精确的中心)和一个边长来比较坐标。我们恰好有一个可以用作中心的坐标位置,所以我想我们需要一个碰撞框的边长。让我们在我们的圆 class 中添加一个新字段来表示碰撞框的边长。以下行将默认它比圆的直径多 50 像素。

    private int hitboxLength = (2 * r) + 50;

现在我们有了一个坐标点,不幸的是,它位于正方形的 中间 和它的边长。那么,我们如何确定一个点是否在我们的方形碰撞箱内?很高兴知道矩形也可以描述为有 4 个顶点。对于以 (0, 0) 为中心的边长 2 的正方形,您将在 (1, 1)、(1, -1)、(-1, -1) 和 (-1, 1) 处拥有顶点。为了找出 (x, y) 是否在我们的矩形内,我们只需要它不在这 4 个坐标内。 X 的范围从 1 到 -1,Y 的范围也从 1 到 -1。意思是如果 x 在 -1 和 1 -1 <= x <= 1 之间并且 y 在 -1 和 1 -1 <= y <= 1.

之间
    boolean isInsideSquare(float xPoint, float yPoint) {
        // The center of a square is HALF it's length from any vertex.
        // Always try to use equivalent number types when comparing
        float halfHitboxLength = this.hitboxLength / 2.0F;
        float xUpperBound = this.x + halfHitboxLength;
        float xLowerBound = this.x - halfHitboxLength;
        // smallest expected X <= xPoint <= largest expected X
        boolean xInSquare = xLowerBound <= xPoint && xPoint <= xUpperBound;

        float yUpperBound = this.y + halfHitboxLength;
        float yLowerBound = this.y - halfHitboxLength;
        boolean yInSquare = yLowerBound <= yPoint && yPoint <= yUpperBound;

        return xInSquare && yInSquare;
    }

毫无疑问,这不是处理此问题的最有效方法,但它应该 为我们提供在给定方块内的逻辑。

现在我们只需要在 onTouchEvent 方法中使用 isInsideSquare 而不是 isInsideCircle

    // This "if"
    if (!isInsideCircle(event.getX(), event.getY())) {
    // Would become
    if (!isInsideSquare(event.getX(), event.getY())) {

但是,我不禁觉得用正方形来表示圆有点奇怪。至少,考虑到它们的形状有多么不同。更不用说 isInsideSquare 是否属于 Circle class 的问题了!想要用另一个稍大的圆圈外观来表示我们圆圈的命中区域如何?

测试稍大的圆圈

我们已经有了检查 (x, y) 坐标对是否在我们当前圆圈内的逻辑。我的意思是有一种方法叫做 isInsideCircle.

        boolean isInsideCircle(float xPoint, float yPoint) {
            float dx = (x - xPoint);
            float dxPow = (float) Math.pow(dx, 2);
            float dy = (y - yPoint);
            float dyPow = (float) Math.pow(dy, 2);
            float radPow = (float) Math.pow(r, 2);
            return ((dxPow + dyPow) <= radPow));
        }

就个人而言,我的注意力会立即被吸引到几个地方

  1. 利用少量变量的returnreturn ((dxPow + dyPow) <= radPow));
  2. 与用户输入无关的变量float radPow = (float) Math.pow(r, 2);

return,因为这是我们确定给定的 (xPointyPoint) 对是否在我们的圆实例内的方式,但更重要的是,radPow 变量,因为它似乎是引用,我只能假设是圆的半径,并且要针对更大的圆进行测试,我们真正需要做的就是调整它的半径!

    int userWithinPixels = 50;
    float radPow = (float) Math.pow(r + userWithinPixels, 2);

嗯,这很轻松!

再次值得指出的是,在 Circle 对象上调用 isInsideCircle 可能会测试它是否在表示的圆圈内而不是稍大的圆圈内,但这仍然是个人偏好,您应该做对项目的程序员有意义的事情。


使用正方形

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Circle(this));
    }
    public class Circle extends View {
        private float x = 300;
        private float y = 300;
        private int r = 150;
        // Add hitbox side length
        private int hitboxLength = (2 * r) + 50;
        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private Random random = new Random();

        // draws circle
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.RED);
            canvas.drawCircle(x, y, r, mPaint);
        }

        // constructor
        public Circle(Context context) {
            super(context);
        }

        // gets random coordinates
        void generateRandom() {

            int w = getWidth() - r - 50;
            int h = getHeight()- r - 50;

            int border = r + 50;

            this.x = border + random.nextInt(w-border);
            this.y = border + random.nextInt(h-border);
        }

        // when screen is tapped, old circle removed, new circle drawn
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (isInsideSquare(event.getX(), event.getY())) { // only register clicks outside of circle
                generateRandom();
                invalidate();
            }
            return super.onTouchEvent(event);
        }

        boolean isInsideSquare(float xPoint, float yPoint) {
            // The center of a square is HALF it's length from any vertex.
            // Always try to use the same number types when comparing/computing
            float halfHitboxLength = this.hitboxLength / 2.0F;
            float xUpperBound = this.x + halfHitboxLength;
            float xLowerBound = this.x - halfHitboxLength;
            // smallest expected X <= xPoint <= largest expected X
            boolean xInSquare = xLowerBound <= xPoint && xPoint <= xUpperBound;

            float yUpperBound = this.y + halfHitboxLength;
            float yLowerBound = this.y - halfHitboxLength;
            boolean yInSquare = yLowerBound <= yPoint && yPoint <= yUpperBound;

            return xInSquare && yInSquare;
        }


        boolean isInsideCircle(float xPoint, float yPoint) {
            float dx = (x - xPoint);
            float dxPow = (float) Math.pow(dx, 2);
            float dy = (y - yPoint);
            float dyPow = (float) Math.pow(dy, 2);
            float radPow = (float) Math.pow(r, 2);
            return ((dxPow + dyPow) <= radPow));
        }
    }
}

使用更大的圆圈

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Circle(this));
    }
    public class Circle extends View {
        private float x = 300;
        private float y = 300;
        private int r = 150;
        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private Random random = new Random();

        // draws circle
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.RED);
            canvas.drawCircle(x, y, r, mPaint);
        }

        // constructor
        public Circle(Context context) {
            super(context);
        }

        // gets random coordinates
        void generateRandom() {

            int w = getWidth() - r - 50;
            int h = getHeight()- r - 50;

            int border = r + 50;

            this.x = border + random.nextInt(w-border);
            this.y = border + random.nextInt(h-border);
        }

        // when screen is tapped, old circle removed, new circle drawn
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (isInsideCircle(event.getX(), event.getY())) { // only register clicks outside of circle
                generateRandom();
                invalidate();
            }
            return super.onTouchEvent(event);
        }


        boolean isInsideCircle(float xPoint, float yPoint) {
            float dx = (x - xPoint);
            float dxPow = (float) Math.pow(dx, 2);
            float dy = (y - yPoint);
            float dyPow = (float) Math.pow(dy, 2);
            // maximum amount of pixels user can click off the circle and trigger event
            float userWithinPixels = 25;
            float radPow = (float) Math.pow(r + userWithinPixels, 2);
            return ((dxPow + dyPow) <= radPow));
        }
    }
}