initial commit

This commit is contained in:
Gered 2013-07-01 12:10:36 -04:00
commit d052cbfa9d
36 changed files with 2888 additions and 0 deletions

7
.classpath Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="src" path="src"/>
<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/gdx-toolbox-libs"/>
<classpathentry kind="output" path="bin"/>
</classpath>

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
.DS_Store
out/
log/
target/
.settings/
*.iml
*.eml
*.class
*.jar

15
.project Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>gdx-toolbox</name>
<comment/>
<projects/>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,81 @@
package com.blarg.gdx;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.TimeUtils;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.graphics.RenderContext;
import com.blarg.gdx.states.StateManager;
public abstract class GameApp implements Disposable {
public final EventManager eventManager;
public final StateManager stateManager;
public final RenderContext renderContext;
boolean logHeapMemUsage = false;
long lastHeapMemLogTime = 0;
public GameApp() {
Gdx.app.debug("GameApp", "ctor");
eventManager = new EventManager();
stateManager = new StateManager(this, eventManager);
renderContext = new RenderContext(true);
}
protected void toggleHeapMemUsageLogging(boolean enable) {
logHeapMemUsage = enable;
if (enable)
lastHeapMemLogTime = TimeUtils.millis();
}
public abstract void onCreate();
public void onResize(int width, int height) {
Gdx.app.debug("GameApp", String.format("onResize(%d, %d)", width, height));
renderContext.onResize(width, height);
}
public void onRender(float delta) {
renderContext.onPreRender();
stateManager.onRender(delta, renderContext);
renderContext.onPostRender();
}
public void onUpdate(float delta) {
renderContext.onUpdate(delta);
stateManager.onUpdate(delta);
if (stateManager.isEmpty()) {
Gdx.app.debug("GameApp", "No states running. Quitting.");
Gdx.app.exit();
return;
}
if (logHeapMemUsage) {
long currentTime = TimeUtils.millis();
if (currentTime - lastHeapMemLogTime > 1000) {
lastHeapMemLogTime = currentTime;
Gdx.app.debug("GameApp", String.format("Heap memory usage: %d", Gdx.app.getJavaHeap()));
}
}
}
public void onPause() {
Gdx.app.debug("GameApp", "onPause");
stateManager.onAppPause();
renderContext.onPause();
}
public void onResume() {
Gdx.app.debug("GameApp", "onResume");
renderContext.onResume();
stateManager.onAppResume();
}
@Override
public void dispose() {
Gdx.app.debug("GameApp", "dispose");
stateManager.dispose();
renderContext.dispose();
}
}

View file

@ -0,0 +1,67 @@
package com.blarg.gdx;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
public class GdxGameAppListener implements ApplicationListener {
Class<? extends GameApp> gameAppType;
GameApp gameApp;
public GdxGameAppListener(Class<? extends GameApp> gameAppType) {
this.gameAppType = gameAppType;
}
@Override
public void create() {
Gdx.app.debug("GdxGameAppListener", "create");
Gdx.app.debug("GdxGameAppListener", String.format("Application type: %s", Gdx.app.getType()));
try {
gameApp = gameAppType.newInstance();
} catch (Exception e) {
Gdx.app.log("GdxGameAppListener", String.format("Instantiation of GameApp object failed: %s", e));
gameApp = null;
}
if (gameApp == null) {
Gdx.app.log("GdxGameAppListener", "Failed to create a GameApp. Aborting.");
Gdx.app.exit();
return;
}
gameApp.onCreate();
}
@Override
public void resize(int width, int height) {
Gdx.app.debug("GdxGameAppListener", String.format("resize(%d, %d)", width, height));
gameApp.onResize(width, height);
}
@Override
public void render() {
// TODO: probably not the best idea to share the same delta with both renders and updates...
float delta = Gdx.graphics.getDeltaTime();
gameApp.onUpdate(delta);
gameApp.onRender(delta);
}
@Override
public void pause() {
Gdx.app.debug("GdxGameAppListener", "pause");
gameApp.onPause();
}
@Override
public void resume() {
Gdx.app.debug("GdxGameAppListener", "resume");
gameApp.onResume();
}
@Override
public void dispose() {
Gdx.app.debug("GdxGameAppListener", "dispose");
if (gameApp != null)
gameApp.dispose();
}
}

View file

@ -0,0 +1,23 @@
package com.blarg.gdx;
import java.lang.reflect.Constructor;
public class ReflectionUtils {
public static <T> T instantiateObject(Class<T> type, Class<?>[] constructorArgTypes, Object[] constructorArgValues) throws Exception {
Constructor<T> constructor;
try {
constructor = type.getConstructor(constructorArgTypes);
} catch (NoSuchMethodException e) {
throw new Exception("No constructor found with these argument types.");
}
T instance;
try {
instance = constructor.newInstance(constructorArgValues);
} catch (Exception e) {
throw new Exception("Could not create new instance of this class.", e);
}
return instance;
}
}

View file

@ -0,0 +1,7 @@
package com.blarg.gdx;
public final class Strings {
public static boolean isNullOrEmpty(String s) {
return (s == null || s.isEmpty());
}
}

View file

@ -0,0 +1,6 @@
package com.blarg.gdx.entities;
import com.badlogic.gdx.utils.Pool;
public abstract class Component implements Pool.Poolable {
}

View file

@ -0,0 +1,38 @@
package com.blarg.gdx.entities;
import com.blarg.gdx.events.Event;
import com.blarg.gdx.events.EventHandler;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.graphics.RenderContext;
public abstract class ComponentSystem extends EventHandler {
public final EntityManager entityManager;
public ComponentSystem(EntityManager entityManager, EventManager eventManager) {
super(eventManager);
if (entityManager == null)
throw new IllegalArgumentException("entityManager can not be null.");
this.entityManager = entityManager;
}
public void onAppPause() {
}
public void onAppResume() {
}
public void onResize() {
}
public void onRender(float delta, RenderContext renderContext) {
}
public void onUpdate(float delta) {
}
@Override
public boolean handle(Event e) {
return false;
}
}

View file

@ -0,0 +1,37 @@
package com.blarg.gdx.entities;
// Yes, this class SHOULD be marked final. No, you ARE wrong for wanting to subclass this.
// There IS a better way to do what you were thinking of doing that DOESN'T involve
// subclassing Entity!
// Still don't agree with me? Read this (or really, any other article on entity systems in games):
// http://t-machine.org/index.php/2010/05/09/entity-system-1-javaandroid/
public final class Entity {
EntityManager entityManager;
/**
* Do not instantiate Entity's directly. Use EntityManager.add().
*/
public Entity(EntityManager entityManager) {
if (entityManager == null)
throw new IllegalArgumentException("entityManager can not be null.");
this.entityManager = entityManager;
}
public <T extends Component> T get(Class<T> componentType) {
return entityManager.getComponent(componentType, this);
}
public <T extends Component> T add(Class<T> componentType) {
return entityManager.addComponent(componentType, this);
}
public <T extends Component> void remove(Class<T> componentType) {
entityManager.removeComponent(componentType, this);
}
public <T extends Component> boolean has(Class<T> componentType) {
return entityManager.hasComponent(componentType, this);
}
}

View file

