Added most of the documentation.

This commit is contained in:
2014-10-27 14:01:06 -04:30
parent 0629dac07e
commit 97deaa93c2
23 changed files with 744 additions and 67 deletions

View File

@@ -37,10 +37,27 @@ import com.gamejolt.mikykr5.ceidecpong.states.LogoScreenState;
import com.gamejolt.mikykr5.ceidecpong.states.MainMenuState; import com.gamejolt.mikykr5.ceidecpong.states.MainMenuState;
import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader; import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader;
/**
* This is the central class of the Game. It is in charge of maintaining the game's
* life cycle and switching between the different application states. It also renders
* the fade effects when switching states.
*
* @author Miguel Astor
*/
public class GameCore extends Game { public class GameCore extends Game {
/**
* Tag used for logging.
*/
private static final String TAG = "GAME_CORE"; private static final String TAG = "GAME_CORE";
/**
* Class name used for logging.
*/
private static final String CLASS_NAME = GameCore.class.getSimpleName(); private static final String CLASS_NAME = GameCore.class.getSimpleName();
/**
* An enumerated type used for state switching.
*/
public enum game_states_t { public enum game_states_t {
LOGO_SCREEN(0), MAIN_MENU(1), IN_GAME(2), QUIT(3), LOADING(4); LOGO_SCREEN(0), MAIN_MENU(1), IN_GAME(2), QUIT(3), LOADING(4);
@@ -59,18 +76,55 @@ public class GameCore extends Game {
} }
}; };
/**
* A pointer to the currently active state.
*/
private game_states_t currState; private game_states_t currState;
/**
* A pointer to the state to switch to. Usually null.
*/
public game_states_t nextState; public game_states_t nextState;
/**
* An array to hold all application states.
*/
private BaseState[] states; private BaseState[] states;
/**
* The {@link SpriteBatch} used to render all 2D graphics in the game.
*/
public SpriteBatch batch; public SpriteBatch batch;
/**
* A pixel perfect camera used to render the fade effects.
*/
private OrthographicCamera pixelPerfectCamera; private OrthographicCamera pixelPerfectCamera;
// Fade in/out effect fields. // Fade in/out effect fields.
/**
* The fade graphic.
*/
private Texture fadeTexture; private Texture fadeTexture;
/**
* A {@link MutableFloat} used to interpolate the transparency of {@link GameCore#fadeTexture}.
*/
private MutableFloat alpha; private MutableFloat alpha;
/**
* A {@link Tween} instance used to interpolate between full transparency to no transparency.
*/
private Tween fadeOut; private Tween fadeOut;
/**
* A {@link Tween} instance used to interpolate between no transparency to full transparency.
*/
private Tween fadeIn; private Tween fadeIn;
/**
* A flag to indicate that a fade effect is in progress.
*/
private boolean fading; private boolean fading;
@Override @Override
@@ -78,7 +132,7 @@ public class GameCore extends Game {
AsyncAssetLoader loader = AsyncAssetLoader.getInstance(); AsyncAssetLoader loader = AsyncAssetLoader.getInstance();
// Set up rendering fields and settings. // Set up rendering fields and settings.
ShaderProgram.pedantic = false; ShaderProgram.pedantic = false; // Not passing all variables to a shader will not close the game.
batch = new SpriteBatch(); batch = new SpriteBatch();
batch.enableBlending(); batch.enableBlending();
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
@@ -91,6 +145,7 @@ public class GameCore extends Game {
fadeTexture = new Texture(pixmap); fadeTexture = new Texture(pixmap);
pixmap.dispose(); pixmap.dispose();
// Create the initial interpolators and start with a fade in effect.
alpha = new MutableFloat(1.0f); alpha = new MutableFloat(1.0f);
fadeOut = Tween.to(alpha, 0, 0.5f).target(1.0f).ease(TweenEquations.easeInQuint); fadeOut = Tween.to(alpha, 0, 0.5f).target(1.0f).ease(TweenEquations.easeInQuint);
fadeIn = Tween.to(alpha, 0, 2.5f).target(0.0f).ease(TweenEquations.easeInQuint); fadeIn = Tween.to(alpha, 0, 2.5f).target(0.0f).ease(TweenEquations.easeInQuint);
@@ -112,6 +167,7 @@ public class GameCore extends Game {
return; return;
} }
// Register every state as an AssetsLoadedListener if the state implements the interface.
for(BaseState state : states){ for(BaseState state : states){
if(state != null && state instanceof AssetsLoadedListener) if(state != null && state instanceof AssetsLoadedListener)
loader.addListener((AssetsLoadedListener)state); loader.addListener((AssetsLoadedListener)state);
@@ -138,6 +194,7 @@ public class GameCore extends Game {
// If the current state set a value for nextState then switch to that state. // If the current state set a value for nextState then switch to that state.
if(nextState != null){ if(nextState != null){
// First disable the current state so that it will no longer catch user inputs.
states[currState.getValue()].onStateDisabled(); states[currState.getValue()].onStateDisabled();
if(!fadeOut.isStarted()){ if(!fadeOut.isStarted()){
@@ -198,12 +255,13 @@ public class GameCore extends Game {
public void dispose(){ public void dispose(){
super.dispose(); super.dispose();
// Dispose screens. // Dispose all states.
for(BaseState state : states){ for(BaseState state : states){
if(state != null) if(state != null)
state.dispose(); state.dispose();
} }
// Dispose other graphics.
fadeTexture.dispose(); fadeTexture.dispose();
batch.dispose(); batch.dispose();
} }

View File

@@ -15,10 +15,34 @@
*/ */
package com.gamejolt.mikykr5.ceidecpong; package com.gamejolt.mikykr5.ceidecpong;
/**
* This class holds some project-wise constants.
*
* @author Miguel Astor
*/
public abstract class ProjectConstants{ public abstract class ProjectConstants{
/**
* What to return when the application terminates successfully.
*/
public static final int EXIT_SUCCESS = 0; public static final int EXIT_SUCCESS = 0;
/**
* What to return when the application terminates closes due to an error.
*/
public static final int EXIT_FAILURE = 1; public static final int EXIT_FAILURE = 1;
public static final boolean DEBUG = true;
/**
* Enable/disable logging.
*/
public static final boolean DEBUG = false;
/**
* Logical screen width.
*/
public static final int FB_WIDTH = 1920; public static final int FB_WIDTH = 1920;
/**
* Logical screen height.
*/
public static final int FB_HEIGHT = 1080; public static final int FB_HEIGHT = 1080;
} }

View File

@@ -19,7 +19,15 @@ import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
/**
* A 2D bounding rectangle component.
*
* @author Miguel Astor
*/
public class BoundingBoxComponent extends Component implements Poolable { public class BoundingBoxComponent extends Component implements Poolable {
/**
* The bounding rectangle.
*/
public Rectangle bbox; public Rectangle bbox;
public BoundingBoxComponent() { public BoundingBoxComponent() {

View File

@@ -17,10 +17,14 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.ComponentMapper; import com.badlogic.ashley.core.ComponentMapper;
/**
* A holder class for all {@link ComponentMapper} instances used by the game.
*
* @author Miguel Astor
*/
public abstract class Mappers { public abstract class Mappers {
public static final ComponentMapper<PositionComponent> positionMapper = ComponentMapper.getFor(PositionComponent.class); public static final ComponentMapper<PositionComponent> positionMapper = ComponentMapper.getFor(PositionComponent.class);
public static final ComponentMapper<VelocityComponent> velocityMapper = ComponentMapper.getFor(VelocityComponent.class); public static final ComponentMapper<VelocityComponent> velocityMapper = ComponentMapper.getFor(VelocityComponent.class);
public static final ComponentMapper<TextureComponent> textureMapper = ComponentMapper.getFor(TextureComponent.class);
public static final ComponentMapper<SpriteComponent> spriteMapper = ComponentMapper.getFor(SpriteComponent.class); public static final ComponentMapper<SpriteComponent> spriteMapper = ComponentMapper.getFor(SpriteComponent.class);
public static final ComponentMapper<BoundingBoxComponent> bboxMapper = ComponentMapper.getFor(BoundingBoxComponent.class); public static final ComponentMapper<BoundingBoxComponent> bboxMapper = ComponentMapper.getFor(BoundingBoxComponent.class);
public static final ComponentMapper<ScoreComponent> scoreMapper = ComponentMapper.getFor(ScoreComponent.class); public static final ComponentMapper<ScoreComponent> scoreMapper = ComponentMapper.getFor(ScoreComponent.class);

View File

@@ -18,10 +18,18 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component; import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
/**
* A Player identity {@link Component}
*
* @author Miguel Astor
*/
public class PlayerComponent extends Component implements Poolable { public class PlayerComponent extends Component implements Poolable {
public static int HUMAN_PLAYER = 0; public static final int HUMAN_PLAYER = 0;
public static int COMPUTER_PLAYER = 1; public static final int COMPUTER_PLAYER = 1;
/**
* An identifier used to determine the type of player.
*/
public int id = -1; public int id = -1;
@Override @Override

View File

@@ -18,10 +18,28 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component; import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
/**
* A 2D position {@link Component}.
*
* @author Miguel Astor.
*/
public class PositionComponent extends Component implements Poolable { public class PositionComponent extends Component implements Poolable {
/**
* The X coordinate.
*/
public float x = 0; public float x = 0;
/**
* The Y coordinate.
*/
public float y = 0; public float y = 0;
/**
* Sets both coordinates simultaneously.
*
* @param x
* @param y
*/
public void setXY(float x, float y){ public void setXY(float x, float y){
this.x = x; this.x = x;
this.y = y; this.y = y;

View File

@@ -18,7 +18,15 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component; import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
/**
* A {@link Component} to hold a player's score.
*
* @author Miguel Astor
*/
public class ScoreComponent extends Component implements Poolable{ public class ScoreComponent extends Component implements Poolable{
/**
* The score.
*/
public int score = 0; public int score = 0;
@Override @Override

View File

@@ -1,9 +1,33 @@
/*
* Copyright (c) 2014, Miguel Angel Astor Romero
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Read the LICENSE file for more details.
*/
package com.gamejolt.mikykr5.ceidecpong.ecs.components; package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component; import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
import com.gamejolt.mikykr5.ceidecpong.utils.managers.CachedSoundManager;
/**
* A sound effect {@link Component}
*
* @author Miguel Astor
*/
public class SoundComponent extends Component implements Poolable { public class SoundComponent extends Component implements Poolable {
/**
* The path of the sound effect. Used as a key by {@link CachedSoundManager}.
*/
public String path = ""; public String path = "";
@Override @Override

View File

@@ -19,7 +19,15 @@ import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
/**}
* A 2D renderable {@link Component}.
*
* @author Miguel Astor
*/
public class SpriteComponent extends Component implements Poolable { public class SpriteComponent extends Component implements Poolable {
/**
* The renderable sprite.
*/
public Sprite sprite; public Sprite sprite;
public SpriteComponent(){ public SpriteComponent(){

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) 2014, Miguel Angel Astor Romero
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Read the LICENSE file for more details.
*/
package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Pool.Poolable;
public class TextureComponent extends Component implements Poolable{
public Texture texture;
public TextureComponent(){
texture = null;
}
public TextureComponent(Texture texture){
this.texture = texture;
}
@Override
public void reset() {
if(texture != null)
texture.dispose();
texture = null;
}
}

View File

@@ -18,10 +18,27 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component; import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
/**
* A 2D velocity {@link Component}
*
* @author Miguel Astor
*/
public class VelocityComponent extends Component implements Poolable { public class VelocityComponent extends Component implements Poolable {
/**
* The velocity in the X axis.
*/
public float vx = 0; public float vx = 0;
/**
* The velocity in the Y axis.
*/
public float vy = 0; public float vy = 0;
/**
* Sets both fields simultaneously.
* @param vx
* @param vy
*/
public void setXY(float vx, float vy){ public void setXY(float vx, float vy){
this.vx = vx; this.vx = vx;
this.vy = vy; this.vy = vy;

View File

@@ -15,10 +15,29 @@
*/ */
package com.gamejolt.mikykr5.ceidecpong.ecs.entities; package com.gamejolt.mikykr5.ceidecpong.ecs.entities;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.PooledEngine; import com.badlogic.ashley.core.PooledEngine;
import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Disposable;
import com.gamejolt.mikykr5.ceidecpong.states.InGameState;
/**
* Base class for entity initializers. Implementations must create all initial {@link Entity} objects
* needed by their respective games.
*
* @author Miguel Astor
*/
public abstract class EntityInitializerBase implements Disposable{ public abstract class EntityInitializerBase implements Disposable{
/**
* Creates all {@link Entity} objects and loads the needed assets.
* @param engine A {@link PooledEngine} instance as used by {@link InGameState}
*/
public abstract void createAllEntities(PooledEngine engine); public abstract void createAllEntities(PooledEngine engine);
/**
* Associates all assets loaded to their respective entities.
*
* @param engine A {@link PooledEngine} instance as used by {@link InGameState}
* @throws IllegalStateException If the entities have not been created before calling this method.
*/
public abstract void setLoadableAssets(PooledEngine engine) throws IllegalStateException; public abstract void setLoadableAssets(PooledEngine engine) throws IllegalStateException;
} }

View File

@@ -34,17 +34,61 @@ import com.gamejolt.mikykr5.ceidecpong.ecs.components.VelocityComponent;
import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader; import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader;
import com.gamejolt.mikykr5.ceidecpong.utils.managers.CachedSoundManager; import com.gamejolt.mikykr5.ceidecpong.utils.managers.CachedSoundManager;
public class PongEntityInitializer extends EntityInitializerBase { /**
* A concrete implementation of a {@link EntityInitializerBase} that creates all the entities
* needed by the Pong game.
*
* @author Miguel Astor
*/
public class PongEntityInitializer extends EntityInitializerBase{
/**
* An assets loader instance.
*/
private AsyncAssetLoader loader; private AsyncAssetLoader loader;
/**
* An entity that plays a sound when the user scores.
*/
private Entity victorySound; private Entity victorySound;
/**
* An entity that plays a sound when the computer scores.
*/
private Entity defeatSound; private Entity defeatSound;
/**
* An entity that represents the ball in the game.
*/
private Entity ball; private Entity ball;
/**
* An entity that represents the human player.
*/
private Entity paddleUser; private Entity paddleUser;
/**
* An entity that represents the computer player.
*/
private Entity paddleComp; private Entity paddleComp;
/**
* An entity that is used to render the background.
*/
private Entity background; private Entity background;
/**
* Flag that indicates that all entities have been created.
*/
private boolean entitiesCreated; private boolean entitiesCreated;
/**
* Flag that indicates that all assets associated to entities have been loaded.
*/
private boolean assetsLoaded; private boolean assetsLoaded;
/**
* Create the initializer and set the flags to false.
*/
public PongEntityInitializer() { public PongEntityInitializer() {
entitiesCreated = false; entitiesCreated = false;
assetsLoaded = false; assetsLoaded = false;
@@ -52,25 +96,29 @@ public class PongEntityInitializer extends EntityInitializerBase {
@Override @Override
public void createAllEntities(PooledEngine engine){ public void createAllEntities(PooledEngine engine){
// Get instances of the needed asset loaders.
loader = AsyncAssetLoader.getInstance(); loader = AsyncAssetLoader.getInstance();
CachedSoundManager soundManager = CachedSoundManager.getInstance(); CachedSoundManager soundManager = CachedSoundManager.getInstance();
// Load all textures and sound effects.
loader.addAssetToLoad("data/gfx/textures/pong_atlas.atlas", TextureAtlas.class); loader.addAssetToLoad("data/gfx/textures/pong_atlas.atlas", TextureAtlas.class);
loader.addAssetToLoad("data/gfx/textures/bckg.png", Texture.class); loader.addAssetToLoad("data/gfx/textures/bckg.png", Texture.class);
soundManager.loadSound("data/sfx/BounceYoFrankie.ogg"); soundManager.loadSound("data/sfx/BounceYoFrankie.ogg");
soundManager.loadSound("data/sfx/oh_yeah_wav_cut.ogg"); soundManager.loadSound("data/sfx/oh_yeah_wav_cut.ogg");
soundManager.loadSound("data/sfx/atari_boom.ogg"); soundManager.loadSound("data/sfx/atari_boom.ogg");
// Create the entities related to the sound effects.
victorySound = engine.createEntity(); victorySound = engine.createEntity();
victorySound.add(engine.createComponent(SoundComponent.class)); victorySound.add(engine.createComponent(SoundComponent.class));
defeatSound = engine.createEntity(); defeatSound = engine.createEntity();
defeatSound.add(engine.createComponent(SoundComponent.class)); defeatSound.add(engine.createComponent(SoundComponent.class));
// Create the background.
background = engine.createEntity(); background = engine.createEntity();
background.add(engine.createComponent(PositionComponent.class)); background.add(engine.createComponent(PositionComponent.class));
background.add(engine.createComponent(SpriteComponent.class)); background.add(engine.createComponent(SpriteComponent.class));
// Create the ball.
ball = engine.createEntity(); ball = engine.createEntity();
ball.add(engine.createComponent(PositionComponent.class)); ball.add(engine.createComponent(PositionComponent.class));
ball.add(engine.createComponent(VelocityComponent.class)); ball.add(engine.createComponent(VelocityComponent.class));
@@ -78,6 +126,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
ball.add(engine.createComponent(BoundingBoxComponent.class)); ball.add(engine.createComponent(BoundingBoxComponent.class));
ball.add(engine.createComponent(SoundComponent.class)); ball.add(engine.createComponent(SoundComponent.class));
// Create the human player.
paddleUser = engine.createEntity(); paddleUser = engine.createEntity();
paddleUser.add(engine.createComponent(PositionComponent.class)); paddleUser.add(engine.createComponent(PositionComponent.class));
paddleUser.add(engine.createComponent(VelocityComponent.class)); paddleUser.add(engine.createComponent(VelocityComponent.class));
@@ -86,6 +135,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
paddleUser.add(engine.createComponent(ScoreComponent.class)); paddleUser.add(engine.createComponent(ScoreComponent.class));
paddleUser.add(engine.createComponent(PlayerComponent.class)); paddleUser.add(engine.createComponent(PlayerComponent.class));
// Create the computer player.
paddleComp = engine.createEntity(); paddleComp = engine.createEntity();
paddleComp.add(engine.createComponent(PositionComponent.class)); paddleComp.add(engine.createComponent(PositionComponent.class));
paddleComp.add(engine.createComponent(VelocityComponent.class)); paddleComp.add(engine.createComponent(VelocityComponent.class));
@@ -94,6 +144,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
paddleComp.add(engine.createComponent(ScoreComponent.class)); paddleComp.add(engine.createComponent(ScoreComponent.class));
paddleComp.add(engine.createComponent(PlayerComponent.class)); paddleComp.add(engine.createComponent(PlayerComponent.class));
// Register all entities.
engine.addEntity(victorySound); engine.addEntity(victorySound);
engine.addEntity(defeatSound); engine.addEntity(defeatSound);
engine.addEntity(background); engine.addEntity(background);
@@ -101,6 +152,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
engine.addEntity(paddleUser); engine.addEntity(paddleUser);
engine.addEntity(paddleComp); engine.addEntity(paddleComp);
// Mark the flag.
entitiesCreated = true; entitiesCreated = true;
} }
@@ -109,33 +161,42 @@ public class PongEntityInitializer extends EntityInitializerBase {
if(!entitiesCreated) if(!entitiesCreated)
throw new IllegalStateException("Entities have not been created before setting assets."); throw new IllegalStateException("Entities have not been created before setting assets.");
// Some variables used to initialize the ball.
Vector2 randomVector = new Vector2().set(Vector2.X).setAngle(MathUtils.random(-60, 60)); Vector2 randomVector = new Vector2().set(Vector2.X).setAngle(MathUtils.random(-60, 60));
int randomSign = MathUtils.random(-1, 1) >= 0 ? 1 : -1; int randomSign = MathUtils.random(-1, 1) >= 0 ? 1 : -1;
// Fetch the assets.
TextureAtlas atlas = loader.getAsset("data/gfx/textures/pong_atlas.atlas", TextureAtlas.class); TextureAtlas atlas = loader.getAsset("data/gfx/textures/pong_atlas.atlas", TextureAtlas.class);
Texture bckg = loader.getAsset("data/gfx/textures/bckg.png", Texture.class); Texture bckg = loader.getAsset("data/gfx/textures/bckg.png", Texture.class);
// Add the sound effects to the entities.
Mappers.soundMapper.get(victorySound).path = "data/sfx/oh_yeah_wav_cut.ogg"; Mappers.soundMapper.get(victorySound).path = "data/sfx/oh_yeah_wav_cut.ogg";
Mappers.soundMapper.get(defeatSound).path = "data/sfx/atari_boom.ogg"; Mappers.soundMapper.get(defeatSound).path = "data/sfx/atari_boom.ogg";
// Set up the background.
Mappers.spriteMapper.get(background).sprite = new Sprite(bckg); Mappers.spriteMapper.get(background).sprite = new Sprite(bckg);
Mappers.positionMapper.get(background).setXY(-(ProjectConstants.FB_WIDTH / 2.0f), -(ProjectConstants.FB_HEIGHT / 2.0f)); Mappers.positionMapper.get(background).setXY(-(ProjectConstants.FB_WIDTH / 2.0f), -(ProjectConstants.FB_HEIGHT / 2.0f));
// Set up the ball.
Mappers.spriteMapper.get(ball).sprite = atlas.createSprite("ball"); Mappers.spriteMapper.get(ball).sprite = atlas.createSprite("ball");
Mappers.positionMapper.get(ball).setXY(-(Mappers.spriteMapper.get(ball).sprite.getWidth() / 2), -(Mappers.spriteMapper.get(ball).sprite.getHeight() / 2)); Mappers.positionMapper.get(ball).setXY(-(Mappers.spriteMapper.get(ball).sprite.getWidth() / 2), -(Mappers.spriteMapper.get(ball).sprite.getHeight() / 2));
Mappers.velocityMapper.get(ball).setXY(randomVector.x * 475.0f * randomSign, randomVector.y * 475.0f * randomSign); Mappers.velocityMapper.get(ball).setXY(randomVector.x * 475.0f * randomSign, randomVector.y * 475.0f * randomSign);
Mappers.bboxMapper.get(ball).bbox.set(Mappers.spriteMapper.get(ball).sprite.getBoundingRectangle()); Mappers.bboxMapper.get(ball).bbox.set(Mappers.spriteMapper.get(ball).sprite.getBoundingRectangle());
Mappers.soundMapper.get(ball).path = "data/sfx/BounceYoFrankie.ogg"; Mappers.soundMapper.get(ball).path = "data/sfx/BounceYoFrankie.ogg";
// Set up the human player.
Mappers.spriteMapper.get(paddleUser).sprite = atlas.createSprite("glasspaddle2"); Mappers.spriteMapper.get(paddleUser).sprite = atlas.createSprite("glasspaddle2");
Mappers.positionMapper.get(paddleUser).setXY(-(ProjectConstants.FB_WIDTH / 2) + 100, -(Mappers.spriteMapper.get(paddleUser).sprite.getHeight() / 2)); Mappers.positionMapper.get(paddleUser).setXY(-(ProjectConstants.FB_WIDTH / 2) + 100, -(Mappers.spriteMapper.get(paddleUser).sprite.getHeight() / 2));
Mappers.bboxMapper.get(paddleUser).bbox.set(Mappers.spriteMapper.get(paddleUser).sprite.getBoundingRectangle()); Mappers.bboxMapper.get(paddleUser).bbox.set(Mappers.spriteMapper.get(paddleUser).sprite.getBoundingRectangle());
Mappers.playerMapper.get(paddleUser).id = PlayerComponent.HUMAN_PLAYER; Mappers.playerMapper.get(paddleUser).id = PlayerComponent.HUMAN_PLAYER;
// Set up the computer player.
Mappers.spriteMapper.get(paddleComp).sprite = atlas.createSprite("paddle"); Mappers.spriteMapper.get(paddleComp).sprite = atlas.createSprite("paddle");
Mappers.positionMapper.get(paddleComp).setXY(((ProjectConstants.FB_WIDTH / 2) - 1) - 100 - Mappers.spriteMapper.get(paddleComp).sprite.getWidth(), -(Mappers.spriteMapper.get(paddleComp).sprite.getHeight() / 2)); Mappers.positionMapper.get(paddleComp).setXY(((ProjectConstants.FB_WIDTH / 2) - 1) - 100 - Mappers.spriteMapper.get(paddleComp).sprite.getWidth(), -(Mappers.spriteMapper.get(paddleComp).sprite.getHeight() / 2));
Mappers.bboxMapper.get(paddleComp).bbox.set(Mappers.spriteMapper.get(paddleComp).sprite.getBoundingRectangle()); Mappers.bboxMapper.get(paddleComp).bbox.set(Mappers.spriteMapper.get(paddleComp).sprite.getBoundingRectangle());
Mappers.playerMapper.get(paddleComp).id = PlayerComponent.COMPUTER_PLAYER; Mappers.playerMapper.get(paddleComp).id = PlayerComponent.COMPUTER_PLAYER;
// Release the assets loader instance and mark the flag.
AsyncAssetLoader.freeInstance(); AsyncAssetLoader.freeInstance();
assetsLoaded = true; assetsLoaded = true;
} }
@@ -148,6 +209,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
if(!assetsLoaded) if(!assetsLoaded)
throw new IllegalStateException("Assets have not been loaded before disposing."); throw new IllegalStateException("Assets have not been loaded before disposing.");
// Release the sound manager instance.
CachedSoundManager.freeInstance(); CachedSoundManager.freeInstance();
} }
} }

View File

@@ -3,10 +3,30 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.systems.messaging;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.badlogic.ashley.core.EntitySystem;
/**
* A message from a {@link EntitySystem }to another.
*
* @author Miguel Astor
*/
public class InterSystemMessage{ public class InterSystemMessage{
/**
* A string to identify the receiver of the message. Can contain anything so long as the intended receiver
* knows.
*/
public final String target; public final String target;
/**
* A holder for arbitrary data.
*/
public final Map<String, Object> data; public final Map<String, Object> data;
/**
* Creates a new message object.
*
* @param target The receiver of the message.
*/
public InterSystemMessage(String target){ public InterSystemMessage(String target){
this.target = target; this.target = target;
this.data = new HashMap<String, Object>(); this.data = new HashMap<String, Object>();

View File

@@ -3,11 +3,26 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.systems.messaging;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
/**
* A messaging queue to communicate two {@link EntitySystem} instances.
*
* @author Miguel Astor
*/
public abstract class InterSystemMessagingQueue{ public abstract class InterSystemMessagingQueue{
/**
* The message queue.
*/
private static Queue<InterSystemMessage> queue = new LinkedList<InterSystemMessage>(); private static Queue<InterSystemMessage> queue = new LinkedList<InterSystemMessage>();
/**
* Adds a message to the queue.
*
* @param message The message to add.
* @throws IllegalArgumentException If message is null.
*/
public static synchronized void pushMessage(InterSystemMessage message) throws IllegalArgumentException{ public static synchronized void pushMessage(InterSystemMessage message) throws IllegalArgumentException{
if(message == null) if(message == null)
throw new IllegalArgumentException("Message is null"); throw new IllegalArgumentException("Message is null");
@@ -15,6 +30,14 @@ public abstract class InterSystemMessagingQueue{
queue.add(message); queue.add(message);
} }
/**
* Fetches a message from the queue if it's intended receiver is the caller of this method. A message is removed from the
* queue only if it was successfully retrieved.
*
* @param receiver The intended receiver.
* @return The message.
* @throws IllegalArgumentException If receiver is null.
*/
public static synchronized InterSystemMessage popMessage(String receiver) throws IllegalArgumentException{ public static synchronized InterSystemMessage popMessage(String receiver) throws IllegalArgumentException{
InterSystemMessage message = null; InterSystemMessage message = null;

View File

@@ -26,92 +26,198 @@ import com.badlogic.gdx.utils.Disposable;
import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener; import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener;
import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader; import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader;
/**
* A reusable scrolling infinite background similar to those used commonly in PSX games.
*
* @author Miguel Astor
*/
public class ScrollingBackground implements Disposable, AssetsLoadedListener{ public class ScrollingBackground implements Disposable, AssetsLoadedListener{
/**
* Tag used for logging.
*/
private static final String TAG = "SCROLLING_BACKGROUND"; private static final String TAG = "SCROLLING_BACKGROUND";
/**
* Class name used for logging.
*/
private static final String CLASS_NAME = ScrollingBackground.class.getSimpleName(); private static final String CLASS_NAME = ScrollingBackground.class.getSimpleName();
/**
* The path of the shader used by the effect.
*/
private static final String SHADER_PATH = "shaders/movingBckg/movingBckg"; private static final String SHADER_PATH = "shaders/movingBckg/movingBckg";
/**
* The assets loader instance.
*/
private AsyncAssetLoader loader; private AsyncAssetLoader loader;
/**
* The texture to render.
*/
private Texture backgroundTexture; private Texture backgroundTexture;
/**
* An sprite to hold the texture.
*/
private Sprite background; private Sprite background;
/**
* A compiled shader object used during rendering.
*/
private ShaderProgram shader; private ShaderProgram shader;
/**
* Holds the location of the u_scaling variable of the shader.
*/
private int u_scaling; private int u_scaling;
/**
* Holds the location of the u_displacement variable of the shader.
*/
private int u_displacement; private int u_displacement;
/**
* The scaling to apply to the texture.
*/
private float scaling; private float scaling;
/**
* The displacement to apply to the texture.
*/
private float displacement; private float displacement;
private String texturePath; private String texturePath;
/**
* Creates a new effect using a default scaling and displacement. The
* texture is loaded with {@link AsyncAssetLoader}.
*
* @param texturePath The internal path of the texture to use.
*/
public ScrollingBackground(String texturePath){ public ScrollingBackground(String texturePath){
this(texturePath, 2.0f, 0.0f, true); this(texturePath, 2.0f, 0.0f, true);
} }
/**
* Creates a new effect using a default scaling and displacement.
*
* @param texturePath The internal path of the texture to use.
* @param loadAsync Whether to use {@link AsyncAssetLoader} or not.
*/
public ScrollingBackground(String texturePath, boolean loadAsync){ public ScrollingBackground(String texturePath, boolean loadAsync){
this(texturePath, 2.0f, 0.0f, loadAsync); this(texturePath, 2.0f, 0.0f, loadAsync);
} }
/**
* Creates a new effect using a default displacement. The
* texture is loaded with {@link AsyncAssetLoader}.
*
* @param texturePath The internal path of the texture to use.
* @param scaling The scaling to apply to the texture.
*/
public ScrollingBackground(String texturePath, float scaling){ public ScrollingBackground(String texturePath, float scaling){
this(texturePath, scaling, 0.0f, true); this(texturePath, scaling, 0.0f, true);
} }
/**
* Creates a new effect using a default displacement.
*
* @param texturePath The internal path of the texture to use.
* @param scaling The scaling to apply to the texture.
* @param loadAsync Whether to use {@link AsyncAssetLoader} or not.
*/
public ScrollingBackground(String texturePath, float scaling, boolean loadAsync){ public ScrollingBackground(String texturePath, float scaling, boolean loadAsync){
this(texturePath, scaling, 0.0f, loadAsync); this(texturePath, scaling, 0.0f, loadAsync);
} }
/**
* Creates a new effect.
*
* @param texturePath The internal path of the texture to use.
* @param scaling The scaling to apply to the texture.
* @param displacement The displacement to apply to the texture.
* @param loadAsync Whether to use {@link AsyncAssetLoader} or not.
*/
public ScrollingBackground(String texturePath, float scaling, float displacement, boolean loadAsync){ public ScrollingBackground(String texturePath, float scaling, float displacement, boolean loadAsync){
if(loadAsync){ if(loadAsync){
// If an asynchronous load was requested then use the assets loader.
loader = AsyncAssetLoader.getInstance(); loader = AsyncAssetLoader.getInstance();
loader.addAssetToLoad(texturePath, Texture.class); loader.addAssetToLoad(texturePath, Texture.class);
loader.addListener(this); loader.addListener(this);
}else{ }else{
// Else load the texture manually.
backgroundTexture = new Texture(Gdx.files.internal(texturePath)); backgroundTexture = new Texture(Gdx.files.internal(texturePath));
initGraphics(); initGraphics();
} }
// Load and compile the shader. If the shader failed to load or compile the disable the effect.
shader = new ShaderProgram(Gdx.files.internal(SHADER_PATH + "_vert.glsl"), Gdx.files.internal(SHADER_PATH + "_frag.glsl")); shader = new ShaderProgram(Gdx.files.internal(SHADER_PATH + "_vert.glsl"), Gdx.files.internal(SHADER_PATH + "_frag.glsl"));
if(!shader.isCompiled()){ if(!shader.isCompiled()){
Gdx.app.error(TAG, CLASS_NAME + ".ScrollingBackground() :: Failed to compile the shader."); Gdx.app.error(TAG, CLASS_NAME + ".ScrollingBackground() :: Failed to compile the shader.");
Gdx.app.error(TAG, CLASS_NAME + shader.getLog()); Gdx.app.error(TAG, CLASS_NAME + shader.getLog());
shader = null; shader = null;
u_scaling = 0;
u_displacement = 0;
}else{
// If the shader compiled fine then cache it's uniforms.
u_scaling = shader.getUniformLocation("u_scaling");
u_displacement = shader.getUniformLocation("u_displacement");
} }
u_scaling = shader.getUniformLocation("u_scaling"); // Set all other fields.
u_displacement = shader.getUniformLocation("u_displacement");
this.texturePath = texturePath; this.texturePath = texturePath;
this.scaling = scaling; this.scaling = scaling;
this.displacement = displacement; this.displacement = displacement;
} }
/**
* Render this effect.
*
* @param batch The {@link SpriteBatch} to use for the rendering.
* @throws IllegalStateException If the {@link SpriteBatch} did not call {@link SpriteBatch#begin()} befor this method was called.
*/
public void render(SpriteBatch batch) throws IllegalStateException{ public void render(SpriteBatch batch) throws IllegalStateException{
if(!batch.isDrawing()) if(!batch.isDrawing())
throw new IllegalStateException("Must be called between SpriteBatch.begin() and SpriteBatch.end()"); throw new IllegalStateException("Must be called between SpriteBatch.begin() and SpriteBatch.end()");
// If the shader was loaded then set it as the current shader.
if(shader != null){ if(shader != null){
batch.setShader(shader); batch.setShader(shader);
shader.setUniformf(u_scaling, scaling); shader.setUniformf(u_scaling, scaling);
shader.setUniformf(u_displacement, displacement); shader.setUniformf(u_displacement, displacement);
} }
// Render.
background.draw(batch); background.draw(batch);
if(shader != null) batch.setShader(null); // If the shader was loaded then disable it.
if(shader != null)
batch.setShader(null);
// Update the displacement a little bit.
// GOTCHA: This will look slower or faster depending on the speed of the computer.
displacement = displacement < 0.0f ? 1.0f : displacement - 0.0005f; displacement = displacement < 0.0f ? 1.0f : displacement - 0.0005f;
} }
@Override @Override
public void dispose(){ public void dispose(){
// Dispose all graphic assets.
backgroundTexture.dispose(); backgroundTexture.dispose();
if(shader != null) shader.dispose(); if(shader != null)
shader.dispose();
} }
@Override @Override
public void onAssetsLoaded(){ public void onAssetsLoaded(){
// Get the graphics and initialize them. Then release the assets loader.
backgroundTexture = loader.getAsset(texturePath, Texture.class); backgroundTexture = loader.getAsset(texturePath, Texture.class);
initGraphics(); initGraphics();
AsyncAssetLoader.freeInstance(); AsyncAssetLoader.freeInstance();
} }
/**
* Sets the graphic asset's parameters and creates the sprite.
*/
private void initGraphics(){ private void initGraphics(){
// Set up the texture. // Set up the texture.
backgroundTexture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); backgroundTexture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);

View File

@@ -15,6 +15,16 @@
*/ */
package com.gamejolt.mikykr5.ceidecpong.interfaces; package com.gamejolt.mikykr5.ceidecpong.interfaces;
public interface AssetsLoadedListener { import com.gamejolt.mikykr5.ceidecpong.utils.AsyncAssetLoader;
/**
* An interface for objects that want to be notified when {@link AsyncAssetLoader} has finished loading all requested assets.
*
* @author Miguel Astor
*/
public interface AssetsLoadedListener{
/**
* Called when {@link AsyncAssetLoader} has finished loading so that observers can fetch their assets.
*/
public void onAssetsLoaded(); public void onAssetsLoaded();
} }

View File

@@ -19,19 +19,53 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.Screen; import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.Vector3;
import com.gamejolt.mikykr5.ceidecpong.GameCore; import com.gamejolt.mikykr5.ceidecpong.GameCore;
/**
* Base class for all the game states.
*
* @author Miguel Astor
*/
public abstract class BaseState implements Screen, InputProcessor{ public abstract class BaseState implements Screen, InputProcessor{
/**
* Class used for logging.
*/
private static final String CLASS_NAME = BaseState.class.getSimpleName(); private static final String CLASS_NAME = BaseState.class.getSimpleName();
/**
* A {@link GameCore} instance to use it's {@link SpriteBatch}
*/
protected GameCore core; protected GameCore core;
protected boolean stateEnabled;
protected OrthographicCamera pixelPerfectCamera;
protected Vector3 win2world;
protected Vector2 touchPointWorldCoords;
/**
* A flag to indicate that the state is the currently active state.
*/
protected boolean stateEnabled;
/**
* A pixel perfect camera used to render all 2D assets for a concrete state instance.
*/
protected OrthographicCamera pixelPerfectCamera;
/**
* An auxiliary vector used to convert screen coordinates to world coordinates.
*/
protected final Vector3 win2world;
/**
* An auxiliary vector used to hold the unprojected world coordinates of a touch or click.
*/
protected final Vector2 touchPointWorldCoords;
/**
* Sets up all the general state fields.
*
* @param core The game core.
* @throws IllegalArgumentException If core is null.
*/
public BaseState(final GameCore core) throws IllegalArgumentException{ public BaseState(final GameCore core) throws IllegalArgumentException{
if(core == null) if(core == null)
throw new IllegalArgumentException(CLASS_NAME + ": Core is null."); throw new IllegalArgumentException(CLASS_NAME + ": Core is null.");

View File

@@ -15,6 +15,7 @@
*/ */
package com.gamejolt.mikykr5.ceidecpong.states; package com.gamejolt.mikykr5.ceidecpong.states;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.PooledEngine; import com.badlogic.ashley.core.PooledEngine;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input; import com.badlogic.gdx.Input;
@@ -40,31 +41,85 @@ import com.gamejolt.mikykr5.ceidecpong.ecs.systems.messaging.InterSystemMessage;
import com.gamejolt.mikykr5.ceidecpong.ecs.systems.messaging.InterSystemMessagingQueue; import com.gamejolt.mikykr5.ceidecpong.ecs.systems.messaging.InterSystemMessagingQueue;
import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener; import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener;
/**
* The state in charge of executing and handling the game itself.
*
* @author Miguel Astor
*/
public class InGameState extends BaseState implements AssetsLoadedListener{ public class InGameState extends BaseState implements AssetsLoadedListener{
/**
* The {@link Engine} responsible for handling the ECS design pattern.
*/
private PooledEngine engine; private PooledEngine engine;
/**
* The entity creator.
*/
private EntityInitializerBase entityInitializer; private EntityInitializerBase entityInitializer;
/**
* A {@link FrameBuffer} used to render to a logical screen. This way the game can be
* designed for a single screen resolution and scaled to the running device's screen resolution.
*/
private FrameBuffer frameBuffer; private FrameBuffer frameBuffer;
/**
* The screen width.
*/
private int w; private int w;
/**
* The screen height.
*/
private int h; private int h;
private final float oldRatio;
/**
* The aspect ratio of the logical screen.
*/
private final float fbAspectRatio;
/**
* Flag to indicate that all assets have been successfully loaded.
*/
private boolean assetsLoaded; private boolean assetsLoaded;
/**
* A pixel perfect camera used for rendering to the frame buffer.
*/
private OrthographicCamera fbCamera; private OrthographicCamera fbCamera;
/**
* The bounding rectangle of the frame buffer.
*/
private Rectangle fbBounds; private Rectangle fbBounds;
/**
* An auxiliary vector for input calculations.
*/
private final Vector3 temp; private final Vector3 temp;
/**
* Creates the state and the entity processing systems.
*
* @param core A GameCore instance. See {@link BaseState#BaseState(GameCore)}.
* @throws IllegalArgumentException If core is null;
*/
public InGameState(final GameCore core) throws IllegalArgumentException{ public InGameState(final GameCore core) throws IllegalArgumentException{
super(core); super(core);
// Initialize all fields.
engine = new PooledEngine(); engine = new PooledEngine();
frameBuffer = new FrameBuffer(Format.RGB565, ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT, false);
fbBounds = new Rectangle();
w = Gdx.graphics.getWidth(); w = Gdx.graphics.getWidth();
w = Gdx.graphics.getHeight(); w = Gdx.graphics.getHeight();
oldRatio = aspectRatio(ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT);
assetsLoaded = false; assetsLoaded = false;
fbCamera = new OrthographicCamera(ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT);
temp = new Vector3(); temp = new Vector3();
// Create the framebuffer.
frameBuffer = new FrameBuffer(Format.RGB565, ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT, false);
fbAspectRatio = aspectRatio(ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT);
fbCamera = new OrthographicCamera(ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT);
fbBounds = new Rectangle();
// Create all entities. // Create all entities.
entityInitializer = new PongEntityInitializer(); entityInitializer = new PongEntityInitializer();
entityInitializer.createAllEntities(engine); entityInitializer.createAllEntities(engine);
@@ -103,7 +158,7 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
// Scale the frame buffer to the current screen size. // Scale the frame buffer to the current screen size.
renderW = w; renderW = w;
renderH = renderW / oldRatio; renderH = renderW / fbAspectRatio;
// Set the rendering position of the frame buffer. // Set the rendering position of the frame buffer.
x = -(renderW / 2.0f); x = -(renderW / 2.0f);
@@ -135,10 +190,13 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
@Override @Override
public boolean keyDown(int keycode){ public boolean keyDown(int keycode){
// If the user pressed the escape key (the back button in Android) then go back
// to the main menu. Else ignore the key.
if(keycode == Input.Keys.BACK || keycode == Input.Keys.ESCAPE){ if(keycode == Input.Keys.BACK || keycode == Input.Keys.ESCAPE){
core.nextState = game_states_t.MAIN_MENU; core.nextState = game_states_t.MAIN_MENU;
return true; return true;
} }
return false; return false;
} }
@@ -146,6 +204,7 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
public boolean touchDown(int screenX, int screenY, int pointer, int button){ public boolean touchDown(int screenX, int screenY, int pointer, int button){
InterSystemMessage message; InterSystemMessage message;
// If the user touched the screen inside the frame buffer then notify the player positioning system.
if(touchInsideFrameBuffer(screenX, screenY)){ if(touchInsideFrameBuffer(screenX, screenY)){
message = new InterSystemMessage(HumanPlayerPositioningSystem.class.getCanonicalName()); message = new InterSystemMessage(HumanPlayerPositioningSystem.class.getCanonicalName());
message.data.put("INPUT_Y", convertWorldYToFrameBufferY(screenY)); message.data.put("INPUT_Y", convertWorldYToFrameBufferY(screenY));
@@ -159,6 +218,7 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
public boolean touchDragged(int screenX, int screenY, int pointer){ public boolean touchDragged(int screenX, int screenY, int pointer){
InterSystemMessage message; InterSystemMessage message;
// If the user touched the screen inside the frame buffer then notify the player positioning system.
if(touchInsideFrameBuffer(screenX, screenY)){ if(touchInsideFrameBuffer(screenX, screenY)){
message = new InterSystemMessage(HumanPlayerPositioningSystem.class.getCanonicalName()); message = new InterSystemMessage(HumanPlayerPositioningSystem.class.getCanonicalName());
message.data.put("INPUT_Y", convertWorldYToFrameBufferY(screenY)); message.data.put("INPUT_Y", convertWorldYToFrameBufferY(screenY));
@@ -174,12 +234,19 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
assetsLoaded = true; assetsLoaded = true;
} }
/**
* Checks if the user clicked or touched a point inside the frame buffer.
*
* @param screenX The X coordinate of the touch point.
* @param screenY The Y coordinate of the touch point.
* @return True if the touch point is inside the frame buffer.
*/
private boolean touchInsideFrameBuffer(int screenX, int screenY){ private boolean touchInsideFrameBuffer(int screenX, int screenY){
float fbW, fbH; float fbW, fbH;
unprojectTouch(screenX, screenY); unprojectTouch(screenX, screenY);
fbW = w; fbW = w;
fbH = fbW / oldRatio; fbH = fbW / fbAspectRatio;
fbBounds.set(-(fbW / 2.0f), -(fbH / 2.0f), fbW, fbH); fbBounds.set(-(fbW / 2.0f), -(fbH / 2.0f), fbW, fbH);
if(fbBounds.contains(touchPointWorldCoords)){ if(fbBounds.contains(touchPointWorldCoords)){
@@ -189,8 +256,14 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
} }
} }
/**
* Converts the Y coordinate of a touch point in screen coordinates to the world coordinates of the framebuffer.
*
* @param y The Y coordinate of a touch point.
* @return The Y coordinate converted to world coordinates.
*/
private float convertWorldYToFrameBufferY(float y){ private float convertWorldYToFrameBufferY(float y){
float fbH = h / oldRatio; float fbH = h / fbAspectRatio;
temp.set(0, y + (fbH / 2.0f), 0); temp.set(0, y + (fbH / 2.0f), 0);
fbCamera.unproject(temp, 0, 0, w, fbH); fbCamera.unproject(temp, 0, 0, w, fbH);

View File

@@ -77,7 +77,6 @@ public class LoadingState extends BaseState{
if(!loadingDone && loader != null){ if(!loadingDone && loader != null){
if(loader.loadAssets()){ if(loader.loadAssets()){
loader.notifyListeners();
loadingDone = true; loadingDone = true;
} }
} }

View File

@@ -22,35 +22,80 @@ import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Disposable;
import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener; import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener;
/**
* An singleton wrapper around an {@link AssetManager} object.
*
* @author Miguel Astor
*
*/
public final class AsyncAssetLoader implements Disposable{ public final class AsyncAssetLoader implements Disposable{
/**
* A list of all listeners registered with this loader.
*/
private LinkedList<AssetsLoadedListener> listeners; private LinkedList<AssetsLoadedListener> listeners;
/**
* The {@link AssetManager} used to load the assets.
*/
private AssetManager manager; private AssetManager manager;
/**
* Creates the listeners list and the assets manager. Made private so that this class cannot be
* instantiated outside of itself.
*/
private AsyncAssetLoader(){ private AsyncAssetLoader(){
listeners = new LinkedList<AssetsLoadedListener>(); listeners = new LinkedList<AssetsLoadedListener>();
manager = new AssetManager(); manager = new AssetManager();
} }
/**
* A holder for the singleton instance of {@link AsyncAssetLoader}.
*/
private static final class SingletonHolder{ private static final class SingletonHolder{
/**
* How many references to the singleton instance there are.
*/
public static int REF_COUNT = 0; public static int REF_COUNT = 0;
/**
* The singleton instance.
*/
public static AsyncAssetLoader INSTANCE; public static AsyncAssetLoader INSTANCE;
} }
/**
* Gets a reference to the singleton instance of this class.
*
* @return The singleton instance.
*/
public static AsyncAssetLoader getInstance(){ public static AsyncAssetLoader getInstance(){
// If the instance does not exists then create it and update it's reference counter.
if(SingletonHolder.REF_COUNT == 0) if(SingletonHolder.REF_COUNT == 0)
SingletonHolder.INSTANCE = new AsyncAssetLoader(); SingletonHolder.INSTANCE = new AsyncAssetLoader();
SingletonHolder.REF_COUNT++; SingletonHolder.REF_COUNT++;
return SingletonHolder.INSTANCE; return SingletonHolder.INSTANCE;
} }
/**
* Releases a reference to the singleton instance of this class.
*/
public static void freeInstance(){ public static void freeInstance(){
SingletonHolder.REF_COUNT--; SingletonHolder.REF_COUNT--;
// If there are no more references to the instance then delete it.
if(SingletonHolder.REF_COUNT <= 0){ if(SingletonHolder.REF_COUNT <= 0){
SingletonHolder.INSTANCE.dispose(); SingletonHolder.INSTANCE.dispose();
SingletonHolder.INSTANCE = null; SingletonHolder.INSTANCE = null;
} }
} }
/**
* Adds a new listener to the listeners queue.
*
* @param listener An {@link AssetsLoadedListener} instance.
* @throws IllegalArgumentException if listener is null.
*/
public void addListener(AssetsLoadedListener listener) throws IllegalArgumentException{ public void addListener(AssetsLoadedListener listener) throws IllegalArgumentException{
try{ try{
checkParametes(listener, "listener"); checkParametes(listener, "listener");
@@ -61,6 +106,13 @@ public final class AsyncAssetLoader implements Disposable{
listeners.add(listener); listeners.add(listener);
} }
/**
* Requests a new asset to be loaded.
*
* @param path The internal path of the asset.
* @param assetClass The class of the asset to load. Must be a class recognized by {@link AssetManager}.
* @throws IllegalArgumentException If either argument is null.
*/
public <T> void addAssetToLoad(String path, Class<T> assetClass) throws IllegalArgumentException{ public <T> void addAssetToLoad(String path, Class<T> assetClass) throws IllegalArgumentException{
try{ try{
checkParametes(path, "path"); checkParametes(path, "path");
@@ -72,6 +124,14 @@ public final class AsyncAssetLoader implements Disposable{
manager.load(path, assetClass); manager.load(path, assetClass);
} }
/**
* Fetches an asset from the manager after it has been loaded.
*
* @param path The internal path of the asset.
* @param assetClass The class of the asset to load. Must be a class recognized by {@link AssetManager}.
* @return The asset.
* @throws IllegalArgumentException If either argument is null.
*/
public <T> T getAsset(String path, Class<T> assetClass) throws IllegalArgumentException{ public <T> T getAsset(String path, Class<T> assetClass) throws IllegalArgumentException{
try{ try{
checkParametes(path, "path"); checkParametes(path, "path");
@@ -83,17 +143,37 @@ public final class AsyncAssetLoader implements Disposable{
return manager.get(path, assetClass); return manager.get(path, assetClass);
} }
/**
* Updates the {@link AssetManager}. Notifies all the registered listeners if the loading finished.
*
* @return True if all assets are loaded.
*/
public boolean loadAssets(){ public boolean loadAssets(){
return manager.update(); boolean done = manager.update();
if(done)
notifyListeners();
return done;
} }
public void notifyListeners(){ /**
* Notifies all listener objects that this loader has finished it's work.
*/
private void notifyListeners(){
for(AssetsLoadedListener listener : listeners) for(AssetsLoadedListener listener : listeners)
listener.onAssetsLoaded(); listener.onAssetsLoaded();
listeners.clear(); listeners.clear();
} }
/**
* Checks if the given parameter is null.
*
* @param parameter The parameter to check.
* @param paramName The name of the parameter to append to the exception if it is null.
* @throws IllegalArgumentException If parameter is null.
*/
private void checkParametes(Object parameter, String paramName) throws IllegalArgumentException{ private void checkParametes(Object parameter, String paramName) throws IllegalArgumentException{
if(parameter == null) throw new IllegalArgumentException("Parameter: " + paramName + " is null."); if(parameter == null) throw new IllegalArgumentException("Parameter: " + paramName + " is null.");
} }

View File

@@ -23,62 +23,125 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
/**
* A {@link BitmapFont} loader and manager with a cache.
*
* @author Miguel Astor
*/
public class CachedFontManager{ public class CachedFontManager{
/**
* The characters used by all the {@link BitmapFont} objects loaded.
*/
public static final String FONT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890:,"; public static final String FONT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890:,";
/**
* The standar font size for all loaded {@link BitmapFont} objects.
*/
public static final int BASE_FONT_SIZE = 40; public static final int BASE_FONT_SIZE = 40;
/**
* The cache of {@link BitmapFont} objects.
*/
private Map<String, BitmapFont> fonts; private Map<String, BitmapFont> fonts;
/**
* Creates the cache. Made private so that this class cannot be instantiated outside of itself.
*/
private CachedFontManager(){ private CachedFontManager(){
fonts = new HashMap<String, BitmapFont>(); fonts = new HashMap<String, BitmapFont>();
} }
/**
* A holder for the singleton instance of {@link CachedFontManager}.
*/
private static final class SingletonHolder{ private static final class SingletonHolder{
/**
* How many references to the singleton instance there are.
*/
public static int REF_COUNT = 0; public static int REF_COUNT = 0;
/**
* The singleton instance.
*/
public static CachedFontManager INSTANCE; public static CachedFontManager INSTANCE;
} }
/**
* Gets a reference to the singleton instance of this class.
*
* @return The singleton instance.
*/
public static CachedFontManager getInstance(){ public static CachedFontManager getInstance(){
// If the instance does not exists then create it and update it's reference counter.
if(SingletonHolder.REF_COUNT == 0) if(SingletonHolder.REF_COUNT == 0)
SingletonHolder.INSTANCE = new CachedFontManager(); SingletonHolder.INSTANCE = new CachedFontManager();
SingletonHolder.REF_COUNT++; SingletonHolder.REF_COUNT++;
return SingletonHolder.INSTANCE; return SingletonHolder.INSTANCE;
} }
/**
* Releases a reference to the singleton instance of this class.
*/
public static void freeInstance(){ public static void freeInstance(){
SingletonHolder.REF_COUNT--; SingletonHolder.REF_COUNT--;
// If there are no more references to the instance then delete it.
if(SingletonHolder.REF_COUNT <= 0){ if(SingletonHolder.REF_COUNT <= 0){
SingletonHolder.INSTANCE.dispose(); SingletonHolder.INSTANCE.dispose();
SingletonHolder.INSTANCE = null; SingletonHolder.INSTANCE = null;
} }
} }
/**
* Loads a {@link BitmapFont} with the given path and {@link CachedFontManager#BASE_FONT_SIZE} as default size.
*
* @param path The internal path of the font to load.
* @return The font.
*/
public BitmapFont loadFont(String path){ public BitmapFont loadFont(String path){
return loadFont(path, BASE_FONT_SIZE); return loadFont(path, BASE_FONT_SIZE);
} }
/**
* Loads a {@link BitmapFont} with the given path and size.
*
* @param path The internal path of the font to load.
* @param size The size of the font to load.
* @return
*/
public BitmapFont loadFont(String path, int size){ public BitmapFont loadFont(String path, int size){
// If the font is already in the cache the return it.
// GOTCHA: The same font cannot be loaded with two different sizes.
if(fonts.containsKey(path)) if(fonts.containsKey(path))
return fonts.get(path); return fonts.get(path);
// Declare all variables used to create a font.
FreeTypeFontGenerator fontGenerator; FreeTypeFontGenerator fontGenerator;
FreeTypeFontParameter fontParameters; FreeTypeFontParameter fontParameters;
BitmapFont font; BitmapFont font;
// Set the parameters of the font to load.
fontParameters = new FreeTypeFontParameter(); fontParameters = new FreeTypeFontParameter();
fontParameters.characters = FONT_CHARS; fontParameters.characters = FONT_CHARS;
fontParameters.size = size; fontParameters.size = size;
fontParameters.flip = false; fontParameters.flip = false;
// Create a new bitmap font from the given TrueType font.
fontGenerator = new FreeTypeFontGenerator(Gdx.files.internal(path)); fontGenerator = new FreeTypeFontGenerator(Gdx.files.internal(path));
font = fontGenerator.generateFont(fontParameters); font = fontGenerator.generateFont(fontParameters);
fonts.put(path, font); fonts.put(path, font);
// Clean the font generator.
fontGenerator.dispose(); fontGenerator.dispose();
return font; return font;
} }
/**
* Removes a specific {@link BitmapFont} from the cache and disposes it.
* @param path
*/
public void unloadFont(String path){ public void unloadFont(String path){
if(fonts.containsKey(path)){ if(fonts.containsKey(path)){
fonts.get(path).dispose(); fonts.get(path).dispose();
@@ -86,9 +149,13 @@ public class CachedFontManager{
} }
} }
/**
* Removes and disposes all the loaded fonts.
*/
private void dispose(){ private void dispose(){
Gdx.app.log("FONT_MANAGER", "Disposing fonts."); Gdx.app.log("FONT_MANAGER", "Disposing fonts.");
//
for(BitmapFont font : fonts.values()) for(BitmapFont font : fonts.values())
font.dispose(); font.dispose();
fonts.clear(); fonts.clear();

View File

@@ -21,33 +21,72 @@ import java.util.Map;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.audio.Sound;
/**
* A {@link Sound} effect loader and manager with a cache.
*
* @author Miguel Astor
*/
public class CachedSoundManager { public class CachedSoundManager {
/**
* The cache of {@link Sound} objects.
*/
private Map<String, Sound> sounds; private Map<String, Sound> sounds;
/**
* Creates the cache. Made private so that this class cannot be instantiated outside of itself.
*/
private CachedSoundManager(){ private CachedSoundManager(){
sounds = new HashMap<String, Sound>(); sounds = new HashMap<String, Sound>();
} }
/**
* A holder for the singleton instance of {@link CachedSoundManager}.
*/
private static final class SingletonHolder{ private static final class SingletonHolder{
/**
* How many references to the singleton instance there are.
*/
public static int REF_COUNT = 0; public static int REF_COUNT = 0;
/**
* The singleton instance.
*/
public static CachedSoundManager INSTANCE; public static CachedSoundManager INSTANCE;
} }
/**
* Gets a reference to the singleton instance of this class.
*
* @return The singleton instance.
*/
public static CachedSoundManager getInstance(){ public static CachedSoundManager getInstance(){
// If the instance does not exists then create it and update it's reference counter.
if(SingletonHolder.REF_COUNT == 0) if(SingletonHolder.REF_COUNT == 0)
SingletonHolder.INSTANCE = new CachedSoundManager(); SingletonHolder.INSTANCE = new CachedSoundManager();
SingletonHolder.REF_COUNT++; SingletonHolder.REF_COUNT++;
return SingletonHolder.INSTANCE; return SingletonHolder.INSTANCE;
} }
/**
* Releases a reference to the singleton instance of this class.
*/
public static void freeInstance(){ public static void freeInstance(){
SingletonHolder.REF_COUNT--; SingletonHolder.REF_COUNT--;
// If there are no more references to the instance then delete it.
if(SingletonHolder.REF_COUNT <= 0){ if(SingletonHolder.REF_COUNT <= 0){
SingletonHolder.INSTANCE.dispose(); SingletonHolder.INSTANCE.dispose();
SingletonHolder.INSTANCE = null; SingletonHolder.INSTANCE = null;
} }
} }
/**
* Loads a {@link Sound} effect with the given path.
*
* @param path The internal path of the sound effect to load.
* @return The sound effect.
*/
public Sound loadSound(String path){ public Sound loadSound(String path){
if(sounds.containsKey(path)) if(sounds.containsKey(path))
return sounds.get(path); return sounds.get(path);
@@ -58,6 +97,10 @@ public class CachedSoundManager {
return s; return s;
} }
/**
* Removes a specific {@link Sound} from the cache and disposes it.
* @param path
*/
public void unloadSound(String path){ public void unloadSound(String path){
if(sounds.containsKey(path)){ if(sounds.containsKey(path)){
sounds.get(path).dispose(); sounds.get(path).dispose();
@@ -65,6 +108,9 @@ public class CachedSoundManager {
} }
} }
/**
* Removes and disposes all the loaded sounds.
*/
private void dispose(){ private void dispose(){
Gdx.app.log("SOUND_MANAGER", "Disposing sounds."); Gdx.app.log("SOUND_MANAGER", "Disposing sounds.");