initial commit
This commit is contained in:
commit
d052cbfa9d
7
.classpath
Normal file
7
.classpath
Normal 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
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
out/
|
||||
log/
|
||||
target/
|
||||
.settings/
|
||||
*.iml
|
||||
*.eml
|
||||
*.class
|
||||
*.jar
|
15
.project
Normal file
15
.project
Normal 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>
|
81
src/com/blarg/gdx/GameApp.java
Normal file
81
src/com/blarg/gdx/GameApp.java
Normal 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();
|
||||
}
|
||||
}
|
67
src/com/blarg/gdx/GdxGameAppListener.java
Normal file
67
src/com/blarg/gdx/GdxGameAppListener.java
Normal 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();
|
||||
}
|
||||
}
|
23
src/com/blarg/gdx/ReflectionUtils.java
Normal file
23
src/com/blarg/gdx/ReflectionUtils.java
Normal 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;
|
||||
}
|
||||
}
|
7
src/com/blarg/gdx/Strings.java
Normal file
7
src/com/blarg/gdx/Strings.java
Normal file
|
@ -0,0 +1,7 @@
|
|||
package com.blarg.gdx;
|
||||
|
||||
public final class Strings {
|
||||
public static boolean isNullOrEmpty(String s) {
|
||||
return (s == null || s.isEmpty());
|
||||
}
|
||||
}
|
6
src/com/blarg/gdx/entities/Component.java
Normal file
6
src/com/blarg/gdx/entities/Component.java
Normal file
|
@ -0,0 +1,6 @@
|
|||
package com.blarg.gdx.entities;
|
||||
|
||||
import com.badlogic.gdx.utils.Pool;
|
||||
|
||||
public abstract class Component implements Pool.Poolable {
|
||||
}
|
38
src/com/blarg/gdx/entities/ComponentSystem.java
Normal file
38
src/com/blarg/gdx/entities/ComponentSystem.java
Normal 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;
|
||||
}
|
||||
}
|
37
src/com/blarg/gdx/entities/Entity.java
Normal file
37
src/com/blarg/gdx/entities/Entity.java
Normal 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);
|
||||
}
|
||||
}
|
263
src/com/blarg/gdx/entities/EntityManager.java
Normal file
263
src/com/blarg/gdx/entities/EntityManager.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
6
src/com/blarg/gdx/events/Event.java
Normal file
6
src/com/blarg/gdx/events/Event.java
Normal file
|
@ -0,0 +1,6 @@
|
|||
package com.blarg.gdx.events;
|
||||
|
||||
import com.badlogic.gdx.utils.Pool;
|
||||
|
||||
public abstract class Event implements Pool.Poolable {
|
||||
}
|
22
src/com/blarg/gdx/events/EventHandler.java
Normal file
22
src/com/blarg/gdx/events/EventHandler.java
Normal 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);
|
||||
}
|
||||
}
|
5
src/com/blarg/gdx/events/EventListener.java
Normal file
5
src/com/blarg/gdx/events/EventListener.java
Normal file
|
@ -0,0 +1,5 @@
|
|||
package com.blarg.gdx.events;
|
||||
|
||||
public interface EventListener {
|
||||
boolean handle(Event e);
|
||||
}
|
183
src/com/blarg/gdx/events/EventManager.java
Normal file
183
src/com/blarg/gdx/events/EventManager.java
Normal 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);
|
||||
}
|
||||
}
|
12
src/com/blarg/gdx/graphics/BillboardSpriteBatch.java
Normal file
12
src/com/blarg/gdx/graphics/BillboardSpriteBatch.java
Normal 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 {
|
||||
|
||||
}
|
47
src/com/blarg/gdx/graphics/DefaultScreenPixelScaler.java
Normal file
47
src/com/blarg/gdx/graphics/DefaultScreenPixelScaler.java
Normal 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;
|
||||
}
|
||||
}
|
203
src/com/blarg/gdx/graphics/DelayedSpriteBatch.java
Normal file
203
src/com/blarg/gdx/graphics/DelayedSpriteBatch.java
Normal 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;
|
||||
}
|
||||
}
|
25
src/com/blarg/gdx/graphics/NoScaleScreenPixelScaler.java
Normal file
25
src/com/blarg/gdx/graphics/NoScaleScreenPixelScaler.java
Normal 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!
|
||||
}
|
||||
}
|
107
src/com/blarg/gdx/graphics/RenderContext.java
Normal file
107
src/com/blarg/gdx/graphics/RenderContext.java
Normal 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();
|
||||
}
|
||||
}
|
10
src/com/blarg/gdx/graphics/ScreenPixelScaler.java
Normal file
10
src/com/blarg/gdx/graphics/ScreenPixelScaler.java
Normal 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);
|
||||
}
|
57
src/com/blarg/gdx/graphics/SolidColorTextureCache.java
Normal file
57
src/com/blarg/gdx/graphics/SolidColorTextureCache.java
Normal 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();
|
||||
}
|
||||
}
|
16
src/com/blarg/gdx/graphics/TTF.java
Normal file
16
src/com/blarg/gdx/graphics/TTF.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
15
src/com/blarg/gdx/graphics/screeneffects/EffectInfo.java
Normal file
15
src/com/blarg/gdx/graphics/screeneffects/EffectInfo.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
37
src/com/blarg/gdx/graphics/screeneffects/ScreenEffect.java
Normal file
37
src/com/blarg/gdx/graphics/screeneffects/ScreenEffect.java
Normal 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() {
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
76
src/com/blarg/gdx/processes/GameProcess.java
Normal file
76
src/com/blarg/gdx/processes/GameProcess.java
Normal 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() {
|
||||
}
|
||||
}
|
35
src/com/blarg/gdx/processes/ProcessInfo.java
Normal file
35
src/com/blarg/gdx/processes/ProcessInfo.java
Normal 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;
|
||||
}
|
||||
}
|
373
src/com/blarg/gdx/processes/ProcessManager.java
Normal file
373
src/com/blarg/gdx/processes/ProcessManager.java
Normal 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;
|
||||
}
|
||||
}
|
105
src/com/blarg/gdx/states/GameState.java
Normal file
105
src/com/blarg/gdx/states/GameState.java
Normal 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;
|
||||
}
|
||||
}
|
37
src/com/blarg/gdx/states/StateInfo.java
Normal file
37
src/com/blarg/gdx/states/StateInfo.java
Normal 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;
|
||||
}
|
||||
}
|
608
src/com/blarg/gdx/states/StateManager.java
Normal file
608
src/com/blarg/gdx/states/StateManager.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue