The context behind the this question is that I am part of a small group working to create a small game on Android. The game we have chosen to develop is a top-down 2D shooter where the player controls a spaceship that must defend itself from alien creatures that move towards the player from the top of the gamescreen, similar to space invader, but different in that they spawn in random patterns.
So far, we have been able to get the game running, have the player and enemies to spawn and move in the right direction, as well as the player's weapon which is labelled as "projectile" (although it currently just spawns on its own and not on the player command). The issue we have though is that we are trying to set up a collision event in the code between the projectile and the enemies so that when they meet, the enemy and bullet both disappear.
However, what we have currently written in the code does not seem to make this work, so I was wondering if perhaps anyone else could see where we are going wrong. I've listed the relevant classes of code below. We would greatly appreciate any help anyone can give.
Collision Detector class( Holds the behavoir for collision detection for all entities in the game).
package uk.ac.qub.eeecs.gage.util;
import uk.ac.qub.eeecs.gage.world.GameObject;
/**
* Collision Detector Helper Library
*
* #version 1.0
*/
public class CollisionDetector {
/**
* Type of collision
*/
public enum CollisionType {
None, Top, Bottom, Left, Right, destroy //for the platform game, not yours
};
/**
* Determine if the two specified bounding boxes are in collision
*
* #param one
* First bounding box
* #param two
* Second bounding box
* #return boolean true if the boxes overlap, false otherwise
*/
//is relevant to your game
public static boolean isCollision(BoundingBox one, BoundingBox two) {
return (one.x - one.halfWidth < two.x + two.halfWidth
&& one.x + one.halfWidth > two.x - two.halfWidth
&& one.y - one.halfHeight < two.y + two.halfHeight && one.y
+ one.halfHeight > two.y - two.halfHeight);
//Do we add the code to set the enemies to disappear/destroy here or in the
//individual classes such as the player?
}
/**
* Determine the type of collision between the two bounding boxes.
* CollisionType.None is returned if there are no collisions.
*
* #param one
* First bounding box
* #param two
* Second bounding box
* #return Collision type
*/
AISpaceship.java class (class used for emeny alien entities and their properties).
package uk.ac.qub.eeecs.game.spaceDemo;
import uk.ac.qub.eeecs.gage.ai.SteeringBehaviours;
import uk.ac.qub.eeecs.gage.engine.ElapsedTime;
import uk.ac.qub.eeecs.gage.util.CollisionDetector;
import uk.ac.qub.eeecs.gage.util.Vector2;
import uk.ac.qub.eeecs.gage.util.CollisionDetector.CollisionType;
import uk.ac.qub.eeecs.gage.world.GameObject;
import uk.ac.qub.eeecs.gage.world.Sprite;
/**
* AI controlled spaceship
*
* #version 1.0
*/
public class AISpaceship extends Sprite {
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
/**
* AI control behaviour
*/
/**
* Acceleration with which the spaceship can move along
* the x-axis
*/
private float RUN_ACCELERATION = 150.0f;
public enum ShipBehaviour {
Turret, Seeker
}
private boolean visible;
private ShipBehaviour mShipBehaviour;
/**
* Distance at which the spaceship should avoid other game objects
*/
private float separateThresholdShip = 75.0f;
private float separateThresholdAsteroid = 125.0f;
/**
* Accumulators used to build up the net steering outcome
*/
private Vector2 accAccumulator = new Vector2();
private Vector2 accComponent = new Vector2();
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a AI controlled spaceship
*
* #param startX
* x location of the AI spaceship
* #param startY
* y location of the AI spaceship
* #param shipBehaviour
* Steering behaviour to be used by the AI ship
* #param gameScreen
* Gamescreen to which AI belongs
*/
public AISpaceship(float startX, float startY, ShipBehaviour shipBehaviour,
SteeringDemoGameScreen gameScreen) {
super(startX, startY, 50.0f, 50.0f, null, gameScreen);
mShipBehaviour = shipBehaviour;
visible = true;
switch (mShipBehaviour) {
case Turret:
maxAcceleration = 0.0f;
maxVelocity = 0.0f;
mBitmap = gameScreen.getGame().getAssetManager().getBitmap("Turret");
break;
case Seeker:
maxAcceleration = -40.0f;
maxVelocity = 50.0f;
mBitmap = gameScreen.getGame().getAssetManager().getBitmap("enemy sprites 7");
break;
}
}
// /////////////////////////////////////////////////////////////////////////
// Methods
// /////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* #see
* uk.ac.qub.eeecs.gage.world.Sprite#update(uk.ac.qub.eeecs.gage.engine.
* ElapsedTime)
*/
#Override
public void update(ElapsedTime elapsedTime) {
switch (mShipBehaviour) {
case Seeker:
//Move down towards the player in a straight line
acceleration.y = RUN_ACCELERATION;
// Seek towards the player
// Try to avoid a collision with the playership
// Try to avoid a collision with the other spaceships
// Try to avoid a collision with the asteroids
SteeringBehaviours.separate(this,
((SteeringDemoGameScreen) mGameScreen).getAsteroids(),
separateThresholdAsteroid, 1.0f, accComponent);
accAccumulator.add(accComponent);
// If we are trying to avoid a collision then combine
// it with the seek behaviour, placing more emphasis on
// avoiding the collision.
if (!accAccumulator.isZero()) {
acceleration.x = 0.3f * acceleration.x + 0.7f
* accAccumulator.x;
acceleration.y = 0.3f * acceleration.y + 0.7f
* accAccumulator.y;
}
// Make sure we point in the direction of travel.
break;
}
// Call the sprite's superclass to apply the determine accelerations
super.update(elapsedTime);
// Check that our new position has not collided by one of the
// defined projectiles. If so, then removing any overlap and
// ensure a valid velocity.
checkForAndResolveCollisions(projectiles);
}
/**
* Check for and then resolve any collision between the AiShip and the
* player projectiles.
*
* #param projectiles
* Array of projectiles to test for collision against
*/
private void checkForAndResolveCollisions(GameObject[] projectiles) {
CollisionType collisionType;
// Consider each platform for a collision
for (GameObject projectile : projectiles) {
collisionType =
CollisionDetector.determineAndResolveCollision(this, Projectile);
switch (collisionType) {
case destroy:
visible = false;
break;
}
}
}
}
Projectile1 (The class used to determine all the game's projectiles and their behavoir).
package uk.ac.qub.eeecs.game.spaceDemo;
import uk.ac.qub.eeecs.gage.ai.SteeringBehaviours;
import uk.ac.qub.eeecs.gage.engine.ElapsedTime;
import uk.ac.qub.eeecs.gage.util.Vector2;
import uk.ac.qub.eeecs.gage.world.Sprite;
/**
* AI controlled spaceship
*
* #version 1.0
*/
public class Projectile1 extends Sprite {
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
/**
* AI control behaviour
*/
/**
* Acceleration with which the projectile can move along
* the x-axis
*/
private float RUN_ACCELERATION = 150.0f;
public enum ShipBehaviour {
bullet
}
private ShipBehaviour mShipBehaviour;
/**
* Distance at which the spaceship should avoid other game objects
*/
/**
* Accumulators used to build up the net steering outcome
*/
private Vector2 accAccumulator = new Vector2();
private Vector2 accComponent = new Vector2();
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a AI controlled spaceship
*
* #param startX
* x location of the AI spaceship
* #param startY
* y location of the AI spaceship
* #param shipBehaviour
* Steering behaviour to be used by the AI ship
* #param gameScreen
* Gamescreen to which AI belongs
*/
public Projectile1(float startX, float startY, ShipBehaviour shipBehaviour,
SteeringDemoGameScreen gameScreen) {
super(startX, startY, 50.0f, 50.0f, null, gameScreen);
mShipBehaviour = shipBehaviour;
switch (mShipBehaviour) {
case bullet:
maxAcceleration = 20.0f;
maxVelocity = 30.0f;
mBitmap = gameScreen.getGame().getAssetManager().getBitmap("Spaceship2");
break;
}
}
// /////////////////////////////////////////////////////////////////////////
// Methods
// /////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* #see
* uk.ac.qub.eeecs.gage.world.Sprite#update(uk.ac.qub.eeecs.gage.engine.
* ElapsedTime)
*/
#Override
public void update(ElapsedTime elapsedTime) {
switch (mShipBehaviour) {
case bullet:
// Seek towards the player
acceleration.y = RUN_ACCELERATION;
break;
}
// Call the sprite's superclass to apply the determine accelerations
super.update(elapsedTime);
}
}
SterringGameDemo (The main class for the level on which the game play occurs and where the player, enemies and projectiles behave and interact with each other (note-it is called "SteeringGameDemo" because this is a leftover from a template we used to help make the class and has not been renamed yet)).
package uk.ac.qub.eeecs.game.spaceDemo;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import uk.ac.qub.eeecs.gage.Game;
import uk.ac.qub.eeecs.gage.engine.AssetStore;
import uk.ac.qub.eeecs.gage.engine.ElapsedTime;
import uk.ac.qub.eeecs.gage.engine.graphics.IGraphics2D;
import uk.ac.qub.eeecs.gage.util.BoundingBox;
import uk.ac.qub.eeecs.gage.util.CollisionDetector;
import uk.ac.qub.eeecs.gage.world.GameObject;
import uk.ac.qub.eeecs.gage.world.GameScreen;
import uk.ac.qub.eeecs.gage.world.LayerViewport;
import uk.ac.qub.eeecs.gage.world.ScreenViewport;
import android.graphics.Color;
/**
* Simple steering game world
*
* #version 1.0
*/
public class SteeringDemoGameScreen extends GameScreen {
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
/**
* Width and height of the level
*/
private final float LEVEL_WIDTH = 480.0f;
private final float LEVEL_HEIGHT = 280.0f;
/**
* Define viewports for this layer and the associated screen projection
*/
private ScreenViewport mScreenViewport;
private LayerViewport mLayerViewport;
/**
* Define a background object, alongside a player controlled
* space ship and separate lists of asteroids and AI controlled
* space ships.
*/
private GameObject mSpaceBackground;
private PlayerSpaceship mPlayerSpaceship;
private final int NUM_ASTEROIDS = 0;
private List<Asteroid> mAsteroids;
private final int NUM_SEEKERS = 2;
private final int NUM_TURRETS = 1;
private final int NUM_PROJECTILE = 1;
private List<Projectile1> mProjectile1;
private List<AISpaceship> mAISpaceships;
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a simple steering game world
*
* #param game
* Game to which this screen belongs
*/
public SteeringDemoGameScreen(Game game) {
super("SteeringDemoGameScreen", game);
// Create the screen viewport
mScreenViewport = new ScreenViewport(0, 0, game.getScreenWidth(),
game.getScreenHeight());
// Create the layer viewport, taking into account the orientation
// and aspect ratio of the screen.
if (mScreenViewport.width > mScreenViewport.height)
mLayerViewport = new LayerViewport(240.0f, 240.0f
* mScreenViewport.height / mScreenViewport.width, 240,
240.0f * mScreenViewport.height / mScreenViewport.width);
else
mLayerViewport = new LayerViewport(240.0f * mScreenViewport.height
/ mScreenViewport.width, 240.0f, 240.0f
* mScreenViewport.height / mScreenViewport.width, 240);
// Load in the assets used by the steering demo
AssetStore assetManager = mGame.getAssetManager();
assetManager.loadAndAddBitmap("Space BG 2", "img/Space BG 2.png");
assetManager.loadAndAddBitmap("Asteroid1", "img/Asteroid1.png");
assetManager.loadAndAddBitmap("Asteroid2", "img/Asteroid2.png");
assetManager.loadAndAddBitmap("enemy sprites 7", "img/enemy sprites 7.png");
assetManager.loadAndAddBitmap("Spaceship2", "img/Spaceship2.png");
assetManager.loadAndAddBitmap("Spaceship3", "img/Spaceship3.png");
assetManager.loadAndAddBitmap("Spaceship3", "img/Spaceship3.png");
assetManager.loadAndAddBitmap("Player sprite", "img/Player sprite.png");
assetManager.loadAndAddBitmap("Turret", "img/Turret.png");
// Create the space background
mSpaceBackground = new GameObject(LEVEL_WIDTH / 2.0f,
LEVEL_HEIGHT / 2.0f, LEVEL_WIDTH, LEVEL_HEIGHT, getGame()
.getAssetManager().getBitmap("Space BG 2"), this);
// Create the player spaceship
mPlayerSpaceship = new PlayerSpaceship(230, 10, this);
// Create a number of randomly positioned asteroids
Random random = new Random();
mAsteroids = new ArrayList<Asteroid>(NUM_ASTEROIDS);
for (int idx = 0; idx < NUM_ASTEROIDS; idx++)
mAsteroids.add(new Asteroid(random.nextFloat() * LEVEL_WIDTH, random.nextFloat() * LEVEL_HEIGHT, this));
// Create a number of randomly positioned AI controlled ships
mAISpaceships = new ArrayList<AISpaceship>(NUM_SEEKERS + NUM_TURRETS);
for (int idx = 0; idx < NUM_SEEKERS; idx++)
mAISpaceships.add(new AISpaceship(random.nextFloat() * LEVEL_WIDTH,
180 + random.nextFloat() * LEVEL_HEIGHT,
AISpaceship.ShipBehaviour.Seeker, this));
for (int idx = 0; idx < NUM_TURRETS; idx++)
mAISpaceships.add(new AISpaceship(random.nextFloat() * LEVEL_WIDTH,
random.nextFloat() * LEVEL_HEIGHT,
AISpaceship.ShipBehaviour.Turret, this));
//Use the above to help spawn the bullets
mProjectile1 = new ArrayList<Projectile1>(NUM_PROJECTILE);
for (int idx = 0; idx < NUM_PROJECTILE; idx++)
mProjectile1.add(new Projectile1(mPlayerSpaceship.position.x, mPlayerSpaceship.position.y,
Projectile1.ShipBehaviour.bullet, this));
}
// /////////////////////////////////////////////////////////////////////////
// Support methods
// /////////////////////////////////////////////////////////////////////////
/**
* Return the player spaceship
*
* #return Player spaceship
*/
public PlayerSpaceship getPlayerSpaceship() {
return mPlayerSpaceship;
}
public List<Projectile1> getProjectile1() {
return mProjectile1;
}
/**
* Return a list of the AI spaceships in the level
*
* #return List of AI controlled spaceships
*/
public List<AISpaceship> getAISpaceships() {
return mAISpaceships;
}
/**
* Return a list of asteroids in the the level
*
* #return List of asteroids in the level
*/
public List<Asteroid> getAsteroids() {
return mAsteroids;
}
// /////////////////////////////////////////////////////////////////////////
// Update and Draw methods
// /////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc) fs
*
* #see
* uk.ac.qub.eeecs.gage.world.GameScreen#update(uk.ac.qub.eeecs.gage.engine
* .ElapsedTime)
*/
#Override
public void update(ElapsedTime elapsedTime) {
// Update the player spaceship
mPlayerSpaceship.update(elapsedTime);
// Ensure the player cannot leave the confines of the world
BoundingBox playerBound = mPlayerSpaceship.getBound();
if (playerBound.getLeft() < 0)
mPlayerSpaceship.position.x -= playerBound.getLeft();
else if (playerBound.getRight() > LEVEL_WIDTH)
mPlayerSpaceship.position.x -= (playerBound.getRight() - LEVEL_WIDTH);
if (playerBound.getBottom() < 0)
mPlayerSpaceship.position.y -= playerBound.getBottom();
else if (playerBound.getTop() > LEVEL_HEIGHT)
mPlayerSpaceship.position.y -= (playerBound.getTop() - LEVEL_HEIGHT);
// Ensure the enemyships cannot leave the confines of the world
//Use the above for player and enemies bullets in this class
//IMPORTANT - THE below code is VITAL for the collision detection and was a
//added by the tutor. It calls the bounding box and collision detector for the
//player and enemy ship. Add this to the above section where the bounding box
// is already called, just below the update method.
BoundingBox playersBound = mPlayerSpaceship.getBound();
for (AISpaceship aiSpaceship : mAISpaceships) {
BoundingBox aiBound = aiSpaceship.getBound();
if(CollisionDetector.isCollision(playersBound, aiBound)) {
}
}
// Focus the layer viewport on the player
// Ensure the viewport cannot leave the confines of the world
if (mLayerViewport.getLeft() < 0)
mLayerViewport.x -= mLayerViewport.getLeft();
else if (mLayerViewport.getRight() > LEVEL_WIDTH)
mLayerViewport.x -= (mLayerViewport.getRight() - LEVEL_WIDTH);
if (mLayerViewport.getBottom() < 0)
mLayerViewport.y -= mLayerViewport.getBottom();
else if (mLayerViewport.getTop() > LEVEL_HEIGHT)
mLayerViewport.y -= (mLayerViewport.getTop() - LEVEL_HEIGHT);
// Update each of the AI controlled spaceships
for (AISpaceship aiSpaceship : mAISpaceships)
aiSpaceship.update(elapsedTime);
// Update each of the asteroids
for (Asteroid asteroid : mAsteroids)
asteroid.update(elapsedTime);
// Update each of the Player Projectile1
for (Projectile1 projectile1 : mProjectile1)
projectile1.update(elapsedTime);
}
/*
* (non-Javadoc)
*
* #see
* uk.ac.qub.eeecs.gage.world.GameScreen#draw(uk.ac.qub.eeecs.gage.engine
* .ElapsedTime, uk.ac.qub.eeecs.gage.engine.graphics.IGraphics2D)
*/
#Override
public void draw(ElapsedTime elapsedTime, IGraphics2D graphics2D) {
// Create the screen to black and define a clip based on the viewport
graphics2D.clear(Color.BLACK);
graphics2D.clipRect(mScreenViewport.toRect());
// Draw the background first of all
mSpaceBackground.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw each of the asteroids
for (Asteroid asteroid : mAsteroids)
asteroid.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw each of the AI controlled spaceships
for (Projectile1 projectile1 : mProjectile1)
projectile1.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw each of the AI controlled spaceships
for (AISpaceship aiSpaceship : mAISpaceships)
aiSpaceship.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
// Draw the player
mPlayerSpaceship.draw(elapsedTime, graphics2D, mLayerViewport,
mScreenViewport);
}
}
Again, I say thank you to anyone who can help.
I've done something similar before.
Try to create a rectangle for every Bullet, Enemy or whatever you've got. The rectangles should have a size thats about the size of the graphics used. Of course the rectangles have to move with the graphics.
You can check if a collision occured simply with
if(rectangleA.intersects(rectangleB)){
doAwesomeStuff();
}
I hope that helps.
Related
I am trying to touch and drag the card and update its position. Firstly, I want the card class to detect the touch drag event when I click on the card.
I have provided the code below for the card class and want help on how to detect touch event. I want to know what methods I should add and how to implement the x and y coordinates.
public class Card extends Sprite {
// /////////////////////////////////////////////////////////////////////////
// Properties:
// /////////////////////////////////////////////////////////////////////////
// Define the default card width and height
private static final int DEFAULT_CARD_WIDTH = 180;
private static final int DEFAULT_CARD_HEIGHT = 260;
// Define the common card base
private Bitmap mCardBase;
// Define the card portrait image
private Bitmap mCardPortrait;
// Define the card digit images
private Bitmap[] mCardDigits = new Bitmap[10];
// Define the offset locations and scaling for the card portrait
// card attack and card health values - all measured relative
// to the centre of the object as a percentage of object size
private Vector2 mAttackOffset = new Vector2(-0.68f, -0.84f);
private Vector2 mAttackScale = new Vector2(0.1f, 0.1f);
private Vector2 mHealthOffset = new Vector2(0.72f, -0.84f);
private Vector2 mHealthScale = new Vector2(0.1f, 0.1f);
private Vector2 mPortraitOffset = new Vector2(0.0f, 0.3f);
private Vector2 mPortraitScale = new Vector2(0.55f, 0.55f);
// Define the health and attack values
private int mAttack;
private int mHealth;
// /////////////////////////////////////////////////////////////////////////
// Constructors
// /////////////////////////////////////////////////////////////////////////
/**
* Create a new platform.
*
* #param x Centre y location of the platform
* #param y Centre x location of the platform
* #param gameScreen Gamescreen to which this platform belongs
*/
public Card(float x, float y, GameScreen gameScreen) {
super(x, y, DEFAULT_CARD_WIDTH, DEFAULT_CARD_HEIGHT, null, gameScreen);
AssetManager assetManager = gameScreen.getGame().getAssetManager();
// Store the common card base image
mCardBase = assetManager.getBitmap("CardBackground");
// Store the card portrait image
mCardPortrait = assetManager.getBitmap("CardPortrait");
// Store each of the damage/health digits
for(int digit = 0; digit <= 9; digit++)
mCardDigits[digit] = assetManager.getBitmap(String.valueOf(digit));
// Set default attack and health values
mAttack = 1;
mHealth = 2;
}
// /////////////////////////////////////////////////////////////////////////
// Methods
// /////////////////////////////////////////////////////////////////////////
/**
* Draw the game platform
*
* #param elapsedTime Elapsed time information
* #param graphics2D Graphics instance
* #param layerViewport Game layer viewport
* #param screenViewport Screen viewport
*/
#Override
public void draw(ElapsedTime elapsedTime, IGraphics2D graphics2D,
LayerViewport layerViewport, ScreenViewport screenViewport) {
// Draw the portrait
drawBitmap(mCardPortrait, mPortraitOffset, mPortraitScale,
graphics2D, layerViewport, screenViewport);
// Draw the card base background
mBitmap = mCardBase;
super.draw(elapsedTime, graphics2D, layerViewport, screenViewport);
// Draw the attack value
drawBitmap(mCardDigits[mAttack], mAttackOffset, mAttackScale,
graphics2D, layerViewport, screenViewport);
// Draw the attack value
drawBitmap(mCardDigits[mHealth], mHealthOffset, mHealthScale,
graphics2D, layerViewport, screenViewport);
}
private BoundingBox bound = new BoundingBox();
/**
* Method to draw out a specified bitmap using a specific offset (relative to the
* position of this game object) and scaling (relative to the size of this game
* object).
*
* #param bitmap Bitmap to draw
* #param offset Offset vector
* #param scale Scaling vector
* #param graphics2D Graphics instance
* #param layerViewport Game layer viewport
* #param screenViewport Screen viewport
*/
private void drawBitmap(Bitmap bitmap, Vector2 offset, Vector2 scale,
IGraphics2D graphics2D, LayerViewport layerViewport, ScreenViewport screenViewport) {
// // Calculate a game layer bound for the bitmap to be drawn
// bound.set(position.x + mBound.halfWidth * offset.x,
// position.y + mBound.halfHeight * offset.y,
// mBound.halfWidth * scale.x,
// mBound.halfHeight * scale.y);
// Calculate the center position of the rotated offset point.
double rotation = Math.toRadians(-this.orientation);
float diffX = mBound.halfWidth * offset.x;
float diffY = mBound.halfHeight * offset.y;
float rotatedX = (float)(Math.cos(rotation) * diffX - Math.sin(rotation) * diffY + position.x);
float rotatedY = (float)(Math.sin(rotation) * diffX + Math.cos(rotation) * diffY + position.y);
// Calculate a game layer bound for the bitmap to be drawn
bound.set(rotatedX, rotatedY,
mBound.halfWidth * scale.x, mBound.halfHeight * scale.y);
// Draw out the specified bitmap using the calculated bound.
// The following code is based on the Sprite's draw method.
if (GraphicsHelper.getSourceAndScreenRect(
bound, bitmap, layerViewport, screenViewport, drawSourceRect, drawScreenRect)) {
// Build an appropriate transformation matrix
drawMatrix.reset();
float scaleX = (float) drawScreenRect.width() / (float) drawSourceRect.width();
float scaleY = (float) drawScreenRect.height() / (float) drawSourceRect.height();
drawMatrix.postScale(scaleX, scaleY);
drawMatrix.postRotate(orientation, scaleX * bitmap.getWidth()
/ 2.0f, scaleY * bitmap.getHeight() / 2.0f);
drawMatrix.postTranslate(drawScreenRect.left, drawScreenRect.top);
// Draw the bitmap
graphics2D.drawBitmap(bitmap, drawMatrix, null);
}
}
}
Whenever I upload a sprite atlas for animation purposes into a scene in overlaps2d version 0.1.2-snapshot. My app crashes with the following line (slightly redacted):
java.lang.NoSuchMethodError: No virtual method getKeyFrame(F)Lcom/badlogic/gdx/graphics/g2d/TextureRegion; in class Lcom/badlogic/gdx/graphics/g2d/Animation; or its super classes (declaration of 'com.badlogic.gdx.graphics.g2d.Animation' appears in /data/data/xxx.xxx.xxx/files/instant-run/dex/slice-gdx-1.9.5_xxx-classes.dex)
And it points to this line in my code:
sceneLoader.getEngine().update(Gdx.graphics.getDeltaTime());
Could this be a issue with a version mismatch as overlap2d hasn't been updated in over a year, but libgdx was just updated about a month ago? The crash only happens with an animated image, otherwise app runs fine. I looked at the libgdx file the error refers to and this is what it looks like:
package com.badlogic.gdx.graphics.g2d;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
public class Animation<T> {
/** Defines possible playback modes for an {#link Animation}. */
public enum PlayMode {
NORMAL,
REVERSED,
LOOP,
LOOP_REVERSED,
LOOP_PINGPONG,
LOOP_RANDOM,
}
/** Length must not be modified without updating {#link #animationDuration}. See {#link #setKeyFrames(T[])}. */
T[] keyFrames;
private float frameDuration;
private float animationDuration;
private int lastFrameNumber;
private float lastStateTime;
private PlayMode playMode = PlayMode.NORMAL;
/** Constructor, storing the frame duration and key frames.
*
* #param frameDuration the time between frames in seconds.
* #param keyFrames the objects representing the frames. */
public Animation (float frameDuration, Array<? extends T> keyFrames) {
this.frameDuration = frameDuration;
T[] frames = (T[]) new Object[keyFrames.size];
for (int i = 0, n = keyFrames.size; i < n; i++) {
frames[i] = keyFrames.get(i);
}
setKeyFrames(frames);
}
/** Constructor, storing the frame duration and key frames.
*
* #param frameDuration the time between frames in seconds.
* #param keyFrames the objects representing the frames. */
public Animation (float frameDuration, Array<? extends T> keyFrames, PlayMode playMode) {
this(frameDuration, keyFrames);
setPlayMode(playMode);
}
/** Constructor, storing the frame duration and key frames.
*
* #param frameDuration the time between frames in seconds.
* #param keyFrames the objects representing the frames. */
public Animation (float frameDuration, T... keyFrames) {
this.frameDuration = frameDuration;
setKeyFrames(keyFrames);
}
/** Returns a frame based on the so called state time. This is the amount of seconds an object has spent in the
* state this Animation instance represents, e.g. running, jumping and so on. The mode specifies whether the animation is
* looping or not.
*
* #param stateTime the time spent in the state represented by this animation.
* #param looping whether the animation is looping or not.
* #return the frame of animation for the given state time. */
public T getKeyFrame (float stateTime, boolean looping) {
// we set the play mode by overriding the previous mode based on looping
// parameter value
PlayMode oldPlayMode = playMode;
if (looping && (playMode == PlayMode.NORMAL || playMode == PlayMode.REVERSED)) {
if (playMode == PlayMode.NORMAL)
playMode = PlayMode.LOOP;
else
playMode = PlayMode.LOOP_REVERSED;
} else if (!looping && !(playMode == PlayMode.NORMAL || playMode == PlayMode.REVERSED)) {
if (playMode == PlayMode.LOOP_REVERSED)
playMode = PlayMode.REVERSED;
else
playMode = PlayMode.LOOP;
}
T frame = getKeyFrame(stateTime);
playMode = oldPlayMode;
return frame;
}
/** Returns a frame based on the so called state time. This is the amount of seconds an object has spent in the
* state this Animation instance represents, e.g. running, jumping and so on using the mode specified by
* {#link #setPlayMode(PlayMode)} method.
*
* #param stateTime
* #return the frame of animation for the given state time. */
public T getKeyFrame (float stateTime) {
int frameNumber = getKeyFrameIndex(stateTime);
return keyFrames[frameNumber];
}
/** Returns the current frame number.
* #param stateTime
* #return current frame number */
public int getKeyFrameIndex (float stateTime) {
if (keyFrames.length == 1) return 0;
int frameNumber = (int)(stateTime / frameDuration);
switch (playMode) {
case NORMAL:
frameNumber = Math.min(keyFrames.length - 1, frameNumber);
break;
case LOOP:
frameNumber = frameNumber % keyFrames.length;
break;
case LOOP_PINGPONG:
frameNumber = frameNumber % ((keyFrames.length * 2) - 2);
if (frameNumber >= keyFrames.length) frameNumber = keyFrames.length - 2 - (frameNumber - keyFrames.length);
break;
case LOOP_RANDOM:
int lastFrameNumber = (int) ((lastStateTime) / frameDuration);
if (lastFrameNumber != frameNumber) {
frameNumber = MathUtils.random(keyFrames.length - 1);
} else {
frameNumber = this.lastFrameNumber;
}
break;
case REVERSED:
frameNumber = Math.max(keyFrames.length - frameNumber - 1, 0);
break;
case LOOP_REVERSED:
frameNumber = frameNumber % keyFrames.length;
frameNumber = keyFrames.length - frameNumber - 1;
break;
}
lastFrameNumber = frameNumber;
lastStateTime = stateTime;
return frameNumber;
}
/** Returns the keyframes[] array where all the frames of the animation are stored.
* #return The keyframes[] field. */
public T[] getKeyFrames () {
return keyFrames;
}
protected void setKeyFrames (T... keyFrames) {
this.keyFrames = keyFrames;
this.animationDuration = keyFrames.length * frameDuration;
}
/** Returns the animation play mode. */
public PlayMode getPlayMode () {
return playMode;
}
/** Sets the animation play mode.
*
* #param playMode The animation {#link PlayMode} to use. */
public void setPlayMode (PlayMode playMode) {
this.playMode = playMode;
}
/** Whether the animation would be finished if played without looping (PlayMode#NORMAL), given the state time.
* #param stateTime
* #return whether the animation is finished. */
public boolean isAnimationFinished (float stateTime) {
int frameNumber = (int)(stateTime / frameDuration);
return keyFrames.length - 1 < frameNumber;
}
/** Sets duration a frame will be displayed.
* #param frameDuration in seconds */
public void setFrameDuration (float frameDuration) {
this.frameDuration = frameDuration;
this.animationDuration = keyFrames.length * frameDuration;
}
/** #return the duration of a frame in seconds */
public float getFrameDuration () {
return frameDuration;
}
/** #return the duration of the entire animation, number of frames times frame duration, in seconds */
public float getAnimationDuration () {
return animationDuration;
}
From what i understand getting the key frame is what retrieves the frame out of the image atlas and changes it based on time to give the illusion of movement.
Use the previous version of libgdx, i.e., 1.9.4. In libgdx version 1.9.5 Animation class having some change, that is not updated with overlap2d snapshot version, so you are facing the problem.
Downgrade version in build.gradle of root project. Hopefully, it may be helpful.
Thanks.
I have limited Java experience and am using the program below to teach a high school level student introductory Java. Most programs from the class should work as is before extending it, like the program below. I am able to get the program running in Eclipse and drawing a Blue ball like it states in the code. BUT, I can not get the ball to move up, down, left, or right. Was something left out of this 'starter' code that draws the ball, but does not respond to the arrow keys? It's as if all my keyPressed() code is ignored. Any insights are much appreciated.
.
import java.awt.*;
import java.awt.event.*; // #1
import javax.swing.*;
/******************************************************************************
*
* KeyListenerDemo.java
* Demonstrates getting keyboard input using the KeyListener interface.
*
* Program 18: Extend this program by adding a few more keystroke commands:
* z (VK_Z) - Cause the ball to jump to a random new location.
* s (VK_S) - Make the ball smaller - multiply its diameter 1/2.
* b (VK_B) - Make the ball bigger - multiply its diameter by 2.
* c (VK_C) - Change the color (in any way you'd like).
*
* In addition, modify the program to ensure the following:
* - The ball goes all the way to the edge of the screen but stays
* completely on the screen.
* - If a doubled diameter doesn't fit, make it as large as possible.
* - Be sure the ball never completely disappears.
*
*****************************************************************************/
public class KeyListenerDemo extends JFrame
implements KeyListener // #2
{
// Class Scope Finals
private static final int SCREEN_WIDTH = 1000;
private static final int SCREEN_HEIGHT = 800;
private static final int START_RADIUS = 25;
private static final int START_X = 100;
private static final int START_Y = 100;
private static final int STEP_SIZE = 10;
// Class Scope Variables
private static int x = START_X; // x at center of the ball
private static int y = START_Y; // y at center of the ball
private static int radius = START_RADIUS; // radius of the ball
// Methods
/**
* Create the window and register this as a KeyListener
*
* #param args
*/
public static void main (String[] args)
{
// Set up the JFrame window.
KeyListenerDemo gp = new KeyListenerDemo();
gp.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
gp.setVisible(true);
gp.addKeyListener(gp); // #3
// If this class had a constructor and you moved this line into
// that constructor it could not refer to gp since that variable
// is local to this method. Instead you would write::
// addKeyListener(this);
}
/**
* Called when a key is first pressed
* Required for any KeyListener
*
* #param e Contains info about the key pressed
*/
public void keyPressed(KeyEvent e) // #4A
{
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT)
{
x = x - STEP_SIZE;
}
else if (keyCode == KeyEvent.VK_RIGHT)
{
x = x + STEP_SIZE;
}
else if (keyCode == KeyEvent.VK_UP)
{
y = y - STEP_SIZE;
}
else if (keyCode == KeyEvent.VK_DOWN)
{
y = y + STEP_SIZE;
}
repaint();
}
/**
* Called when typing of a key is completed
* Required for any KeyListener
*
* #param e Contains info about the key typed
*/
public void keyTyped(KeyEvent e) // #4B
{
}
/**
* Called when a key is released
* Required for any KeyListener
*
* #param e Contains info about the key released
*/
public void keyReleased(KeyEvent e) // #4C
{
}
/**
* paint - draw the figure
*
* #param g Graphics object to draw in
*/
public void paint(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
g.setColor(Color.blue);
g.fillOval(x - radius, y - radius, 2 * radius, 2 * radius);
}
}
I've been playing around with Slick2D for Java and I managed to get it to display maps and have my character sprite move around.
I've tried to implement a camera that follows the player so that the map scrolls. While the map is scrolling, that characters move speed is faster than it should be (possibly due to the camera srolling it as well as it moving with the keys)
I'm stumped on how to solve it though
The camera code is something i found on the slick forums, and modified slightly to draw each layer seperatly, modifying both drawmap methods to add the layer in. I would ask on the forums but they seem dead.
This is the camera code
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package engine;
/**
*
* #author Ceri
*/
import java.awt.geom.Point2D;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.tiled.TiledMap;
public class Camera {
/**
* the map used for our scene
*/
protected TiledMap map;
/**
* the number of tiles in x-direction (width)
*/
protected int numTilesX;
/**
* the number of tiles in y-direction (height)
*/
protected int numTilesY;
/**
* the height of the map in pixel
*/
protected int mapHeight;
/**
* the width of the map in pixel
*/
protected int mapWidth;
/**
* the width of one tile of the map in pixel
*/
protected int tileWidth;
/**
* the height of one tile of the map in pixel
*/
protected int tileHeight;
/**
* the GameContainer, used for getting the size of the GameCanvas
*/
protected GameContainer gc;
/**
* the x-position of our "camera" in pixel
*/
protected float cameraX;
/**
* the y-position of our "camera" in pixel
*/
protected float cameraY;
protected Point2D.Float currentCenterPoint = new Point2D.Float(0, 0);
/**
* Create a new camera
*
* #param gc the GameContainer, used for getting the size of the GameCanvas
* #param map the TiledMap used for the current scene
*/
public Camera(GameContainer gc, TiledMap map) {
this.map = map;
this.numTilesX = map.getWidth();
this.numTilesY = map.getHeight();
this.tileWidth = map.getTileWidth();
this.tileHeight = map.getTileHeight();
this.mapWidth = this.numTilesX * this.tileWidth;
this.mapHeight = this.numTilesY * this.tileHeight;
this.gc = gc;
}
/**
* "locks" the camera on the given coordinates. The camera tries to keep the
* location in it's center.
*
* #param x the real x-coordinate (in pixel) which should be centered on the
* screen
* #param y the real y-coordinate (in pixel) which should be centered on the
* screen
* #return
*/
public Point2D.Float centerOn(float x, float y) {
//try to set the given position as center of the camera by default
cameraX = x - gc.getWidth() / 2;
cameraY = y - gc.getHeight() / 2;
//if the camera is at the right or left edge lock it to prevent a black bar
if (cameraX < 0) {
cameraX = 0;
}
if (cameraX + gc.getWidth() > mapWidth) {
cameraX = mapWidth - gc.getWidth();
}
//if the camera is at the top or bottom edge lock it to prevent a black bar
if (cameraY < 0) {
cameraY = 0;
}
if (cameraY + gc.getHeight() > mapHeight) {
cameraY = mapHeight - gc.getHeight();
}
currentCenterPoint.setLocation(cameraX, cameraY);
return currentCenterPoint;
}
/**
* "locks" the camera on the center of the given Rectangle. The camera tries
* to keep the location in it's center.
*
* #param x the x-coordinate (in pixel) of the top-left corner of the
* rectangle
* #param y the y-coordinate (in pixel) of the top-left corner of the
* rectangle
* #param height the height (in pixel) of the rectangle
* #param width the width (in pixel) of the rectangle
*/
public void centerOn(float x, float y, float height, float width) {
this.centerOn(x + width / 2, y + height / 2);
}
/**
* "locks the camera on the center of the given Shape. The camera tries to
* keep the location in it's center.
*
* #param shape the Shape which should be centered on the screen
*/
public void centerOn(Shape shape) {
this.centerOn(shape.getCenterX(), shape.getCenterY());
}
/**
* draws the part of the map which is currently focussed by the camera on
* the screen
*/
public void drawMap(int layer) {
this.drawMap(0, 0, layer);
}
/**
* draws the part of the map which is currently focussed by the camera on
* the screen.<br>
* You need to draw something over the offset, to prevent the edge of the
* map to be displayed below it<br>
* Has to be called before Camera.translateGraphics() !
*
* #param offsetX the x-coordinate (in pixel) where the camera should start
* drawing the map at
* #param offsetY the y-coordinate (in pixel) where the camera should start
* drawing the map at
*/
public void drawMap(int offsetX, int offsetY, int layer) {
//calculate the offset to the next tile (needed by TiledMap.render())
int tileOffsetX = (int) -(cameraX % tileWidth);
int tileOffsetY = (int) -(cameraY % tileHeight);
//calculate the index of the leftmost tile that is being displayed
int tileIndexX = (int) (cameraX / tileWidth);
int tileIndexY = (int) (cameraY / tileHeight);
//finally draw the section of the map on the screen
map.render(
tileOffsetX + offsetX,
tileOffsetY + offsetY,
tileIndexX,
tileIndexY,
(gc.getWidth() - tileOffsetX) / tileWidth + 1,
(gc.getHeight() - tileOffsetY) / tileHeight + 1, layer, false);
}
/**
* Translates the Graphics-context to the coordinates of the map - now
* everything can be drawn with it's NATURAL coordinates.
*/
public void translateGraphics() {
gc.getGraphics().translate(-cameraX, -cameraY);
}
/**
* Reverses the Graphics-translation of Camera.translatesGraphics(). Call
* this before drawing HUD-elements or the like
*/
public void untranslateGraphics() {
gc.getGraphics().translate(cameraX, cameraY);
}
}
and this is how its being called
In the engine class
public void render(GameContainer gc, Graphics g) throws SlickException {
camera = new Camera(gc, world.map);
camera.centerOn(player.getX(), player.getY());
camera.drawMap(0);
camera.drawMap(1);
player.draw();
camera.drawMap(2);
}
This is how the player class is
public Player(MapClass m) throws SlickException {
map = m;
Image[] movementUp = {new Image("Images/Player/u1.png"), new Image("Images/Player/u2.png"), new Image("Images/Player/u3.png"), new Image("Images/Player/u4.png")};
Image[] movementDown = {new Image("Images/Player/d1.png"), new Image("Images/Player/d2.png"), new Image("Images/Player/d3.png"), new Image("Images/Player/d4.png")};
Image[] movementLeft = {new Image("Images/Player/l1.png"), new Image("Images/Player/l2.png"), new Image("Images/Player/l3.png"), new Image("Images/Player/l4.png")};
Image[] movementRight = {new Image("Images/Player/r1.png"), new Image("Images/Player/r2.png"), new Image("Images/Player/r3.png"), new Image("Images/Player/r4.png")};
int[] duration = {100, 100, 100, 100};
up = new Animation(movementUp, duration, false);
down = new Animation(movementDown, duration, false);
left = new Animation(movementLeft, duration, false);
right = new Animation(movementRight, duration, false);
// Original orientation of the sprite. It will look right.
sprite = right;
}
public void update(GameContainer container, int delta) throws SlickException {
Input input = container.getInput();
if (input.isKeyDown(Input.KEY_UP)) {
sprite = up;
sprite.update(delta);
// The lower the delta the slowest the sprite will animate.
if (!map.isBlocked(x, y - delta * 0.1f))
y -= delta * 0.1f;
} else if (input.isKeyDown(Input.KEY_DOWN)) {
sprite = down;
sprite.update(delta);
if (!map.isBlocked(x, y + 16 + delta * 0.1f))
y += delta * 0.1f;
} else if (input.isKeyDown(Input.KEY_LEFT)) {
sprite = left;
sprite.update(delta);
if (!map.isBlocked(x - delta * 0.1f, y))
x -= delta * 0.1f;
} else if (input.isKeyDown(Input.KEY_RIGHT)) {
sprite = right;
sprite.update(delta);
if (!map.isBlocked(x + 16 + delta * 0.1f, y))
x += delta * 0.1f;
}
}
public void draw() {
sprite.draw(x, y);
}
Fixed it. Moved the map draw out of the camera class into the map class. used the camera x/y created in the camera class in the map and player class
This is a very simple extension of HelloPhysics in the JME3 tutorial. When you click on a brick, the brick is removed from the game and the physicsspace. If you remove the bricks fast enough, before the wall settles, it crumbles like you would expect, but if you wait a little bit before removing bricks, nothing happens at all.
I think the physics turn off after things have stopped moving to prevent excessive jittering, and I want to manually start it up again.
package jme3test.helloworld;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
/**
* Example 12 - how to give objects physical properties so they bounce and fall.
* #author base code by double1984, updated by zathras
*/
class HelloPhysics extends SimpleApplication {
public static void main(String args[]) {
HelloPhysics app = new HelloPhysics();
app.start();
}
/** Prepare the Physics Application State (jBullet) */
private BulletAppState bulletAppState;
/** Prepare Materials */
Material wall_mat;
Material stone_mat;
Material floor_mat;
/** Prepare geometries and physical nodes for bricks and cannon balls. */
Node removables;
private RigidBodyControl brick_phy;
private static final Box box;
private RigidBodyControl ball_phy;
private static final Sphere sphere;
private RigidBodyControl floor_phy;
private static final Box floor;
/** dimensions used for bricks and wall */
private static final float brickLength = 0.48f;
private static final float brickWidth = 0.24f;
private static final float brickHeight = 0.12f;
static {
/** Initialize the cannon ball geometry */
sphere = new Sphere(32, 32, 0.4f, true, false);
sphere.setTextureMode(TextureMode.Projected);
/** Initialize the brick geometry */
box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
box.scaleTextureCoordinates(new Vector2f(1f, .5f));
/** Initialize the floor geometry */
floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
floor.scaleTextureCoordinates(new Vector2f(3, 6));
}
#Override
public void simpleInitApp() {
/** Set up Physics Game */
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
/** Configure cam to look at scene */
cam.setLocation(new Vector3f(0, 4f, 6f));
cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
/** Add InputManager action: Left click triggers shooting. */
inputManager.addMapping("shoot",
new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "shoot");
removables = new Node("Removables");
rootNode.attachChild(removables);
/** Initialize the scene, materials, and physics space */
initMaterials();
initWall();
initFloor();
initKeys();
initCrossHairs();
}
/** Declaring the "Shoot" action and mapping to its triggers. */
private void initKeys() {
inputManager.addMapping("Shoot",
new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
inputManager.addListener(actionListener, "Shoot");
}
/** Initialize the materials used in this scene. */
public void initMaterials() {
wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
key.setGenerateMips(true);
Texture tex = assetManager.loadTexture(key);
wall_mat.setTexture("ColorMap", tex);
stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
key2.setGenerateMips(true);
Texture tex2 = assetManager.loadTexture(key2);
stone_mat.setTexture("ColorMap", tex2);
floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
key3.setGenerateMips(true);
Texture tex3 = assetManager.loadTexture(key3);
tex3.setWrap(WrapMode.Repeat);
floor_mat.setTexture("ColorMap", tex3);
}
/** Make a solid floor and add it to the scene. */
public void initFloor() {
Geometry floor_geo = new Geometry("Floor", floor);
floor_geo.setMaterial(floor_mat);
floor_geo.setLocalTranslation(0, -0.1f, 0);
this.rootNode.attachChild(floor_geo);
/* Make the floor physical with mass 0.0f! */
floor_phy = new RigidBodyControl(0.0f);
floor_geo.addControl(floor_phy);
bulletAppState.getPhysicsSpace().add(floor_phy);
}
/** This loop builds a wall out of individual bricks. */
public void initWall() {
float startpt = brickLength / 4;
float height = 0;
for (int j = 0; j < 15; j++) {
for (int i = 0; i < 6; i++) {
Vector3f vt =
new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
makeBrick(vt);
}
startpt = -startpt;
height += 2 * brickHeight;
}
}
/** This method creates one individual physical brick. */
public void makeBrick(Vector3f loc) {
/** Create a brick geometry and attach to scene graph. */
Geometry brick_geo = new Geometry("brick", box);
brick_geo.setMaterial(wall_mat);
/** Position the brick geometry */
brick_geo.setLocalTranslation(loc);
/** Make brick physical with a mass > 0.0f. */
brick_phy = new RigidBodyControl(2f);
/** Add physical brick to physics space. */
brick_geo.addControl(brick_phy);
bulletAppState.getPhysicsSpace().add(brick_phy);
removables.attachChild(brick_geo);
}
/** This method creates one individual physical cannon ball.
* By defaul, the ball is accelerated and flies
* from the camera position in the camera direction.*/
public void makeCannonBall() {
/** Create a cannon ball geometry and attach to scene graph. */
Geometry ball_geo = new Geometry("cannon ball", sphere);
ball_geo.setMaterial(stone_mat);
rootNode.attachChild(ball_geo);
/** Position the cannon ball */
ball_geo.setLocalTranslation(cam.getLocation());
/** Make the ball physcial with a mass > 0.0f */
ball_phy = new RigidBodyControl(1f);
/** Add physical ball to physics space. */
ball_geo.addControl(ball_phy);
bulletAppState.getPhysicsSpace().add(ball_phy);
/** Accelerate the physcial ball to shoot it. */
ball_phy.setLinearVelocity(cam.getDirection().mult(25));
}
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("Shoot") && !keyPressed) {
// 1. Reset results list.
CollisionResults results = new CollisionResults();
// 2. Aim the ray from cam loc to cam direction.
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
// 3. Collect intersections between Ray and Shootables in results list.
removables.collideWith(ray, results);
// 4. Print the results
System.out.println("----- Collisions? " + results.size() + "-----");
for (int i = 0; i < results.size(); i++) {
// For each hit, we know distance, impact point, name of geometry.
float dist = results.getCollision(i).getDistance();
Vector3f pt = results.getCollision(i).getContactPoint();
String hit = results.getCollision(i).getGeometry().getName();
System.out.println("* Collision #" + i);
System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away.");
}
// 5. Use the results (we mark the hit object)
if (results.size() > 0) {
// The closest collision point is what was truly hit:
Geometry closest = results.getClosestCollision().getGeometry();
// Let's interact - we mark the hit with a red dot.
bulletAppState.getPhysicsSpace().remove(closest.getControl(0));
removables.detachChild(closest);
bulletAppState.getPhysicsSpace().clearForces();
bulletAppState.getPhysicsSpace().applyGravity();
System.out.println("removing a "+closest.getName());
}
}
}
};
/** A plus sign used as crosshairs to help the player with aiming.*/
protected void initCrossHairs() {
guiNode.detachAllChildren();
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
ch.setText("+"); // fake crosshairs :)
ch.setLocalTranslation( // center
settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
}
Objects which aren't moving sleep
Your initial thought was correct, when an object comes to rest it is put to sleep, this is for a number of reasons; avoiding jitter is one, but it also increases efficiency significantly.
While sleeping an object doesn't experience gravity, so if you remove the object supporting it it doesn't notice.
Usually you don't have to worry about this; if the object experiences an effect "internal to the physics engine" such as being involved with a collision it is automatically woken. However if you make external changes (such as removing an object) you have to worry about waking the object up.
Wake the objects that could have been affected by the external change
You can wake a sleeping object up by calling .activate() on the rigidBodyControl, remember sleep is done on a per object basis so you may need to wake more than 1.
At present the program does not keep a track of all the RigidBodyControl so I have added a HashSet<RigidBodyControl>() to hold them, when a brick is added to the scene it's rigidBodyControl should be added to this HashSet and when it is removed from the scene it should be removed from the hashset.
For an efficient solution you should only wake objects that could have been affected by the removal, but in this case it's probably everything anyway and I have simply woken everything by running through all of the HashSet<RigidBodyControl>() and calling activate() on them.
A complete program that demonstrates these changes is shown below
public class HelloPhysics extends SimpleApplication {
public static void main(String args[]) {
HelloPhysics app = new HelloPhysics();
app.start();
}
/** Prepare the Physics Application State (jBullet) */
private BulletAppState bulletAppState;
/** Prepare Materials */
Material wall_mat;
Material stone_mat;
Material floor_mat;
/** Prepare geometries and physical nodes for bricks and cannon balls. */
Node removables;
private static final Box box;
private RigidBodyControl ball_phy;
private static final Sphere sphere;
private RigidBodyControl floor_phy;
private static final Box floor;
private static Collection<RigidBodyControl> objectsThatNeedWaking=new HashSet<RigidBodyControl>();
/** dimensions used for bricks and wall */
private static final float brickLength = 0.48f;
private static final float brickWidth = 0.24f;
private static final float brickHeight = 0.12f;
static {
/** Initialize the cannon ball geometry */
sphere = new Sphere(32, 32, 0.4f, true, false);
sphere.setTextureMode(TextureMode.Projected);
/** Initialize the brick geometry */
box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
box.scaleTextureCoordinates(new Vector2f(1f, .5f));
/** Initialize the floor geometry */
floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
floor.scaleTextureCoordinates(new Vector2f(3, 6));
}
#Override
public void simpleInitApp() {
/** Set up Physics Game */
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
/** Configure cam to look at scene */
cam.setLocation(new Vector3f(0, 4f, 6f));
cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
/** Add InputManager action: Left click triggers shooting. */
inputManager.addMapping("shoot",
new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "shoot");
removables = new Node("Removables");
rootNode.attachChild(removables);
/** Initialize the scene, materials, and physics space */
initMaterials();
initWall();
initFloor();
initKeys();
initCrossHairs();
}
/** Declaring the "Shoot" action and mapping to its triggers. */
private void initKeys() {
inputManager.addMapping("Shoot",
new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar
new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click
inputManager.addListener(actionListener, "Shoot");
}
/** Initialize the materials used in this scene. */
public void initMaterials() {
wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
key.setGenerateMips(true);
Texture tex = assetManager.loadTexture(key);
wall_mat.setTexture("ColorMap", tex);
stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
key2.setGenerateMips(true);
Texture tex2 = assetManager.loadTexture(key2);
stone_mat.setTexture("ColorMap", tex2);
floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
key3.setGenerateMips(true);
Texture tex3 = assetManager.loadTexture(key3);
tex3.setWrap(WrapMode.Repeat);
floor_mat.setTexture("ColorMap", tex3);
}
/** Make a solid floor and add it to the scene. */
public void initFloor() {
Geometry floor_geo = new Geometry("Floor", floor);
floor_geo.setMaterial(floor_mat);
floor_geo.setLocalTranslation(0, -0.1f, 0);
this.rootNode.attachChild(floor_geo);
/* Make the floor physical with mass 0.0f! */
floor_phy = new RigidBodyControl(0.0f);
floor_geo.addControl(floor_phy);
bulletAppState.getPhysicsSpace().add(floor_phy);
}
/** This loop builds a wall out of individual bricks. */
public void initWall() {
float startpt = brickLength / 4;
float height = 0;
for (int j = 0; j < 15; j++) {
for (int i = 0; i < 6; i++) {
Vector3f vt =
new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
makeBrick(vt);
}
startpt = -startpt;
height += 2 * brickHeight;
}
}
/** This method creates one individual physical brick. */
public void makeBrick(Vector3f loc) {
/** Create a brick geometry and attach to scene graph. */
Geometry brick_geo = new Geometry("brick", box);
brick_geo.setMaterial(wall_mat);
/** Position the brick geometry */
brick_geo.setLocalTranslation(loc);
/** Make brick physical with a mass > 0.0f. */
RigidBodyControl brick_phy = new RigidBodyControl(2f);
objectsThatNeedWaking.add(brick_phy);
/** Add physical brick to physics space. */
brick_geo.addControl(brick_phy);
bulletAppState.getPhysicsSpace().add(brick_phy);
removables.attachChild(brick_geo);
}
/** This method creates one individual physical cannon ball.
* By defaul, the ball is accelerated and flies
* from the camera position in the camera direction.*/
public void makeCannonBall() {
/** Create a cannon ball geometry and attach to scene graph. */
Geometry ball_geo = new Geometry("cannon ball", sphere);
ball_geo.setMaterial(stone_mat);
rootNode.attachChild(ball_geo);
/** Position the cannon ball */
ball_geo.setLocalTranslation(cam.getLocation());
/** Make the ball physcial with a mass > 0.0f */
ball_phy = new RigidBodyControl(1f);
/** Add physical ball to physics space. */
ball_geo.addControl(ball_phy);
bulletAppState.getPhysicsSpace().add(ball_phy);
/** Accelerate the physcial ball to shoot it. */
ball_phy.setLinearVelocity(cam.getDirection().mult(25));
}
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("Shoot") && !keyPressed) {
// 1. Reset results list.
CollisionResults results = new CollisionResults();
// 2. Aim the ray from cam loc to cam direction.
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
// 3. Collect intersections between Ray and Shootables in results list.
removables.collideWith(ray, results);
// 4. Print the results
System.out.println("----- Collisions? " + results.size() + "-----");
for (int i = 0; i < results.size(); i++) {
// For each hit, we know distance, impact point, name of geometry.
float dist = results.getCollision(i).getDistance();
Vector3f pt = results.getCollision(i).getContactPoint();
String hit = results.getCollision(i).getGeometry().getName();
System.out.println("* Collision #" + i);
System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away.");
}
// 5. Use the results (we mark the hit object)
if (results.size() > 0) {
// The closest collision point is what was truly hit:
Geometry closest = results.getClosestCollision().getGeometry();
// Let's interact - we mark the hit with a red dot.
bulletAppState.getPhysicsSpace().remove(closest.getControl(0));
objectsThatNeedWaking.remove(closest.getControl(0));
removables.detachChild(closest);
bulletAppState.getPhysicsSpace().clearForces();
bulletAppState.getPhysicsSpace().applyGravity();
System.out.println("removing a "+closest.getName());
for(RigidBodyControl wakeMeUp: objectsThatNeedWaking){
wakeMeUp.activate();
}
}
}
}
};
/** A plus sign used as crosshairs to help the player with aiming.*/
protected void initCrossHairs() {
guiNode.detachAllChildren();
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
ch.setText("+"); // fake crosshairs :)
ch.setLocalTranslation( // center
settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
}