Skip to content

Instantly share code, notes, and snippets.

@lyze237
Last active August 2, 2020 19:18
Show Gist options
  • Save lyze237/9a8698d27e985e0858d0700c883c062f to your computer and use it in GitHub Desktop.
Save lyze237/9a8698d27e985e0858d0700c883c062f to your computer and use it in GitHub Desktop.
Ball/Projectile/Nades/... reflection/bounce off walls
package dev.lyze.reflection;
import com.badlogic.gdx.math.Vector2;
public class Projectile {
private Vector2 pos;
private Vector2 dir;
public Projectile(Vector2 pos, Vector2 dir) {
this.pos = pos;
this.dir = dir.scl(0.05f);
}
public float getX() {
return pos.x;
}
public void setX(float x) {
this.pos.x = x;
}
public float getY() {
return pos.y;
}
public void setY(float y) {
this.pos.y = y;
}
public float getDx() {
return dir.x;
}
public void setDx(float dx) {
this.dir.x = dx;
}
public float getDy() {
return dir.y;
}
public void setDy(float dy) {
this.dir.y = dy;
}
public Vector2 getPos() {
return pos;
}
public void setPos(Vector2 pos) {
this.pos = pos;
}
public Vector2 getDir() {
return dir;
}
public void setDir(Vector2 dir) {
this.dir = dir;
}
@Override
public String toString() {
return "Projectile{" +
"x=" + getX() +
", y=" + getY() +
", dx=" + getDx() +
", dy=" + getDy() +
'}';
}
}
package dev.lyze.reflection;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import java.util.ArrayList;
public class ReflectionTests extends ApplicationAdapter {
private ShapeRenderer shapeRenderer;
private ScreenViewport screenViewport;
private final ArrayList<Projectile> projectiles = new ArrayList<>();
private final ArrayList<Wall> walls = new ArrayList<>();
private final Vector2 projectileCalculatedPosition = new Vector2();
private final Vector2 normalVector1 = new Vector2(), normalVector2 = new Vector2();
private final Vector2 endpointVector1 = new Vector2(), endpointVector2 = new Vector2();
private final Vector2 collisionPoint = new Vector2();
private final Vector2 spawnNewProjectilePosition = new Vector2();
private boolean spawnNewProjectile;
private final Vector2 spawnNewWallPosition = new Vector2();
private boolean spawnNewWall;
private final Rectangle windowRectangle = new Rectangle();
@Override
public void create() {
shapeRenderer = new ShapeRenderer();
var orthographicCamera = new OrthographicCamera();
orthographicCamera.setToOrtho(true);
screenViewport = new ScreenViewport(orthographicCamera);
}
@Override
public void render() {
Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
screenViewport.apply();
shapeRenderer.setProjectionMatrix(screenViewport.getCamera().combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
checkSpawnProjectile();
checkSpawnWall();
update();
drawEverything();
shapeRenderer.end();
}
// press left click to set position of new projectile
// press left click again to set direction vector of projectile relative to whatever the mouse cursor moved
private void checkSpawnProjectile() {
if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
if (!spawnNewProjectile) {
spawnNewProjectilePosition.set(Gdx.input.getX(), Gdx.input.getY());
spawnNewProjectile = true;
}
else {
projectiles.add(new Projectile(new Vector2(spawnNewProjectilePosition.x, spawnNewProjectilePosition.y), new Vector2(Gdx.input.getX() - spawnNewProjectilePosition.x, Gdx.input.getY() - spawnNewProjectilePosition.y)));
spawnNewProjectile = false;
}
}
if (spawnNewProjectile) {
shapeRenderer.setColor(Color.BROWN);
shapeRenderer.line(spawnNewProjectilePosition.x, spawnNewProjectilePosition.y, Gdx.input.getX(), Gdx.input.getY());
}
}
// press right click to set begin position of wall
// press right click again to set end position of wall and spawn it
private void checkSpawnWall() {
if (Gdx.input.isButtonJustPressed(Input.Buttons.RIGHT)) {
if (!spawnNewWall) {
spawnNewWallPosition.set(Gdx.input.getX(), Gdx.input.getY());
spawnNewWall = true;
}
else {
walls.add(new Wall(new Vector2(spawnNewWallPosition.x, spawnNewWallPosition.y), new Vector2(Gdx.input.getX(), Gdx.input.getY())));
spawnNewWall = false;
}
}
if (spawnNewWall) {
shapeRenderer.setColor(Color.FIREBRICK);
shapeRenderer.line(spawnNewWallPosition.x, spawnNewWallPosition.y, Gdx.input.getX(), Gdx.input.getY());
}
}
private void update() {
for (int i = projectiles.size() - 1; i >= 0; i--) {
Projectile p = projectiles.get(i);
var hit = false;
for (Wall wall : walls)
hit |= checkProjectileHitWall(p, wall);
if (!hit)
p.getPos().add(p.getDir());
// remove out of bounds projectiles
if (!windowRectangle.contains(p.getPos()))
projectiles.remove(i);
}
}
private void drawEverything() {
shapeRenderer.setColor(Color.GREEN);
walls.forEach(wall -> shapeRenderer.line(wall.getBegin(), wall.getEnd()));
for (Projectile p: projectiles) {
shapeRenderer.setColor(Color.BLUE);
shapeRenderer.circle(p.getX(), p.getY(), 5);
shapeRenderer.setColor(Color.RED);
shapeRenderer.line(p.getX(), p.getY(), p.getX() + p.getDx(), p.getY() + p.getDy());
}
}
private boolean checkProjectileHitWall(Projectile p, Wall w) {
projectileCalculatedPosition.set(p.getX() + p.getDx(), p.getY() + p.getDy());
// when projectile intersects with wall, bounce
if (Intersector.intersectSegments(w.getBegin(), w.getEnd(), p.getPos(), projectileCalculatedPosition, collisionPoint)) {
// calculate wall normals
var wallNx = w.getEnd().x - w.getBegin().x;
var wallNy = w.getEnd().y - w.getBegin().y;
normalVector1.set(-wallNy, wallNx);
normalVector2.set(wallNy, -wallNx);
// figure out which normal is the one we're looking for (=> closer to the projectile)
// len2() doesn't square the result, so it's more performant
endpointVector1.set(collisionPoint.x + normalVector1.x, collisionPoint.y + normalVector1.y);
endpointVector2.set(collisionPoint.x + normalVector2.x, collisionPoint.y + normalVector2.y);
var length1 = endpointVector1.sub(p.getPos()).len2();
var length2 = endpointVector2.sub(p.getPos()).len2();
var normalVector = length1 < length2 ? normalVector1 : normalVector2;
shapeRenderer.setColor(Color.PINK);
shapeRenderer.line(collisionPoint.x, collisionPoint.y, normalVector.x + collisionPoint.x, normalVector.y + collisionPoint.y);
// do magic https://stackoverflow.com/a/573206/8482314
// kept variables instead of moving them together so it's easier recognizable with the SO answer
var v = p.getDir();
var n = normalVector.nor();
var u = n.scl(v.dot(n));
var ww = v.sub(u);
var v2 = ww.sub(u);
p.setDir(v2);
return true;
}
shapeRenderer.setColor(Color.YELLOW);
shapeRenderer.circle(collisionPoint.x, collisionPoint.y, 5);
return false;
}
@Override
public void resize(int width, int height) {
screenViewport.update(width, height, true);
windowRectangle.set(0, 0, width,height);
}
}
package dev.lyze.reflection;
import com.badlogic.gdx.math.Vector2;
public class Wall {
private Vector2 begin, end;
public Wall(Vector2 begin, Vector2 end) {
this.begin = begin;
this.end = end;
}
public Vector2 getBegin() {
return begin;
}
public void setBegin(Vector2 begin) {
this.begin = begin;
}
public Vector2 getEnd() {
return end;
}
public void setEnd(Vector2 end) {
this.end = end;
}
@Override
public String toString() {
return "Wall{" +
"begin=" + begin +
", end=" + end +
'}';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment