Played around a bit. Here is what I came up with...
Code:
/**
* @author Nathan Sweet <misc@n4te.com>
*/
public class CrossFadeTransition implements Transition {
private GameState firstState;
private GameState secondState;
private FadeOutTransition fade;
public CrossFadeTransition (int fadeMillis) {
fade = new FadeOutTransition(Color.black, fadeMillis);
}
public void init (GameState firstState, GameState secondState) {
this.firstState = firstState;
this.secondState = secondState;
}
public void preRender (StateBasedGame game, GameContainer container, Graphics g) throws SlickException {
}
public void postRender (StateBasedGame game, GameContainer container, Graphics g) throws SlickException {
firstState.render(container, game, g);
g.clearAlphaMap();
g.setDrawMode(Graphics.MODE_ALPHA_MAP);
fade.postRender(game, container, g);
g.setDrawMode(Graphics.MODE_ALPHA_BLEND);
secondState.render(container, game, g);
g.setDrawMode(Graphics.MODE_NORMAL);
}
public void update (StateBasedGame game, GameContainer container, int delta) throws SlickException {
fade.update(game, container, delta);
}
public boolean isComplete () {
return fade.isComplete();
}
}
1) Is this an efficient way to achieve my goal? I messed with writing to an Image, but couldn't get the Image to be transparent when drawn. I found g.drawImage(Image, int, int, Color) tints the image the specified Color, but doesn't draw the image with transparency.
2) Since it messes with the graphics context, should it be in a SlickCallable safe block?
Notice I didn't use CrossStateTransition. I found it to just get in the way more than anything.
Overall, something seems not right with the transition API. Once you start to call render on GameState's you lose the ability to chain transiitions. Eg, take this transition...
Code:
/**
* @author Nathan Sweet <misc@n4te.com>
*/
public class EnlargeTransition implements Transition {
private static SGL GL = Renderer.get();
private int fadeMillis;
private float percentComplete, enlargedPercent;
private GameState firstState;
private GameState secondState;
public EnlargeTransition (int fadeMillis, float enlargedPercent) {
this.fadeMillis = fadeMillis;
this.enlargedPercent = enlargedPercent;
}
public void init (GameState firstState, GameState secondState) {
this.firstState = firstState;
}
public void preRender (StateBasedGame game, GameContainer container, Graphics g) throws SlickException {
}
public void postRender (StateBasedGame game, GameContainer container, Graphics g) throws SlickException {
float enlargeFactor = (enlargedPercent * percentComplete) + 1;
int normalWidth = container.getWidth();
int normalHeight = container.getHeight();
int enlargedWidth = (int)(normalWidth * enlargeFactor);
int enlargedHeight = (int)(normalHeight * enlargeFactor);
int offsetX = (normalWidth - enlargedWidth) / 2;
int offsetY = (normalHeight - enlargedHeight) / 2;
SlickCallable.enterSafeBlock();
GL.glTranslatef(offsetX, offsetY, 0);
GL.glScalef(enlargeFactor, enlargeFactor, 0);
GL.glPushMatrix();
firstState.render(container, game, g);
GL.glPopMatrix();
SlickCallable.leaveSafeBlock();
}
public void update (StateBasedGame game, GameContainer container, int delta) throws SlickException {
percentComplete += delta * (1.0f / fadeMillis);
if (percentComplete > 1) percentComplete = 1;
}
public boolean isComplete () {
return percentComplete >= 1;
}
}
What happens if I want to use these two transitions so the resulting transition fades out as it enlarges? From what I can see, it just doesn't work with the current API.
It is just a rough idea right now, but what about a system like this... if there is a leaveTransition, StateBasedGame#render() calls Transition#render() *instead* of the GameState#render(). Transition#render() takes an argument of type Transition. The GameState is wrapped in a Transition that just calls GameState#render. That wrapper Transition is wrapped in the leaveTransition. The leaveTransition does what it needs, then calls render on it's Transition, which calls render on the GameState. This way any number of Transitions could be layered and all could manipulate the graphics context before or after the GameState is rendered.
See any holes in the approach? Might it be a problem if some implementations of GameState#render undo changes made to the graphics context by the transitions before rendering the GameState?
BUG: CombinedTransition doesn't call init() on the list of transitions. Missing code:
Code:
public void init (GameState firstState, GameState secondState) {
for (int i = transitions.size() - 1; i >= 0; i--) {
((Transition)transitions.get(i)).init(firstState, secondState);
}
}
BTW, here is a test for the transitions above...
Code:
public class CrossFadeTest extends StateBasedGame {
private GameState state1 = new SimpleState(1, "testdata/wallpaper/paper1.png");
private GameState state2 = new SimpleState(2, "testdata/wallpaper/paper2.png");
public CrossFadeTest () {
super("CrossFadeTest");
}
public void initStatesList (GameContainer container) throws SlickException {
addState(state1);
addState(state2);
}
public void keyPressed (int key, char c) {
Transition transition = new CrossFadeTransition(2000);
// Transition transition = new EnlargeTransition(2000, 1.3f);
switch (key) {
case Input.KEY_1:
enterState(state1.getID(), transition, null);
break;
case Input.KEY_2:
enterState(state2.getID(), transition, null);
break;
}
}
public static void main (String[] args) throws Exception {
CanvasGameContainer container = new CanvasGameContainer(new CrossFadeTest());
container.setSize(512, 512);
JFrame frame = new JFrame("CrossFadeTest");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(container);
frame.pack();
frame.setVisible(true);
container.start();
}
static private class SimpleState extends BasicGameState {
private final int id;
private final String imageRef;
private Image image;
public SimpleState (int id, String imageRef) {
this.id = id;
this.imageRef = imageRef;
}
public int getID () {
return id;
}
public void init (GameContainer container, StateBasedGame game) throws SlickException {
image = new Image(imageRef);
}
public void render (GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
g.drawImage(image, 0, 0);
}
public void update (GameContainer container, StateBasedGame game, int delta) throws SlickException {
}
}
}
Edit: CrossFadeTransition works fine in the test, but when fading between 2 black screens that have white text, the screen I'm fading to looks like crap. The white text is very thick until the transition is over. Blah... sleepy time...