@ -0,0 +1,263 @@
package com.blarg.gdx.entities;
import com.badlogic.gdx.utils.*;
import com.blarg.gdx.entities.systemcomponents.InactiveComponent;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.ReflectionUtils;
import com.blarg.gdx.graphics.RenderContext;
public class EntityManager {
public final EventManager eventManager;
ObjectSet<Entity> entities;
ObjectMap<Class<? extends Component>, ObjectMap<Entity, Component>> componentStore;
ObjectMap<Class<? extends Component>, Component> globalComponents;
Array<ComponentSystem> componentSystems;
Pool<Entity> entityPool = new Pool<Entity>() {
@Override
protected Entity newObject() {
return new Entity(EntityManager.this);
}
};
ObjectMap<Entity, Component> empty;
public EntityManager(EventManager eventManager) {
if (eventManager == null)
throw new IllegalArgumentException("eventManager can not be null.");
this.eventManager = eventManager;
entities = new ObjectSet<Entity>();
componentStore = new ObjectMap<Class<? extends Component>, ObjectMap<Entity, Component>>();
globalComponents = new ObjectMap<Class<? extends Component>, Component>();
componentSystems = new Array<ComponentSystem>();
// possibly ugliness, but this allows us to return empty.keys() in getAllWith()
// when there are no entities with a given component, preventing the calling code
// from needing to check for null before a for-loop
empty = new ObjectMap<Entity, Component>();
}
/*** public ComponentSystem management */
public <T extends ComponentSystem> T addSubsystem(Class<T> componentSystemType) {
if (getSubsystem(componentSystemType) != null)
throw new UnsupportedOperationException("ComponentSystem of that type is already registered.");
T subsystem;
try {
subsystem = ReflectionUtils.instantiateObject(componentSystemType,
new Class<?>[] { EntityManager.class, EventManager.class },
new Object[] { this, eventManager });
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate this type of ComponentSystem.", e);
}
componentSystems.add(subsystem);
return subsystem;
}
public <T extends ComponentSystem> T getSubsystem(Class<T> componentSystemType) {
int i = getSubsystemIndex(componentSystemType);
if (i == -1)
return null;
else
return componentSystemType.cast(componentSystems.get(i));
}
public <T extends ComponentSystem> void removeSubsystem(Class<T> componentSystemType) {
int i = getSubsystemIndex(componentSystemType);
if (i == -1)
return;
componentSystems.removeIndex(i);
}
public void removeAllSubsystems() {
componentSystems.clear();
}
/*** public Entity management ***/
public Entity add() {
Entity entity = entityPool.obtain();
entities.add(entity);
return entity;
}
public <T extends Component> Entity getFirstWith(Class<T> componentType) {
ObjectMap<Entity, Component> componentEntities = componentStore.get(componentType);
if (componentEntities == null)
return null;
if (componentEntities.size > 0)
return componentEntities.keys().next();
else
return null;
}
public <T extends Component> ObjectMap.Keys<Entity> getAllWith(Class<T> componentType) {
ObjectMap<Entity, Component> componentEntities = componentStore.get(componentType);
if (componentEntities == null)
return empty.keys(); // calling code won't need to check for null
else
return componentEntities.keys();
}
public void remove(Entity entity) {
if (entity == null)
throw new IllegalArgumentException("entity can not be null.");
removeAllComponentsFrom(entity);
entities.remove(entity);
entityPool.free(entity);
}
public void removeAll() {
for (Entity i : entities)
removeAllComponentsFrom(i);
entities.clear();
}
/*** public Entity Component management ***/
public <T extends Component> T addComponent(Class<T> componentType, Entity entity) {
if (getComponent(componentType, entity) != null)
throw new UnsupportedOperationException("Component of that type has been added to this entity already.");
// find the component-to-entity list for this component type, or create it if it doesn't exist yet
ObjectMap<Entity, Component> componentEntities = componentStore.get(componentType);
if (componentEntities == null) {
componentEntities = new ObjectMap<Entity, Component>();
componentStore.put(componentType, componentEntities);
}
T component = Pools.obtain(componentType);
componentEntities.put(entity, component);
return componentType.cast(component);
}
public <T extends Component> T getComponent(Class<T> componentType, Entity entity) {
ObjectMap<Entity, Component> componentEntities = componentStore.get(componentType);
if (componentEntities == null)
return null;
Component existing = componentEntities.get(entity);
if (existing == null)
return null;
else
return componentType.cast(existing);
}
public <T extends Component> void removeComponent(Class<T> componentType, Entity entity) {
ObjectMap<Entity, Component> componentEntities = componentStore.get(componentType);
if (componentEntities == null)
return;
Component component = componentEntities.remove(entity);
Pools.free(component);
}
public <T extends Component> boolean hasComponent(Class<T> componentType, Entity entity) {
ObjectMap<Entity, Component> componentEntities = componentStore.get(componentType);
if (componentEntities == null)
return false;
return componentEntities.containsKey(entity);
}
public void getAllComponentsFor(Entity entity, Array<Component> list) {
if (list == null)
throw new IllegalArgumentException("list can not be null.");
for (ObjectMap.Entry<Class<? extends Component>, ObjectMap<Entity, Component>> i : componentStore.entries()) {
ObjectMap<Entity, Component> entitiesWithComponent = i.value;
Component component = entitiesWithComponent.get(entity);
if (component != null)
list.add(component);
}
}
/*** global component management ***/
public <T extends Component> T addGlobal(Class<T> componentType) {
if (getGlobal(componentType) != null)
throw new UnsupportedOperationException("Global component of that type has been added already.");
T component = Pools.obtain(componentType);
globalComponents.put(componentType, component);
return componentType.cast(component);
}
public <T extends Component> T getGlobal(Class<T> componentType) {
Component existing = globalComponents.get(componentType);
if (existing == null)
return null;
else
return componentType.cast(existing);
}
public <T extends Component> void removeGlobal(Class<T> componentType) {
Component component = globalComponents.remove(componentType);
Pools.free(component);
}
public <T extends Component> boolean hasGlobal(Class<T> componentType) {
return globalComponents.containsKey(componentType);
}
/*** events ***/
public void onAppResume() {
for (int i = 0; i < componentSystems.size; ++i)
componentSystems.get(i).onAppResume();
}
public void onAppPause() {
for (int i = 0; i < componentSystems.size; ++i)
componentSystems.get(i).onAppPause();
}
public void onResize() {
for (int i = 0; i < componentSystems.size; ++i)
componentSystems.get(i).onResize();
}
public void onRender(float delta, RenderContext renderContext) {
for (int i = 0; i < componentSystems.size; ++i)
componentSystems.get(i).onRender(delta, renderContext);
}
public void onUpdate(float delta) {
for (Entity i : getAllWith(InactiveComponent.class))
remove(i);
for (int i = 0; i < componentSystems.size; ++i)
componentSystems.get(i).onUpdate(delta);
}
/*** private Entity/Component management ***/
private void removeAllComponentsFrom(Entity entity) {
if (entity == null)
throw new IllegalArgumentException("entity can not be null.");
for (ObjectMap.Entry<Class<? extends Component>, ObjectMap<Entity, Component>> i : componentStore.entries()) {
ObjectMap<Entity, Component> entitiesWithComponent = i.value;
entitiesWithComponent.remove(entity);
}
}
private <T extends ComponentSystem> int getSubsystemIndex(Class<T> componentSystemType) {
for (int i = 0; i < componentSystems.size; ++i) {
if (componentSystems.get(i).getClass() == componentSystemType)
return i;
}
return -1;
}
}

View file

@ -0,0 +1,9 @@
package com.blarg.gdx.entities.systemcomponents;
import com.blarg.gdx.entities.Component;
public class InactiveComponent extends Component {
@Override
public void reset() {
}
}

View file

@ -0,0 +1,6 @@
package com.blarg.gdx.events;
import com.badlogic.gdx.utils.Pool;
public abstract class Event implements Pool.Poolable {
}

View file

@ -0,0 +1,22 @@
package com.blarg.gdx.events;
// "EventHandler" is a poor name, but better then what I used to call it: "EventListenerEx"
public abstract class EventHandler implements EventListener {
public final EventManager eventManager;
public EventHandler(EventManager eventManager) {
if (eventManager == null)
throw new IllegalArgumentException("eventManager can not be null.");
this.eventManager = eventManager;
}
public <T extends Event> boolean listenFor(Class<T> eventType) {
return eventManager.addListener(eventType, this);
}
public <T extends Event> boolean stopListeningFor(Class<T> eventType) {
return eventManager.removeListener(eventType, this);
}
}

View file

@ -0,0 +1,5 @@
package com.blarg.gdx.events;
public interface EventListener {
boolean handle(Event e);
}

View file

@ -0,0 +1,183 @@
package com.blarg.gdx.events;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectSet;
import com.badlogic.gdx.utils.Pools;
import java.util.LinkedList;
@SuppressWarnings("unchecked")
public class EventManager {
static final int NUM_EVENT_QUEUES = 2;
ObjectSet<Class<? extends Event>> typeList;
ObjectMap<Class<? extends Event>, Array<EventListener>> registry;
LinkedList<Event>[] queues;
int activeQueue;
public EventManager() {
Gdx.app.debug("EventManager", "ctor");
typeList = new ObjectSet<Class<? extends Event>>();
registry = new ObjectMap<Class<? extends Event>, Array<EventListener>>();
queues = new LinkedList[NUM_EVENT_QUEUES];
for (int i = 0; i < queues.length; ++i)
queues[i] = new LinkedList<Event>();
activeQueue = 0;
}
public <T extends Event> boolean addListener(Class<T> eventType, EventListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener can not be null.");
Array<EventListener> listeners = registry.get(eventType);
if (listeners == null) {
// need to register this listener for the given type
listeners = new Array<EventListener>();
registry.put(eventType, listeners);
}
if (listeners.contains(listener, true))
throw new IllegalArgumentException("Duplicate event listener registration.");
listeners.add(listener);
Gdx.app.debug("EventManager", String.format("Added listener for event type: %s", eventType.getSimpleName()));
// also update the list of currently registered event types
typeList.add(eventType);
return true;
}
public <T extends Event> boolean removeListener(Class<T> eventType, EventListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener can not be null.");
// get the listeners for this event type
Array<EventListener> listeners = registry.get(eventType);
if (listeners == null || !listeners.contains(listener, true))
return false; // either no listeners for this type, or the listener wasn't registered with us
listeners.removeValue(listener, true);
Gdx.app.debug("EventManager", String.format("Removed listener for event type: %s", eventType.getSimpleName()));
// if there are no more listeners for this type, remove the event type
// from the list of registered event types
if (listeners.size == 0)
typeList.remove(eventType);
return true;
}
public boolean trigger(Event e) {
if (e == null)
throw new IllegalArgumentException("event can not be null.");
Class<? extends Event> type = e.getClass().asSubclass(Event.class);
// find the listeners for this event type
Array<EventListener> listeners = registry.get(type);
if (listeners == null)
return false; // no listeners for this event type have been registered -- we can't handle the event
// trigger event in each listener
boolean result = false;
for (EventListener listener : listeners) {
if (listener.handle(e)) {
result = true;
break; // don't let other listeners handle the event if this one signals it handled it
}
}
// TODO: maybe, for trigger() only, it's better to force the calling code
// to "putback" the event object being triggered? since we handle the
// event immediately, unlike with queue() where it makes a lot more
// sense for us to place it back in the pool ourselves ...
free(e);
// a result of "false" merely indicates that no listener indicates that it "handled" the event
return result;
}
public boolean queue(Event e) {
if (e == null)
throw new IllegalArgumentException("event can not be null.");
// validate that there is infact a listener for this event type
// (otherwise, we don't queue this event)
Class<? extends Event> type = e.getClass().asSubclass(Event.class);
if (!typeList.contains(type))
return false;
queues[activeQueue].add(e);
return true;
}
public <T extends Event> boolean abort(Class<T> eventType) {
return abort(eventType, true);
}
public <T extends Event> boolean abort(Class<T> eventType, boolean stopAfterFirstRemoval) {
// validate that there is infact a listener for this event type
// (otherwise, we don't queue this event)
if (!typeList.contains(eventType))
return false;
boolean result = false;
// walk through the queue and remove matching events
LinkedList<Event> queue = queues[activeQueue];
int i = 0;
while (i < queue.size()) {
if (queue.get(i).getClass() == eventType) {
Event e = queue.remove(i);
free(e);
result = true;
if (stopAfterFirstRemoval)
break;
} else
i++;
}
return result;
}
public boolean onUpdate(float delta) {
// swap active queues and empty the new queue
int queueToProcess = activeQueue;
activeQueue = (activeQueue + 1) % NUM_EVENT_QUEUES;
queues[activeQueue].clear();
// process the "old" queue
LinkedList<Event> queue = queues[queueToProcess];
while (queue.size() > 0) {
Event e = queue.pop();
Class<? extends Event> type = e.getClass().asSubclass(Event.class);
// find the listeners for this event type
Array<EventListener> listeners = registry.get(type);
if (listeners != null) {
for (EventListener listener : listeners) {
if (listener.handle(e))
break; // don't let other listeners handle the event if this one signals it handled it
}
}
free(e);
}
return true;
}
public <T extends Event> T create(Class<T> eventType) {
return Pools.obtain(eventType);
}
public <T extends Event> void free(T event) {
Pools.free(event);
}
}

