Related
I am making an RPG game, however, I have an error at the moment. The player's character can move in all four cardinal directions, but the player becomes stuck if you move right, up or down.
Also, the error seems to have some logic to it:
if moving down, the character is stuck in a loop moving down
unless the up arrow is pressed, then the player will begin a new infinite loop for up
unless the right arrow is pressed, then the player will begin a new infinite loop for right
So right seems to take precedence over up which takes precedence over down.
Strangely, the leftward movement works perfectly. Even when the character is stuck in an infinite loop, the left arrow key will always cause the player to move left in the correct way.
I don't understand where the error is in my code. I don't know why the leftward movement works while the other three directions do not. I don't understand why there seems to be some sort of priority ordering between the other three directions as well.
I have looked through the program and I don't think I treat leftward movement differently to any other direction. However, I might be wrong, considering this error.
Due to the 30k word limit, I do not have enough space to include all the code here. Therefore, I have included a link to my github, so you can see the code there instead.
https://github.com/davey67/bludbourne
I think these classes are probably the most important classes though:
package com.bludbourne.game.desktop;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.bludbourne.game.BludBourne;
public class DesktopLauncher {
public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title="BludBourne";
config.useGL30=false;
config.width =480;
config.height=480;
Application app = new LwjglApplication(new BludBourne(),config);
Gdx.app=app;
Gdx.app.setLogLevel(Application.LOG_DEBUG);
}
}
/
package com.bludbourne.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.bludbourne.game.screens.MainGameScreen;
import com.badlogic.gdx.Game;
public class BludBourne extends Game {
public static final MainGameScreen _mainGameScreen = new MainGameScreen();
#Override
public void create () {
setScreen(_mainGameScreen);
}
#Override
public void dispose () {
_mainGameScreen.dispose();
}
}
/
package com.bludbourne.game;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.math.Vector3;
public class PlayerController implements InputProcessor
{
private final static String TAG = PlayerController.class.getSimpleName();
enum Keys{
LEFT,RIGHT,UP,DOWN,QUIT
}
enum Mouse{
SELECT,DOACTION
}
private static Map<Keys,Boolean> keys=new HashMap<PlayerController.Keys,Boolean>();
private static Map<Mouse,Boolean> mouseButtons = new HashMap<PlayerController.Mouse,Boolean>();
private Vector3 lastMouseCoordinates;
static {
keys.put(Keys.LEFT,false);
keys.put(Keys.RIGHT,false);
keys.put(Keys.UP,false);
keys.put(Keys.DOWN, false);
keys.put(Keys.QUIT, false);
}
static {
mouseButtons.put(Mouse.SELECT, false);
mouseButtons.put(Mouse.DOACTION, false);
}
private Entity _player;
public PlayerController(Entity player) {
this.lastMouseCoordinates=new Vector3();
this._player=player;
}
#Override
public boolean keyDown(int keycode)
{
if(keycode ==Input.Keys.LEFT||keycode==Input.Keys.A) {
this.leftPressed();
}
if(keycode ==Input.Keys.RIGHT||keycode==Input.Keys.D) {
this.rightPressed();
}
if(keycode ==Input.Keys.UP||keycode==Input.Keys.W) {
this.upPressed();
}
if(keycode ==Input.Keys.DOWN||keycode==Input.Keys.S) {
this.downPressed();
}
if(keycode==Input.Keys.Q) {
this.quitPressed();
}
return true;
}
#Override
public boolean keyUp(int keycode)
{
if(keycode ==Input.Keys.LEFT||keycode==Input.Keys.A) {
this.leftReleased();
}
if(keycode ==Input.Keys.RIGHT||keycode==Input.Keys.D) {
this.rightReleased();
}
if(keycode ==Input.Keys.UP||keycode==Input.Keys.W) {
this.upReleased();
}
if(keycode ==Input.Keys.DOWN||keycode==Input.Keys.S) {
this.downReleased();
}
if(keycode==Input.Keys.Q) {
this.quitReleased();
}
return true;
}
#Override
public boolean keyTyped(char character)
{
// TODO Auto-generated method stub
return false;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button)
{
if(button==Input.Buttons.LEFT||button==Input.Buttons.RIGHT) {
this.setClickedMouseCoordinates(screenX,screenY);
}
if(button==Input.Buttons.LEFT) {
this.selectMouseButtonPressed(screenX,screenY);
}
if(button==Input.Buttons.RIGHT) {
this.doActionMouseButtonPressed(screenX,screenY);
}
return true;
}
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button)
{
if(button==Input.Buttons.LEFT) {
this.selectMouseButtonReleased(screenX,screenY);
}
if(button==Input.Buttons.RIGHT) {
this.doActionMouseButtonReleased(screenX,screenY);
}
return true;
}
#Override
public boolean touchDragged(int screenX, int screenY, int pointer)
{
// TODO Auto-generated method stub
return false;
}
#Override
public boolean mouseMoved(int screenX, int screenY)
{
// TODO Auto-generated method stub
return false;
}
#Override
public boolean scrolled(int amount)
{
// TODO Auto-generated method stub
return false;
}
public void dispose() {
}
public void leftPressed() {
keys.put(Keys.LEFT,true);
}
public void rightPressed() {
keys.put(Keys.RIGHT,true);
}
public void upPressed() {
keys.put(Keys.UP,true);
}
public void downPressed() {
keys.put(Keys.DOWN,true);
}
public void quitPressed() {
keys.put(Keys.QUIT, true);
}
public void setClickedMouseCoordinates(int x,int y) {
lastMouseCoordinates.set(x,y,0);
}
public void selectMouseButtonPressed(int x,int y) {
mouseButtons.put(Mouse.SELECT,true);
}
public void doActionMouseButtonPressed(int x,int y) {
mouseButtons.put(Mouse.DOACTION, true);
}
public void leftReleased() {
keys.put(Keys.LEFT,false);
}
public void rightReleased() {
keys.put(Keys.RIGHT,true);
}
public void upReleased() {
keys.put(Keys.UP,true);
}
public void downReleased() {
keys.put(Keys.DOWN,true);
}
public void quitReleased() {
keys.put(Keys.QUIT, true);
}
public void selectMouseButtonReleased(int x,int y) {
mouseButtons.put(Mouse.SELECT, false);
}
public void doActionMouseButtonReleased(int x ,int y) {
mouseButtons.put(Mouse.DOACTION, false);
}
public void update(float delta) {
processInput(delta);
}
public static void hide() {
keys.put(Keys.LEFT, false);
keys.put(Keys.RIGHT, false);
keys.put(Keys.UP, false);
keys.put(Keys.DOWN, false);
keys.put(Keys.QUIT, false);
}
private void processInput(float delta) {
if(keys.get(Keys.LEFT)) {
_player.calculateNextPosition(Entity.Direction.LEFT,delta);
_player.setState(Entity.State.WALKING);
_player.setDirection(Entity.Direction.LEFT,delta);
}
else if(keys.get(Keys.RIGHT)) {
_player.calculateNextPosition(Entity.Direction.RIGHT,delta);
_player.setState(Entity.State.WALKING);
_player.setDirection(Entity.Direction.RIGHT,delta);
}
else if(keys.get(Keys.UP)) {
_player.calculateNextPosition(Entity.Direction.UP,delta);
_player.setState(Entity.State.WALKING);
_player.setDirection(Entity.Direction.UP,delta);
}
else if(keys.get(Keys.DOWN)) {
_player.calculateNextPosition(Entity.Direction.DOWN,delta);
_player.setState(Entity.State.WALKING);
_player.setDirection(Entity.Direction.DOWN,delta);
}
else if(keys.get(Keys.QUIT)) {
Gdx.app.exit();
}
else {
_player.setState(Entity.State.IDLE);
}
if(mouseButtons.get(Mouse.SELECT)) {
mouseButtons.put(Mouse.SELECT, false);
}
}
}
/
package com.bludbourne.game;
import java.util.UUID;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
public class Entity
{
private static final String TAG = Entity.class.getSimpleName();
private static final String _defaultSpritePath = "sprites/characters/Warrior.png";
private Vector2 _velocity;
private String _entityID;
private Direction _currentDirection = Direction.LEFT;
private Direction _previousDirection = Direction.UP;
private Animation _walkLeftAnimation;
private Animation _walkRightAnimation;
private Animation _walkUpAnimation;
private Animation _walkDownAnimation;
private Array<TextureRegion> _walkLeftFrames;
private Array<TextureRegion> _walkRightFrames;
private Array<TextureRegion> _walkUpFrames;
private Array<TextureRegion> _walkDownFrames;
protected Vector2 _nextPlayerPosition;
protected Vector2 _currentPlayerPosition;
protected State _state = State.IDLE;
protected float _frameTime = 0f;
protected Sprite _frameSprite = null;
protected TextureRegion _currentFrame = null;
public final int FRAME_WIDTH = 16;
public final int FRAME_HEIGHT = 16;
public static Rectangle boundingBox;
public enum State
{
IDLE, WALKING
}
public enum Direction
{
UP, RIGHT, DOWN, LEFT
}
public Entity() {
initEntity();
}
public void initEntity()
{
this._entityID = UUID.randomUUID().toString();
this._nextPlayerPosition = new Vector2();
this._currentPlayerPosition = new Vector2();
this.boundingBox = new Rectangle();
this._velocity = new Vector2(2f, 2f);
Utility.loadTextureAsset(_defaultSpritePath);
loadDefaultSprite();
loadAllAnimations();
}
public void update(float delta)
{
_frameTime = (_frameTime + delta) % 5;
setBoundingBoxSize(0f, 0.5f);
}
public void init(float startX, float startY)
{
this._currentPlayerPosition.x = startX;
this._currentPlayerPosition.y = startY;
this._nextPlayerPosition.x = startX;
this._nextPlayerPosition.y = startY;
}
public void setBoundingBoxSize(float percentageWidthReduced, float percentageHeightReduced)
{
float width;
float height;
float widthReductionAmount = 1.0f - percentageWidthReduced;
float heightReductionAmount = 1.0f - percentageHeightReduced;
if (widthReductionAmount > 0 && widthReductionAmount < 1)
{
width = FRAME_WIDTH * widthReductionAmount;
}
else
{
width = FRAME_WIDTH;
}
if (heightReductionAmount > 0 && heightReductionAmount < 1)
{
height = FRAME_HEIGHT * heightReductionAmount;
}
else
{
height = FRAME_HEIGHT;
}
if (width == 0 || height == 0)
{
Gdx.app.debug(TAG, "Width and Height are 0!! " + width + ":" + height);
}
float minX;
float minY;
if (MapManager.UNIT_SCALE > 0)
{
minX = _nextPlayerPosition.x / MapManager.UNIT_SCALE;
minY = _nextPlayerPosition.y / MapManager.UNIT_SCALE;
}
else
{
minX = _nextPlayerPosition.x;
minY = _nextPlayerPosition.y;
}
boundingBox.set(minX, minY, width, height);
}
private void loadDefaultSprite()
{
Texture texture = Utility.getTextureAsset(_defaultSpritePath);
TextureRegion[][] textureFrames = TextureRegion.split(texture, FRAME_WIDTH, FRAME_HEIGHT);
_frameSprite = new Sprite(textureFrames[0][0].getTexture(), 0, 0, FRAME_WIDTH, FRAME_HEIGHT);
_currentFrame = textureFrames[0][0];
}
public void loadAllAnimations()
{
Texture texture = Utility.getTextureAsset(_defaultSpritePath);
TextureRegion[][] textureFrames = TextureRegion.split(texture, FRAME_WIDTH, FRAME_HEIGHT);
_walkDownFrames = new Array<TextureRegion>(4);
_walkLeftFrames = new Array<TextureRegion>(4);
_walkRightFrames = new Array<TextureRegion>(4);
_walkUpFrames = new Array<TextureRegion>(4);
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
TextureRegion region=textureFrames[i][j];
if(region==null) {
Gdx.app.debug(TAG, "Got null animation frame "+i+","+j);
}
switch(i) {
case 0:
_walkDownFrames.insert(j,region);
break;
case 1:
_walkLeftFrames.insert(j,region);
break;
case 2:
_walkRightFrames.insert(j,region);
break;
case 3:
_walkUpFrames.insert(j,region);
break;
}
}
}
_walkDownAnimation = new Animation(0.25f,_walkDownFrames,Animation.PlayMode.LOOP);
_walkLeftAnimation = new Animation(0.25f,_walkLeftFrames,Animation.PlayMode.LOOP);
_walkRightAnimation = new Animation(0.25f,_walkRightFrames,Animation.PlayMode.LOOP);
_walkUpAnimation = new Animation(0.25f,_walkUpFrames,Animation.PlayMode.LOOP);
}
public void dispose() {
Utility.unloadAsset(_defaultSpritePath);
}
public void setState(State state) {
this._state = state;
}
public Sprite getFrameSprite() {
return _frameSprite;
}
public TextureRegion getFrame() {
return _currentFrame;
}
public Vector2 getCurrentPosition() {
return _currentPlayerPosition;
}
public void setCurrentPosition(float currentPositionX,float currentPositionY) {
_frameSprite.setX(currentPositionX);
_frameSprite.setY(currentPositionY);
this._currentPlayerPosition.x=currentPositionX;
this._currentPlayerPosition.y=currentPositionY;
}
public void setDirection(Direction direction,float deltaTime) {
this._previousDirection=this._currentDirection;
this._currentDirection=direction;
switch(_currentDirection) {
//not sure about this
case DOWN:
_currentFrame=(TextureRegion) _walkDownAnimation.getKeyFrame(_frameTime);
break;
case LEFT:
_currentFrame=(TextureRegion) _walkLeftAnimation.getKeyFrame(_frameTime);
break;
case RIGHT:
_currentFrame=(TextureRegion) _walkRightAnimation.getKeyFrame(_frameTime);
break;
case UP:
_currentFrame=(TextureRegion) _walkUpAnimation.getKeyFrame(_frameTime);
break;
default:
break;
}
}
public void setNextPositionToCurrent() {
setCurrentPosition(_nextPlayerPosition.x,_nextPlayerPosition.y);
}
public void calculateNextPosition(Direction currentDirection,float deltaTime) {
float testX=_currentPlayerPosition.x;
float testY=_currentPlayerPosition.y;
_velocity.scl(deltaTime);
switch(currentDirection) {
case LEFT:
testX-=_velocity.x;
break;
case RIGHT:
testX+=_velocity.x;
break;
case UP:
testY+=_velocity.y;
break;
case DOWN:
testY-=_velocity.y;
break;
default:
break;
}
_nextPlayerPosition.x=testX;
_nextPlayerPosition.y=testY;
_velocity.scl(1/deltaTime);
}
}
I feel that I am probably missing something obvious, however, I still cannot see the error. Help is much appreciated. Thank you.
My github also contains the sprite sheet and the three maps, all of which would be stored in the asset folder of my project.
May I draw your attention to these four methods in PlayerInput
public void leftReleased() {
keys.put(Keys.LEFT,false);
}
public void rightReleased() {
keys.put(Keys.RIGHT,true);
}
public void upReleased() {
keys.put(Keys.UP,true);
}
public void downReleased() {
keys.put(Keys.DOWN,true);
}
I believe it should be false, not true when the key is released. I've verified that this prevents the infinite loop.
The priority order of right, up, then down is due to the order in which the keys are checked in processInput.
Let me be brief. I am creating a n-body simulation for a school-project. I've encountered a problem: I get the error IndexOutOfBoundsException only sometimes when I run the simulation. There does not seem to be any correlation between when I get the error. I am assuming I am destroying the bodies (via the collision detection method) too quickly for it to register, and it tries to access an index that no longer exists. I thought
if(bodies.get(i)!=null&&bodies.get(n)!= null)
in the update method would fix it, but it did not. Could someone take a look at the code and tell me what might be causing the error? To recreate the error: Simply press 'o' in the simulation, or randomly spawn massive bodies with 'm' until it occurs. Please keep in mind that I am only in high school, and I have not got a lot of programming experience.
MAIN:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main extends JPanel implements Runnable, KeyListener{
public static int width = 1400;
public static int height = (width/16) * 9;
public String title = "Orbital Mechanics Simulator -";
public Dimension dim = new Dimension(width, height);
public boolean running;
Color bgColor = new Color(0x000000);
public static long timeScale = (long) Math.pow(10,7.5); //how many simulation-seconds one IRL-second represents.
public static long distanceScale = ((Physics.astUnit*10L) / width); //how many meters does one pixel represent
ArrayList<Body> bodies = new ArrayList<Body>();
long initFpsTime;
long secondFpsTime;
long nowTime;
long lastTime;
long timeElapsed;
float deltaTime;
int fps = 200;
public Main(){
this.setPreferredSize(dim);
this.setBackground(bgColor);
this.start();
}
public void start(){
running = true;
Thread program = new Thread(this, "update");
program.start();
}
public void run(){
lastTime = System.nanoTime();
while(running){
nowTime = System.nanoTime();
deltaTime = nowTime - lastTime;
update(deltaTime);
initFpsTime = System.currentTimeMillis();
if((initFpsTime - secondFpsTime) > Math.pow(10,3) / fps){
render();
secondFpsTime = System.currentTimeMillis();
}
}
}
public void update(float deltaTime){
for(int i=0; i<bodies.size();i++){
resetForces();
bodies.get(i).update((float)(deltaTime / Math.pow(10,9))*timeScale);
lastTime = System.nanoTime();
//sets the forces for all bodies
for(int n=0; n<bodies.size();n++){
if(bodies.get(i)!=bodies.get(n)){
if(bodies.get(i)!=null&&bodies.get(n)!= null)
bodies.get(i).setForce(Physics.getFx(bodies.get(i), bodies.get(n)), Physics.getFy(bodies.get(i), bodies.get(n)));
//collision detection
if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
collision(bodies.get(i),bodies.get(n));
}
}
}
}
}
//DIFFERENT SYSTEMS
public void solarSystem(){
Body sun;
Body earth;
Body mars;
sun = new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0);
earth = new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0);
mars = new Body("Mars", Physics.massEarth, 10, (long)(1.5*Physics.astUnit/distanceScale) ,0, new Color(0x00ff00), (float)0, (float)0);
earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));
bodies.add(sun);
bodies.add(earth);
bodies.add(mars);
}
public void twoBodies(){
bodies.add(new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0));
bodies.add(new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0));
bodies.add(new Body("Mars", Physics.massMars, 10, (long)(1.5*Physics.astUnit/(distanceScale)),0 ,new Color(0x00ff00), (float)0, (float)0));
//earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
//mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));
}
public void createRandomBody(){
bodies.add(new Body("randomBody",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));
}
public void createMassiveBody(){
bodies.add(new Body("Sun",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));
}
public void createSmallBody(){
bodies.add(new Body("Earth",Physics.massEarth,10, 0, 0, Physics.randomColor(), (float)0, (float)0));
}
public void createSystem(){
for(int i=0; i<20;i++){
for(int n=0; n<20;n++){
bodies.add(new Body("Random", Physics.massSun, 4, -width/2 + n*20 , height/2 - i*20 , Color.WHITE, (float)0, (float)0 ));
}
}
}
public void resetForces(){
if(bodies.get(0) != null);
for(int i=0;i<bodies.size();i++){
if(i<=0 && i < bodies.size()){
bodies.get(i).resetForce();
}
}
}
public void collision(Body a, Body b){
Body newBody = new Body("newBody", a.mass+b.mass, 20, (a.xPos+b.xPos)/2, (a.yPos+b.yPos)/2, Physics.randomColor(), (a.mass*a.vx + b.mass*b.vx)/(a.mass+b.mass), (a.mass*a.vy+b.mass*b.vy)/(a.mass+b.mass));
bodies.remove(a);
bodies.remove(b);
bodies.add(newBody);
}
public void render(){
repaint();
}
public void keyPressed(KeyEvent event) {
switch(event.getKeyChar()){
case 'r': createRandomBody();
break;
case 'f': twoBodies();
break;
case 'm':
createMassiveBody();
break;
case 'z':
createSmallBody();
break;
case 'o':
createSystem();
break;
case 's':
solarSystem();
break;
}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //quick vs quality - preferring quality
for(int i=0;i<bodies.size();i++){
bodies.get(i).displayPlanet(g2d);
}
}
public static void gui(){
Main main = new Main();
JFrame frame = new JFrame();
frame.setResizable(false);
frame.setTitle(main.title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(main);
frame.addKeyListener(main);
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable(){ //Event dispatching thread
public void run(){
gui();
}
});
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
BODY
import java.awt.Color;
import java.awt.Graphics;
public class Body {
public static int xOrigo = Main.width / 2;
public static int yOrigo = ((Main.width * 9) / 16) / 2;
public static int numOfBodies; // how many bodies the program contains.
public static float distanceScale = Main.distanceScale;
public String name;
public float mass, fx, fy, accX, accY, vx, vy, initVx, initVy, deltaX, deltaY;
public long xDisplay, yDisplay;
public long xPos, yPos;
public int radius;
public Color color;
public Body(String name, float mass, int radius, long xPos, long yPos, Color color, float initVx, float initVy){
this.name = name;
this.mass = mass;
this.radius = radius;
this.color = color;
numOfBodies++;
setX(xPos);
setY(yPos);
setVelocity(initVx, initVy);
}
public void update(float deltaTime){
this.deltaX += this.vx * deltaTime;
if(this.deltaX >= distanceScale){
this.incX();
deltaX = 0;
}else if(this.deltaX <= -distanceScale){
this.decX();
deltaX = 0;
}
this.deltaY += this.vy * deltaTime;
if(this.deltaY >= distanceScale){
this.incY();
deltaY = 0;
}else if(this.deltaY <= -distanceScale){
this.decY();
deltaY = 0;
}
this.vx += (this.accX * deltaTime);
this.vy += (this.accY * deltaTime);
}
public void setX(long xPos){
this.xPos = xPos;
this.xDisplay = xOrigo + xPos - radius;
}
public void setY(long yPos){
this.yPos = yPos;
this.yDisplay = yOrigo - yPos - radius;
}
public void incX(){
this.xPos++;
this.xDisplay = xOrigo + xPos - radius;
}
public void decX(){
this.xPos--;
this.xDisplay = xOrigo + xPos - radius;
}
public void incY(){
this.yPos++;
this.yDisplay = yOrigo - yPos - radius;
}
public void decY(){
this.yPos--;
this.yDisplay = yOrigo - yPos - radius;
}
public void setForce(float fx, float fy){
this.fx += fx;
this.accX = fx / this.mass;
this.fy += fy;
this.accY = fy / this.mass;
}
public void setVelocity(float vx, float vy){
this.vx += vx;
this.vy += vy;
}
public void displayPlanet(Graphics g){
g.setColor(this.color);
g.fillOval((int )this.xDisplay, (int)this.yDisplay, this.radius*2, this.radius*2); //temporary fix
}
public long getXPos(){
return this.xPos;
}
public long getYPos(){
return this.yPos;
}
public void resetForce(){
this.fx = 0;
this.fy = 0;
}
}
PHYSICS:
import java.awt.Color;
import java.util.Random;
public class Physics {
public static Random rand = new Random();
public static final double G = 6.67384*(Math.pow(10, -11));
public static long astUnit = 149597871000L; //L IS TO INDICATE IT'S A LONG VALUE, otherwise neg value
public static float massEarth = (float)(5.97219*Math.pow(10,24));
public static float massSun = (float)(1.9891*Math.pow(10,30));
public static float massMars = (float) (6.41693*(Math.pow(10,23)));
public static float randomMass(){
return (float) Math.pow(((rand.nextDouble()*(massSun-massEarth))),rand.nextDouble())+massEarth;
}
public static double randInitV(){
return (double) rand.nextDouble()*Math.pow(10,4);
}
public static int randomXPos(){
return rand.nextInt(Main.width)-Main.width/2;
}
public static int randomYPos(){
return rand.nextInt(Main.height)-Main.height/2;
}
public static Color randomColor(){
return new Color(rand.nextInt(0xffffff));
}
public static int randomRadius(){
return rand.nextInt(50)+5;
}
public Physics(){
}
/*
public static Vector getVectorBetween(Body a, Body b){
float force = (float)((G*a.mass*b.mass) / Math.pow(getDistanceBetween(a,b),2));
double angle = Math.atan2(Math.abs(a.y - b.y),Math.abs(a.x - b.x));
Vector vector = new Vector(force,angle);
return vector;
}
*/
public static double getInitVy(long d, Body a){
return Math.sqrt((G*a.mass) / d);
}
public static float getFx(Body a, Body b){
float force = getForceBetween(a,b);
double angle = getAngleBetween(a,b);
float fx = (float)(force*Math.cos(angle));
if(a.xPos > b.xPos){
return -fx;
}else{
return fx;
}
}
public static float getFy(Body a, Body b){
float force = getForceBetween(a,b);
double angle = getAngleBetween(a,b);
float fy = (float)(force*Math.sin(angle));
if(a.yPos > b.yPos){
return -fy;
}else{
return fy;
}
}
public static float getForceBetween(Body a, Body b){
float force = (float)((G*a.mass*b.mass) / Math.pow(getDistanceBetween(a,b),2));
return force;
}
public static double getDistanceBetween(Body a, Body b){
double xKatet = Math.abs(a.getXPos()*Main.distanceScale - b.getXPos()*Main.distanceScale);
double yKatet = Math.abs(a.getYPos()*Main.distanceScale - b.getYPos()*Main.distanceScale);
double distance = Math.hypot(xKatet, yKatet);
return distance;
}
public static double getAngleBetween(Body a, Body b){
long deltaX = Math.abs(a.xPos-b.xPos);
long deltaY = Math.abs(a.yPos-b.yPos);
double angle = Math.atan2(deltaY, deltaX);
return angle;
}
}
(Given the few information and the wall of code, I may be going too far out on a limb, but in doubt, I can delete the answer)
You are accessing the same list with different threads. The collision handling method
public void collision(Body a, Body b) {
...
bodies.remove(a);
bodies.remove(b);
bodies.add(newBody);
}
is executed by the main physics thread that is started in the Main#start() method. This thread is modifying the list. And this may happen while the Swing Event Dispatch Thread (that is also reponsible for painting) is iterating over the bodies in the paintComponent method:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
...
for(int i=0;i<bodies.size();i++){
bodies.get(i).displayPlanet(g2d);
}
}
Adding additional checks will not solve this issue. You'll need some form of synchronization. The brute-force-hammer would be to just synchronize on the bodies list:
public void collision(Body a, Body b) {
...
synchronized (bodies)
{
bodies.remove(a);
bodies.remove(b);
bodies.add(newBody);
}
}
and
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
...
synchronized (bodies)
{
for(int i=0;i<bodies.size();i++){
bodies.get(i).displayPlanet(g2d);
}
}
}
(basically, something like this would have to be done in all places where one thread might read the list while another thread is writing to the list).
But again: This is rather pragmatic. You could consider one of the thread-safe data structures from the java.util.concurrent package, or some manual, more fine-grained locking solution, probably with some ReadWriteLock.
It's far too much code to give a more focussed answer here (but at least, I wanted to point out what is (almost certainly) the reason for your problem)
You have a problem in your method Main.update(), the essentials of which are:
public void update(float deltaTime){
for(int i=0; i<bodies.size();i++){
// ...
//sets the forces for all bodies
for(int n=0; n<bodies.size();n++){
// ...
//collision detection
if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
collision(bodies.get(i),bodies.get(n));
}
}
}
}
In the event that i is bodies.size() - 1 and there is a collision with another body, the two colliding bodies are replaced with a single body, reducing the total number of bodies by 1. At that point i is bodies.size(), which is not a valid index into bodies. You nevertheless continue the inner loop, during which you perform bodies.get(i) again, generating an IndexOutOfBoundsException.
What the title says basically.
The jump worked perfectly until I decided to make my game more object-oriented by adding a GameObject class. The sprite should jump whenever you tap the screen (added a print statement in jump() to verify that this method DOES get called). Can anyone spot the mistake?
The way I've coded the jump:
player is constantly decelerating until he reaches the ground
if the screen is tapped, the players ySpeed is increased by accelerationJump, then decreased slowly by g while he's in the air
Player:
public class Player extends GameObject implements InputProcessor {
private float g;
private float accelerationJump = 9f;
private boolean allowJump = true;
public Player(Sprite spr) {
super(spr);
setxPos(0);
setyPos(getHeight() / 6);
setxSpeed(5.5f);
setySpeed(0);
spr.setX(getxPos());
spr.setY(getyPos());
g = 0.2f;
accelerationX = 0.02f;
}
public void update() {
setySpeed(-g);
//have the sprite constantly moving
moveBy(getxSpeed(), getySpeed());
//if sprite reaches end of screen, move it to the start of the screen again
if (getxPos() > getWidth()) {
setxPos(0);
}
if (getyPos() < getHeight() / 6) {
moveTo(getSprite().getX(), getHeight() / 6);
}
//the player can only jump if he is on the ground. once player is on the ground, he must stop decelerating
if (onGround()) {
allowJump = true;
setySpeed(0);
}
}
public void jump() {
System.out.println("dasdasddadsdasd");
setySpeed(getySpeed() + accelerationJump);
}
// checks whether or not player is on the ground
public boolean onGround() {
return (getSprite().getY() == getHeight() / 6);
}
#Override
public boolean keyDown(int keycode) {
return false;
}
#Override
public boolean keyUp(int keycode) {
return false;
}
#Override
public boolean keyTyped(char character) {
return false;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
return true;
}
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
//if (allowJump && isDragged == false) {
//System.out.println("dsdasdasd");
if(allowJump){
allowJump = false;
jump();
}
return false;
}
#Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
/*if (isDragged == false) {
isDragged = true;
}*/
return false;
}
#Override
public boolean mouseMoved(int screenX, int screenY) {
return false;
}
#Override
public boolean scrolled(int amount) {
return false;
}
}
GameObject
public abstract class GameObject {
private Sprite spr;
private float xSpeed;
private float ySpeed;
public GameObject(Sprite spr) {
this.spr=spr;
}
public void moveTo(float xPos2, float yPos2) {
setxPos(xPos2);
setyPos(yPos2);
}
public void moveBy(float dx, float dy) {
spr.setY(spr.getY() + dy);
spr.setX(spr.getX() + dx);
}
public float getxPos() {
return spr.getX();
}
public float getyPos() {
return spr.getY();
}
public void setxPos(float xPos2) {
spr.setX(xPos2);
}
public void setyPos(float yPos) {
spr.setY(yPos);
}
public float getxSpeed() {
return xSpeed;
}
public float getySpeed() {
return ySpeed;
}
public void setxSpeed(float xSpeed2) {
xSpeed = xSpeed2;
}
public void setySpeed(float ySpeed2) {
ySpeed = ySpeed2;
}
public int getWidth() {
return Gdx.graphics.getWidth();
}
public int getHeight() {
return Gdx.graphics.getHeight();
}
public void draw(SpriteBatch batch) {
spr.draw(batch);
}
public Sprite getSprite() {
return spr;
}
}
Any help is HIGHLY appreciated.
You are setting ySpeed to -g every update()
I am trying to make a 2d game in java with realistic physics. The game is supposed to be set in space from a top down view. The best way I can explain the view is to use this link: 3d graph found one gooogle images with search "3d graph xyz". The view is supposed to be set so that you are looking from the top of the +y towards the -y.
In the game I currently only have it showing one sun and one planet to test the gravity. However, when I run the game the planet only moves away from the sun in a towards the bottom right. I believe this to be due to me misusing the equation when moving it to java. For reference the equations I used were Newton's Law of Universal Gravitation (F=(G*M1*M2)/D^2) and Newton's Second Law of Motion (F=MA, but used as A=F/M).
In short, My question is what did I do wrong with my equations? I will post all my code below, but fair warning it is designed to be expanded upon so there is a lot of excess currently.
Thanks for any help in advance.
Core class:
package src.main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import src.main.input.Input;
import src.main.input.InputHandler;
import src.main.map.Map;
public class Core extends JPanel
{
private static InputHandler iHandler = new InputHandler();
private static Input input = new Input(iHandler);
private long ticks;
private Map map;
public static Dimension SIZE;
public static Random rand = new Random();
public Core(Dimension d)
{
setPreferredSize(d);
SIZE = d;
ticks = 0;
map = new Map(SIZE.width, SIZE.height);
start();
}
public synchronized void update()
{
ticks++;
iHandler.update(ticks);
/*if (iHandler.getWheelRotation() != 0)
{
int i = iHandler.getWheelRotation();
map.changeMagnification(i > 0);
if (i > 0)
i--;
else
i++;
iHandler.setWheelRotation(i);
if (i < 0 && map.getMagnification() == 0)
iHandler.setWheelRotation(0);
if (i > 0 && map.getMagnification() == Map.MAX_MAGNIFY)
iHandler.setWheelRotation(0);
}*/
map.update(ticks);
}
public synchronized void paintComponent(Graphics g2)
{
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, SIZE.width, SIZE.height);
map.draw(g);
}
public void start()
{
Thread thread = new Thread()
{
public void run()
{
while (true)
{
long time = System.currentTimeMillis();
update();
repaint();
time = (1000 / 128) - (System.currentTimeMillis() - time);
if (time > 0)
{
try
{
Thread.sleep(time);
} catch (Exception ex)
{
ex.printStackTrace();
}
}
}
}
};
thread.start();
}
public static void main(String args[])
{
SwingUtilities.invokeLater(new Runnable()
{
Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0].getDefaultConfiguration().getBounds();
public void run()
{
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new Core(new Dimension(r.width, r.height)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.addKeyListener(input);
frame.addMouseListener(input);
frame.addMouseMotionListener(input);
frame.addMouseWheelListener(input);
}
});
}
}
Map Class:
package src.main.map;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import src.main.Core;
import src.main.celestials.SolarSystem;
public class Map
{
private int magnification;
public static final int MAX_MAGNIFY = 15;
private ArrayList<SolarSystem> systems = new ArrayList<SolarSystem>();
public Map(int x, int y)
{
SolarSystem s=new SolarSystem(new Point(500,500), "sun", (int) (1.9891 * Math.pow(10, 30)));
s.addPlanet(new Point(550, 550), "planet");
systems.add(s);
}
public void update(long ticks)
{
for(int i = 0; i < systems.size(); i++)
systems.get(i).update(ticks);
}
public void draw(Graphics2D g)
{
g.setColor(Color.DARK_GRAY);
magnification= 1;
for(int i=0; i<systems.size(); i++)
{
systems.get(i).draw(g, magnification);
}
}
public int getMagnification()
{
return magnification;
}
public void changeMagnification(boolean bigger)
{
if (bigger)
magnification++;
else
magnification--;
if (magnification > MAX_MAGNIFY)
magnification = MAX_MAGNIFY;
if (magnification < 1)
magnification = 1;
}
}
Body Class:
package src.main.celestials;
import java.awt.Point;
import src.main.Core;
import src.main.map.Map;
public abstract class Body
{
protected String name;
protected double x, y, mass;
public Body(Point t, String s, int m)
{
x = t.x;
y = t.y;
name = s;
mass = m;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getMass() {
return mass;
}
public void setMass(double mass) {
this.mass = mass;
}
public abstract void update(long ticks);
}
SolarSystem Class:
package src.main.celestials;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import src.main.celestials.Planet;
public class SolarSystem extends Body {
private ArrayList<Planet> planets = new ArrayList<Planet>();
private BufferedImage[] images = new BufferedImage[7];
private BufferedImage sun;
public SolarSystem(Point t, String s, int m) {
super(t, s, m);
try
{
String loc = System.getProperty("user.home") + "\\Desktop\\Proof of Concept Game\\Proof of Concept Game\\Gravity Test\\Suns\\";
images[0] = ImageIO.read(new File(loc + "Sun_Blue.png"));
images[1] = ImageIO.read(new File(loc + "Sun_BlueWhite.png"));
images[2] = ImageIO.read(new File(loc + "Sun_Orange.png"));
images[3] = ImageIO.read(new File(loc + "Sun_Red.png"));
images[4] = ImageIO.read(new File(loc + "Sun_White.png"));
images[5] = ImageIO.read(new File(loc + "Sun_Yellow.png"));
images[6] = ImageIO.read(new File(loc + "Sun_YellowWhite.png"));
} catch (Exception e)
{
e.printStackTrace();
}
sun = images[(int) (Math.random() * images.length)];
}
//earth mass:5.97219 × 10^24KG
//sun mass: 1.9891 × 10^30KG
//sun/earth ratio: 333060.4016
#Override
public void update(long ticks) {
// run interactions between planets and solar systems
for(int i=0; i<planets.size(); i++)
{
planets.get(i).setxAccel(genAccelX(i));
planets.get(i).setyAccel(genAccelY(i));
}
// update using acceleration and velocity
for(int i=0; i<planets.size(); i++)
{
planets.get(i).update(ticks);
}
}
public void addPlanet(Point point, String name)
{
planets.add(new Planet(point, name, (int) (5.97219*Math.pow(10, 24))));
}
/*public double genAccel(int i)
{
double G=6.67*Math.pow(10, -11);
double Dx=Math.pow(planets.get(i).getX()-getX(), 2);
double Dy=Math.pow(planets.get(i).getY()-getY(), 2);
double D=Math.sqrt(Dx+Dy);
return G*planets.get(i).getMass()/Math.pow(D, 2);
}
public double genAccelX(int i)
{
double Dx=Math.pow(planets.get(i).getX()-getX(), 2);
double Dy=Math.pow(planets.get(i).getY()-getY(), 2);
double D=Math.sqrt(Dx+Dy);
double A=genAccel(i);
return Dx*A/D;
}
public double genAccelY(int i)
{
double Dx=Math.pow(planets.get(i).getX()-getX(), 2);
double Dy=Math.pow(planets.get(i).getY()-getY(), 2);
double D=Math.sqrt(Dx+Dy);
double A=genAccel(i);
return Dy*A/D;
}*/
public double genAccelX(int i)
{
double x1=0;
double x2=0;
/*if(getX()>planets.get(i).getX())
{*/
x1=getX();
x2=planets.get(i).getX();
/*}
else
{
x1=planets.get(i).getX();
x2=getX();
}*/
double G=6.67e-11;
double d=x1-x2;
d*=d;
planets.get(i).setxForce((G*getMass()*planets.get(i).getMass())/d);
return planets.get(i).getxForce()/planets.get(i).getMass();
}
public double genAccelY(int i)
{
double y1=0;
double y2=0;
/*if(getY()<planets.get(i).getY())
{*/
y1=getY();
y2=planets.get(i).getY();
/*}
else
{
y1=planets.get(i).getY();
y2=getY();
}*/
double G=6.67e-11;
double d=y1-y2;
d*=d;
planets.get(i).setyForce((G*getMass()*planets.get(i).getMass())/d);
return planets.get(i).getyForce()/planets.get(i).getMass();
}
public void draw(Graphics2D g, int magnification) {
g.drawImage(sun, (int)(x) * magnification + 2, (int)(y) * magnification + 2, 50, 50, null);
for(int i=0; i<planets.size(); i++)
{
g.setColor(Color.PINK);
g.fillOval((int)planets.get(i).getX(), (int)planets.get(i).getY(), 20, 20);
}
}
}
Planet Class:
package src.main.celestials;
import java.awt.Point;
public class Planet extends Body {
double xForce, yForce, xAccel, yAccel, xVel, yVel;
public Planet(Point t, String s, int m) {
super(t, s, m);
}
public double getxForce() {
return xForce;
}
public void setxForce(double xForce) {
this.xForce = xForce;
}
public double getyForce() {
return yForce;
}
public void setyForce(double yForce) {
this.yForce = yForce;
}
public double getxAccel() {
return xAccel;
}
public void setxAccel(double xAccel) {
this.xAccel = xAccel;
}
public double getyAccel() {
return yAccel;
}
public void setyAccel(double yAccel) {
this.yAccel = yAccel;
}
public double getxVel() {
return xVel;
}
public void setxVel(double xVel) {
this.xVel = xVel;
}
public double getyVel() {
return yVel;
}
public void setyVel(double yVel) {
this.yVel = yVel;
}
#Override
public void update(long ticks) {
xVel+=xAccel;
yVel+=yAccel;
x+=xVel;
y+=yVel;
System.out.println("X: "+(int)x+"\t\t"+"Y: "+(int)y);
System.out.println("XVel: "+xVel+"\t\t"+"YVel: "+yVel);
System.out.println("XAccel: "+xAccel+"\t"+"YAccel: "+yAccel);
}
}
First of all, concerning your comment
I thought I was in the java forum?
(also #PM77-1 :) StackOverflow is not a forum at all! Also see https://meta.stackexchange.com/a/92110 . It is a "Question And Answer" site, and this imposes some constraints on how you should ask, and what to expect as a response. (Actually, the fact that I'm writing this here is already a "violation" of these rules, and I'm risking downvotes for that). The question in its current form is hardly suitable for any site of the StackExchange network. If you write a question here, then you should ...
Include a clear and focussed question (namely, one that goes beyond "What is wrong with my code?")
Provide Minimal, Complete, and Verifiable example (preferably, if possible and appropriate, as a single code block, with no dependencies to other, unrelated classes, and no dependencies to external resources etc.)
That said, regarding your actual "question": The formula that you are using is correct. But it is not properly implemented. The formula is
(according to Wikipedia)
The "r" refers to the distance between the centers of mass of the objects.
In your code, you tried to implement this in the SolarSystem#genAccelX and SolarSystem#genAccelY methods:
...
double x1=0;
double x2=0;
x1=getX();
x2=planets.get(i).getX();
double G=6.67e-11;
double d=x1-x2;
d*=d;
planets.get(i).setxForce((G*getMass()*planets.get(i).getMass())/d);
return planets.get(i).getxForce()/planets.get(i).getMass();
But the problem is that you are computing this separately for x and for y. Thus, the resulting force is losing its actual direction (and is "wrong" anyhow).
To put it simply: The d that you are computing there is negative, because the planet should move to the left. But by taking d*=d, it becomes positive (and this, the planet is moving to the right)
The solution:
You should describe all positions, velocities and forces as vectors instead of computing everything separately for x and y. Basically, it will boil down to a class like
class Vector {
private double x, y;
// Setters, getters...
....
double distanceSquared(Vector other) {
double dx = x - other.x;
double dy = y - other.y;
return Math.sqrt(dx*dx+dy*dy);
}
double distance(Vector other) {
return Math.sqrt(distanceSquared(other));
}
// Some other useful methods:
double length() { ... }
void normalize(double factor) { ... }
void scale(double factor) { ... }
void add(Vector other) { ... }
void addScaled(double factor, Vector other) { ... }
void sub(Vector other) { ... }
Vector difference(Vector other) { ... }
}
Given such a class, the method for computing the force and acceleration could roughly look like this:
// The bodies here may be the sun and a planet:
public double computeForceStrength(Body body0, Body body1)
{
double G=6.67e-11;
Vector position0 = body0.getPosition();
Vector position1 = body1.getPosition();
double distanceSquared = position0.distanceSquared(position1);
// The formula from wikipedia:
double f = G * body0.getMass() * body1.getMass() / distanceSquared;
return f;
}
void performSomeTimeStep(Body body0, Body body1)
{
Vector direction = position1.difference(position0);
direction.normalize();
double f = computeForceStrength(body0, body1);
body0.getVelocity().addScaled(direction, f / body0.getMass());
body1.getVelocity().addScaled(direction, -f / body1.getMass());
}
(just to show the basic idea - again: There are many degrees of freedom)
There are several other (stylistic) issues with the code, but ... when it is working, you may want to submit it to https://codereview.stackexchange.com/ ....
I'm very new to game design (this is my first attempt) and this project will be used to create an android game.
I'm trying to make a simple game (as simple as possible).
What I need:
A background
a ship (that can move left an right at the bottom of the screen)
Enemies (Bombs dropping down from the sky)
projectiles (to shoot bombs with, shoot straight up)
Score (in the upper corner)
I have studied this tutorial:
http://www.kilobolt.com/game-development-tutorial.html
and changed code to get this:
http://i297.photobucket.com/albums/mm231/mabee84/Battleship.png
the black rectangles are projectiles.
Now I need to create the bombs but I can't figure out how to implement them.
they need to spawn at fixed y-value and a random x-value (within the screen)
Upon shooting on the bombs they should die but if bombs hit the ship game is over.
Please help i'm a bit stuck.
package kiloboltgame;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;
import java.util.ArrayList;
public class StartingClass extends Applet implements Runnable, KeyListener {
private Ship ship;
public static Bomb b1, b2;
public static int score = 0;
private Font font = new Font(null, Font.BOLD, 30);
private Image image, Battleship, Background, Bomb;
private static Background bg1, bg2;
private URL base;
private Graphics second;
#Override
public void init() {
setSize(800, 480);
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(this);
Frame frame = (Frame) this.getParent().getParent();
frame.setTitle("BattleShip");
try{
base = getDocumentBase();
}catch (Exception e){
//TODO: handle exception
}
//Image Setups
Battleship = getImage(base, "data/Battleship.png");
Background = getImage(base, "data/Background.png");
Bomb = getImage(base, "data/Bomb1.png");
}
#Override
public void start() {
bg1 = new Background(0, 0);
bg2 = new Background(800, 0);
ship = new Ship();
b1 = new Bomb(340, 100);
b2 = new Bomb(700, 100);
Thread thread = new Thread(this);
thread.start();
}
#Override
public void stop() {
// TODO Auto-generated method stub
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void run() {
while (true) {
ship.update();
ArrayList projectiles = ship.getProjectiles();
for(int i = 0; i < projectiles.size(); i++){
Projectile p = (Projectile) projectiles.get(i);
if(p.isVisible() == true){
p.update();
}else{
projectiles.remove(i);
}
}
b1.update();
b2.update();
bg1.update();
bg2.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
public void update(Graphics g) {
if(image == null){
image = createImage(this.getWidth(), this.getHeight());
second = image.getGraphics();
}
second.setColor(getBackground());
second.fillRect(0, 0, getWidth(), getHeight());
second.setColor(getForeground());
paint(second);
g.drawImage(image, 0, 0, this);
}
#Override
public void paint(Graphics g) {
g.drawImage(Background, bg1.getBgX(), bg1.getBgY(), this);
ArrayList projectiles = ship.getProjectiles();
for(int i = 0; i < projectiles.size(); i++){
Projectile p = (Projectile) projectiles.get(i);
g.setColor(Color.BLACK);
g.fillRect(p.getX(), p.getY(), 5, 10);
}
g.drawImage(Battleship, ship.getCenterX() + 230, ship.getCenterY() -23, this);
g.drawImage(Bomb, b1.getCenterX() - 20, b1.getCenterY() - 20, this);
g.drawImage(Bomb, b2.getCenterX() - 20, b2.getCenterY() - 20, this);
g.setFont(font);
g.setColor(Color.BLACK);
g.drawString(Integer.toString(score), 710, 30);
}
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_LEFT:
ship.moveLeft();
break;
case KeyEvent.VK_RIGHT:
ship.moveRight();
break;
case KeyEvent.VK_CONTROL:
ship.shoot();
score = score +100;
break;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
ship.stop();
break;
case KeyEvent.VK_RIGHT:
ship.stop();
break;
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
public static Background getBg1() {
return bg1;
}
}
package kiloboltgame;
import java.util.ArrayList;
public class Ship {
//In Java, Class Variables should be private so that only its methods can change them.
private int centerX = 100;
private int centerY = 382;
private int speedX = 0;
private int speedY = 1;
private ArrayList<Projectile> projectiles = new ArrayList<Projectile>();
public void update() {
// Moves Character or Scrolls Background accordingly.
if (speedX < 0) {
centerX += speedX;
} else if (speedX == 0) {
System.out.println("Do not scroll the background.");
} else {
if (centerX <= 440) {
centerX += speedX;
} else {
System.out.println("Scroll Background Here");
}
}
// Updates Y Position
if (centerY + speedY >= 382) {
centerY = 382;
}else{
centerY += speedY;
}
// Prevents going beyond X coordinate of 0
if (centerX + speedX <= -230) {
centerX = -229;
}
}
public void moveRight() {
speedX = 6;
}
public void moveLeft() {
speedX = -6;
}
public void shoot(){
Projectile p = new Projectile(centerX + 285, centerY -10);
projectiles.add(p);
}
public ArrayList getProjectiles(){
return projectiles;
}
public void stop() {
speedX = 0;
}
public int getCenterX() {
return centerX;
}
public int getCenterY() {
return centerY;
}
public int getSpeedX() {
return speedX;
}
public int getSpeedY() {
return speedY;
}
public void setCenterX(int centerX) {
this.centerX = centerX;
}
public void setCenterY(int centerY) {
this.centerY = centerY;
}
public void setSpeedX(int speedX) {
this.speedX = speedX;
}
public void setSpeedY(int speedY) {
this.speedY = speedY;
}
}
package kiloboltgame;
public class Background {
private int bgX, bgY, speedX;
public Background(int x, int y){
bgX = x;
bgY = y;
speedX = 0;
}
public void update() {
bgX += speedX;
if (bgX <= -800){
bgX += 1600;
}
}
public int getBgX() {
return bgX;
}
public int getBgY() {
return bgY;
}
public int getSpeedX() {
return speedX;
}
public void setBgX(int bgX) {
this.bgX = bgX;
}
public void setBgY(int bgY) {
this.bgY = bgY;
}
public void setSpeedX(int speedX) {
this.speedX = speedX;
}
}
public class Projectile {
private int x, y, speedY;
private boolean visible;
public Projectile(int startX, int startY) {
x = startX;
y = startY;
speedY = -7;
visible = true;
}
public void update() {
y += speedY;
if(y > 480){
visible = false;
}
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getSpeedY() {
return speedY;
}
public boolean isVisible() {
return visible;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setSpeedY(int speedY) {
this.speedY = speedY;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
}
package kiloboltgame;
public class Enemy {
private int maxHealth, currentHealth, power, speedX, centerX, centerY;
private Background bg = StartingClass.getBg1();
//Behavioral Methods
public void update(){
centerX += speedX;
speedX = bg.getSpeedX();
}
public void die(){
}
public void attack(){
}
public int getMaxHealth() {
return maxHealth;
}
public int getCurrentHealth() {
return currentHealth;
}
public int getPower() {
return power;
}
public int getSpeedX() {
return speedX;
}
public int getCenterX() {
return centerX;
}
public int getCenterY() {
return centerY;
}
public Background getBg() {
return bg;
}
public void setMaxHealth(int maxHealth) {
this.maxHealth = maxHealth;
}
public void setCurrentHealth(int currentHealth) {
this.currentHealth = currentHealth;
}
public void setPower(int power) {
this.power = power;
}
public void setSpeedX(int speedX) {
this.speedX = speedX;
}
public void setCenterX(int centerX) {
this.centerX = centerX;
}
public void setCenterY(int centerY) {
this.centerY = centerY;
}
public void setBg(Background bg) {
this.bg = bg;
}
}
package kiloboltgame;
public class Bomb extends Enemy {
public Bomb(int centerX, int centerY) {
setCenterX(centerX);
setCenterY(centerY);
}
}
This is all code that i have so far (I know the background is f*ed since the game this is based on is scrolling right and i haven't fixed it yet.
I recommend putting all object creation in a seperate part of the program. I'd make a BombFactory with a makeBomb mathod that returns a new Bomb instance. Inside the factory, figure out the x-coordinate, for instance using a randomiser. As parameters, you could specify a y-coordinate and possibly an upper and lower bound for the x. This way you can make new Bombs on the fly.
public class BombFactory {
private final Random rand;
public BombFactory() {
this.rand = new Random();
}
public Bomb makeBomb(int lowerboundX, int rangeX, int yPos) {
final int xPos = lowerboundX + rand.nextInt(rangeX);
return new Bomb(xPos, yPos);
}
}
As for the behaviour, I'd look into inheritance and interfaces some more. I see a lot of methods occurring more than once. You generally want to avoid that kind of duplication. You can start by taking all the methods having something to do with coords or movement and putting them in an abstract base class.
You can make a method inside Enemy that checks for a collision and responds to that in different ways, depending on how the subclass overrides it. In case of a Bomb, it would probably always kill itself and whatever it came in contact with.