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