View file

@ -0,0 +1,12 @@
package com.blarg.gdx.graphics;
/***
* <p>
* Wrapper over libgdx's included {@link DecalBatch} with automatic easy management of
* {@link Decal} objects. This is intended to make "on the fly" rendering of decals/billboards
* as easy as rendering 2D sprites is with SpriteBatch / DelayedSpriteBatch.
* </p>
*/
public class BillboardSpriteBatch {
}

View file

@ -0,0 +1,47 @@
package com.blarg.gdx.graphics;
public class DefaultScreenPixelScaler implements ScreenPixelScaler {
int scale = 0;
int viewportWidth = 0;
int viewportHeight = 0;
int scaledViewportWidth = 0;
int scaledViewportHeight = 0;
@Override
public int getScale() {
return scale;
}
@Override
public int getScaledWidth() {
return scaledViewportWidth;
}
@Override
public int getScaledHeight() {
return scaledViewportHeight;
}
@Override
public void calculateScale(int screenWidth, int screenHeight) {
viewportWidth = screenWidth;
viewportHeight = screenHeight;
// TODO: these might need tweaking, this is fairly arbitrary
if (viewportWidth < 640 || viewportHeight < 480)
scale = 1;
else if (viewportWidth < 960 || viewportHeight < 720)
scale = 2;
else if (viewportWidth < 1280 || viewportHeight < 960)
scale = 3;
else if (viewportWidth < 1920 || viewportHeight < 1080)
scale = 4;
else
scale = 5;
// TODO: desktop "retina" / 4K display sizes? 1440p?
scaledViewportWidth = viewportWidth / scale;
scaledViewportHeight = viewportHeight / scale;
}
}

View file

@ -0,0 +1,203 @@
package com.blarg.gdx.graphics;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
/***
* <p>
* Wrapper over libgdx's included {@link SpriteBatch} which doesn't manipulate _any_ OpenGL
* state until the call to {@link SpriteBatch#end()}. This allows a begin/end block to
* wrap over a large amount of code that might manipulate OpenGL state one or more times.
* Sprites rendered with this are simply queued up until the end() call and rendered in that
* same order with {@link SpriteBatch}.
* </p>
*
* <p>
* This "delayed" queueing behaviour will not necessarily be desirable behaviour-wise /
* performance-wise in all situations. This class introduces a small bit of performance overhead
* compared to using {@link SpriteBatch} directly.
* </p>
*/
public class DelayedSpriteBatch {
static final int CAPACITY_INCREMEMT = 128;
Array<Sprite> sprites;
int pointer;
boolean hasBegun;
SpriteBatch spriteBatch;
public DelayedSpriteBatch() {
sprites = new Array<Sprite>(true, CAPACITY_INCREMEMT, Sprite.class);
pointer = 0;
increaseCapacity();
hasBegun = false;
spriteBatch = null;
}
public void begin(SpriteBatch spriteBatch) {
if (hasBegun)
throw new IllegalStateException("Cannot be called within an existing begin/end block.");
if (spriteBatch == null)
throw new IllegalArgumentException();
this.spriteBatch = spriteBatch;
hasBegun = true;
pointer = 0;
}
public void draw(Texture texture, float x, float y) {
draw(texture, x, y, texture.getWidth(), texture.getHeight(), Color.WHITE);
}
public void draw(Texture texture, float x, float y, Color tint) {
draw(texture, x, y, texture.getWidth(), texture.getHeight(), tint);
}
public void draw(Texture texture, float x, float y, float width, float height) {
draw(texture, x, y, width, height, Color.WHITE);
}
public void draw(Texture texture, float x, float y, float width, float height, Color tint) {
Sprite sprite = nextUsable();
sprite.setTexture(texture);
sprite.setRegion(0, 0, texture.getWidth(), texture.getHeight());
sprite.setColor(tint);
sprite.setSize(width, height);
sprite.setPosition(x, y);
}
public void draw(Texture texture, float x, float y, float width, float height, float u, float v, float u2, float v2) {
draw(texture, x, y, width, height, u, v, u2, v2, Color.WHITE);
}
public void draw(Texture texture, float x, float y, float width, float height, float u, float v, float u2, float v2, Color tint) {
Sprite sprite = nextUsable();
sprite.setTexture(texture);
sprite.setRegion(u, v, u2, v2);
sprite.setColor(tint);
sprite.setSize(width, height);
sprite.setPosition(x, y);
}
public void draw(Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight) {
draw(texture, x, y, srcX, srcY, srcWidth, srcHeight, Color.WHITE);
}
public void draw(Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight, Color tint) {
Sprite sprite = nextUsable();
sprite.setTexture(texture);
sprite.setRegion(srcX, srcY, srcWidth, srcHeight);
sprite.setColor(tint);
sprite.setSize(Math.abs(srcWidth), Math.abs(srcHeight));
sprite.setPosition(x, y);
}
public void draw(TextureRegion region, float x, float y) {
draw(region, x, y, region.getRegionWidth(), region.getRegionWidth(), Color.WHITE);
}
public void draw(TextureRegion region, float x, float y, Color tint) {
draw(region, x, y, region.getRegionWidth(), region.getRegionWidth(), tint);
}
public void draw(TextureRegion region, float x, float y, float width, float height) {
draw(region, x, y, width, height, Color.WHITE);
}
public void draw(TextureRegion region, float x, float y, float width, float height, Color tint) {
Sprite sprite = nextUsable();
sprite.setRegion(region);
sprite.setColor(tint);
sprite.setSize(width, height);
sprite.setPosition(x, y);
}
public void draw(BitmapFont font, float x, float y, CharSequence str) {
draw(font, x, y, str, Color.WHITE);
}
public void draw(BitmapFont font, float x, float y, CharSequence str, Color tint) {
BitmapFont.BitmapFontData fontData = font.getData();
Texture fontTexture = font.getRegion().getTexture();
float currentX = x;
float currentY = y;
for (int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
// multiline support
if (c == '\r')
continue; // can't render this anyway, and likely a '\n' is right behind ...
if (c == '\n') {
currentY -= fontData.lineHeight;
currentX = x;
continue;
}
BitmapFont.Glyph glyph = fontData.getGlyph(c);
if (glyph == null) {
// TODO: maybe rendering some special char here instead would be better?
currentX += fontData.spaceWidth;
continue;
}
draw(fontTexture, currentX + glyph.xoffset, currentY + glyph.yoffset, glyph.srcX, glyph.srcY, glyph.width, glyph.height, tint);
currentX += glyph.xadvance;
}
}
public void flush() {
if (!hasBegun)
throw new IllegalStateException("Cannot call outside of a begin/end block.");
spriteBatch.setColor(Color.WHITE);
spriteBatch.begin();
for (int i = 0; i < pointer; ++i) {
Sprite sprite = sprites.items[i];
sprite.draw(spriteBatch);
sprite.setTexture(null); // don't keep references!
}
spriteBatch.end();
pointer = 0;
}
public void end() {
if (!hasBegun)
throw new IllegalStateException("Must call begin() first.");
flush();
hasBegun = false;
spriteBatch = null; // don't need to hold on to this particular reference anymore
}
private void increaseCapacity() {
int newCapacity = sprites.items.length + CAPACITY_INCREMEMT;
sprites.ensureCapacity(newCapacity);
for (int i = 0; i < CAPACITY_INCREMEMT; ++i)
sprites.add(new Sprite());
}
private int getRemainingSpace() {
return sprites.size - pointer;
}
private Sprite nextUsable() {
if (getRemainingSpace() == 0)
increaseCapacity();
Sprite usable = sprites.items[pointer];
pointer++;
return usable;
}
}

View file

@ -0,0 +1,25 @@
package com.blarg.gdx.graphics;
import com.badlogic.gdx.Gdx;
public class NoScaleScreenPixelScaler implements ScreenPixelScaler {
@Override
public int getScale() {
return 1;
}
@Override
public int getScaledWidth() {
return Gdx.graphics.getWidth();
}
@Override
public int getScaledHeight() {
return Gdx.graphics.getHeight();
}
@Override
public void calculateScale(int screenWidth, int screenHeight) {
// nothing!
}
}

View file

