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.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 {
/**
* Tag used for logging.
*/
private static final String TAG = "GAME_CORE";
/**
* Class name used for logging.
*/
private static final String CLASS_NAME = GameCore.class.getSimpleName();
/**
* An enumerated type used for state switching.
*/
public enum game_states_t {
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;
/**
* A pointer to the state to switch to. Usually null.
*/
public game_states_t nextState;
/**
* An array to hold all application states.
*/
private BaseState[] states;
/**
* The {@link SpriteBatch} used to render all 2D graphics in the game.
*/
public SpriteBatch batch;
/**
* A pixel perfect camera used to render the fade effects.
*/
private OrthographicCamera pixelPerfectCamera;
// Fade in/out effect fields.
/**
* The fade graphic.
*/
private Texture fadeTexture;
/**
* A {@link MutableFloat} used to interpolate the transparency of {@link GameCore#fadeTexture}.
*/
private MutableFloat alpha;
/**
* A {@link Tween} instance used to interpolate between full transparency to no transparency.
*/
private Tween fadeOut;
/**
* A {@link Tween} instance used to interpolate between no transparency to full transparency.
*/
private Tween fadeIn;
/**
* A flag to indicate that a fade effect is in progress.
*/
private boolean fading;
@Override
@@ -78,7 +132,7 @@ public class GameCore extends Game {
AsyncAssetLoader loader = AsyncAssetLoader.getInstance();
// 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.enableBlending();
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);
pixmap.dispose();
// Create the initial interpolators and start with a fade in effect.
alpha = new MutableFloat(1.0f);
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);
@@ -112,6 +167,7 @@ public class GameCore extends Game {
return;
}
// Register every state as an AssetsLoadedListener if the state implements the interface.
for(BaseState state : states){
if(state != null && state instanceof AssetsLoadedListener)
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(nextState != null){
// First disable the current state so that it will no longer catch user inputs.
states[currState.getValue()].onStateDisabled();
if(!fadeOut.isStarted()){
@@ -198,12 +255,13 @@ public class GameCore extends Game {
public void dispose(){
super.dispose();
// Dispose screens.
// Dispose all states.
for(BaseState state : states){
if(state != null)
state.dispose();
}
// Dispose other graphics.
fadeTexture.dispose();
batch.dispose();
}

View File

@@ -15,10 +15,34 @@
*/
package com.gamejolt.mikykr5.ceidecpong;
/**
* This class holds some project-wise constants.
*
* @author Miguel Astor
*/
public abstract class ProjectConstants{
/**
* What to return when the application terminates successfully.
*/
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 boolean DEBUG = true;
/**
* Enable/disable logging.
*/
public static final boolean DEBUG = false;
/**
* Logical screen width.
*/
public static final int FB_WIDTH = 1920;
/**
* Logical screen height.
*/
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.utils.Pool.Poolable;
/**
* A 2D bounding rectangle component.
*
* @author Miguel Astor
*/
public class BoundingBoxComponent extends Component implements Poolable {
/**
* The bounding rectangle.
*/
public Rectangle bbox;
public BoundingBoxComponent() {

View File

@@ -17,10 +17,14 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
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 static final ComponentMapper<PositionComponent> positionMapper = ComponentMapper.getFor(PositionComponent.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<BoundingBoxComponent> bboxMapper = ComponentMapper.getFor(BoundingBoxComponent.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.gdx.utils.Pool.Poolable;
/**
* A Player identity {@link Component}
*
* @author Miguel Astor
*/
public class PlayerComponent extends Component implements Poolable {
public static int HUMAN_PLAYER = 0;
public static int COMPUTER_PLAYER = 1;
public static final int HUMAN_PLAYER = 0;
public static final int COMPUTER_PLAYER = 1;
/**
* An identifier used to determine the type of player.
*/
public int id = -1;
@Override

View File

@@ -18,10 +18,28 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.components;
import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.utils.Pool.Poolable;
/**
* A 2D position {@link Component}.
*
* @author Miguel Astor.
*/
public class PositionComponent extends Component implements Poolable {
/**
* The X coordinate.
*/
public float x = 0;
/**
* The Y coordinate.
*/
public float y = 0;
/**
* Sets both coordinates simultaneously.
*
* @param x
* @param y
*/
public void setXY(float x, float y){
this.x = x;
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.gdx.utils.Pool.Poolable;
/**
* A {@link Component} to hold a player's score.
*
* @author Miguel Astor
*/
public class ScoreComponent extends Component implements Poolable{
/**
* The score.
*/
public int score = 0;
@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;
import com.badlogic.ashley.core.Component;
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 {
/**
* The path of the sound effect. Used as a key by {@link CachedSoundManager}.
*/
public String path = "";
@Override

View File

@@ -19,7 +19,15 @@ import com.badlogic.ashley.core.Component;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.utils.Pool.Poolable;
/**}
* A 2D renderable {@link Component}.
*
* @author Miguel Astor
*/
public class SpriteComponent extends Component implements Poolable {
/**
* The renderable sprite.
*/
public Sprite sprite;
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.gdx.utils.Pool.Poolable;
/**
* A 2D velocity {@link Component}
*
* @author Miguel Astor
*/
public class VelocityComponent extends Component implements Poolable {
/**
* The velocity in the X axis.
*/
public float vx = 0;
/**
* The velocity in the Y axis.
*/
public float vy = 0;
/**
* Sets both fields simultaneously.
* @param vx
* @param vy
*/
public void setXY(float vx, float vy){
this.vx = vx;
this.vy = vy;

View File

@@ -15,10 +15,29 @@
*/
package com.gamejolt.mikykr5.ceidecpong.ecs.entities;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.PooledEngine;
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{
/**
* 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);
/**
* 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;
}

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.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;
/**
* An entity that plays a sound when the user scores.
*/
private Entity victorySound;
/**
* An entity that plays a sound when the computer scores.
*/
private Entity defeatSound;
/**
* An entity that represents the ball in the game.
*/
private Entity ball;
/**
* An entity that represents the human player.
*/
private Entity paddleUser;
/**
* An entity that represents the computer player.
*/
private Entity paddleComp;
/**
* An entity that is used to render the background.
*/
private Entity background;
/**
* Flag that indicates that all entities have been created.
*/
private boolean entitiesCreated;
/**
* Flag that indicates that all assets associated to entities have been loaded.
*/
private boolean assetsLoaded;
/**
* Create the initializer and set the flags to false.
*/
public PongEntityInitializer() {
entitiesCreated = false;
assetsLoaded = false;
@@ -52,25 +96,29 @@ public class PongEntityInitializer extends EntityInitializerBase {
@Override
public void createAllEntities(PooledEngine engine){
// Get instances of the needed asset loaders.
loader = AsyncAssetLoader.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/bckg.png", Texture.class);
soundManager.loadSound("data/sfx/BounceYoFrankie.ogg");
soundManager.loadSound("data/sfx/oh_yeah_wav_cut.ogg");
soundManager.loadSound("data/sfx/atari_boom.ogg");
// Create the entities related to the sound effects.
victorySound = engine.createEntity();
victorySound.add(engine.createComponent(SoundComponent.class));
defeatSound = engine.createEntity();
defeatSound.add(engine.createComponent(SoundComponent.class));
// Create the background.
background = engine.createEntity();
background.add(engine.createComponent(PositionComponent.class));
background.add(engine.createComponent(SpriteComponent.class));
// Create the ball.
ball = engine.createEntity();
ball.add(engine.createComponent(PositionComponent.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(SoundComponent.class));
// Create the human player.
paddleUser = engine.createEntity();
paddleUser.add(engine.createComponent(PositionComponent.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(PlayerComponent.class));
// Create the computer player.
paddleComp = engine.createEntity();
paddleComp.add(engine.createComponent(PositionComponent.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(PlayerComponent.class));
// Register all entities.
engine.addEntity(victorySound);
engine.addEntity(defeatSound);
engine.addEntity(background);
@@ -101,6 +152,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
engine.addEntity(paddleUser);
engine.addEntity(paddleComp);
// Mark the flag.
entitiesCreated = true;
}
@@ -109,33 +161,42 @@ public class PongEntityInitializer extends EntityInitializerBase {
if(!entitiesCreated)
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));
int randomSign = MathUtils.random(-1, 1) >= 0 ? 1 : -1;
// Fetch the assets.
TextureAtlas atlas = loader.getAsset("data/gfx/textures/pong_atlas.atlas", TextureAtlas.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(defeatSound).path = "data/sfx/atari_boom.ogg";
// Set up the background.
Mappers.spriteMapper.get(background).sprite = new Sprite(bckg);
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.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.bboxMapper.get(ball).bbox.set(Mappers.spriteMapper.get(ball).sprite.getBoundingRectangle());
Mappers.soundMapper.get(ball).path = "data/sfx/BounceYoFrankie.ogg";
// Set up the human player.
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.bboxMapper.get(paddleUser).bbox.set(Mappers.spriteMapper.get(paddleUser).sprite.getBoundingRectangle());
Mappers.playerMapper.get(paddleUser).id = PlayerComponent.HUMAN_PLAYER;
// Set up the computer player.
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.bboxMapper.get(paddleComp).bbox.set(Mappers.spriteMapper.get(paddleComp).sprite.getBoundingRectangle());
Mappers.playerMapper.get(paddleComp).id = PlayerComponent.COMPUTER_PLAYER;
// Release the assets loader instance and mark the flag.
AsyncAssetLoader.freeInstance();
assetsLoaded = true;
}
@@ -148,6 +209,7 @@ public class PongEntityInitializer extends EntityInitializerBase {
if(!assetsLoaded)
throw new IllegalStateException("Assets have not been loaded before disposing.");
// Release the sound manager instance.
CachedSoundManager.freeInstance();
}
}

View File

@@ -3,10 +3,30 @@ package com.gamejolt.mikykr5.ceidecpong.ecs.systems.messaging;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.ashley.core.EntitySystem;
/**
* A message from a {@link EntitySystem }to another.
*
* @author Miguel Astor
*/
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;
/**
* A holder for arbitrary data.
*/
public final Map<String, Object> data;
/**
* Creates a new message object.
*
* @param target The receiver of the message.
*/
public InterSystemMessage(String target){
this.target = target;
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.Queue;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.gdx.Gdx;
/**
* A messaging queue to communicate two {@link EntitySystem} instances.
*
* @author Miguel Astor
*/
public abstract class InterSystemMessagingQueue{
/**
* The message queue.
*/
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{
if(message == null)
throw new IllegalArgumentException("Message is null");
@@ -15,6 +30,14 @@ public abstract class InterSystemMessagingQueue{
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{
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.utils.AsyncAssetLoader;
/**
* A reusable scrolling infinite background similar to those used commonly in PSX games.
*
* @author Miguel Astor
*/
public class ScrollingBackground implements Disposable, AssetsLoadedListener{
/**
* Tag used for logging.
*/
private static final String TAG = "SCROLLING_BACKGROUND";
/**
* Class name used for logging.
*/
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";
/**
* The assets loader instance.
*/
private AsyncAssetLoader loader;
/**
* The texture to render.
*/
private Texture backgroundTexture;
/**
* An sprite to hold the texture.
*/
private Sprite background;
/**
* A compiled shader object used during rendering.
*/
private ShaderProgram shader;
/**
* Holds the location of the u_scaling variable of the shader.
*/
private int u_scaling;
/**
* Holds the location of the u_displacement variable of the shader.
*/
private int u_displacement;
/**
* The scaling to apply to the texture.
*/
private float scaling;
/**
* The displacement to apply to the texture.
*/
private float displacement;
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){
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){
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){
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){
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){
if(loadAsync){
// If an asynchronous load was requested then use the assets loader.
loader = AsyncAssetLoader.getInstance();
loader.addAssetToLoad(texturePath, Texture.class);
loader.addListener(this);
}else{
// Else load the texture manually.
backgroundTexture = new Texture(Gdx.files.internal(texturePath));
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"));
if(!shader.isCompiled()){
Gdx.app.error(TAG, CLASS_NAME + ".ScrollingBackground() :: Failed to compile the shader.");
Gdx.app.error(TAG, CLASS_NAME + shader.getLog());
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");
u_displacement = shader.getUniformLocation("u_displacement");
// Set all other fields.
this.texturePath = texturePath;
this.scaling = scaling;
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{
if(!batch.isDrawing())
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){
batch.setShader(shader);
shader.setUniformf(u_scaling, scaling);
shader.setUniformf(u_displacement, displacement);
}
// Render.
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;
}
@Override
public void dispose(){
// Dispose all graphic assets.
backgroundTexture.dispose();
if(shader != null) shader.dispose();
if(shader != null)
shader.dispose();
}
@Override
public void onAssetsLoaded(){
// Get the graphics and initialize them. Then release the assets loader.
backgroundTexture = loader.getAsset(texturePath, Texture.class);
initGraphics();
AsyncAssetLoader.freeInstance();
}
/**
* Sets the graphic asset's parameters and creates the sprite.
*/
private void initGraphics(){
// Set up the texture.
backgroundTexture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);

View File

@@ -15,6 +15,16 @@
*/
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();
}

View File

@@ -19,19 +19,53 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.gamejolt.mikykr5.ceidecpong.GameCore;
/**
* Base class for all the game states.
*
* @author Miguel Astor
*/
public abstract class BaseState implements Screen, InputProcessor{
/**
* Class used for logging.
*/
private static final String CLASS_NAME = BaseState.class.getSimpleName();
/**
* A {@link GameCore} instance to use it's {@link SpriteBatch}
*/
protected GameCore core;
/**
* 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;
protected Vector3 win2world;
protected Vector2 touchPointWorldCoords;
/**
* 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{
if(core == null)
throw new IllegalArgumentException(CLASS_NAME + ": Core is null.");

View File

@@ -15,6 +15,7 @@
*/
package com.gamejolt.mikykr5.ceidecpong.states;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.PooledEngine;
import com.badlogic.gdx.Gdx;
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.interfaces.AssetsLoadedListener;
/**
* The state in charge of executing and handling the game itself.
*
* @author Miguel Astor
*/
public class InGameState extends BaseState implements AssetsLoadedListener{
/**
* The {@link Engine} responsible for handling the ECS design pattern.
*/
private PooledEngine engine;
/**
* The entity creator.
*/
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;
/**
* The screen width.
*/
private int w;
/**
* The screen height.
*/
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;
/**
* A pixel perfect camera used for rendering to the frame buffer.
*/
private OrthographicCamera fbCamera;
/**
* The bounding rectangle of the frame buffer.
*/
private Rectangle fbBounds;
/**
* An auxiliary vector for input calculations.
*/
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{
super(core);
// Initialize all fields.
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.getHeight();
oldRatio = aspectRatio(ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT);
assetsLoaded = false;
fbCamera = new OrthographicCamera(ProjectConstants.FB_WIDTH, ProjectConstants.FB_HEIGHT);
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.
entityInitializer = new PongEntityInitializer();
entityInitializer.createAllEntities(engine);
@@ -103,7 +158,7 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
// Scale the frame buffer to the current screen size.
renderW = w;
renderH = renderW / oldRatio;
renderH = renderW / fbAspectRatio;
// Set the rendering position of the frame buffer.
x = -(renderW / 2.0f);
@@ -135,10 +190,13 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
@Override
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){
core.nextState = game_states_t.MAIN_MENU;
return true;
}
return false;
}
@@ -146,6 +204,7 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
public boolean touchDown(int screenX, int screenY, int pointer, int button){
InterSystemMessage message;
// If the user touched the screen inside the frame buffer then notify the player positioning system.
if(touchInsideFrameBuffer(screenX, screenY)){
message = new InterSystemMessage(HumanPlayerPositioningSystem.class.getCanonicalName());
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){
InterSystemMessage message;
// If the user touched the screen inside the frame buffer then notify the player positioning system.
if(touchInsideFrameBuffer(screenX, screenY)){
message = new InterSystemMessage(HumanPlayerPositioningSystem.class.getCanonicalName());
message.data.put("INPUT_Y", convertWorldYToFrameBufferY(screenY));
@@ -174,12 +234,19 @@ public class InGameState extends BaseState implements AssetsLoadedListener{
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){
float fbW, fbH;
unprojectTouch(screenX, screenY);
fbW = w;
fbH = fbW / oldRatio;
fbH = fbW / fbAspectRatio;
fbBounds.set(-(fbW / 2.0f), -(fbH / 2.0f), fbW, fbH);
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){
float fbH = h / oldRatio;
float fbH = h / fbAspectRatio;
temp.set(0, y + (fbH / 2.0f), 0);
fbCamera.unproject(temp, 0, 0, w, fbH);

View File

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

View File

@@ -22,35 +22,80 @@ import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.utils.Disposable;
import com.gamejolt.mikykr5.ceidecpong.interfaces.AssetsLoadedListener;
/**
* An singleton wrapper around an {@link AssetManager} object.
*
* @author Miguel Astor
*
*/
public final class AsyncAssetLoader implements Disposable{
/**
* A list of all listeners registered with this loader.
*/
private LinkedList<AssetsLoadedListener> listeners;
/**
* The {@link AssetManager} used to load the assets.
*/
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(){
listeners = new LinkedList<AssetsLoadedListener>();
manager = new AssetManager();
}
/**
* A holder for the singleton instance of {@link AsyncAssetLoader}.
*/
private static final class SingletonHolder{
/**
* How many references to the singleton instance there are.
*/
public static int REF_COUNT = 0;
/**
* The singleton instance.
*/
public static AsyncAssetLoader INSTANCE;
}
/**
* Gets a reference to the singleton instance of this class.
*
* @return The singleton instance.
*/
public static AsyncAssetLoader getInstance(){
// If the instance does not exists then create it and update it's reference counter.
if(SingletonHolder.REF_COUNT == 0)
SingletonHolder.INSTANCE = new AsyncAssetLoader();
SingletonHolder.REF_COUNT++;
return SingletonHolder.INSTANCE;
}
/**
* Releases a reference to the singleton instance of this class.
*/
public static void freeInstance(){
SingletonHolder.REF_COUNT--;
// If there are no more references to the instance then delete it.
if(SingletonHolder.REF_COUNT <= 0){
SingletonHolder.INSTANCE.dispose();
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{
try{
checkParametes(listener, "listener");
@@ -61,6 +106,13 @@ public final class AsyncAssetLoader implements Disposable{
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{
try{
checkParametes(path, "path");
@@ -72,6 +124,14 @@ public final class AsyncAssetLoader implements Disposable{
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{
try{
checkParametes(path, "path");
@@ -83,17 +143,37 @@ public final class AsyncAssetLoader implements Disposable{
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(){
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)
listener.onAssetsLoaded();
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{
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.FreeTypeFontParameter;
/**
* A {@link BitmapFont} loader and manager with a cache.
*
* @author Miguel Astor
*/
public class CachedFontManager{
/**
* The characters used by all the {@link BitmapFont} objects loaded.
*/
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;
/**
* The cache of {@link BitmapFont} objects.
*/
private Map<String, BitmapFont> fonts;
/**
* Creates the cache. Made private so that this class cannot be instantiated outside of itself.
*/
private CachedFontManager(){
fonts = new HashMap<String, BitmapFont>();
}
/**
* A holder for the singleton instance of {@link CachedFontManager}.
*/
private static final class SingletonHolder{
/**
* How many references to the singleton instance there are.
*/
public static int REF_COUNT = 0;
/**
* The singleton instance.
*/
public static CachedFontManager INSTANCE;
}
/**
* Gets a reference to the singleton instance of this class.
*
* @return The singleton instance.
*/
public static CachedFontManager getInstance(){
// If the instance does not exists then create it and update it's reference counter.
if(SingletonHolder.REF_COUNT == 0)
SingletonHolder.INSTANCE = new CachedFontManager();
SingletonHolder.REF_COUNT++;
return SingletonHolder.INSTANCE;
}
/**
* Releases a reference to the singleton instance of this class.
*/
public static void freeInstance(){
SingletonHolder.REF_COUNT--;
// If there are no more references to the instance then delete it.
if(SingletonHolder.REF_COUNT <= 0){
SingletonHolder.INSTANCE.dispose();
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){
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){
// 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))
return fonts.get(path);
// Declare all variables used to create a font.
FreeTypeFontGenerator fontGenerator;
FreeTypeFontParameter fontParameters;
BitmapFont font;
// Set the parameters of the font to load.
fontParameters = new FreeTypeFontParameter();
fontParameters.characters = FONT_CHARS;
fontParameters.size = size;
fontParameters.flip = false;
// Create a new bitmap font from the given TrueType font.
fontGenerator = new FreeTypeFontGenerator(Gdx.files.internal(path));
font = fontGenerator.generateFont(fontParameters);
fonts.put(path, font);
// Clean the font generator.
fontGenerator.dispose();
return font;
}
/**
* Removes a specific {@link BitmapFont} from the cache and disposes it.
* @param path
*/
public void unloadFont(String path){
if(fonts.containsKey(path)){
fonts.get(path).dispose();
@@ -86,9 +149,13 @@ public class CachedFontManager{
}
}
/**
* Removes and disposes all the loaded fonts.
*/
private void dispose(){
Gdx.app.log("FONT_MANAGER", "Disposing fonts.");
//
for(BitmapFont font : fonts.values())
font.dispose();
fonts.clear();

View File

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