/******************************************************************************* * Copyright 2014 Rafael Garcia Moreno. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.bladecoder.engine.model; import java.util.ArrayList; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonValue; import com.bladecoder.engine.actions.ActionCallback; import com.bladecoder.engine.anim.AnimationDesc; import com.bladecoder.engine.anim.Tween; import com.bladecoder.engine.anim.Tween.Type; import com.bladecoder.engine.anim.WalkTween; import com.bladecoder.engine.assets.AssetConsumer; import com.bladecoder.engine.assets.EngineAssetManager; import com.bladecoder.engine.serialization.BladeJson; import com.bladecoder.engine.serialization.BladeJson.Mode; import com.bladecoder.engine.util.EngineLogger; public class SpriteActor extends InteractiveActor implements AssetConsumer { protected ActorRenderer renderer; protected ArrayList<Tween<SpriteActor>> tweens = new ArrayList<>(0); private float rot = 0.0f; private float scaleX = 1.0f; private float scaleY = 1.0f; private Color tint; private boolean fakeDepth = false; private boolean bboxFromRenderer = false; private String playingSound; public void setRenderer(ActorRenderer r) { renderer = r; } public ActorRenderer getRenderer() { return renderer; } public boolean getFakeDepth() { return fakeDepth; } public void setFakeDepth(boolean fd) { fakeDepth = fd; setDirtyProp(DirtyProps.FAKE_DEPTH); } @Override public void setPosition(float x, float y) { super.setPosition(x, y); if (scene != null) { if (fakeDepth) { // interpolation equation float s = scene.getFakeDepthScale(y); setScale(s); } } } public boolean isBboxFromRenderer() { return bboxFromRenderer; } public void setBboxFromRenderer(boolean v) { this.bboxFromRenderer = v; if (v) renderer.updateBboxFromRenderer(getBBox()); else renderer.updateBboxFromRenderer(null); setDirtyProp(DirtyProps.BBOX_FROM_RENDERER); } public float getWidth() { return renderer.getWidth() * scaleX; } public float getHeight() { return renderer.getHeight() * scaleY; } public float getScale() { return scaleX; } public float getScaleX() { return scaleX; } public float getScaleY() { return scaleY; } public Color getTint() { return tint; } public void setTint(Color tint) { this.tint = tint; setDirtyProp(DirtyProps.TINT); } public void setScale(float scale) { setScale(scale, scale); } public void setScale(float scaleX, float scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; if (bboxFromRenderer) getBBox().setScale(scaleX, scaleY); else { float worldScale = EngineAssetManager.getInstance().getScale(); getBBox().setScale(scaleX * worldScale, scaleY * worldScale); } setDirtyProp(DirtyProps.SCALEX); setDirtyProp(DirtyProps.SCALEY); } public void setRot(float rot) { this.rot = rot; getBBox().setRotation(rot); setDirtyProp(DirtyProps.ROT); } public float getRot() { return rot; } @Override public void update(float delta) { super.update(delta); if (isVisible()) { renderer.update(delta); for (int i = 0; i < tweens.size(); i++) { Tween<SpriteActor> t = tweens.get(i); t.update(delta); // Needs extra checks before remove because the update can remove the tween if (t.isComplete() && i < tweens.size() && tweens.get(i) == t) { tweens.remove(i); i--; } } } } public void draw(SpriteBatch batch) { if (isVisible()) { if (scaleX != 0 && scaleY != 0) { renderer.draw(batch, getX(), getY(), scaleX, scaleY, rot, tint); } } } public void startAnimation(String id, ActionCallback cb) { startAnimation(id, Tween.Type.SPRITE_DEFINED, 1, cb); } public void startAnimation(String id, Tween.Type repeatType, int count, ActionCallback cb) { if (!(renderer instanceof AnimationRenderer)) return; inAnim(); // resets posTween when walking removeTween(WalkTween.class); EngineLogger.debug("ANIMATION: " + this.id + "." + id); ((AnimationRenderer) renderer).startAnimation(id, repeatType, count, cb); outAnim(repeatType); } public void removeTween(Class<?> clazz) { for (int i = 0; i < tweens.size(); i++) { Tween<SpriteActor> t = tweens.get(i); if (clazz.isInstance(t)) { tweens.remove(i); i--; } } } /** * Actions to do when setting an animation: - stop previous animation sound - * add 'out' distance from previous animation */ protected void inAnim() { AnimationDesc fa = ((AnimationRenderer) renderer).getCurrentAnimation(); if (fa != null) { if (fa.sound != null) { // Backwards compatibility String sid = fa.sound; if (scene != null && scene.getWorld().getSounds().get(sid) == null) sid = id + "_" + fa.sound; // it will not play the sound in inventory if (scene != null) scene.getSoundManager().stopSound(sid); } Vector2 outD = fa.outD; if (outD != null) { float s = EngineAssetManager.getInstance().getScale(); setPosition(getX() + outD.x * s, getY() + outD.y * s); } } } /** * Actions to do when setting an animation: - play animation sound - add 'in' * distance * * @param repeatType */ protected void outAnim(Type repeatType) { AnimationDesc fa = ((AnimationRenderer) renderer).getCurrentAnimation(); if (fa != null) { if (fa.sound != null && repeatType != Tween.Type.REVERSE) { // Backwards compatibility String sid = fa.sound; if (scene != null && scene.getWorld().getSounds().get(sid) == null) sid = id + "_" + fa.sound; // it will not play the sound in inventory if (scene != null) scene.getSoundManager().playSound(sid); } Vector2 inD = fa.inD; if (inD != null) { float s = EngineAssetManager.getInstance().getScale(); setPosition(getX() + inD.x * s, getY() + inD.y * s); } } } public void addTween(Tween<SpriteActor> tween) { removeTween(tween.getClass()); tweens.add(tween); } @Override public String toString() { StringBuilder sb = new StringBuilder(super.toString()); sb.append(" Sprite Bbox: ").append(getBBox().toString()); sb.append(renderer); return sb.toString(); } @Override public void loadAssets() { renderer.loadAssets(); } @Override public void retrieveAssets() { renderer.retrieveAssets(); // Call setPosition to recalc fake depth and camera follow setPosition(getBBox().getX(), getBBox().getY()); } @Override public void dispose() { // EngineLogger.debug("DISPOSE: " + id); renderer.dispose(); } @Override public void write(Json json) { BladeJson bjson = (BladeJson) json; // Reset vertices if bboxFromRenderer to save always with 0.0 value if (bboxFromRenderer && bjson.getMode() == Mode.MODEL) { float[] verts = getBBox().getVertices(); getBBox().setVertices(new float[8]); super.write(json); getBBox().setVertices(verts); } else { super.write(json); } if (bjson.getMode() == Mode.MODEL) { json.writeValue("renderer", renderer, null); if (tint != null) json.writeValue("tint", tint); } else { json.writeValue("renderer", renderer); json.writeValue("tweens", tweens, ArrayList.class, Tween.class); if (playingSound != null) json.writeValue("playingSound", playingSound); if (isDirty(DirtyProps.TINT)) json.writeValue("tint", tint); } if (bjson.getMode() == Mode.MODEL || isDirty(DirtyProps.SCALEX)) json.writeValue("scaleX", scaleX); if (bjson.getMode() == Mode.MODEL || isDirty(DirtyProps.SCALEY)) json.writeValue("scaleY", scaleY); if (bjson.getMode() == Mode.MODEL || isDirty(DirtyProps.ROT)) json.writeValue("rot", rot); if (bjson.getMode() == Mode.MODEL || isDirty(DirtyProps.FAKE_DEPTH)) json.writeValue("fakeDepth", fakeDepth); if (bjson.getMode() == Mode.MODEL || isDirty(DirtyProps.BBOX_FROM_RENDERER)) json.writeValue("bboxFromRenderer", bboxFromRenderer); } @SuppressWarnings("unchecked") @Override public void read(Json json, JsonValue jsonData) { super.read(json, jsonData); BladeJson bjson = (BladeJson) json; if (bjson.getMode() == Mode.MODEL) { renderer = json.readValue("renderer", ActorRenderer.class, jsonData); } else { tweens = json.readValue("tweens", ArrayList.class, Tween.class, jsonData); if (tweens == null) { EngineLogger.debug("Couldn't load state of actor: " + id); return; } for (Tween<SpriteActor> t : tweens) t.setTarget(this); renderer.read(json, jsonData.get("renderer")); playingSound = json.readValue("playingSound", String.class, jsonData); } renderer.setWorld(bjson.getWorld()); if (jsonData.get("scale") != null) { scaleX = json.readValue("scale", float.class, jsonData); scaleY = scaleX; } else { scaleX = json.readValue("scaleX", float.class, scaleX, jsonData); scaleY = json.readValue("scaleY", float.class, scaleY, jsonData); } rot = json.readValue("rot", float.class, rot, jsonData); tint = json.readValue("tint", Color.class, tint, jsonData); // backwards compatibility fakeDepth if (jsonData.get("depthType") != null) { String depthType = json.readValue("depthType", String.class, (String) null, jsonData); fakeDepth = "VECTOR".equals(depthType); } else { fakeDepth = json.readValue("fakeDepth", boolean.class, fakeDepth, jsonData); } bboxFromRenderer = json.readValue("bboxFromRenderer", boolean.class, bboxFromRenderer, jsonData); if (bboxFromRenderer) renderer.updateBboxFromRenderer(getBBox()); setScale(scaleX, scaleY); setRot(rot); // restore dirtyProps after rotation and scale dirtyProps = json.readValue("dirtyProps", long.class, 0L, jsonData); } }