@ -0,0 +1,107 @@
package com.blarg.gdx.graphics;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.Disposable;
public class RenderContext implements Disposable {
public final SpriteBatch spriteBatch;
public final DelayedSpriteBatch delayedSpriteBatch;
public final ShapeRenderer debugGeometryRenderer;
public final ModelBatch modelBatch;
public final ScreenPixelScaler pixelScaler;
public final SolidColorTextureCache solidColorTextures;
Camera perspectiveCamera;
OrthographicCamera orthographicCamera;
public RenderContext(boolean use2dPixelScaling) {
Gdx.app.debug("RenderContext", "ctor");
spriteBatch = new SpriteBatch();
delayedSpriteBatch = new DelayedSpriteBatch();
debugGeometryRenderer = new ShapeRenderer();
modelBatch = new ModelBatch();
solidColorTextures = new SolidColorTextureCache();
if (use2dPixelScaling)
pixelScaler = new DefaultScreenPixelScaler();
else
pixelScaler = new NoScaleScreenPixelScaler();
pixelScaler.calculateScale(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
orthographicCamera = new OrthographicCamera(pixelScaler.getScaledWidth(), pixelScaler.getScaledHeight());
setDefaultPerspectiveCamera();
}
public Camera getPerspectiveCamera() {
return perspectiveCamera;
}
public OrthographicCamera getOrthographicCamera() {
return orthographicCamera;
}
public void setPerspectiveCamera(Camera camera) {
perspectiveCamera = camera;
}
public void setDefaultPerspectiveCamera() {
perspectiveCamera = new PerspectiveCamera(60.0f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
public void clear() {
clear(0.0f, 0.0f, 0.0f, 1.0f);
}
public void clear(float red, float green, float blue, float alpha) {
Gdx.graphics.getGL20().glClearColor(red, green, blue, alpha);
Gdx.graphics.getGL20().glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
}
public void onPreRender() {
spriteBatch.setProjectionMatrix(orthographicCamera.combined);
debugGeometryRenderer.begin(ShapeRenderer.ShapeType.Line);
delayedSpriteBatch.begin(spriteBatch);
modelBatch.begin(perspectiveCamera);
}
public void onPostRender() {
modelBatch.end();
delayedSpriteBatch.end();
debugGeometryRenderer.end();
}
public void onUpdate(float delta) {
perspectiveCamera.update();
orthographicCamera.update();
}
public void onResize(int width, int height) {
Gdx.app.debug("RenderContext", String.format("onResize(%d, %d)", width, height));
pixelScaler.calculateScale(width, height);
orthographicCamera.setToOrtho(false, pixelScaler.getScaledWidth(), pixelScaler.getScaledHeight());
}
public void onPause() {
Gdx.app.debug("RenderContext", String.format("onPause"));
solidColorTextures.onPause();
}
public void onResume() {
Gdx.app.debug("RenderContext", String.format("onResume"));
solidColorTextures.onResume();
}
@Override
public void dispose() {
Gdx.app.debug("RenderContext", String.format("dispose"));
solidColorTextures.dispose();
spriteBatch.dispose();
}
}

View file

@ -0,0 +1,10 @@
package com.blarg.gdx.graphics;
public interface ScreenPixelScaler {
int getScale();
int getScaledWidth();
int getScaledHeight();
void calculateScale(int screenWidth, int screenHeight);
}

View file

@ -0,0 +1,57 @@
package com.blarg.gdx.graphics;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.ObjectMap;
public class SolidColorTextureCache implements Disposable
{
ObjectMap<Integer, Texture> cache;
public SolidColorTextureCache() {
cache = new ObjectMap<Integer, Texture>();
}
public void onResume() {
}
public void onPause() {
}
public Texture get(Color color) {
return get(Color.rgba8888(color));
}
public Texture get(float r, float g, float b, float a) {
return get(Color.rgba8888(r, g, b, a));
}
public Texture get(int color) {
Texture tex = cache.get(color);
if (tex == null) {
tex = create(color);
cache.put(color, tex);
}
return tex;
}
private Texture create(int color) {
Pixmap pixmap = new Pixmap(8, 8, Pixmap.Format.RGBA8888);
pixmap.setColor(color);
pixmap.fill();
Texture result = new Texture(pixmap);
return result;
}
public void dispose() {
for (ObjectMap.Entries<Integer, Texture> i = cache.entries(); i.hasNext(); ) {
ObjectMap.Entry<Integer, Texture> entry = i.next();
entry.value.dispose();
}
cache.clear();
}
}

View file

@ -0,0 +1,16 @@
package com.blarg.gdx.graphics;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
public final class TTF
{
public static BitmapFont make(String file, int size) {
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal(file));
BitmapFont result = generator.generateFont(size);
generator.dispose();
return result;
}
}

View file

@ -0,0 +1,38 @@
package com.blarg.gdx.graphics.screeneffects;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.blarg.gdx.graphics.RenderContext;
public class DimScreenEffect extends ScreenEffect
{
public static final Color DEFAULT_DIM_COLOR = Color.BLACK;
public static final float DEFAULT_DIM_ALPHA = 0.5f;
public final Color color;
public float alpha;
Color renderColor;
public DimScreenEffect()
{
color = new Color(DEFAULT_DIM_COLOR);
alpha = DEFAULT_DIM_ALPHA;
renderColor = new Color(color);
}
@Override
public void onRender(float delta, RenderContext renderContext)
{
renderColor.set(color);
renderColor.a = alpha;
Texture texture = renderContext.solidColorTextures.get(color);
renderContext.delayedSpriteBatch.draw(
texture,
0, 0,
renderContext.pixelScaler.getScaledWidth(), renderContext.pixelScaler.getScaledHeight(),
renderColor);
}
}

View file

@ -0,0 +1,15 @@
package com.blarg.gdx.graphics.screeneffects;
class EffectInfo
{
public final ScreenEffect effect;
public boolean isLocal;
public EffectInfo(ScreenEffect effect, boolean isLocal) {
if (effect == null)
throw new IllegalArgumentException("effect can not be null.");
this.effect = effect;
this.isLocal = isLocal;
}
}

View file

@ -0,0 +1,89 @@
package com.blarg.gdx.graphics.screeneffects;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.blarg.gdx.graphics.RenderContext;
public class FadeScreenEffect extends ScreenEffect
{
public static final float DEFAULT_FADE_SPEED = 3.0f;
float fadeSpeed;
boolean isFadingOut;
float alpha;
Color color;
float fadeToAlpha;
boolean isDoneFading;
public FadeScreenEffect() {
color = new Color();
}
public boolean isDoneFading() {
return isDoneFading;
}
public void fadeOut(float toAlpha, Color color) {
fadeOut(alpha, color, DEFAULT_FADE_SPEED);
}
public void fadeOut(float toAlpha, Color color, float speed) {
if (toAlpha < 0.0f || toAlpha > 1.0f)
throw new IllegalArgumentException("toAlpha needs to be between 0.0 and 1.0");
color.set(color);
fadeSpeed = speed;
isFadingOut = true;
alpha = 0.0f;
fadeToAlpha = toAlpha;
}
public void fadeIn(float toAlpha, Color color) {
fadeIn(alpha, color, DEFAULT_FADE_SPEED);
}
public void fadeIn(float toAlpha, Color color, float speed) {
if (toAlpha < 0.0f || toAlpha > 1.0f)
throw new IllegalArgumentException("toAlpha needs to be between 0.0 and 1.0");
color.set(color);
fadeSpeed = speed;
isFadingOut = false;
alpha = 1.0f;
fadeToAlpha = toAlpha;
}
@Override
public void onRender(float delta, RenderContext renderContext)
{
Texture texture = renderContext.solidColorTextures.get(Color.WHITE);
color.a = alpha;
renderContext.delayedSpriteBatch.draw(
texture,
0, 0,
renderContext.pixelScaler.getScaledWidth(), renderContext.pixelScaler.getScaledHeight(),
color);
}
@Override
public void onUpdate(float delta)
{
if (isDoneFading)
return;
if (isFadingOut) {
alpha += (delta + fadeSpeed);
if (alpha >= fadeToAlpha) {
alpha = fadeToAlpha;
isDoneFading = true;
}
} else {
alpha -= (delta + fadeSpeed);
if (alpha < fadeToAlpha) {
alpha = fadeToAlpha;
isDoneFading = true;
}
}
}
}

View file

@ -0,0 +1,63 @@
package com.blarg.gdx.graphics.screeneffects;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.blarg.gdx.graphics.RenderContext;
public class FlashScreenEffect extends ScreenEffect
{
public static final float DEFAULT_FLASH_SPEED = 16.0f;
public static final float DEFAULT_MAX_INTENSITY = 1.0f;
public float flashInSpeed;
public float flashOutSpeed;
public float maximumIntensity;
public final Color color;
boolean isFlashingIn;
float alpha;
public float getAlpha() {
return alpha;
}
public FlashScreenEffect() {
isFlashingIn = true;
flashInSpeed = DEFAULT_FLASH_SPEED;
flashOutSpeed = DEFAULT_FLASH_SPEED;
maximumIntensity = DEFAULT_MAX_INTENSITY;
color = new Color(Color.WHITE);
}
@Override
public void onRender(float delta, RenderContext renderContext)
{
Texture texture = renderContext.solidColorTextures.get(Color.WHITE);
color.a = alpha;
renderContext.delayedSpriteBatch.draw(
texture,
0, 0,
renderContext.pixelScaler.getScaledWidth(), renderContext.pixelScaler.getScaledHeight(),
color);
}
@Override
public void onUpdate(float delta)
{
if (isFlashingIn) {
alpha += (delta * flashInSpeed);
if (alpha >= maximumIntensity) {
alpha = maximumIntensity;
isFlashingIn = false;
}
} else {
alpha -= (delta * flashOutSpeed);
if (alpha < 0.0f)
alpha = 0.0f;
}
if (alpha == 0.0f && isFlashingIn == false)
isActive = false;
}
}

View file

@ -0,0 +1,37 @@
package com.blarg.gdx.graphics.screeneffects;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.graphics.RenderContext;
public abstract class ScreenEffect implements Disposable
{
public boolean isActive;
public ScreenEffect() {
isActive = true;
}
public void onAdd() {
}
public void onRemove() {
}
public void onAppPause() {
}
public void onAppResume() {
}
public void onResize() {
}
public void onRender(float delta, RenderContext renderContext) {
}
public void onUpdate(float delta) {
}
public void dispose() {
}
}

View file

@ -0,0 +1,157 @@
package com.blarg.gdx.graphics.screeneffects;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.graphics.RenderContext;
import java.util.LinkedList;
public class ScreenEffectManager implements Disposable
{
LinkedList<EffectInfo> effects;
int numLocalEffects;
int numGlobalEffects;
public ScreenEffectManager() {
effects = new LinkedList<EffectInfo>();
numLocalEffects = 0;
numGlobalEffects = 0;
}
/*** Get / Add / Remove ***/
public <T extends ScreenEffect> T add(Class<T> effectType) {
return add(effectType, true);
}
public <T extends ScreenEffect> T add(Class<T> effectType, boolean isLocalEffect) {
int existingIndex = getIndexFor(effectType);
if (existingIndex != -1)
throw new UnsupportedOperationException("Cannot add an effect of the same type as an existing effect already being managed.");
T newEffect;
try {
newEffect = effectType.newInstance();
} catch (Exception e) {
return null;
}
EffectInfo effectInfo = new EffectInfo(newEffect, isLocalEffect);
add(effectInfo);
return newEffect;
}
public <T extends ScreenEffect> T get(Class<T> effectType) {
int index = getIndexFor(effectType);
if (index == -1)
return null;
else
return effectType.cast(effects.get(index).effect);
}
public <T extends ScreenEffect> void remove(Class<T> effectType) {
int existingIndex = getIndexFor(effectType);
if (existingIndex != -1)
remove(existingIndex);
}
public void removeAll() {
while (effects.size() > 0)
remove(0);
}
private void add(EffectInfo newEffectInfo) {
if (newEffectInfo == null)
throw new IllegalArgumentException("newEffectInfo cannot be null.");
effects.add(newEffectInfo);
newEffectInfo.effect.onAdd();
if (newEffectInfo.isLocal)
++numLocalEffects;
else
++numGlobalEffects;
}
private void remove(int index) {
if (index < 0 || index >= effects.size())
throw new IllegalArgumentException("Invalid effect index.");
EffectInfo effectInfo = effects.get(index);
if (effectInfo.isLocal)
--numLocalEffects;
else
--numGlobalEffects;
effectInfo.effect.onRemove();
effectInfo.effect.dispose();
effects.remove(index);
}
/*** events ***/
public void onAppPause() {
for (int i = 0; i < effects.size(); ++i)
effects.get(i).effect.onAppPause();
}
public void onAppResume() {
for (int i = 0; i < effects.size(); ++i)
effects.get(i).effect.onAppResume();
}
public void onResize() {
for (int i = 0; i < effects.size(); ++i)
effects.get(i).effect.onResize();
}
public void onRenderLocal(float delta, RenderContext renderContext) {
if (numLocalEffects == 0)
return;
for (int i = 0; i < effects.size(); ++i) {
EffectInfo effectInfo = effects.get(i);
if (effectInfo.isLocal)
effectInfo.effect.onRender(delta, renderContext);
}
}
public void onRenderGlobal(float delta, RenderContext renderContext) {
if (numGlobalEffects == 0)
return;
for (int i = 0; i < effects.size(); ++i) {
EffectInfo effectInfo = effects.get(i);
if (!effectInfo.isLocal)
effectInfo.effect.onRender(delta, renderContext);
}
}
public void onUpdate(float delta) {
int i = 0;
while (i < effects.size()) {
EffectInfo effectInfo = effects.get(i);
if (!effectInfo.effect.isActive) {
// index doesn't change, we're removing one, so next index now equals this index
remove(i);
} else {
effectInfo.effect.onUpdate(delta);
++i;
}
}
}
/*** misc ***/
private <T extends ScreenEffect> int getIndexFor(Class<T> effectType) {
for (int i = 0; i < effects.size(); ++i) {
if (effects.get(i).effect.getClass() == effectType)
return i;
}
return -1;
}
/*** cleanup ***/
public void dispose() {
}
}

View file

@ -0,0 +1,76 @@
package com.blarg.gdx.processes;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.GameApp;
import com.blarg.gdx.events.Event;
import com.blarg.gdx.events.EventHandler;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.graphics.RenderContext;
import com.blarg.gdx.states.GameState;
public abstract class GameProcess extends EventHandler implements Disposable {
public final ProcessManager processManager;
public final GameApp gameApp;
public final GameState gameState;
boolean isFinished;
public GameProcess(ProcessManager processManager, EventManager eventManager) {
super(eventManager);
if (processManager == null)
throw new IllegalArgumentException("processManager cannot be null.");
this.processManager = processManager;
gameState = this.processManager.gameState;
gameApp = gameState.gameApp;
}
public boolean isFinished() {
return isFinished;
}
public void setFinished() {
isFinished = true;
}
public void onAdd() {
}
public void onRemove() {
}
public void onPause(boolean dueToOverlay) {
}
public void onResume(boolean fromOverlay) {
}
public void onAppPause() {
}
public void onAppResume() {
}
public void onResize() {
}
public void onRender(float delta, RenderContext renderContext) {
}
public void onUpdate(float delta) {
}
public boolean onTransition(float delta, boolean isTransitioningOut, boolean started) {
return true;
}
@Override
public boolean handle(Event e)
{
return false;
}
public void dispose() {
}
}

View file

@ -0,0 +1,35 @@
package com.blarg.gdx.processes;
import com.blarg.gdx.Strings;
class ProcessInfo {
public final GameProcess process;
public final String name;
public boolean isTransitioning;
public boolean isTransitioningOut;
public boolean isTransitionStarting;
public boolean isInactive;
public boolean isBeingRemoved;
String descriptor;
public ProcessInfo(GameProcess process, String name) {
if (process == null)
throw new IllegalArgumentException("process cannot be null.");
this.process = process;
this.name = name;
isInactive = true;
if (Strings.isNullOrEmpty(this.name))
descriptor = this.process.getClass().getSimpleName();
else
descriptor = String.format("%s[%s]", this.process.getClass().getSimpleName(), this.name);
}
@Override
public String toString() {
return descriptor;
}
}

View file

@ -0,0 +1,373 @@
package com.blarg.gdx.processes;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.Strings;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.graphics.RenderContext;
import com.blarg.gdx.states.GameState;
import com.blarg.gdx.ReflectionUtils;
import java.util.LinkedList;
public class ProcessManager implements Disposable {
public final GameState gameState;
LinkedList<ProcessInfo> processes;
LinkedList<ProcessInfo> queue;
String descriptor;
public ProcessManager(GameState gameState) {
if (gameState == null)
throw new IllegalArgumentException("gameState cannot be null.");
this.gameState = gameState;
this.descriptor = String.format("[%s]", this.gameState.getClass().getSimpleName());
Gdx.app.debug("ProcessManager", String.format("%s ctor", descriptor));
processes = new LinkedList<ProcessInfo>();
queue = new LinkedList<ProcessInfo>();
}
/*** public process getters ***/
public boolean isTransitioning() {
for (int i = 0; i < processes.size(); ++i) {
if (processes.get(i).isTransitioning)
return true;
}
return false;
}
public boolean isEmpty() {
return (processes.size() == 0 && queue.size() == 0);
}
public boolean isProcessTransitioning(GameProcess process) {
ProcessInfo processInfo = getProcessInfoFor(process);
return (processInfo == null ? false : processInfo.isTransitioning);
}
public boolean hasProcess(String name) {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo info = processes.get(i);
if (!Strings.isNullOrEmpty(info.name) && info.name.equals(name))
return true;
}
return false;
}
/** Add / Remove ***/
public <T extends GameProcess> T add(Class<T> processType) {
return add(processType, null);
}
public <T extends GameProcess> T add(Class<T> processType, String name) {
T newProcess;
try {
newProcess = ReflectionUtils.instantiateObject(processType,
new Class<?>[] { ProcessManager.class, EventManager.class },
new Object[] { this, gameState.eventManager });
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate a GameProcess instance of that type.");
}
ProcessInfo processInfo = new ProcessInfo(newProcess, name);
queue(processInfo);
return newProcess;
}
public void remove(String name) {
int i = getIndexOf(name);
if (i == -1)
throw new IllegalArgumentException("No process with that name.");
startTransitionOut(processes.get(i), true);
}
public <T extends GameProcess> void removeFirstOf(Class<T> processType) {
int i = getIndexForFirstOfType(processType);
if (i == -1)
throw new IllegalArgumentException("No processes of that type.");
startTransitionOut(processes.get(i), true);
}
public void removeAll() {
Gdx.app.debug("ProcessManager", String.format("%s Transitioning out all processes pending removal.", descriptor));
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isTransitioning && !processInfo.isInactive)
startTransitionOut(processInfo, true);
}
}
private void queue(ProcessInfo newProcessInfo) {
if (newProcessInfo == null)
throw new IllegalArgumentException("newProcessInfo cannot be null.");
if (newProcessInfo.process == null)
throw new IllegalArgumentException("New ProcessInfo object has null GameProcess.");
Gdx.app.debug("ProcessManager", String.format("%s Queueing process %s.", descriptor, newProcessInfo));
queue.add(newProcessInfo);
}
/*** events ***/
public void onPause(boolean dueToOverlay) {
if (processes.size() == 0)
return;
if (dueToOverlay) {
Gdx.app.debug("ProcessManager", String.format("%s Pausing all active processes due to state being overlayed on to the parent state.", descriptor));
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive) {
Gdx.app.debug("ProcessManager", String.format("%s Pausing process %s due to parent state overlay.", descriptor, processInfo));
processInfo.process.onPause(true);
}
}
} else {
Gdx.app.debug("ProcessManager", String.format("%s Transitioning out all active processes pending pause.", descriptor));
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive)
startTransitionOut(processInfo, false);
}
}
}
public void onResume(boolean fromOverlay) {
if (processes.size() == 0)
return;
if (fromOverlay) {
Gdx.app.debug("ProcessManager", String.format("%s Resuming all active processes due to overlay state being removed from overtop of parent state.", descriptor));
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive) {
Gdx.app.debug("ProcessManager", String.format("%s Resuming process %s due to overlay state removal.", descriptor, processInfo));
processInfo.process.onResume(true);
}
}
} else {
Gdx.app.debug("ProcessManager", String.format("%s Resuming processes.", descriptor));
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (processInfo.isInactive && !processInfo.isBeingRemoved) {
Gdx.app.debug("ProcessManager", String.format("%s Resuming process %s.", descriptor, processInfo));
processInfo.process.onResume(true);
startTransitionIn(processInfo);
}
}
}
}
public void onAppPause() {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive)
processInfo.process.onAppPause();
}
}
public void onAppResume() {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive)
processInfo.process.onAppResume();
}
}
public void onResize() {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive)
processInfo.process.onResize();
}
}
public void onRender(float delta, RenderContext renderContext) {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive)
processInfo.process.onRender(delta, renderContext);
}
}
public void onUpdate(float delta) {
cleanupInactiveProcesses();
checkForFinishedProcesses();
processQueue();
updateTransitions(delta);
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive)
processInfo.process.onUpdate(delta);
}
}
/*** internal process management functions ***/
private void startTransitionIn(ProcessInfo processInfo) {
if (processInfo == null)
throw new IllegalArgumentException("processInfo cannot be null.");
if (!processInfo.isInactive || processInfo.isTransitioning)
throw new UnsupportedOperationException();
processInfo.isInactive = false;
processInfo.isTransitioning = true;
processInfo.isTransitioningOut = false;
processInfo.isTransitionStarting = true;
Gdx.app.debug("ProcessManager", String.format("%s Transition into process %s started.", descriptor, processInfo));
}
private void startTransitionOut(ProcessInfo processInfo, boolean forRemoval) {
if (processInfo == null)
throw new IllegalArgumentException("processInfo cannot be null.");
if (!processInfo.isInactive || processInfo.isTransitioning)
throw new UnsupportedOperationException();
processInfo.isTransitioning = true;
processInfo.isTransitioningOut = true;
processInfo.isTransitionStarting = true;
processInfo.isBeingRemoved = forRemoval;
Gdx.app.debug("ProcessManager", String.format("%s Transition out of process %s started pending %s.", descriptor, processInfo, (forRemoval ? "removal" : "pause")));
}
private void cleanupInactiveProcesses() {
int i = 0;
while (i < processes.size()) {
ProcessInfo processInfo = processes.get(i);
if (processInfo.isInactive && processInfo.isBeingRemoved) {
// remove this process and move to the next node
// (index doesn't change, we're removing one, so next index now equals this index)
processes.remove(i);
Gdx.app.debug("ProcessManager", String.format("%s Deleting inactive process %s.", descriptor, processInfo));
processInfo.process.dispose();
} else {
i++;
}
}
}
private void checkForFinishedProcesses() {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!processInfo.isInactive && processInfo.process.isFinished() && !processInfo.isTransitioning) {
Gdx.app.debug("ProcessManager", String.format("%s Process %s marked as finished.", descriptor, processInfo));
startTransitionOut(processInfo, true);
}
}
}
private void processQueue() {
while (queue.size() > 0) {
ProcessInfo processInfo = queue.removeFirst();
Gdx.app.debug("ProcessManager", String.format("%s Adding process %s from queue.", descriptor, processInfo));
processes.add(processInfo);
processInfo.process.onAdd();
startTransitionIn(processInfo);
}
}
private void updateTransitions(float delta) {
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (processInfo.isTransitioning) {
boolean isDone = processInfo.process.onTransition(delta, processInfo.isTransitioningOut, processInfo.isTransitionStarting);
if (isDone) {
Gdx.app.debug("ProcessManager", String.format("%s Transition %s into process %s finished.",
descriptor,
(processInfo.isTransitioningOut ? "out of" : "into"),
processInfo));
// if the process was being transitioned out, then we should mark it as
// inactive, and trigger it's onRemove() event now
if (processInfo.isTransitioningOut) {
if (processInfo.isBeingRemoved) {
Gdx.app.debug("ProcessManager", String.format("%s Removing process %s.", descriptor, processInfo));
processInfo.process.onRemove();
} else {
Gdx.app.debug("ProcessManager", String.format("%s Pausing process %s.", descriptor, processInfo));
processInfo.process.onPause(false);
}
processInfo.isInactive = true;
}
// done transitioning
processInfo.isTransitioning = false;
processInfo.isTransitioningOut = false;
}
processInfo.isTransitionStarting = false;
}
}
}
/*** private process getters ***/
private int getIndexOf(String processName) {
if (Strings.isNullOrEmpty(processName))
throw new IllegalArgumentException("processName should be specified.");
for (int i = 0; i < processes.size(); ++i) {
ProcessInfo processInfo = processes.get(i);
if (!Strings.isNullOrEmpty(processInfo.name) && processInfo.name.equals(processName))
return i;
}
return -1;
}
private <T extends GameProcess> int getIndexForFirstOfType(Class<T> processType) {
for (int i = 0; i < processes.size(); ++i) {
if (processes.get(i).process.getClass() == processType)
return i;
}
return -1;
}
private ProcessInfo getProcessInfoFor(GameProcess process) {
if (process == null)
throw new IllegalArgumentException("process cannot be null.");
for (int i = 0; i < processes.size(); ++i) {
if (processes.get(i).process == process)
return processes.get(i);
}
return null;
}
/*** cleanup ***/
public void dispose() {
if (processes == null)
return;
Gdx.app.debug("ProcessManager", String.format("%s dispose", descriptor));
while (processes.size() > 0) {
ProcessInfo processInfo = processes.getLast();
Gdx.app.debug("ProcessManager", String.format("%s Removing process %s as part of ProcessManager shutdown.", descriptor, processInfo));
processInfo.process.onRemove();
processInfo.process.dispose();
processes.removeLast();
}
// the queue will likely not have anything in it, but just in case ...
while (queue.size() > 0) {
ProcessInfo processInfo = processes.removeFirst();
Gdx.app.debug("ProcessManager", String.format("%s Removing queued process %s as part of ProcessManager shutdown.", descriptor, processInfo));
processInfo.process.dispose();
}
processes = null;
queue = null;
}
}

