Make touchable bg of touchpad [LibGdx] - java

Here's my touchpad.
What I want is this: when I touch background of touchpad (not knob), knob move to this position. Code, which I use for touchpad from github/libgdx/gdx/src/com/badlogic/gdx/scenes/scene2d/ui/Touchpad.java
package com.liketurbo.funnyGame;
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
import com.badlogic.gdx.scenes.scene2d.Event;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.scenes.scene2d.ui.Widget;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Circle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.Pools;
/** An on-screen joystick. The movement area of the joystick is circular, centered on the touchpad, and its size determined by the
* smaller touchpad dimension.
* <p>
* The preferred size of the touchpad is determined by the background.
* <p>
* {#link ChangeEvent} is fired when the touchpad knob is moved. Cancelling the event will move the knob to where it was
* previously.
* #author Josh Street */
public class Touchpad extends Widget {
private TouchpadStyle style;
boolean touched;
boolean resetOnTouchUp = true;
private float deadzoneRadius;
private final Circle knobBounds = new Circle(0, 0, 0);
private final Circle touchBounds = new Circle(0, 0, 0);
private final Circle deadzoneBounds = new Circle(0, 0, 0);
private final Vector2 knobPosition = new Vector2();
private final Vector2 knobPercent = new Vector2();
/** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */
public Touchpad (float deadzoneRadius, Skin skin) {
this(deadzoneRadius, skin.get(TouchpadStyle.class));
}
/** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */
public Touchpad (float deadzoneRadius, Skin skin, String styleName) {
this(deadzoneRadius, skin.get(styleName, TouchpadStyle.class));
}
/** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */
public Touchpad (float deadzoneRadius, TouchpadStyle style) {
if (deadzoneRadius < 0) throw new IllegalArgumentException("deadzoneRadius must be > 0");
this.deadzoneRadius = deadzoneRadius;
knobPosition.set(getWidth() / 2f, getHeight() / 2f);
setStyle(style);
setSize(getPrefWidth(), getPrefHeight());
addListener(new InputListener() {
#Override
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
if (touched) return false;
touched = true;
calculatePositionAndValue(x, y, false);
return true;
}
#Override
public void touchDragged (InputEvent event, float x, float y, int pointer) {
calculatePositionAndValue(x, y, false);
}
#Override
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
touched = false;
calculatePositionAndValue(x, y, resetOnTouchUp);
}
});
}
void calculatePositionAndValue (float x, float y, boolean isTouchUp) {
float oldPositionX = knobPosition.x;
float oldPositionY = knobPosition.y;
float oldPercentX = knobPercent.x;
float oldPercentY = knobPercent.y;
float centerX = knobBounds.x;
float centerY = knobBounds.y;
y = centerY;
knobPosition.set(centerX, centerY);
knobPercent.set(0f, 0f);
if (!isTouchUp) {
if (!deadzoneBounds.contains(x, y)) {
knobPercent.set((x - centerX) / knobBounds.radius*3.5f, 0);
float length = knobPercent.len();
if (length > 1) knobPercent.scl(1 / length);
if (knobBounds.contains(x, y)) {
knobPosition.set(x, y);
} else {
knobPosition.set(knobPercent).nor().scl(knobBounds.radius*3.5f).add(knobBounds.x, knobBounds.y);
}
}
}
if (oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) {
ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class);
if (fire(changeEvent)) {
knobPercent.set(oldPercentX, oldPercentY);
knobPosition.set(oldPositionX, oldPositionY);
}
Pools.free(changeEvent);
}
}
public void setStyle (TouchpadStyle style) {
if (style == null) throw new IllegalArgumentException("style cannot be null");
this.style = style;
invalidateHierarchy();
}
/** Returns the touchpad's style. Modifying the returned style may not have an effect until {#link #setStyle(TouchpadStyle)} is
* called. */
public TouchpadStyle getStyle () {
return style;
}
#Override
public Actor hit (float x, float y, boolean touchable) {
return touchBounds.contains(x, y) ? this : null;
}
#Override
public void layout () {
// Recalc pad and deadzone bounds
float halfWidth = getWidth() / 2;
float halfHeight = getHeight() / 2;
float radius = Math.min(halfWidth, halfHeight);
touchBounds.set(halfWidth, halfHeight, radius);
if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2;
knobBounds.set(halfWidth, halfHeight, radius);
deadzoneBounds.set(halfWidth, halfHeight, deadzoneRadius);
// Recalc pad values and knob position
knobPosition.set(halfWidth, halfHeight);
knobPercent.set(0, 0);
}
#Override
public void draw (Batch batch, float parentAlpha) {
validate();
Color c = getColor();
batch.setColor(c.r, c.g, c.b, c.a * parentAlpha);
float x = getX();
float y = getY();
float w = getWidth();
float h = getHeight();
final Drawable bg = style.background;
if (bg != null) bg.draw(batch, x, y, w, h);
final Drawable knob = style.knob;
if (knob != null) {
x += knobPosition.x - (knob.getMinWidth()*2) / 2f;
y += knobPosition.y - (knob.getMinHeight()*2) / 2f;
knob.draw(batch, x, y, knob.getMinWidth()*2, knob.getMinHeight()*2);
}
}
#Override
public float getPrefWidth () {
return style.background != null ? style.background.getMinWidth() : 0;
}
#Override
public float getPrefHeight () {
return style.background != null ? style.background.getMinHeight() : 0;
}
public boolean isTouched () {
return touched;
}
public boolean getResetOnTouchUp () {
return resetOnTouchUp;
}
/** #param reset Whether to reset the knob to the center on touch up. */
public void setResetOnTouchUp (boolean reset) {
this.resetOnTouchUp = reset;
}
/** #param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */
public void setDeadzone (float deadzoneRadius) {
if (deadzoneRadius < 0) throw new IllegalArgumentException("deadzoneRadius must be > 0");
this.deadzoneRadius = deadzoneRadius;
invalidate();
}
/** Returns the x-position of the knob relative to the center of the widget. The positive direction is right. */
public float getKnobX () {
return knobPosition.x;
}
/** Returns the y-position of the knob relative to the center of the widget. The positive direction is up. */
public float getKnobY () {
return knobPosition.y;
}
/** Returns the x-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement
* area. The positive direction is right. */
public float getKnobPercentX () {
return knobPercent.x;
}
/** Returns the y-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement
* area. The positive direction is up. */
public float getKnobPercentY () {
return knobPercent.y;
}
/** The style for a {#link Touchpad}.
* #author Josh Street */
public static class TouchpadStyle {
/** Stretched in both directions. Optional. */
public Drawable background;
/** Optional. */
public Drawable knob;
public TouchpadStyle () {
}
public TouchpadStyle (Drawable background, Drawable knob) {
this.background = background;
this.knob = knob;
}
public TouchpadStyle (TouchpadStyle style) {
this.background = style.background;
this.knob = style.knob;
}
}
}
Line addListener(new InputListener() {...} works only for knob, if I can figure out how make it work for touchpad's background too, it solved my problem. Does somebody know how solve this problem? Thank you in advance.

You shouldn't add listener to your touchpad. Just limit touch area of the screen where touchpad can be used.
Here is code to activate only on right half of a screen:
private Touchpad touchpad;
private Vector2 screenPos = new Vector2(); // position where touch occurs
private Vector2 localPos = new Vector2(); // position in local coordinates
private InputEvent fakeTouchDownEvent = new InputEvent(); // fake touch for correct work
private Vector2 stagePos; // position to fire touch event
private void getDirection() {
if (Gdx.input.justTouched()) {
// Get the touch point in screen coordinates.
screenPos.set(Gdx.input.getX(), Gdx.input.getY());
if (screenPos.x > Gdx.graphics.getWidth() / 2) { // right half of a screen
// Convert the touch point into local coordinates, place the touchpad and show it.
localPos.set(screenPos);
localPos = touchpad.getParent().screenToLocalCoordinates(localPos);
touchpad.setPosition(localPos.x - touchpad.getWidth() / 2, localPos.y - touchpad.getHeight() / 2);
// Fire a touch down event to get the touchpad working.
Vector2 stagePos = touchpad.getStage().screenToStageCoordinates(screenPos);
fakeTouchDownEvent.setStageX(stagePos.x);
fakeTouchDownEvent.setStageY(stagePos.y);
touchpad.fire(fakeTouchDownEvent);
}
}
}

Related

How to print arrayList from another class using a Vaadin Label?

I have a Canvas.java class where I have a mousemove code to track the mouse position, and have it saved to an array. If I print this code out from this class itself, the mouse position is correctly printed. However, is there any way I can access this array from another class, and have it printed still (eg. I want it to appear as a Vaadin label in MainLayout.java)? It seems that the label is only showing '[]' in place of the coordinates I want printed.
Canvas.java:
package com.vaadin.starter.beveragebuddy.ui.components;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import elemental.json.JsonObject;
import java.util.ArrayList;
/**
* Canvas component that you can draw shapes and images on. It's a Java wrapper
* for the
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5
* canvas</a>.
* <p>
* Use {#link #getContext()} to get API for rendering shapes and images on the
* canvas.
* <p>
*/
#Tag("canvas")
#SuppressWarnings("serial")
public class Canvas extends Component implements HasStyle, HasSize {
private CanvasRenderingContext2D context;
private Element element;
private boolean isDrawing = false;
private boolean mouseIsDown = false;
private double endX;
private double endY;
public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>();
public static ArrayList<BoundingBox> getArrayBoxes() {
return arrayBoxes;
}
public static ArrayList<MousePosition> getMousePosArray() {
return mousePosArray;
}
public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) {
Canvas.mousePosArray = mousePosArray;
}
/**
* Creates a new canvas component with the given size.
* <p>
* Use the API provided by {#link #getContext()} to render graphics on the
* canvas.
* <p>
* The width and height parameters will be used for the canvas' coordinate
* system. They will determine the size of the component in pixels, unless
* you explicitly set the component's size with {#link #setWidth(String)} or
* {#link #setHeight(String)}.
*
* #param width
* the width of the canvas
* #param height
* the height of the canvas
*/
public Canvas(int width, int height) {
context = new CanvasRenderingContext2D(this);
element = getElement();
element.getStyle().set("border", "1px solid");
getElement().setAttribute("width", String.valueOf(width));
getElement().setAttribute("height", String.valueOf(height));
element.addEventListener("mousedown", event -> { // Retrieve Starting Position on MouseDown
Element boundingBoxResult = ElementFactory.createDiv();
element.appendChild(boundingBoxResult);
JsonObject evtData = event.getEventData();
double xBox = evtData.getNumber("event.x");
double yBox = evtData.getNumber("event.y");
boundingBoxResult.setAttribute("data-x", String.format("%f", xBox));
boundingBoxResult.setAttribute("data-y", String.format("%f", yBox));
BoundingBox newBox = new BoundingBox(0, xBox, yBox, 0.0, 0.0);
arrayBoxes.add(newBox);
isDrawing = true;
mouseIsDown=true;
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mouseup", event -> { // Draw Box on MouseUp
Element boundingBoxResult2 = ElementFactory.createDiv();
element.appendChild(boundingBoxResult2);
JsonObject evtData2 = event.getEventData();
endX = evtData2.getNumber("event.x");
endY = evtData2.getNumber("event.y");
boundingBoxResult2.setAttribute("end-x", String.format("%f", endX));
boundingBoxResult2.setAttribute("end-y", String.format("%f", endY));
double xcoordi = 0;
double ycoordi = 0;
double boxWidth = 0;
double boxHeight = 0;
for (int i = 0; i < arrayBoxes.size(); i++) {
arrayBoxes.get(i).setName(i + 1);
arrayBoxes.get(i).setWidth(endX, arrayBoxes.get(i).xcoordi);
arrayBoxes.get(i).setHeight(endY, arrayBoxes.get(i).ycoordi);
xcoordi = arrayBoxes.get(i).getXcoordi();
ycoordi = arrayBoxes.get(i).getYcoordi();
boxWidth = arrayBoxes.get(i).getWidth();
boxHeight = arrayBoxes.get(i).getHeight();
}
mouseIsDown=false;
context.beginPath();
context.setFillStyle("limegreen");
context.setLineWidth(2);
context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight);
context.fill();
System.out.println(arrayBoxes.toString());
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mousemove", event -> { // Retrieve Mouse Position when Moving
JsonObject mousePos = event.getEventData();
double mouseX = mousePos.getNumber("event.x");
double mouseY = mousePos.getNumber("event.y");
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
System.out.println(mousePosArray.get(0));
// System.out.println(mousePosArray.get(mousePosArray.size() -1));
}).addEventData("event.x").addEventData("event.y");
}
MainLayout.java:
package com.vaadin.starter.beveragebuddy.backend;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;
import com.vaadin.starter.beveragebuddy.ui.components.BoundingBox;
import com.vaadin.starter.beveragebuddy.ui.components.Canvas;
import com.vaadin.starter.beveragebuddy.ui.components.CanvasRenderingContext2D;
import com.vaadin.starter.beveragebuddy.ui.components.MousePosition;
import com.vaadin.flow.component.textfield.TextField;
import java.util.ArrayList;
#HtmlImport("frontend://styles/shared-styles.html")
#Route("")
public class MainLayout extends Div {
private CanvasRenderingContext2D ctx;
private Canvas canvas;
ArrayList<MousePosition> mousePosArray = Canvas.getMousePosArray();
ArrayList<BoundingBox> bb = Canvas.getArrayBoxes();
public MainLayout() {
H2 title = new H2("Annotation UI");
title.addClassName("main-layout__title");
canvas = new Canvas(1580, 700);
ctx = canvas.getContext();
Label label = new Label("Coordinates: " + mousePosArray);
canvas.addComponent(label);
add(label);
}
}
CanvasRenderingContext2D.java:
package com.vaadin.starter.beveragebuddy.ui.components;
import java.io.Serializable;
import java.util.ArrayList;
/**
* The context for rendering shapes and images on a canvas.
* <p>
* This is a Java wrapper for the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
* client-side API</a>.
*/
public class CanvasRenderingContext2D {
private Canvas canvas;
public static ArrayList<BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
protected CanvasRenderingContext2D(Canvas canvas) {
this.canvas = canvas;
}
public void setFillStyle(String fillStyle) {
setProperty("fillStyle", fillStyle);
}
public void setStrokeStyle(String strokeStyle) {
setProperty("fillStyle", strokeStyle);
}
public void setLineWidth(double lineWidth) {
setProperty("lineWidth", lineWidth);
}
public void setFont(String font) {
setProperty("font", font);
}
public void arc(double x, double y, double radius, double startAngle,
double endAngle, boolean antiClockwise) {
callJsMethod("arc", x, y, radius, startAngle, endAngle, antiClockwise);
}
public void beginPath() {
callJsMethod("beginPath");
}
public void clearRect(double x, double y, double width, double height) {
callJsMethod("clearRect", x, y, width, height);
Canvas.arrayBoxes.clear();
}
public void closePath() {
callJsMethod("closePath");
}
/**
* Fetches the image from the given location and draws it on the canvas.
* <p>
* <b>NOTE:</b> The drawing will happen asynchronously after the browser has
* received the image.
*
* #param src
* the url of the image to draw
* #param x
* the x-coordinate of the top-left corner of the image
* #param y
* the y-coordinate of the top-left corner of the image
*/
public void drawImage(String src, double x, double y) {
runScript(String.format(
"var zwKqdZ = new Image();" + "zwKqdZ.onload = function () {"
+ "$0.getContext('2d').drawImage(zwKqdZ, %s, %s);};"
+ "zwKqdZ.src='%s';",
x, y, src));
}
/**
* Fetches the image from the given location and draws it on the canvas.
* <p>
* <b>NOTE:</b> The drawing will happen asynchronously after the browser has
* received the image.
*
* #param src
* the url of the image to draw
* #param x
* the x-coordinate of the top-left corner of the image
* #param y
* the y-coordinate of the top-left corner of the image
* #param width
* the width for the image
* #param height
* the height for the image
*/
public void drawImage(String src, double x, double y, double width,
double height) {
runScript(String.format("var zwKqdZ = new Image();"
+ "zwKqdZ.onload = function () {"
+ "$0.getContext('2d').drawImage(zwKqdZ, %s, %s, %s, %s);};"
+ "zwKqdZ.src='%s';", x, y, width, height, src));
}
public void fill() {
callJsMethod("fill");
}
public void fillRect(double x, double y, double width, double height) {
callJsMethod("fillRect", x, y, width, height);
}
public void fillText(String text, double x, double y) {
callJsMethod("fillText", text, x, y);
}
public void lineTo(double x, double y) {
callJsMethod("lineTo", x, y);
}
public void moveTo(double x, double y) {
callJsMethod("moveTo", x, y);
}
public void rect(double x, double y, double width, double height) {
callJsMethod("rect", x, y, width, height);
}
public void restore() {
callJsMethod("restore");
}
public void rotate(double angle) {
callJsMethod("rotate", angle);
}
public void save() {
callJsMethod("save");
}
public void scale(double x, double y) {
callJsMethod("scale", x, y);
}
public void stroke() {
callJsMethod("stroke");
}
public void strokeRect(double x, double y, double width, double height) {
callJsMethod("strokeRect", x, y, width, height);
}
public void strokeText(String text, double x, double y) {
callJsMethod("strokeText", text, x, y);
}
public void translate(double x, double y) {
callJsMethod("translate", x, y);
}
protected void setProperty(String propertyName, Serializable value) {
runScript(String.format("$0.getContext('2d').%s='%s'", propertyName,
value));
}
/**
* Runs the given js so that the execution order works with callJsMethod().
* Any $0 in the script will refer to the canvas element.
*/
private void runScript(String script) {
canvas.getElement().getNode().runWhenAttached(
// This structure is needed to make the execution order work
// with Element.callFunction() which is used in callJsMethod()
ui -> ui.getInternals().getStateTree().beforeClientResponse(
canvas.getElement().getNode(),
context -> ui.getPage().executeJavaScript(script,
canvas.getElement())));
}
protected void callJsMethod(String methodName, Serializable... parameters) {
canvas.getElement().callFunction("getContext('2d')." + methodName,
parameters);
}
}
BoundingBox.java:
package com.vaadin.starter.beveragebuddy.ui.components;
public class BoundingBox {
public double xcoordi = 0;
public double ycoordi = 0;
public double boxWidth = 0;
public double boxHeight = 0;
public int name;
public BoundingBox(int name, double xcoordi, double ycoordi, double boxWidth, double boxHeight) {
this.name = name;
this.xcoordi = xcoordi;
this.ycoordi = ycoordi;
this.boxWidth = boxWidth;
this.boxHeight = boxHeight;
}
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
public double getXcoordi() {
return xcoordi;
}
public void setXcoordi(double xcoordi) {
this.xcoordi = xcoordi;
}
public double getYcoordi() {
return ycoordi;
}
public void setYcoordi(double ycoordi) {
this.ycoordi = ycoordi;
}
public double getWidth() {
return boxWidth;
}
public void setWidth(double endX, double xcoordi) {
boxWidth = endX - xcoordi;
}
public double getHeight() {
return boxHeight;
}
public void setHeight(double endY, double ycoordi) {
boxHeight = endY - ycoordi;
}
#Override
public String toString() {
return "{" +
"Box=" + name +
", X=" + xcoordi +
", Y=" + ycoordi +
", Width=" + boxWidth +
", Height=" + boxHeight +
'}';
}
}
MousePosition.java:
package com.vaadin.starter.beveragebuddy.ui.components;
public class MousePosition {
public double mouseX;
public double mouseY;
public MousePosition(double mouseX, double mouseY) {
this.mouseX = mouseX;
this.mouseY = mouseY;
}
public double getMouseX() {
return mouseX;
}
public void setMouseX(double mouseX) {
this.mouseX = mouseX;
}
public double getMouseY() {
return mouseY;
}
public void setMouseY(double mouseY) {
this.mouseY = mouseY;
}
#Override
public String toString() {
return "MousePosition{" +
"mouseX=" + mouseX +
", mouseY=" + mouseY +
'}';
}
}
Any help is much appreciated, thank you!
Alright, with the updated code it seems your problem is that you never update the label.
You set the label text initially, but it will not be automatically updated when the array changes. What you can do is allow adding a listener to the canvas, and notify it when the value changes.
In the simplest case, a listener can be just a Java Runnable (or Supplier if you want to pass a value). My example returns a Vaadin Registration, which can be used to remove the listener by calling registration.remove().
In Canvas.java
private List<Runnable> mouseMoveListeners = new ArrayList<>(0);
...
public Registration addMouseMoveListener(Runnable listener) {
mouseMoveListeners.add(listener);
return () -> mouseMoveListeners.remove(listener);
}
And in your current mousemove listener
// Your current code in the mousemove listener
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
System.out.println(mousePosArray.get(0));
// Add this to run all the listeners that have been added to the canvas
mouseMoveListeners.forEach(Runnable::run);
Then at the end of your MainLayout constructor, something like this
canvas.addMouseMoveListener(() -> label.setValue("Coordinates: " + mousePosArray));
This is untested code, but an approach similar to this should work. The point being that you somehow must notify the MainLayout when the array has changed.

