Basically I have been asked to create a pinball game that fires a ball on to a board and the user can control flippers etc to keep the ball from hitting an absorber with added shapes to act as bumpers to keep the ball in play.
However, I've run in to a little problem with my collisions. The user can click a grid square on the board to highlight it and then use the 'Add Square' button to add a square of height 20 and width 20 to that highlighted square also returning its (x,y) position.
public void addASquare(Point p) {
System.out.println("Add square to point: " + p.x + ", " + p.y);
Square square = new Square(p.x, p.y, L, L);
bumperList.add(square);
setChanged();
notifyObservers(bumperList);
}
Once, this has been done I then add Line Segments to the square using the (x,y) coordinates from the use adding the square to prepare for collision detection with a ball.
public ArrayList<LineSegment> getLineSeg() {
ArrayList<LineSegment> lines = new ArrayList<LineSegment>();
LineSegment l1 = new LineSegment(x, y, x + 1, y); // top
LineSegment l2 = new LineSegment(x, y + 1, x + 1, y + 1); // bottom
LineSegment l3 = new LineSegment(x, y, x, y + 1); // left
LineSegment l4 = new LineSegment(x + 1, y, x + 1, y + 1); // right
lines.add(l1);
lines.add(l2);
lines.add(l3);
lines.add(l4);
return lines;
}
..and here is the code for the collision detection when the ball hits either side of the square.
ArrayList<LineSegment> lseg = sq.getLineSeg();
for (LineSegment line : lseg) {
time = Geometry.timeUntilWallCollision(line, ball,
velocity);
if (time < minimumTime) {
minimumTime = time;
newVelocity = Geometry.reflectWall(line, ball.getVelocity(), 1.0);
return new CollisionDetails(minimumTime, newVelocity);
}
}
However, when I run the program the ball just carries on through the square. I think I am overlooking some key detail that I can't seem to solve which is why I'm here, so any help or pointers at all will be greatly appreciated.
You say your square has side lengths of 20 units which I suppose is the value of your constant L but your line segments only have a length of 1. Try replacing the values of 1 by your constant L.
Related
I am trying to write a small program that has a given number of balls (in the example code below it's 3) travel back and forth across the screen at different speeds and phases (start offset).
This much has been achieved in the code. Although I want to be able to select the balls (one at a time) using a mouse click.
I have used the word "HIT!!!" to signify in the console that a ball has been clicked.
My problem is that when I run the code below, I only get a "HIT!" in the console when I click the top ball. That is when the first element y[0] matches with the click_Y variable. When I am sure (but obviously mistaken somehow) that there should be matches when I click in the vicinity of y[1] & y[2].
I'd really be grateful for any help with these. As it's gotten to the point where I am starting to stare blankly at the screen. Thanks.
int noCircles; // the number of items in the array (# of circles)
float[] y; // y-position of each circle (fixed)
float[] speed; // speed of each circle
float[] phase; // phase of each circle
float red = 120;
float green = 120;
float blue = 120;
float click_X;
float click_Y;
void setup() {
size(500, 500);
noCircles = 3;
// allocate space for each array
y = new float[noCircles];
speed = new float[noCircles];
phase = new float[noCircles];
// calculate the vertical gap between each circle based on the total number
// of circles
float gap = height / (noCircles + 1);
//setup an initial value for each item in the array
for (int i=0; i<noCircles; i++) {
y[i] = gap * (i + 1);
// y is constant for each so can be calculated once
speed[i] = random(10);
phase[i] = random(TWO_PI);
}
}
void draw() {
background(155);
for (int i=0; i<noCircles; i++) {
// calculate the x-position of each ball based on the speed, phase and
//current frame
float x = width/2 + sin(radians(frameCount*speed[i] ) + phase[i])* 200;
if (dist(x, y[i], click_X, click_Y) <= 20){
println("HIT!!!!!!!!!!!!!!!!!!");
}
ellipse(x, y[i], 20, 20);
click_X = 0;
click_Y = 0;
}
}
void mousePressed() {
println("You clicked******************************************");
click_X = mouseX;
click_Y = mouseY;
println("click_X =" + click_X);
println("click_Y =" + click_Y);
}
Problems like these are best solved by debugging your program. Start by tracing through the code by hand, then add print statements (more than you've already added), and if that doesn't work then don't be afraid to use the debugger.
You're using the click_X and click_Y variables to check the position of the mouse against the position of each ball. Trace through the for loop in your draw() function. What happens at the end of the first iteration?
You reset the values of click_X and click_Y. That's why you aren't detecting any hits on the other circles.
You could probably refactor your code to only reset those variables if something has been hit, but really, I would stop using them altogether.
I'm guessing that you're using those variables because you only want to check when the mouse is pressed? Just use the mousePressed variable for that. Then you can use the mouseX and mouseY variables directly.
Then your if statement would look like this:
if (mousePressed && dist(x, y[i], mouseX, mouseY) <= 20) {
println("HIT: " + i);
}
Also, using separate arrays like this is called parallel arrays, and is general a bad habit to get into. You should probably use classes instead.
I have a project where I have to fill a 600x400 window (JavaFX) with 30 random sized circles with no filling. The largest circle must be filled with a translucent red (and if there are multiple large circles with the same radius only one can be filled). I'm able to get all the circles on the screen fine. My problem is getting the largest circle to be red. I haven't been taught arrays which were used in almost all of my many google searches. I cant figure out how exactly to track the largest circle. His hint to us is : "When it comes to keeping track of the largest circle, remember that two reference variables can point to the same Circle object. Maintain a separate Circle reference variable that always points to the largest circle (so far created). You may want to initialize this variable to a circle that has a radius of 0. You can get the radius of a circle using the getRadius method." I created a circle object and a largestCircle object but don't understand how to make the largestCircle object have the highest radius.
This is the code I have so far:
{
Random gen = new Random();
int x = 0;
int y = 0;
int radius = 0;
double largestRadius = Math.max(radius);
Circle largestCircle = null;
Group root = new Group();
//prints out 30 circles
for (int i = 0; i <= 30; i++)
{
Circle circle = new Circle(x, y, radius);
{
radius = gen.nextInt(66) + 10; //generates random radius from 10 to 75
x = gen.nextInt(600 - 2 * radius) + radius;
y = gen.nextInt(400 - 2 * radius) + radius;
}
if (circle.getRadius() == largestRadius)
{
largestCircle = circle;
largestCircle.setFill(Color.rgb(255, 0, 0, 0.3));
}
circle.setFill(null);
circle.setStroke(Color.rgb(gen.nextInt(256), + gen.nextInt(256), gen.nextInt(256)));
circle.setStrokeWidth(3);
root.getChildren().add(circle);
}
after I generate the random circles how to I find the max radius that was generated and set it to largestCircle? the highest radius a circle can be is 75, but sometimes none of the circles have a radius of 75. How do I set the max to be the highest number the program randomly generates?
Any help would be greatly appreciated! Thank you for your time
How about the following.
It has a two fixes.
-1, use > and not == when figuring if current circle is the largest.
-2, change the color of the largest circle at the end, after all the circles have been made... else you might make multiple circles red.
{
Random gen = new Random();
int x = 0;
int y = 0;
int radius = 0;
double largestRadius = Math.max(radius);
Circle largestCircle = null;
Group root = new Group();
//prints out 30 circles
for (int i = 0; i <= 30; i++)
{
Circle circle = new Circle(x, y, radius);
if (circle.getRadius() > largestRadius)
{
largestCircle = circle;
}
{
radius = gen.nextInt(66) + 10; //generates random radius from 10 to 75
x = gen.nextInt(600 - 2 * radius) + radius;
y = gen.nextInt(400 - 2 * radius) + radius;
}
circle.setFill(null);
circle.setStroke(Color.rgb(gen.nextInt(256), + gen.nextInt(256), gen.nextInt(256)));
circle.setStrokeWidth(3);
root.getChildren().add(circle);
}
largestCircle.setFill(Color.rgb(255, 0, 0, 0.3));
It is generally a good idea to initialize any max variable with a small number that is out of scope for your project. In this case, since radius can not be -1, I would do
double largestRadius = -1;
After this, it doesn't matter how big the radius can be, any radius bigger than -1 will change the largestRadius.
It looks to me like you are only missing one part and that is the if the newly created circle has a radius > largestRadius.
if(circle.getRadius() > largestRadius){
largestCircle = circle;
largestRadius = circle.getRadius();
}
After this, you have checked for if the new circle has a radius greater than AND you have checked if the new circle has a radius equal to. Keeping the if statement that you already have, you will always reference the newest circle with the largestRadius.
I would keep the circle objects in an array. Use a double (or whatever number type is appropriate for your random values) to track the high value with a simple comparison (is my current high value less than the new random value? if so, update high value) each time you generate a random value and create a circle of that size.
Once you have your 30 circles in your array simply loop through it until you find the first occurrence of your high value, when you find it make that circle whatever color.
Circle[] myCircles=new Circle[30];
double largestCircle;
for(int i=0;i<30;i++){
// determine your x,y, and radius here
myCircles[i]=new Circle(x,y,radius);
if(radius>largestCircle) largestCircle=radius;
}
Then to loop thru your myCircles and do things with each one
for(int i=0;i<30;i++){
if(myCircles[i].getRadius()==largestCircle){
// make myCircles[i] red here
}
}
made two circles one of radius 8(image 16x16)
and one of radius 20( image 40x40)
i am calling the circle over overlap method and the collsion is just off. It is colliding with a circle that is around the 0,0 point of where ever my image of the ball is. the bullet can go within the ball on the bottom and right sides.
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture ballImage, bulletImage;
OrthographicCamera cam;
Circle ball;
Array <Circle> bullets;
long lastShot;
#Override
public void create ()
{
System.out.println("game created");
ballImage = new Texture(Gdx.files.internal("ball.png"));
bulletImage = new Texture(Gdx.files.internal("bullet.png"));
cam = new OrthographicCamera();
cam.setToOrtho(true,320,480);//true starts top right false starts top left
batch = new SpriteBatch();
ball = new Circle();
ball.radius=20;
ball.x=320/2-ball.radius; // half screen size - half image
ball.y=480/2-ball.radius;
bullets = new Array<Circle>();
spawnBullet();
/*
batch.draw(bulletImage,bullet.x,bullet.y);
bullet.x++;
bullet.y++; */
}
public void spawnBullet()
{
Circle bullet = new Circle();
bullet.radius=8;
bullet.x=0;
bullet.y=0;
bullets.add(bullet);
lastShot = TimeUtils.nanoTime();
}
#Override
public void render ()
{
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
cam.update();
batch.setProjectionMatrix(cam.combined);
batch.begin();
batch.draw(ballImage,ball.x,ball.y);
for(Circle bullet: bullets)
{
batch.draw(bulletImage, bullet.x, bullet.y);
}
batch.end();
if(Gdx.input.isTouched())
{
Vector3 pos = new Vector3();
pos.set(Gdx.input.getX(), Gdx.input.getY(),0);
cam.unproject(pos);
ball.y = pos.y - ball.radius;
ball.x = pos.x - ball.radius ;
}
//if(TimeUtils.nanoTime()-lastShot >1000000000) one second
//spawnBullet();
Iterator<Circle> i = bullets.iterator();
while(i.hasNext())
{
Circle bullet = i.next();
bullet.x++;
bullet.y++;
if(bullet.overlaps(ball))
{
System.out.println("overlap");
i.remove();
}
}
}
}
If your bullet and the ball are 2 circles, like you said you don't need an overlap method.
It is simple: 2 circles collide, if their distance is smaller then the sum of their radiuses.
To calculate the distance you need to make a squareroot. This is a pretty expensive calculation, so it would be better to use squared distance and squared sum of radiuses:
float xD = ball.x - bullet.x; // delta x
float yD = ball.y - bullet.y; // delta y
float sqDist = xD * xD + yD * yD; // square distance
boolean collision = sqDist <= (ball.radius+bullet.radius) * (ball.radius+bullet.radius);
Thats it.
Also in your cam.setToOrtho you wrote a cooment:
//true starts top right false starts top left
Thats wrong, it is top left or bottom left. By default it is bottom left, because this is the way a coordinate system works normaly. The top left is, because the monitor addresses pixels starting from top left = pixel 1.
EDIT: this should be the problem: The coordinates you give the batch.draw method are the left lower corner of the Texture by default, if you are using the "y = Down"-System it should be the top left corner (you have to try i am not sure).
The Circles position instead is its center.
To solve the problem you need to adjust the position like this (for "y = Up"-System):
batch.draw(bulletImage, bullet.x - bullet.radius, bullet.y - bullet.radius);
It is possible, that the same formula works also for the "y = Down"-System but i am not sure
So im working on a java RPG and I am stuck on the collision detecting. This is my map so far
Im trying to get it so that he cant walk on the blue but can on the white ones. The way I am doing the movement is by making the map move by applying x offset and y offset to each textures x and y, and adding/taking away from them as the wasd keys are pressed. This gives the affect that the player is moving,even though its the map. This is the code I tryed for the detection.
int x_offset = InGame.current_level.xoffset;
int y_offset = InGame.current_level.yoffset;
Rectangle player = new Rectangle(Math.round((x + x_offset) / 256),Math.round((y + y_offset) / 256),256,256);
for(int x = 0;x!= InGame.current_level.width;x++){
for(int y = 0;y!= InGame.current_level.height;y++){
int tile = InGame.current_level.tiles[x+y* InGame.current_level.width];
Rectangle tileRect = new Rectangle(x + x_offset ,y + y_offset,256,256);
if(player.contains(tileRect)){
System.out.println("Standing on " + tile);
return;
}
}
}
But somtimes it says that he is not standing on any tile,and it is often wrong when it says he is on a tile. Each Tile is rendered by doing (x * 256) + xoffset and the same for y. the map is stored in int[] where each part of the array is a number,which corresponds to a tile id
I am making a java game using tiles. I am having trouble with the collision element. I am defining rectangles for each tile on the map, and another rectangle for the player. What I am having trouble with is knowing which side the player is coming from when it hits the rectangle, and then pushing the player off in the direction the player came from. I have already made a method that checks how much the character is inside the rectangle, so it can know how much to push it out, but I can't figure out how to tell which side the character is coming from.
here is my current collision methoe - note rect1 is the character and rect2 is the tile
public void collision(Rectangle rect1, Rectangle rect2) {
float xAdd;
float xAdd2;
float yAdd;
float yAdd2;
boolean hitRight = false;
boolean hitLeft = false;
boolean hitTop = false;
boolean hitBot = false;
Vector2f rect1Origin = new Vector2f(rect1.x, rect1.y);
Vector2f rect2Origin = new Vector2f(rect2.x, rect2.y);
Vector2f rect1Mid = new Vector2f((rect1.x + rect1.width) / 2,(rect1.y + rect1.height) / 2);
Vector2f rect2Mid = new Vector2f((rect2.x + rect2.width) / 2,(rect2.y + rect2.height) / 2);
Vector2f rect1A = new Vector2f(rect1Origin.x + rect1.width, rect1.y);
Vector2f rect1B = new Vector2f(rect1Origin.x, rect1Origin.y+ rect1.height);
Vector2f rect1C = new Vector2f(rect1Origin.x + rect1.width,rect1Origin.y + rect1.height);
Vector2f rect2A = new Vector2f(rect2Origin.x + rect2.width, rect2.y);
Vector2f rect2B = new Vector2f(rect2Origin.x, rect2Origin.y
+ rect2.height);
Vector2f rect2C = new Vector2f(rect2Origin.x + rect2.width,
rect2Origin.y + rect2.height);
xAdd = rect2C.x - rect1B.x;
xAdd2 = rect1C.x - rect2B.x;
yAdd = rect2A.y - rect1B.y;
yAdd2 = rect2C.y - rect1A.y;
if (rect1Mid.y < rect2Mid.y) {
if (rect1.intersects(rect2)) {
y_pos += yAdd;
}
}
if (rect1Mid.y > rect2Mid.y) {
if (rect1.intersects(rect2)) {
System.out.println(yAdd2);
y_pos += yAdd2;
}
}
if(rect1Mid.x > rect2Mid.x){
if(rect1.intersects(rect2)){
hitRight = true; x_pos += xAdd;
}
}
if(rect1Mid.x< rect2Mid.x){
if(rect1.intersects(rect2)) {
x_pos += -xAdd2;
}
}
}
Any help would be greatly appreciated
Thanks
Keep two positions for your character - where the it is (as of last frame, move, etc.), and where you want to move it. Then only move it if you don't detect collision - if you don't allow for corrupt state, you don't have to fix it.
Edit: The collision method should be boolean - it should be done before actually moving the character, like
if (!collision(character, tile))
{
doMove(character);
}
else
{
//custom handling if required
}
Edit2: The previous would work only for a small step, if you need a partial move, you really need to know the character's original position, like move(originalPosition, desiredPosition, tile), where you can deduce the direction from the originalPosition and the tile.
The main point is, you don't actually move the character, before you have a valid position for it.
First of all, any Sprite (the character and the tile here) should have four members: xPos, yPos, xVec, yVec. With that in mind, you know where the character was and will be in the next frame.
sprite.xPos += sprite.xVec;
sprite.yPos += sprite.yVec;
Also take a look at your code, you should improve the propositions (for instance, you check if(rect1.intersects(rect2)) in the four if statements). Instead, check only once for the intersects(), and inside check for each possible case (left, right, top, bottom hit).
Finally, the collision method should receive 2 sprites objects (for instance, sprite1 and sprite2), and then check for the intersects() method, taking into account the original positions of both sprites and its vectors. If the sprites intersect in the new position, then you invert the object's vector accordingly.
Assuming you can detect the collision with your code. Here is a way to determine the position of your character with respect to the rectangle object.
Find the middle point of the rectangle. ( Rx,Ry).
Find the middle point of your character sprite. ( Sx,Sy).
Now you can compare Rx,Ry , Sx , Sy to determine in which side is Sx and Sy of Rx and Ry
for ex:
if Sx < Rx then
Sx = left side of Rx
Was struggling with this myself, but after some thinking this check can be done by having all your entities in a List, in my code I had to block the player from moving through blocks. All blocks is in a List. Now, when checking players position I create a new Rectangle and put the players position one frame forward in the direction the update will happened next frame, if the the rectangle intersects that update for that direction will not happened. Heres my code for this:
if (isKeyPressed(KeyEvent.VK_LEFT)) {
if(!collisionWithBlocks(1)){
pl.x = pl.x - updatePlayerPosition;
}
}
if (isKeyPressed(KeyEvent.VK_RIGHT)) {
if(!collisionWithBlocks(0)){
pl.x = pl.x + updatePlayerPosition;
}
}
if (isKeyPressed(KeyEvent.VK_UP)) {
if(!collisionWithBlocks(3)){
pl.y = pl.y - updatePlayerPosition;
}
}
if (isKeyPressed(KeyEvent.VK_DOWN)) {
if(!collisionWithBlocks(2)){
pl.y = pl.y + updatePlayerPosition;
}
}
collisionWithBlocks():
public boolean collisionWithBlocks(int side){
for(Block b : main.blocks){
Rectangle block = b.getBounds();
Rectangle player = null;
if(side == 0){
player = new Rectangle(pl.x + updatePlayerPosition, pl.y, pl.getWidth(), pl.getHeight());
} else if(side == 1){
player = new Rectangle(pl.x - updatePlayerPosition, pl.y, pl.getWidth(), pl.getHeight());
} else if(side == 2){
player = new Rectangle(pl.x, pl.y + updatePlayerPosition, pl.getWidth(), pl.getHeight());
} else if(side == 3){
player = new Rectangle(pl.x, pl.y - updatePlayerPosition, pl.getWidth(), pl.getHeight());
}
if(player.intersects(block)){
return true;
}
}
return false;
}
updatePlayerPosition is 2, and changes in my code but enough about that.
In your case, I recommend putting the entities in a List and then checking like I did over.