View file

@ -0,0 +1,105 @@
package com.blarg.gdx.states;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.GameApp;
import com.blarg.gdx.events.Event;
import com.blarg.gdx.events.EventHandler;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.graphics.RenderContext;
import com.blarg.gdx.graphics.screeneffects.ScreenEffectManager;
import com.blarg.gdx.processes.ProcessManager;
public abstract class GameState extends EventHandler implements Disposable {
public final StateManager stateManager;
public final ScreenEffectManager effectManager;
public final ProcessManager processManager;
public final GameApp gameApp;
boolean isFinished;
public GameState(StateManager stateManager, EventManager eventManager) {
super(eventManager);
if (stateManager == null)
throw new IllegalArgumentException("stateManager cannot be null.");
this.stateManager = stateManager;
gameApp = stateManager.gameApp;
effectManager = new ScreenEffectManager();
processManager = new ProcessManager(this);
}
public boolean isTransitioning() {
return stateManager.isStateTransitioning(this);
}
public boolean isTopState() {
return stateManager.isTopState(this);
}
public boolean isFinished() {
return isFinished;
}
public void setFinished() {
isFinished = true;
}
public void dispose() {
effectManager.dispose();
processManager.dispose();
}
public void onPush() {
}
public void onPop() {
}
public void onPause(boolean dueToOverlay) {
processManager.onPause(dueToOverlay);
}
public void onResume(boolean fromOverlay) {
processManager.onResume(fromOverlay);
}
public void onAppPause() {
processManager.onAppPause();
effectManager.onAppPause();
}
public void onAppResume() {
processManager.onAppResume();
effectManager.onAppResume();
}
public void onResize() {
processManager.onResize();
effectManager.onResize();
}
public void onRender(float delta, RenderContext renderContext) {
// switch it up and do effects before processes here so that processes
// (which would commonly be used for UI overlay elements) don't get
// overwritten by local effects (e.g. flashes, etc.)
effectManager.onRenderLocal(delta, renderContext);
processManager.onRender(delta, renderContext);
}
public void onUpdate(float delta) {
effectManager.onUpdate(delta);
processManager.onUpdate(delta);
}
public boolean onTransition(float delta, boolean isTransitioningOut, boolean started) {
return true;
}
@Override
public boolean handle(Event e)
{
return false;
}
}

