Menu

Cross-fading Textures

Let's take a look inside the new features of R49!

One thing that is bread-and-butter gotta have is cross-fading between textures. You have some object, and it has to appear with more than one different texture, and of course you want a nice "transition" when that changes, don't you?

So to start, we need a component that knows how to interpolate [0..1] over a given time interval, and do some other necessary bookkeeping. Let's paste that right in:

public class AnimateRatio extends GameObject implements TimerCallback, RequireResourceLoader, Effect, LoadedCallback, UnloadedCallback {
    final MixTextures mt;
    final String[] targets;
    final long dur;
    final Material ft;
    public AnimateRatio(String name, long dur, MixTextures mt, Material ft, String target) {
        super(name, true);
        if(mt == null) throw new IllegalArgumentException("mt");
        if(ft == null) throw new IllegalArgumentException("ft");
        this.dur = dur;
        this.mt = mt;
        this.ft = ft;
        this.targets = new String[] { name, target };
    }
    @Override
    public void execute(long delta, long elapsed, boolean last, Locator arg3, Pipelines arg4) {
        final float ratio = Math.min(1f, (float)elapsed/(float)dur);
        mt.setRatio(ratio);
        if(last) {
            try {
                arg4.uninstall(name, targets);
            } catch (Exception e) {
                Log.e("AR", "uninstall", e);
            }
        }
    }
    @Override
    public boolean getRegisterOnInstall() { return true; }
    @Override
    public void setConfig(TimerConfig tc, int arg1) {
        tc.durationMS = dur;
        tc.continuous = true;
        tc.autoRepeat = false;
    }
    @Override
    public void load(ResourceLoader arg0, Services arg1) {
        mt.load(arg0, arg1);
        if(ft instanceof RequireResourceLoader) {
            ((RequireResourceLoader)ft).load(arg0, arg1);
        }
    }
    @Override
    public Shader getShader() { return mt.getShader(); }
    @Override
    public void setup() { mt.setup(); }
    @Override
    public void unloaded(GameObject arg0, Exception arg1, Locator arg2, Pipelines pps) {
        if (arg0 == this) {
            final GameObjectWithProperties go = arg2.locate(targets[1]);
            if (go != null) {
                go.set(Constants.Property.MATERIAL, ft);
            }
        }
    }
    @Override
    public void loaded(GameObject arg0, Exception arg1, Locator arg2, Pipelines pps) {
    }
}

This is a typical GO in terms of callbacks implemented, but it does lots:

  • Load some textures, as delegated to MixTextures.
  • Act as an Effect, as delegated to MixTextures.
  • Compute the actual ratio based on the elapsed time. I went with linear, but an Interpolator would be nicer!
  • Drive the MixTextures component, in particular the mix ratio.
  • Uninstall pipeline on self when timer expires, target self and a designated "target" GO.
  • Restore the "previous material" when unloading.

Consult the Javadoc for details on MixTextures; it does what its name says.

In the Bashing Game, the "field" pieces change "actions" based on a random "spawner" GO that doles out randomly-generated actions, e.g. "switch to score multiplier x5" which come in as Event Pipeline to these field GOs. When these GOs receive Box 2D Contact Events, they perform the corresponding action, e.g. "add N points to the score".

Actions eventually expire, and new actions are assigned as the game continues. Each action has a texture associated with it, and the field has already a default Material ("cpv" shader) attached.

Finally we get to AnimateRatio! As actions get "accepted" the field GO starts a transition from its default Material to a TextureMaterial corresponding to the action. At the end of the transition, the action's material get applied as the current Material. When the action expires, the transition is reversed, back to the field's original Material.

Here is an example from the field GO, how it starts the first transition:

    void animateOn(Pipelines pps) {
        if(animating) return;
        // transition visual to this action
        if(mtoriginal != null) {
            mtswitchto = new TextureMaterial(sa.txname);
            final AnimateRatio ar = new AnimateRatio(pps.freshName("AnimateOn"), 2000L,
                    new MixTextures("MixTextures", false, mtoriginal.name, mtswitchto.name), mtswitchto, name);
            try {
                pps.install(ar, new String[] { ar.name, name });
                animating = true;
            } catch (Exception e) {
                animating = false;
                mtswitchto = null;
            }
        }
    }

And how it starts the second transition:

    void animateOff(Pipelines pps) {
        if(animating) return;
        if(mtswitchto != null) {
        // transition visual back to normal
            if (mtoriginal != null) {
                final AnimateRatio ar = new AnimateRatio(pps.freshName("AnimateOff"), 2000L,
                        new MixTextures("MixTextures", false, mtswitchto.name, mtoriginal.name), mtoriginal, name);
                try {
                    pps.install(ar, new String[] { ar.name, name });
                    animating = true;
                    mtswitchto = null;
                } catch (Exception e) {
                    animating = false;
                    mtswitchto = null;
                }
            }
        }
    }

qed.

Posted by g-dollar 2014-11-25 Labels: info

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.