JavaFX Asteroids Game Clone: Having trouble bouncing asteroids off eachother

I am trying to make an Asteroids game clone in JavaFX. So far, I have been able to draw the ship and asteroids onto the screen (where Rectangles represent them, for now). I have also implemented movement for the ship, and randomized movements for the asteroids.
I am having trouble implementing the code needed to bounce the asteroids off each other. The current method that does the collision checking (called checkAsteroidCollisions) is bugged in that all asteroids start stuttering in place. They don't move, but rather oscillate back and forth in place rapidly. Without the call to this method, all asteroids begin moving normally and as expected.
Instead, I want each asteroid to move freely and, when coming into contact with another asteroid, bounce off each other like in the actual Asteroids game.
MainApp.java
import java.util.ArrayList;
import java.util.HashSet;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MainApp extends Application {
private static final int WIDTH = 700;
private static final int HEIGHT = 900;
private static final int NUM_OF_ASTEROIDS = 12;
private static final Color ASTEROID_COLOR = Color.GRAY;
private static final Color PLAYER_COLOR = Color.BLUE;
private Player player;
private ArrayList<Entity> asteroids;
long lastNanoTime; // For AnimationTimer
HashSet<String> inputs; // For inputs
private static final int MAX_SPEED = 150;
private static final int SPEED = 10;
private static final int ASTEROID_SPEED = 150;
private StackPane background;
/*
* Generates a random number between min and max, inclusive.
*/
private float genRandom(int min, int max) {
return (float) Math.floor(Math.random() * (max - min + 1) + min);
}
/*
* Initializes the asteroids
*/
private void initAsteroids() {
this.asteroids = new ArrayList<Entity>();
for (int i = 0; i < NUM_OF_ASTEROIDS; i++) {
Entity asteroid = new Entity(50, 50, ASTEROID_COLOR, EntityType.ASTEROID);
float px = (float) genRandom(200, WIDTH - 50);
float py = (float) genRandom(200, HEIGHT - 50);
asteroid.setPos(px, py);
// Keep recalculating position until there are no collisions
while (asteroid.intersectsWith(this.asteroids)) {
px = (float) genRandom(200, WIDTH - 50);
py = (float) genRandom(200, HEIGHT - 50);
asteroid.setPos(px, py);
}
// Randomly generate numbers to change velocity by
float dx = this.genRandom(-ASTEROID_SPEED, ASTEROID_SPEED);
float dy = this.genRandom(-ASTEROID_SPEED, ASTEROID_SPEED);
asteroid.changeVelocity(dx, dy);
this.asteroids.add(asteroid);
}
}
/*
* Initializes the player
*/
private void initPlayer() {
this.player = new Player(30, 30, PLAYER_COLOR, EntityType.PLAYER);
this.player.setPos(WIDTH / 2, 50);
}
/*
* Checks collisions with screen boundaries
*/
private void checkOffScreenCollisions(Entity e) {
if (e.getX() < -50)
e.setX(WIDTH);
if (e.getX() > WIDTH)
e.setX(0);
if (e.getY() < -50)
e.setY(HEIGHT);
if (e.getY() > HEIGHT)
e.setY(0);
}
/*
* Controls speed
*/
private void controlSpeed(Entity e) {
if (e.getDx() < -MAX_SPEED)
e.setDx(-MAX_SPEED);
if (e.getDx() > MAX_SPEED)
e.setDx(MAX_SPEED);
if (e.getDy() < -MAX_SPEED)
e.setDy(-MAX_SPEED);
if (e.getDy() > MAX_SPEED)
e.setDy(MAX_SPEED);
}
/*
* Controls each asteroid's speed and collision off screen
*/
private void controlAsteroids(ArrayList<Entity> asteroids) {
for (Entity asteroid : asteroids) {
this.checkOffScreenCollisions(asteroid);
this.controlSpeed(asteroid);
}
}
/*
* Checks an asteroid's collision with another asteroid
*/
private void checkAsteroidCollisions() {
for (int i = 0; i < NUM_OF_ASTEROIDS; i++) {
Entity asteroid = this.asteroids.get(i);
if (asteroid.intersectsWith(this.asteroids)){
float dx = (float) asteroid.getDx();
float dy = (float) asteroid.getDy();
asteroid.setDx(0);
asteroid.setDy(0);
asteroid.changeVelocity(-dx, -dy);
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hello World!");
this.initAsteroids();
this.initPlayer();
background = new StackPane();
background.setStyle("-fx-background-color: pink");
this.inputs = new HashSet<String>();
Group root = new Group();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
String code = e.getCode().toString();
inputs.add(code);
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
String code = e.getCode().toString();
inputs.remove(code);
}
});
Canvas canvas = new Canvas(WIDTH, HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
background.getChildren().add(canvas);
root.getChildren().add(background);
lastNanoTime = System.nanoTime();
new AnimationTimer() {
#Override
public void handle(long currentNanoTime) {
float elapsedTime = (float) ((currentNanoTime - lastNanoTime) / 1000000000.0);
lastNanoTime = currentNanoTime;
/* PLAYER */
// Game Logic
if (inputs.contains("A"))
player.changeVelocity(-SPEED, 0);
if (inputs.contains("D"))
player.changeVelocity(SPEED, 0);
if (inputs.contains("W"))
player.changeVelocity(0, -SPEED);
if (inputs.contains("S"))
player.changeVelocity(0, SPEED);
// Collision with edge of map
checkOffScreenCollisions(player);
// Control speed
controlSpeed(player);
player.update(elapsedTime);
/* ASTEROIDS */
gc.setFill(ASTEROID_COLOR);
for(int i = 0; i < NUM_OF_ASTEROIDS; i++) {
checkAsteroidCollisions(i); // BUGGY CODE
}
controlAsteroids(asteroids);
gc.clearRect(0, 0, WIDTH, HEIGHT);
for (Entity asteroid : asteroids) {
asteroid.update(elapsedTime);
asteroid.render(gc);
}
gc.setFill(PLAYER_COLOR);
player.render(gc);
}
}.start();
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Entity.java:
import java.util.ArrayList;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Entity {
private Color color;
private double x, y, width, height, dx, dy;
private EntityType entityType; // ID of this Entity
public Entity(float width, float height, Color color, EntityType type) {
this.x = this.dx = 0;
this.y = this.dy = 0;
this.width = width;
this.height = height;
this.color = color;
this.entityType = type;
}
/*
* Getters and setters
*/
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public double getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public double getDx() {
return dx;
}
public void setDx(float dx) {
this.dx = dx;
}
public double getDy() {
return dy;
}
public void setDy(float dy) {
this.dy = dy;
}
public EntityType getEntityType() {
return entityType;
}
/*
* Adds to dx and dy (velocity)
*/
public void changeVelocity(float dx, float dy) {
this.dx += dx;
this.dy += dy;
}
/*
* Sets position
*/
public void setPos(float x, float y) {
this.setX(x);
this.setY(y);
}
/*
* Gets new position of the Entity based on velocity and time
*/
public void update(float time) {
this.x += this.dx * time;
this.y += this.dy * time;
}
/*
* Used for collisions
*/
public Rectangle2D getBoundary() {
return new Rectangle2D(this.x, this.y, this.width, this.height);
}
/*
* Checks for intersections
*/
public boolean intersectsWith(Entity e) {
return e.getBoundary().intersects(this.getBoundary());
}
/*
* If any of the entities in the passed in ArrayList
* intersects with this, then return true;
*/
public boolean intersectsWith(ArrayList<Entity> entities) {
for(Entity e : entities) {
if(e.getBoundary().intersects(this.getBoundary()))
return true;
}
return false;
}
/*
* Draws the shape
*/
public void render(GraphicsContext gc) {
gc.fillRoundRect(x, y, width, height, 10, 10);
}
#Override
public String toString() {
return "Entity [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ", entityType=" + entityType
+ "]";
}
}
I think you will need to change the position of the two colliding asteroids slightly so their state is not on collision anymore.
What happens right now is that you change their movement after the collision but I guess that your algorithm notices that they are still touching, so it will try to change the movement again with the same result as before.
What you need to implement now is a change in the position of the two asteroids so your collision detection won´t act up again immediately after it´s first call.
I hope I could help you.

Image layering in Java

I'm new to the site, as well as to Java. I'm playing around in BlueJ for one of my programming classes and we created a drawing and a sunset using slowMoveVertical, but I can't get my sun to "set" behind the horizon... you continue to see it set above it. Is there a way to change the layering so I can get it to "set" behind the horizon? Here's the whole code for the "Picture" class.
public class Picture
{
private Circle hill;
private Square wall;
private Square window;
private Triangle roof;
private Circle sun;
/**
* Constructor for objects of class Picture
*/
public Picture()
{
// nothing to do... instance variables are automatically set to null
}
/**
* Draw this picture.
*/
public void draw()
{
wall = new Square();
wall.moveVertical(80);
wall.changeSize(100);
wall.makeVisible();
window = new Square();
window.changeColor("black");
window.moveHorizontal(20);
window.moveVertical(100);
window.makeVisible();
roof = new Triangle();
roof.changeSize(50, 140);
roof.changeColor("blue");
roof.moveHorizontal(60);
roof.moveVertical(70);
roof.makeVisible();
sun = new Circle();
sun.changeColor("yellow");
sun.moveHorizontal(180);
sun.moveVertical(-10);
sun.changeSize(60);
sun.makeVisible();
hill = new Circle();
hill.changeColor("green");
hill.moveHorizontal(-360);
hill.moveVertical(160);
hill.changeSize(1000);
hill.makeVisible();
}
/**
* Change this picture to black/white display
*/
public void setBlackAndWhite()
{
if(wall != null) // only if it's painted already...
{
wall.changeColor("black");
window.changeColor("white");
roof.changeColor("black");
sun.changeColor("black");
hill.changeColor("black");
}
}
/**
* Change this picture to use color display
*/
public void setColor()
{
if(wall != null) // only if it's painted already...
{
wall.changeColor("red");
window.changeColor("black");
roof.changeColor("blue");
sun.changeColor("yellow");
hill.changeColor("green");
}
}
/**
* Change this picture to make the sun go down
*/
public void setSunset()
{
if(wall != null) // only if the sun is already up...
{
sun.slowMoveVertical(255);
}
And here is the code for class "Circle".
public class Circle
{
private int diameter;
private int xPosition;
private int yPosition;
private String color;
private boolean isVisible;
/**
* Create a new circle at default position with default color.
*/
public Circle()
{
diameter = 30;
xPosition = 20;
yPosition = 60;
color = "blue";
isVisible = false;
}
/**
* Make this circle visible. If it was already visible, do nothing.
*/
public void makeVisible()
{
isVisible = true;
draw();
}
/**
* Make this circle invisible. If it was already invisible, do nothing.
*/
public void makeInvisible()
{
erase();
isVisible = false;
}
/**
* Move the circle a few pixels to the right.
*/
public void moveRight()
{
moveHorizontal(20);
}
/**
* Move the circle a few pixels to the left.
*/
public void moveLeft()
{
moveHorizontal(-20);
}
/**
* Move the circle a few pixels up.
*/
public void moveUp()
{
moveVertical(-20);
}
/**
* Move the circle a few pixels down.
*/
public void moveDown()
{
moveVertical(20);
}
/**
* Move the circle horizontally by 'distance' pixels.
*/
public void moveHorizontal(int distance)
{
erase();
xPosition += distance;
draw();
}
/**
* Move the circle vertically by 'distance' pixels.
*/
public void moveVertical(int distance)
{
erase();
yPosition += distance;
draw();
}
/**
* Slowly move the circle horizontally by 'distance' pixels.
*/
public void slowMoveHorizontal(int distance)
{
int delta;
if(distance < 0)
{
delta = -1;
distance = -distance;
}
else
{
delta = 1;
}
for(int i = 0; i < distance; i++)
{
xPosition += delta;
draw();
}
}
/**
* Slowly move the circle vertically by 'distance' pixels.
*/
public void slowMoveVertical(int distance)
{
int delta;
if(distance < 0)
{
delta = -1;
distance = -distance;
}
else
{
delta = 1;
}
for(int i = 0; i < distance; i++)
{
yPosition += delta;
draw();
}
}
/**
* Change the size to the new size (in pixels). Size must be >= 0.
*/
public void changeSize(int newDiameter)
{
erase();
diameter = newDiameter;
draw();
}
/**
* Change the color. Valid colors are "red", "yellow", "blue", "green",
* "magenta" and "black".
*/
public void changeColor(String newColor)
{
color = newColor;
draw();
}
/*
* Draw the circle with current specifications on screen.
*/
private void draw()
{
if(isVisible) {
Canvas canvas = Canvas.getCanvas();
canvas.draw(this, color, new Ellipse2D.Double(xPosition, yPosition,
diameter, diameter));
canvas.wait(10);
}
}
/*
* Erase the circle on screen.
*/
private void erase()
{
if(isVisible) {
Canvas canvas = Canvas.getCanvas();
canvas.erase(this);
}
}
The loop in slowMoveVertical pretty much locks you into drawing the sun in front. If your assignment allows you to bypass slowMoveVertical, then you can manually do the loop it does and render your sun and the hill yourself so the layering is right.
for(int i = 0; i < 190; i++) {
sun.moveHorizontal(1);
hill.draw();
}
edit...
There's a wait() at the end of the draw call, which would really only be used on the last object in the "batch". Making another draw method with an optional wait call allows the caller to control it instead of the circle. example...
public void draw() {
draw(true); // retain current behavior
{
public void draw(boolean useWait) {
if(isVisible) {
Canvas canvas = Canvas.getCanvas();
canvas.draw(this, color, new Ellipse2D.Double(xPosition, yPosition,
diameter, diameter));
if (useWait) {
canvas.wait(10);
}
}
}
Now you can call sun.draw(false) and hill.draw(true) and it shouldn't flicker (or a at least a lot less).

Java call to create new Area doesn't create shape?

I was working on an issue where I was getting a cast problem trying to cast from Shape to Area (see previous post cast exception question). Now it seems that my shape that is create is not getting created correctly. Instead of posting all of my source code here I am attaching a link to all the source files here.
Essentially I create the shape as follows with a standard call of
YingYang shape = new YingYang();
shape = shape.moveTo(x, y);
shape = shape.scaleBy(size);
shape.setColor(getNextColor());
and the calls to the Area Class are:
public YingYang()
{
Area mainCircle = new Area(new Ellipse2D.Double(...)
...
yingYang.add(mainCircle);
}
The MoveTo call:
public YingYang moveTo(double x, double y)
{
at.translate(x, y);
at.setToTranslation(x, y);
yingYang.transform(at);
return new YingYang(at.createTransformedShape(yingYang));
}
The ScaleBy:
public YingYang scaleBy(double scale)
{
double cx = this.getBounds2D().getCenterX();
double cy = this.getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.scale(scale, scale);
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(yingYang));
}
When I call the paintComponent() in my drawing panel:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for(YingYang s : shapes)
{
System.out.println(s.getBounds2D());
g2.setColor(s.getColor());
g2.fill(s);
}
}
The print statement prints out:
java.awt.geom.Rectangle2D$Double[x=0.0,y=0.0,w=0.0,h=0.0]
I'm at a loss... Any Ideas?
It looks like you have combined both my recommendations into one piece of code. If you are going to use your variable yingYang then you should implement the shape on the class. However if you are going to extend the area you need to remove the yingYang variable and use the class as the area eg: yingYang.add(mainCircle); becomes add(mainCircle);... essentially remove all references of the yingYang variable.
So instead of the "yingYang" variable you are using "this". heres is a modified version of your YingYang class with the references removed.
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class YingYang extends Area
{
AffineTransform at = new AffineTransform();
private boolean movingRight = true;
private boolean movingUp = true;
private Color color = Color.BLACK;
private int dx = 10, dy = 10;
public YingYang(Shape shape)
{
super(shape);
}
public YingYang()
{
// Construct the Outer Circle & Lower Dot
Area mainCircle = new Area(new Ellipse2D.Double(-210, -210, 420, 420));
Area lowerDot = new Area(new Ellipse2D.Double(-10, 90, 40, 40));
mainCircle.subtract(lowerDot);
// Begin Construction of the whit side of symbol
Area whiteSide = new Area(new Ellipse2D.Double(-200, -200, 400, 400));
Area rect = new Area(new Rectangle2D.Double(0, -200, 200, 400));
whiteSide.subtract(rect);
// Construct the upper white Circle
Area upperCircle = new Area(new Ellipse2D.Double(-100, -200, 200, 200));
whiteSide.add(upperCircle);
// Construct the Upper Dot
Area upperDot = new Area(new Ellipse2D.Double(-10, -110, 40, 40));
whiteSide.subtract(upperDot);
// Remove the lower circle portion
Area lowerCircle = new Area(new Ellipse2D.Double(-100, 0, 200, 200));
whiteSide.subtract(lowerCircle);
// Add Main Circle
add(mainCircle);
// Subtract the white side
subtract(whiteSide);
}
//------------------------ Methods -----------------------------------------
/**
* Sets this shapes color
* (must call getColor before drawing this shape)
* #param color
*/
public void setColor(Color color)
{
this.color = color;
}
/**
* Gets this shapes current color
* #return color
*/
public Color getColor()
{
return this.color;
}
/**
* Determines if the shape is moving left to right
* #return - boolean
*/
public boolean isMovingRight()
{
return movingRight;
}
/**
* Determines if the shape is moving from down to up
* #return - boolean
*/
public boolean isMovingUp()
{
return movingUp;
}
/**
* Changes the Horizontal Path that this shape is traveling
*/
public void changeHorizonalMovement()
{
if(isMovingRight())
{
movingRight = false;
}
else
{
movingRight = true;
}
}
/**
* Changes the Vertical Path that this shape is traveling
*/
public void changeVerticalMovement()
{
if(isMovingUp())
{
movingUp = false;
}
else
{
movingUp = true;
}
}
/**
* Sets the direction of the Horizontal Path of this shape
* true = left to right : false = right to left
* #param dir - boolean
*/
public void setHorizonalMovement(boolean dir)
{
this.movingRight = dir;
}
/**
* Sets the direction of the Vertical Path of this shape
* true = down to up : false = up to down
* #param dir - boolean
*/
public void setVerticalMovement(boolean dir){
this.movingUp = dir;
}
/**
* Moves the current shape by the amount x,y
* #param x - double
* #param y - double
*/
public YingYang moveTo(double x, double y)
{
at.translate(x, y);
at.setToTranslation(x, y);
transform(at);
return new YingYang(at.createTransformedShape(this));
}
/**
* Rotate this shape
* #param theta - amount to rotate shape by
* #return
*/
public YingYang rotate(double theta)
{
double cx = getBounds2D().getCenterX();
double cy = getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.rotate(Math.toRadians(theta));
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
public YingYang moveToAndRotate(double x, double y, double theta)
{
double cx = getBounds2D().getCenterX();
double cy = getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.translate(x, y);
at.rotate(Math.toRadians(theta));
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
/**
* Scales this shape uniformly by the amount of scale
* about the origin
* #param scale - double
*/
public YingYang scaleBy(double scale)
{
double cx = this.getBounds2D().getCenterX();
double cy = this.getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.scale(scale, scale);
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
/**
* Rotates this shape theta degrees about the origin
*/
public YingYang rotate(Double theta)
{
double cx = this.getBounds2D().getCenterX();
double cy = this.getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.rotate(Math.toRadians(theta));
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
public int getDx()
{
return this.dx;
}
public void setDx(int x)
{
this.dx = x;
}
public int getDy()
{
return this.dy;
}
public void setDy(int y)
{
this.dy = y;
}
}

Bounce ball back android

I have take Example of Accelerator with SensorManager, in which canvas(ball) are get update its position as per device Accelerator are rotated. Here is image :
As shown in the image there is a ball and one line. The ball's position is frequently updated, while the line's position is static.
I would like to have the ball bounce back when it touches the line. I have tried since from 3 day, but don't understand how I can do this.
here is my code:
public class ballsensor extends Activity implements SensorEventListener {
// sensor-related
private SensorManager mSensorManager;
private Sensor mAccelerometer;
// animated view
private ShapeView mShapeView;
// screen size
private int mWidthScreen;
private int mHeightScreen;
// motion parameters
private final float FACTOR_FRICTION = 0.5f; // imaginary friction on the
// screen
private final float GRAVITY = 9.8f; // acceleration of gravity
private float mAx; // acceleration along x axis
private float mAy; // acceleration along y axis
private final float mDeltaT = 0.5f; // imaginary time interval between each
// acceleration updates
// timer
private Timer mTimer;
private Handler mHandler;
private boolean isTimerStarted = false;
private long mStart;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set the screen always portait
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// initializing sensors
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// obtain screen width and height
Display display = ((WindowManager) this
.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mWidthScreen = display.getWidth();
mHeightScreen = display.getHeight() - 35;
// initializing the view that renders the ball
mShapeView = new ShapeView(this);
mShapeView.setOvalCenter((int) (mWidthScreen * 0.6),
(int) (mHeightScreen * 0.6));
setContentView(mShapeView);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent event) {
// obtain the three accelerations from sensors
mAx = event.values[0];
mAy = event.values[1];
float mAz = event.values[2];
// taking into account the frictions
mAx = Math.signum(mAx) * Math.abs(mAx)
* (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
mAy = Math.signum(mAy) * Math.abs(mAy)
* (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
}
#Override
protected void onResume() {
super.onResume();
// start sensor sensing
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onPause() {
super.onPause();
// stop senser sensing
mSensorManager.unregisterListener(this);
}
// the view that renders the ball
private class ShapeView extends SurfaceView implements
SurfaceHolder.Callback {
private final int RADIUS = 30;
private final float FACTOR_BOUNCEBACK = 0.50f;
private int mXCenter;
private int mYCenter;
private RectF mRectF;
private final Paint mPaint;
private ShapeThread mThread;
private float mVx;
private float mVy;
public ShapeView(Context context) {
super(context);
getHolder().addCallback(this);
mThread = new ShapeThread(getHolder(), this);
setFocusable(true);
mPaint = new Paint();
mPaint.setColor(0xFFFFFFFF);
mPaint.setAlpha(192);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setAntiAlias(true);
mRectF = new RectF();
}
// set the position of the ball
public boolean setOvalCenter(int x, int y) {
mXCenter = x;
mYCenter = y;
return true;
}
// calculate and update the ball's position
public boolean updateOvalCenter() {
mVx -= mAx * mDeltaT;
mVy += mAy * mDeltaT;
System.out.println("mVx is ::" + mVx);
System.out.println("mVy is ::" + mVy);
mXCenter += (int) (mDeltaT * (mVx + 0.6 * mAx * mDeltaT));
mYCenter += (int) (mDeltaT * (mVy + 0.6 * mAy * mDeltaT));
if (mXCenter < RADIUS) {
mXCenter = RADIUS;
mVx = -mVx * FACTOR_BOUNCEBACK;
}
if (mYCenter < RADIUS) {
mYCenter = RADIUS;
mVy = -mVy * FACTOR_BOUNCEBACK;
}
if (mXCenter > mWidthScreen - RADIUS) {
mXCenter = mWidthScreen - RADIUS;
mVx = -mVx * FACTOR_BOUNCEBACK;
}
if (mYCenter > mHeightScreen - 2 * RADIUS) {
mYCenter = mHeightScreen - 2 * RADIUS;
mVy = -mVy * FACTOR_BOUNCEBACK;
}
return true;
}
// update the canvas.
#Override
protected void onDraw(Canvas canvas) {
if (mRectF != null) {
mRectF.set(mXCenter - RADIUS, mYCenter - RADIUS, mXCenter
+ RADIUS, mYCenter + RADIUS);
canvas.drawColor(0XFF000000);
// canvas.drawOval(mRectF, mPaint);
Bitmap kangoo = BitmapFactory.decodeResource(getResources(),
R.drawable.stripe1);
Bitmap ball = BitmapFactory.decodeResource(getResources(),
R.drawable.blackwhiteball);
canvas.drawBitmap(ball, mXCenter - RADIUS, mYCenter - RADIUS,
mPaint);
canvas.drawBitmap(kangoo, 130, 10, null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
class ShapeThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private ShapeView mShapeView;
private boolean mRun = false;
public ShapeThread(SurfaceHolder surfaceHolder, ShapeView shapeView) {
mSurfaceHolder = surfaceHolder;
mShapeView = shapeView;
}
public void setRunning(boolean run) {
mRun = run;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
#Override
public void run() {
Canvas c;
while (mRun) {
mShapeView.updateOvalCenter();
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
mShapeView.onDraw(c);
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
Rather than try to fix your code, work at the design level by developing a software architecture that has two components: physics model and display. The key is to separate the physics of the problem from the display. Modelling the physics becomes much easier when done separately from the display. Likewise the display also becomes easier. Have two separate packages - one for the physics and one for the display.
Start with a simpler version of the problem where the physics world just has a point and a line. Model the point reflecting off the line. You have some code that does this. Just rip it out of the current code. Make sure the physics does what you expect it to without worrying about the display.
Design a class for the ball. The ball has velocity and position properties. It has a move method that updates the position based on the velocity for one time click. The move method checks to see if it has interacted (collided) with the wall and changes the velocity according the physics you want your world to have. The collision detection is done by asking the wall were it is. The physics could be angle of incidence equals angle of reflection, or you could have a spin property on the ball that changes how the ball bounces. The key is that all of the physics modelling is done separately from the display. Similarly, you create a class for the wall. Initially the wall is fixed, but you could add movement to it. The nice thing is that if you've designed the ball class correctly changing the wall to make it move doesn't effect the design of the ball class. Also, none of these changes to the physics effect how the display is done.
Make a display that simply translates the physics into a presentation on the screen.
From there you can add complexity to your model. Make the point a circle. Redo the physics to make it work with this new complexity. The display won't change much, but keep them separate.
I have my CS1 class do versions of this same problem. Two years ago, I had them make a pong game. Last year a version of Centipede. This coming semester they'll have Breakout as a project. When they model the physics separately from the display, they get it working. When they don't, it is usually a muddled mess.
Physics modyle shall run in separate thread, and use best available time resolution for position updates. ( milliseconds should be enough ) This is how I design gameloop:
lastFrameTime = System.currentTimeMillis();
// as long as we run we move
while (state == GameState.RUNNING) {
currentFrame++;
timeNow = System.currentTimeMillis();
// sleep until this frame is scheduled
long l = lastFrameTime + FRAME_DELAY - timeNow;
updatePositions();
redraw();
if (l > 0L) {
try {
Thread.sleep(l);
}
catch (Exception exception) {
}
} else {
// something long kept us from updating, reset delays
lastFrameTime = timeNow;
l = FRAME_DELAY;
}
lastFrameTime = timeNow + l;
// be polite, let others play
Thread.yield();
}
It is important to give up control of the thread, for UI tasks which will be processing events and hive commands to your phyiscs engine.
As for collision detection - it is pretty simple math. Your line is vertical, and zou must just check whether difference in x-coord of line and center is got less than radius - then reverse x-componen of velocity
You can use Rect.intersects(Rect, Rect) to detect collisions. Use your bitmap width and height to set up the new Rects.
Here is a dirty example:
import java.util.Timer;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
public class ballsensor extends Activity implements SensorEventListener {
// sensor-related
private SensorManager mSensorManager;
private Sensor mAccelerometer;
// animated view
private ShapeView mShapeView;
// screen size
private int mWidthScreen;
private int mHeightScreen;
// motion parameters
private final float FACTOR_FRICTION = 0.5f; // imaginary friction on the
// screen
private final float GRAVITY = 9.8f; // acceleration of gravity
private float mAx; // acceleration along x axis
private float mAy; // acceleration along y axis
private final float mDeltaT = 0.5f; // imaginary time interval between each
// acceleration updates
// timer
private Timer mTimer;
private Handler mHandler;
private final boolean isTimerStarted = false;
private long mStart;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set the screen always portait
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// initializing sensors
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// obtain screen width and height
final Display display = ((WindowManager) this
.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mWidthScreen = display.getWidth();
mHeightScreen = display.getHeight() - 35;
// initializing the view that renders the ball
mShapeView = new ShapeView(this);
mShapeView.setOvalCenter((int) (mWidthScreen * 0.6),
(int) (mHeightScreen * 0.6));
setContentView(mShapeView);
}
#Override
public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
}
#Override
public void onSensorChanged(final SensorEvent event) {
// obtain the three accelerations from sensors
mAx = event.values[0];
mAy = event.values[1];
final float mAz = event.values[2];
// taking into account the frictions
mAx = Math.signum(mAx) * Math.abs(mAx)
* (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
mAy = Math.signum(mAy) * Math.abs(mAy)
* (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
}
#Override
protected void onResume() {
super.onResume();
// start sensor sensing
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onPause() {
super.onPause();
// stop senser sensing
mSensorManager.unregisterListener(this);
}
// the view that renders the ball
private class ShapeView extends SurfaceView implements
SurfaceHolder.Callback {
private final int RADIUS = 30;
private final float FACTOR_BOUNCEBACK = 0.50f;
private int mXCenter;
private int mYCenter;
private final RectF mRectF;
private final Paint mPaint;
private final ShapeThread mThread;
private float mVx;
private float mVy;
private final Rect lineRect = new Rect();
private final Rect ballRect = new Rect();
public ShapeView(final Context context) {
super(context);
getHolder().addCallback(this);
mThread = new ShapeThread(getHolder(), this);
setFocusable(true);
mPaint = new Paint();
mPaint.setColor(0xFFFFFFFF);
mPaint.setAlpha(192);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setAntiAlias(true);
mRectF = new RectF();
}
// set the position of the ball
public boolean setOvalCenter(final int x, final int y) {
mXCenter = x;
mYCenter = y;
return true;
}
// calculate and update the ball's position
public boolean updateOvalCenter() {
mVx -= mAx * mDeltaT;
mVy += mAy * mDeltaT;
System.out.println("mVx is ::" + mVx);
System.out.println("mVy is ::" + mVy);
mXCenter += (int) (mDeltaT * (mVx + 0.6 * mAx * mDeltaT));
mYCenter += (int) (mDeltaT * (mVy + 0.6 * mAy * mDeltaT));
if (mXCenter < RADIUS) {
mXCenter = RADIUS;
mVx = -mVx * FACTOR_BOUNCEBACK;
}
if (mYCenter < RADIUS) {
mYCenter = RADIUS;
mVy = -mVy * FACTOR_BOUNCEBACK;
}
if (mXCenter > mWidthScreen - RADIUS) {
mXCenter = mWidthScreen - RADIUS;
mVx = -mVx * FACTOR_BOUNCEBACK;
}
if (mYCenter > mHeightScreen - 2 * RADIUS) {
mYCenter = mHeightScreen - 2 * RADIUS;
mVy = -mVy * FACTOR_BOUNCEBACK;
}
if(Rect.intersects(lineRect, ballRect)){
mVx = -mVx * FACTOR_BOUNCEBACK;
mVy = -mVy * FACTOR_BOUNCEBACK;
mXCenter += (int) (mDeltaT * (mVx + 0.6 * mAx * mDeltaT)) * 5;
mYCenter += (int) (mDeltaT * (mVy + 0.6 * mAy * mDeltaT)) * 5;
}
return true;
}
// update the canvas.
#Override
protected void onDraw(final Canvas canvas) {
if (mRectF != null) {
mRectF.set(mXCenter - RADIUS, mYCenter - RADIUS, mXCenter
+ RADIUS, mYCenter + RADIUS);
canvas.drawColor(0XFF000000);
// canvas.drawOval(mRectF, mPaint);
final Bitmap kangoo = BitmapFactory.decodeResource(getResources(),
R.drawable.blankcard);
lineRect.set(130, 10, 130 + kangoo.getWidth(), 10 + kangoo.getHeight());
final Bitmap ball = BitmapFactory.decodeResource(getResources(),
R.drawable.blankcard);
ballRect.set(mXCenter - RADIUS, mYCenter - RADIUS, mXCenter - RADIUS + ball.getWidth(), mYCenter - RADIUS + ball.getHeight());
canvas.drawBitmap(ball, mXCenter - RADIUS, mYCenter - RADIUS,
mPaint);
canvas.drawBitmap(kangoo, 130, 10, null);
}
}
#Override
public void surfaceChanged(final SurfaceHolder holder, final int format, final int width,
final int height) {
}
#Override
public void surfaceCreated(final SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
#Override
public void surfaceDestroyed(final SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
while (retry) {
try {
mThread.join();
retry = false;
} catch (final InterruptedException e) {
}
}
}
}
class ShapeThread extends Thread {
private final SurfaceHolder mSurfaceHolder;
private final ShapeView mShapeView;
private boolean mRun = false;
public ShapeThread(final SurfaceHolder surfaceHolder, final ShapeView shapeView) {
mSurfaceHolder = surfaceHolder;
mShapeView = shapeView;
}
public void setRunning(final boolean run) {
mRun = run;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
#Override
public void run() {
Canvas c;
while (mRun) {
mShapeView.updateOvalCenter();
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
mShapeView.onDraw(c);
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
Needs improvement but might get you on the right track.

Categories

Resources