View file

@ -0,0 +1,37 @@
package com.blarg.gdx.states;
import com.blarg.gdx.Strings;
class StateInfo {
public final GameState state;
public final String name;
public boolean isOverlay;
public boolean isOverlayed;
public boolean isTransitioning;
public boolean isTransitioningOut;
public boolean isTransitionStarting;
public boolean isInactive;
public boolean isBeingPopped;
String descriptor;
public StateInfo(GameState state, String name) {
if (state == null)
throw new IllegalArgumentException("state cannot be null.");
this.state = state;
this.name = name;
isInactive = true;
if (Strings.isNullOrEmpty(this.name))
descriptor = this.state.getClass().getSimpleName();
else
descriptor = String.format("%s[%s]", this.state.getClass().getSimpleName(), this.name);
}
@Override
public String toString() {
return descriptor;
}
}

View file

@ -0,0 +1,608 @@
package com.blarg.gdx.states;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.GameApp;
import com.blarg.gdx.Strings;
import com.blarg.gdx.events.EventManager;
import com.blarg.gdx.ReflectionUtils;
import com.blarg.gdx.graphics.RenderContext;
import java.util.LinkedList;
public class StateManager implements Disposable {
public final GameApp gameApp;
public final EventManager eventManager;
LinkedList<StateInfo> states;
LinkedList<StateInfo> pushQueue;
LinkedList<StateInfo> swapQueue;
boolean pushQueueHasOverlay;
boolean swapQueueHasOverlay;
boolean lastCleanedStatesWereAllOverlays;
public StateManager(GameApp gameApp, EventManager eventManager) {
if (gameApp == null)
throw new IllegalArgumentException("gameApp cannot be null.");
Gdx.app.debug("StateManager", "ctor");
states = new LinkedList<StateInfo>();
pushQueue = new LinkedList<StateInfo>();
swapQueue = new LinkedList<StateInfo>();
this.gameApp = gameApp;
this.eventManager = eventManager;
}
/*** public state getters ***/
public GameState getTopState() {
StateInfo top = getTop();
return (top == null ? null : top.state);
}
public GameState getTopNonOverlayState() {
StateInfo top = getTopNonOverlay();
return (top == null ? null : top.state);
}
public boolean isTransitioning() {
for (int i = 0; i < states.size(); ++i) {
if (states.get(i).isTransitioning)
return true;
}
return false;
}
public boolean isEmpty() {
return (states.size() == 0 && pushQueue.size() == 0 && swapQueue.size() == 0);
}
public boolean isStateTransitioning(GameState state) {
if (state == null)
throw new IllegalArgumentException("state cannot be null.");
StateInfo info = getStateInfoFor(state);
return (info == null ? false : info.isTransitioning);
}
public boolean isTopState(GameState state) {
if (state == null)
throw new IllegalArgumentException("state cannot be null.");
StateInfo info = getTop();
return (info == null ? false : (info.state == state));
}
public boolean hasState(String name) {
for (int i = 0; i < states.size(); ++i) {
StateInfo info = states.get(i);
if (!Strings.isNullOrEmpty(info.name) && info.name.equals(name))
return true;
}
return false;
}
/** Push / Pop / Overlay / Swap / Queue ***/
public <T extends GameState> T push(Class<T> stateType) {
return push(stateType, null);
}
public <T extends GameState> T push(Class<T> stateType, String name) {
T newState;
try {
newState = ReflectionUtils.instantiateObject(stateType,
new Class<?>[] { StateManager.class, EventManager.class },
new Object[] { this, eventManager });
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate a GameState instance of that type.");
}
StateInfo stateInfo = new StateInfo(newState, name);
queueForPush(stateInfo);
return newState;
}
public <T extends GameState> T overlay(Class<T> stateType) {
return overlay(stateType);
}
public <T extends GameState> T overlay(Class<T> stateType, String name) {
T newState;
try {
newState = ReflectionUtils.instantiateObject(stateType,
new Class<?>[] { StateManager.class, EventManager.class },
new Object[] { this, eventManager });
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate a GameState instance of that type.");
}
StateInfo stateInfo = new StateInfo(newState, name);
stateInfo.isOverlay = true;
queueForPush(stateInfo);
return newState;
}
public <T extends GameState> T swapTopWith(Class<T> stateType) {
return swapTopWith(stateType);
}
public <T extends GameState> T swapTopWith(Class<T> stateType, String name) {
// figure out if the current top state is an overlay or not. use that
// same setting for the new state that is being swapped in
StateInfo currentTopStateInfo = getTop();
if (currentTopStateInfo == null)
throw new UnsupportedOperationException("Cannot swap, no existing states.");
boolean isOverlay = currentTopStateInfo.isOverlay;
T newState;
try {
newState = ReflectionUtils.instantiateObject(stateType,
new Class<?>[] { StateManager.class, EventManager.class },
new Object[] { this, eventManager });
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate a GameState instance of that type.");
}
StateInfo stateInfo = new StateInfo(newState, name);
stateInfo.isOverlay = isOverlay;
queueForSwap(stateInfo, false);
return newState;
}
public <T extends GameState> T swapTopNonOverlayWith(Class<T> stateType) {
return swapTopWith(stateType);
}
public <T extends GameState> T swapTopNonOverlayWith(Class<T> stateType, String name) {
T newState;
try {
newState = ReflectionUtils.instantiateObject(stateType,
new Class<?>[] { StateManager.class, EventManager.class },
new Object[] { this, eventManager });
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate a GameState instance of that type.");
}
StateInfo stateInfo = new StateInfo(newState, name);
queueForSwap(stateInfo, true);
return newState;
}
public void pop() {
if (isTransitioning())
throw new UnsupportedOperationException();
Gdx.app.debug("StateManager", "Pop initiated for top-most state only.");
startOnlyTopStateTransitioningOut(false);
}
public void popTopNonOverlay() {
if (isTransitioning())
throw new UnsupportedOperationException();
Gdx.app.debug("StateManager", "Pop initiated for all top active states.");
startTopStatesTransitioningOut(false);
}
private void queueForPush(StateInfo newStateInfo) {
if (newStateInfo == null)
throw new IllegalArgumentException("newStateInfo cannot be null.");
if (newStateInfo.state == null)
throw new IllegalArgumentException("New StateInfo object has null GameState.");
if (pushQueueHasOverlay && !newStateInfo.isOverlay)
throw new UnsupportedOperationException("Cannot queue new non-overlay state while queue is active with overlay states.");
Gdx.app.debug("StateManager", String.format("Queueing state %s for pushing.", newStateInfo));
if (!newStateInfo.isOverlay)
startTopStatesTransitioningOut(true);
pushQueue.add(newStateInfo);
if (newStateInfo.isOverlay)
pushQueueHasOverlay = true;
}
private void queueForSwap(StateInfo newStateInfo, boolean swapTopNonOverlay) {
if (newStateInfo == null)
throw new IllegalArgumentException("newStateInfo cannot be null.");
if (newStateInfo.state == null)
throw new IllegalArgumentException("New StateInfo object has null GameState.");
if (swapQueueHasOverlay && !newStateInfo.isOverlay)
throw new UnsupportedOperationException("Cannot queue new non-overlay state while queue is active with overlay states.");
Gdx.app.debug("StateManager", String.format("Queueing state %s for swapping with %s.", newStateInfo, (swapTopNonOverlay ? "all top active states" : "only top-most active state.")));
if (swapTopNonOverlay)
startTopStatesTransitioningOut(false);
else
startOnlyTopStateTransitioningOut(false);
swapQueue.add(newStateInfo);
if (newStateInfo.isOverlay)
swapQueueHasOverlay = true;
}
/*** events ***/
public void onAppPause() {
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (!stateInfo.isInactive)
stateInfo.state.onAppPause();
}
}
public void onAppResume() {
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (!stateInfo.isInactive)
stateInfo.state.onAppResume();
}
}
public void onResize() {
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (!stateInfo.isInactive)
stateInfo.state.onResize();
}
}
public void onRender(float delta, RenderContext renderContext) {
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (!stateInfo.isInactive) {
stateInfo.state.onRender(delta, renderContext);
stateInfo.state.effectManager.onRenderGlobal(delta, renderContext);
}
}
}
public void onUpdate(float delta) {
lastCleanedStatesWereAllOverlays = false;
cleanupInactiveStates();
checkForFinishedStates();
processQueues();
resumeStatesIfNeeded();
updateTransitions(delta);
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (!stateInfo.isInactive)
stateInfo.state.onUpdate(delta);
}
}
/*** internal state management functions ***/
private void startTopStatesTransitioningOut(boolean pausing) {
int i = getTopNonOverlayIndex();
if (i == -1)
return;
for (; i < states.size(); ++i) {
// only look at active states, since inactive ones have already been
// transitioned out and will be removed on the next onUpdate()
if (!states.get(i).isInactive)
transitionOut(states.get(i), !pausing);
}
}
private void startOnlyTopStateTransitioningOut(boolean pausing) {
StateInfo info = getTop();
// if it's not active, then it's just been transitioned out and will be
// removed on the next onUpdate()
if (!info.isInactive)
transitionOut(info, !pausing);
}
private void cleanupInactiveStates() {
// we don't want to remove any states until everything is done transitioning.
// this is to avoid the scenario where the top non-overlay state finishes
// transitioning before one of the overlays. if we removed it, the overlays
// would then be overlayed over an inactive non-overlay (which wouldn't get
// resumed until the current active overlays were done being transitioned)
if (isTransitioning())
return;
boolean cleanedUpSomething = false;
boolean cleanedUpNonOverlay = false;
int i = 0;
while (i < states.size()) {
StateInfo stateInfo = states.get(i);
if (stateInfo.isInactive && stateInfo.isBeingPopped) {
cleanedUpSomething = true;
if (!stateInfo.isOverlay)
cleanedUpNonOverlay = true;
// remove this state and move to the next node
// (index doesn't change, we're removing one, so next index now equals this index)
states.remove(i);
Gdx.app.debug("StateManager", String.format("Deleting inactive popped state %s.", stateInfo));
stateInfo.state.dispose();
} else {
i++;
}
}
if (cleanedUpSomething && !cleanedUpNonOverlay)
lastCleanedStatesWereAllOverlays = true;
}
private void checkForFinishedStates() {
if (states.size() == 0)
return;
// don't do anything if something is currently transitioning
if (isTransitioning())
return;
boolean needToAlsoTransitionOutOverlays = false;
// check the top non-overlay state first to see if it's finished
// and should be transitioned out
StateInfo topNonOverlayStateInfo = getTopNonOverlay();
if (!topNonOverlayStateInfo.isInactive && topNonOverlayStateInfo.state.isFinished()) {
Gdx.app.debug("StateManager", String.format("State %s marked as finished.", topNonOverlayStateInfo));
transitionOut(topNonOverlayStateInfo, true);
needToAlsoTransitionOutOverlays = true;
}
// now also check the overlay states (if there were any). we force them to
// transition out if the non-overlay state started to transition out so that
// we don't end up with overlay states without a parent non-overlay state
// start the loop off 1 beyond the top non-overlay (which is where the
// overlays are, if any)
int i = getTopNonOverlayIndex();
if (i != -1) {
for (++i; i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (!stateInfo.isInactive && (stateInfo.state.isFinished() || needToAlsoTransitionOutOverlays)) {
Gdx.app.debug("StateManager", String.format("State %s marked as finished.", stateInfo));
transitionOut(stateInfo, true);
}
}
}
}
private void processQueues() {
// don't do anything if stuff is currently transitioning
if (isTransitioning())
return;
if (pushQueue.size() > 0 && swapQueue.size() > 0)
throw new UnsupportedOperationException("Cannot process queues when both the swap and push queues have items currently in them.");
// for each state in the queu, add it to the main list and start transitioning it in
// (note, only one of these queues will be processed each tick due to the above check!)
while (pushQueue.size() > 0) {
StateInfo stateInfo = pushQueue.removeFirst();
if (states.size() > 0) {
// if this new state is an overlay, and the current top state is both
// currently active and is not currently marked as being overlay-ed
// then we should pause it due to overlay
StateInfo currentTopStateInfo = getTop();
if (stateInfo.isOverlay && !currentTopStateInfo.isInactive && !currentTopStateInfo.isOverlayed) {
Gdx.app.debug("StateManager", String.format("Pausing %sstate %s due to overlay.", (currentTopStateInfo.isOverlay ? "overlay " : ""), currentTopStateInfo));
currentTopStateInfo.state.onPause(true);
// also mark the current top state as being overlay-ed
currentTopStateInfo.isOverlayed = true;
}
}
Gdx.app.debug("StateManager", String.format("Pushing %sstate %s from push-queue.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
stateInfo.state.onPush();
transitionIn(stateInfo, false);
states.addLast(stateInfo);
}
while (swapQueue.size() > 0) {
StateInfo stateInfo = swapQueue.removeFirst();
// if this new state is an overlay, and the current top state is both
// currently active and is not currently marked as being overlay-ed
// then we should pause it due to overlay
StateInfo currentTopStateInfo = getTop();
if (stateInfo.isOverlay && !currentTopStateInfo.isInactive && !currentTopStateInfo.isOverlayed) {
Gdx.app.debug("StateManager", String.format("Pausing %sstate %s due to overlay.", (currentTopStateInfo.isOverlay ? "overlay " : ""), currentTopStateInfo));
currentTopStateInfo.state.onPause(true);
// also mark the current top state as being overlay-ed
currentTopStateInfo.isOverlayed = true;
}
Gdx.app.debug("StateManager", String.format("Pushing %sstate %s from swap-queue.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
stateInfo.state.onPush();
transitionIn(stateInfo, false);
states.addLast(stateInfo);
}
pushQueueHasOverlay = false;
swapQueueHasOverlay = false;
}
private void resumeStatesIfNeeded() {
if (states.size() == 0)
return;
// don't do anything if stuff is currently transitioning
if (isTransitioning())
return;
// did we just clean up one or more overlay states?
if (lastCleanedStatesWereAllOverlays) {
// then we need to resume the current top state
// (those paused with the flag "from an overlay")
StateInfo stateInfo = getTop();
if (stateInfo.isInactive || !stateInfo.isOverlayed)
throw new UnsupportedOperationException();
Gdx.app.debug("StateManager", String.format("Resuming %sstate %s due to overlay removal.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
stateInfo.state.onResume(true);
stateInfo.isOverlayed = false;
return;
}
// if the top state is no inactive, then we don't need to resume anything
if (!getTop().isInactive)
return;
Gdx.app.debug("StateManager", "Top-most state is inactive. Resuming all top states up to and including the next non-overlay.");
// top state is inactive. time to reusme one or more states...
// find the topmost non-overlay state and take it and all overlay states that
// are above it, and transition them in
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
Gdx.app.debug("StateManager", String.format("Resuming %sstate %s.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
stateInfo.state.onResume(false);
transitionIn(stateInfo, true);
}
}
private void updateTransitions(float delta) {
for (int i = getTopNonOverlayIndex(); i != -1 && i < states.size(); ++i) {
StateInfo stateInfo = states.get(i);
if (stateInfo.isTransitioning) {
boolean isDone = stateInfo.state.onTransition(delta, stateInfo.isTransitioningOut, stateInfo.isTransitionStarting);
if (isDone) {
Gdx.app.debug("StateManager", String.format("Transition %s %sstate %s finished.",
(stateInfo.isTransitioningOut ? "out of" : "into"),
(stateInfo.isOverlay ? "overlay " : ""),
stateInfo));
// if the state was being transitioned out, then we should mark
// it as inactive, and trigger it's onPop() or onPause() event now
if (stateInfo.isTransitioningOut) {
if (stateInfo.isBeingPopped) {
Gdx.app.debug("StateManager", String.format("Popping %sstate %s", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
stateInfo.state.onPop();
// TODO: do I care enough to port the return value stuff which goes here?
} else {
Gdx.app.debug("StateManager", String.format("Pausing %sstate %s.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
stateInfo.state.onPause(false);
}
stateInfo.isInactive = true;
}
// done transitioning
stateInfo.isTransitioning = false;
stateInfo.isTransitioningOut = false;
}
stateInfo.isTransitionStarting = false;
}
}
}
private void transitionIn(StateInfo stateInfo, boolean forResuming) {
stateInfo.isInactive = false;
stateInfo.isTransitioning = true;
stateInfo.isTransitioningOut = false;
stateInfo.isTransitionStarting = true;
Gdx.app.debug("StateManager", String.format("Transition into %sstate %s started.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
//if (forResuming)
// stateInfo.getState().getProcessManager().onResume(false);
}
private void transitionOut(StateInfo stateInfo, boolean forPopping) {
stateInfo.isTransitioning = true;
stateInfo.isTransitioningOut = true;
stateInfo.isTransitionStarting = true;
stateInfo.isBeingPopped = forPopping;
Gdx.app.debug("StateManager", String.format("Transition out of %sstate %s started.", (stateInfo.isOverlay ? "overlay " : ""), stateInfo));
//if (forPopping)
// stateInfo.getState().getProcessManager().removeAll();
//else
// stateInfo.getState().getProcessManager().onPause(false);
}
/*** private state getters ***/
private StateInfo getStateInfoFor(GameState state) {
if (state == null)
throw new IllegalArgumentException("state cannot be null.");
for (int i = 0; i < states.size(); ++i) {
if (states.get(i).state == state)
return states.get(i);
}
return null;
}
private StateInfo getTop() {
return (states.isEmpty() ? null : states.getLast());
}
private StateInfo getTopNonOverlay() {
int index = getTopNonOverlayIndex();
return (index == -1 ? null : states.get(index));
}
private int getTopNonOverlayIndex() {
for (int i = states.size() - 1; i >= 0; i--) {
if (!states.get(i).isOverlay)
return i;
}
return (states.size() > 0 ? 0 : -1);
}
/*** cleanup ***/
public void dispose() {
if (states == null)
return;
Gdx.app.debug("StateManager", "dispose");
while (states.size() > 0) {
StateInfo stateInfo = states.getLast();
Gdx.app.debug("StateManager", String.format("Popping state %s as part of StateManager shutdown.", stateInfo));
stateInfo.state.onPop();
stateInfo.state.dispose();
states.removeLast();
}
// these queues will likely not have anything in them, but just in case ...
while (pushQueue.size() > 0) {
StateInfo stateInfo = pushQueue.removeFirst();
Gdx.app.debug("StateManager", String.format("Deleting push-queued state %s as part of StateManager shutdown.", stateInfo));
stateInfo.state.dispose();
}
while (swapQueue.size() > 0) {
StateInfo stateInfo = swapQueue.removeFirst();
Gdx.app.debug("StateManager", String.format("Deleting swap-queued state %s as part of StateManager shutdown.", stateInfo));
stateInfo.state.dispose();
}
states = null;
pushQueue = null;
swapQueue = null;
}
}