package com.jonathan.survivor.renderers; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.esotericsoftware.spine.AnimationState; import com.esotericsoftware.spine.AnimationState.AnimationStateListener; import com.esotericsoftware.spine.AnimationStateData; import com.esotericsoftware.spine.Event; import com.esotericsoftware.spine.Skeleton; import com.jonathan.survivor.Assets; import com.jonathan.survivor.SoundListener.Sound; import com.jonathan.survivor.World; import com.jonathan.survivor.entity.Human.Direction; import com.jonathan.survivor.entity.Human.Mode; import com.jonathan.survivor.entity.Human.State; import com.jonathan.survivor.entity.Zombie; public class ZombieRenderer { /** Stores the world whose methods are called, for instance, when an Earthquake needs to be spawned by a zombie. */ private World world; /** Stores the SpriteBatcher used to draw the zombie's sprites. */ private SpriteBatch batcher; /** Stores the Assets singleton which stores all of the visual assets needed to draw the zombie. */ private Assets assets = Assets.instance; /** Stores the color of transparent zombies, when they are on different layers than the player. */ private static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0.4f); /** Holds the color of the zombie when he is being targetted by the player. */ private static final Color TARGETTED_COLOR = new Color(0.6f, 0.7f, 1, 1); /** Defines the crossfading times between the zombies' animations. */ public static AnimationStateData animStateData; /** Stores the AnimationStateListener that receives animation events. */ private AnimationStateListener animationListener; /** Helper Color instance used to color the zombies and avoid creating new color instances. */ private Color workingColor; /** Stores the integers assigned to each event in Spine. Used to indicate which event was caught in the AnimationStateListener. */ private static final int HIT_GROUND = 0; /** Accepts the World instance whose methods are called when needed, and the SpriteBatch used to draw the zombies. */ public ZombieRenderer(World world, SpriteBatch batcher) { //Stores the given arguments into their respective member variables this.world = world; this.batcher = batcher; //Instantiates the helper Color object used to color the ItemObjects. workingColor = new Color(Color.WHITE); //Sets up the animation states of the zombie, along with the crossfading times between animations/ setupAnimationStates(); } /** Populates the AnimationStateData and AnimationData instances used by the zombie. */ private void setupAnimationStates() { //Creates a new AnimationStateData instance from the zombie's skeleton to define the crossfading times between animations. animStateData = new AnimationStateData(assets.zombieSkeletonData); //Defines the crossfading times between animations. First two arguments specify the animations to crossfade. Third argument specifies crossfading time. animStateData.setMix(assets.zombieWalk, assets.zombieIdle, 0.3f); animStateData.setMix(assets.zombieIdle, assets.zombieWalk, 0.1f); animStateData.setMix(assets.zombieIdle, assets.zombieAlerted, 0.1f); animStateData.setMix(assets.zombieIdle, assets.zombieCharge_Start, 0.1f); animStateData.setMix(assets.zombieIdle, assets.zombieSmash, 0.1f); animStateData.setMix(assets.zombieAlerted, assets.zombieIdle, 0.1f); animStateData.setMix(assets.zombieWalk, assets.zombieAlerted, 0.1f); animStateData.setMix(assets.zombieAlerted, assets.zombieWalk, 0.1f); animStateData.setMix(assets.zombieCharge_Start, assets.zombieCharge, 0.2f); } private class ZombieAnimationListener implements AnimationStateListener { /** Stores the Zombie instance which delegates the methods in this listener. */ private Zombie zombie; //Creates a new AnimationListener for the given Zombie. This Zombie will delegate the listener's methods. */ public ZombieAnimationListener(Zombie zombie) { //Registers the Zombie which will trigger this listener's methods. this.zombie = zombie; } @Override public void event(int trackIndex, Event event) { //If the zombie has hit the ground while performing his SMASH animation, spawn an Earthquake at the zombie's position. if(event.getInt() == HIT_GROUND) { //Make the zombie spawn an earthquake at his feet. world.spawnEarthquake(zombie); } } @Override public void complete(int trackIndex, int loopCount) { //If the zombie just completed his ALERTED animation if(zombie.getState() == State.ALERTED) { //Set the zombie to IDLE state so that the ZombieManager knows to make him follow the player. zombie.setState(State.IDLE); //Remove the image of the yellow exclamation point on top of the zombie's head, since his ALERTED state is over, and he thus no longer requires it. zombie.getSkeleton().setAttachment("Alarm", null); } //Else, if the ENTER_COMBAT animation has just finished playing else if(zombie.getState() == State.ENTER_COMBAT) { //Set the zombie back to IDLE state so that his correct animation plays. zombie.setState(State.IDLE); } //Else, if the zombie has finished playing its charge taunting animation else if(zombie.getState() == State.CHARGE_START) { //Tell the zombie to charge at the player. zombie.setState(State.CHARGE); } //Else, if the zombie has completed playing its SMASH animation else if(zombie.getState() == State.SMASH) { if(Math.random() > 0.5f) { zombie.setPreviousState(State.IDLE); zombie.setState(State.SMASH); } //Set the zombie back to IDLE state so that he chooses his next move. zombie.setState(State.IDLE); } //Else, if the player was hit else if(zombie.getState() == State.HIT) { //If the zombie is in EXPLORATION mode if(zombie.getMode() == Mode.EXPLORING) { } //Else, if the zombie is in COMBAT mode with the player. else if(zombie.getMode() == Mode.COMBAT) { //Set the zombie to WALK state, telling him to walk back to his starting position facing the player. zombie.setState(State.WALK); //Tell the zombie to walk to the RIGHT to go back to his original position. zombie.setDirection(Direction.RIGHT); } } //Else, if the zombie was hit in the head. else if(zombie.getState() == State.HIT_HEAD) { //If the zombie is in EXPLORATION mode if(zombie.getMode() == Mode.EXPLORING) { } //Else, if the zombie is in COMBAT mode with the player. else if(zombie.getMode() == Mode.COMBAT) { //Set the zombie to WALK state, telling him to walk back to his starting position facing the player. zombie.setState(State.WALK); //Tell the zombie to walk to the RIGHT to go back to his original position. zombie.setDirection(Direction.RIGHT); } } } @Override public void start(int trackIndex) { // TODO Auto-generated method stub } @Override public void end(int trackIndex) { // TODO Auto-generated method stub } } /** Draws the zombie using his Spine skeleton, which stores his animations, sprites, and everything needed to draw the zombie. Accepts a boolean which depicts * whether or not the zombie should be drawn transparently. */ public void draw(Zombie zombie, boolean transparent, float deltaTime) { //Retrieves the Spine skeleton used to animate and display the zombie. Skeleton skeleton = zombie.getSkeleton(); //If the zombie's AnimationState instance is null, create it. Note that the animationState for the zombies are always created in this renderer. if(zombie.getAnimationState() == null) { //Creates and sets a new AnimationState instance used to control the zombie's animations. zombie.setAnimationState(new AnimationState(ZombieRenderer.animStateData)); //Creates a new AnimationListener for the zombie and registers it to his AnimationState. Passing the zombie as a constructor argument ensures //that the listener knows that this zombie triggers the listener's methods. zombie.getAnimationState().addListener(new ZombieAnimationListener(zombie)); } //Stores the AnimationState used to change and control the zombie's animations. AnimationState animationState = zombie.getAnimationState(); //If the zombie is looking left if(zombie.getDirection() == Direction.LEFT) //Flip his skeleton to look the other way. The skeleton looks right by default. skeleton.setFlipX(true); //Else, if the zombie is facing right else //Don't flip his skeleton, since his skeleton is made to look to the right by default. skeleton.setFlipX(false); //Set the zombie skeleton's bottom-center position to the bottom-center position of the Player GameObject. skeleton.setX(zombie.getX()); skeleton.setY(zombie.getY()); //Updates the attachments on the zombie, such as his attached weapons. updateAttachments(zombie); //Change the animation if the zombie's state has changed. Re-setting the AnimationState to the same animation twice causes errors. if(zombie.getState() != zombie.getPreviousState()) { //Update the zombie's animation since his state has changed. Passes in the zombie whose animations need to be updated. updateAnimation(zombie); } //Sets the zombie to be the correct color depending on the zombie's current state, and whether or not it should be transparent. updateColor(zombie, transparent); //Updates the state of the current zombie animation. animationState.update(deltaTime); //Applies the current animation to the zombie's skeleton. animationState.apply(skeleton); //Updates the skeleton world transform. Must be done to reset SRT coordinates (or something). skeleton.updateWorldTransform(); //Draws the skeleton using the universal SkeletonRenderer instance used by the game. assets.skeletonRenderer.draw(batcher, skeleton); } /** Updates the current animation of the zombie accroding to his current state. Accepts the zombie to update. */ private void updateAnimation(Zombie zombie) { //Retrieves the Spine skeleton used to animate and display the zombie. Skeleton skeleton = zombie.getSkeleton(); //Stores the AnimationState used to change and control the zombie's animations. AnimationState animationState = zombie.getAnimationState(); //Stores the previous state of the zombie to determine if his state changes on the next render() call. zombie.setPreviousState(zombie.getState()); //If the zombie has just spawned if(zombie.getState() == State.SPAWN) { //Set the zombie's skeleton back to default pose. skeleton.setToSetupPose(); //Sets the zombie to IDLE state, indicating that the renderer has received the message that the zombie spawned. zombie.setState(State.IDLE); } else if(zombie.getState() == State.IDLE) { //Sets the zombie to his idle animation. First argument is an arbitrary index, and third argument specifies to loop the animation. animationState.setAnimation(0, assets.zombieIdle, true); } else if(zombie.getState() == State.WALK) { //Plays the walk animation. First argument is an arbitrary index, and third argument specifies to loop the walk animation. animationState.setAnimation(0, assets.zombieWalk, true); } //Else, if the zombie has just been alerted that the player is close to him else if(zombie.getState() == State.ALERTED) { //Plays the alerted animation. First argument is an arbitrary index, and third argument specifies to play the animation only one. animationState.setAnimation(0, assets.zombieAlerted, false); //Display the image of the yellow exclamation point on top of the zombie's head, since he is in ALERTED state. zombie.getSkeleton().setAttachment("Alarm", "AlarmSymbol"); //Play the sound of the zombie starting to charge, which is simply a sound of a zombie growling. world.playSound(Sound.ZOMBIE_CHARGE_START); } //Else, if the zombie has just entered combat else if(zombie.getState() == State.ENTER_COMBAT) { //Plays the ENTER_COMBAT animation. First argument is an arbitrary index, and third argument specifies to play the animation only once. animationState.setAnimation(0, assets.zombieEnterCombat, false); } //Else, if the zombie is getting ready to charge towards the player else if(zombie.getState() == State.CHARGE_START) { //Plays the CHARGE_START animation. First argument is an arbitrary index, and third argument specifies to loop the walk animation. animationState.setAnimation(0, assets.zombieCharge_Start, true); //Play the sound of the zombie starting to charge. world.playSound(Sound.ZOMBIE_CHARGE_START); } //Else, if the zombie is charging towards the player else if(zombie.getState() == State.CHARGE) { //Plays the CHARGE animation. First argument is an arbitrary index, and third argument specifies to loop the walk animation. animationState.setAnimation(0, assets.zombieCharge, true); //Play the sound of the zombie charging, since the zombie just started charging. world.playSound(Sound.ZOMBIE_CHARGE); } //Else, if the zombie is performing a SMASH which will cause an earthquake else if(zombie.getState() == State.SMASH) { //Play the zombie's SMASH animation. First argument is an arbitrary index, and third argument specifies to play the animation only once. animationState.setAnimation(0, assets.zombieSmash, true); //Play the sound of the zombie starting to charge, which is simply a sound of a zombie growling. world.playSound(Sound.ZOMBIE_CHARGE_START); } //Else, if the zombie was hit by the player else if(zombie.getState() == State.HIT) { //Plays the HIT animation. First argument is an arbitrary index, and third argument specifies to play the animation only once. animationState.setAnimation(0, assets.zombieHitHead, false); //Play the sound of the zombie getting hit. world.playSound(Sound.ZOMBIE_HIT); } //Else, if the zombie was hit on the head by the player else if(zombie.getState() == State.HIT_HEAD) { //Plays the HIT_HEAD animation. First argument is an arbitrary index, and third argument specifies to play the animation only once. animationState.setAnimation(0, assets.zombieHitHead, false); //Play the sound of the zombie getting hit. world.playSound(Sound.ZOMBIE_HIT); } //Else, if the zombie is dead else if(zombie.getState() == State.DEAD) { //Plays the zombie's DEAD animation. First argument is an arbitrary index, and third argument specifies to play the animation only once. animationState.setAnimation(0, assets.zombieDead, false); //Play the sound of the zombie getting hit. world.playSound(Sound.ZOMBIE_HIT); } //Updates the speed at which the zombie's animations play, depending on the zombie's current state. updateTimeScale(zombie); } /** Updates the attachments being rendered on the zombie. */ private void updateAttachments(Zombie zombie) { //Update the myriad extra colliders attached to the zombie. zombie.updateColliders(); } /** Updates the zombie's color depending on whether its being targetted, and whether or not it should be drawn transparent. */ private void updateColor(Zombie zombie, boolean transparent) { //Retrieves the Spine skeleton used to animate and display the zombie. Skeleton skeleton = zombie.getSkeleton(); //Reset the working color instance to WHITE so that the helper color starts from a clean slate. workingColor.set(Color.WHITE); //If the zombie is being targetted by the player if(zombie.isTargetted()) { //Updates the zombie's color so that the player knows he's targetted. workingColor.mul(TARGETTED_COLOR); } //If the ItemObject is supposed to be drawn transparent if(transparent) //Multiply the working color by the TRANSPARENT_COLOR constant so that the ItemObject is drawn transparent. workingColor.mul(TRANSPARENT_COLOR); //Set the Item GameObject's color to the workingColor instance, which stored the color that the ItemObject should be. skeleton.getColor().set(workingColor); } /** Updates the Zombie's TimeScale so that its animations play faster or slower, depending on the zombie's current state. */ private void updateTimeScale(Zombie zombie) { //Stores the AnimationState used to change and control the zombie's animations. AnimationState animationState = zombie.getAnimationState(); //If the zombie is exploring the world, check his state to determine the speed of his animations. if(zombie.getMode() == Mode.EXPLORING) { //If the zombie is alerted if(zombie.isAlerted()) //Make the animation go faster since the zombie is walking faster animationState.setTimeScale(Zombie.ALERTED_ANIM_SPEED); //Else, if the zombie is not aware of the palyer's presence else //Make the zombie's animations go at normal speed if the zombie is not alerted of the player's presence. animationState.setTimeScale(1); } //If the zombie is in combat with the player, check his state to determine the speed of his animations. else if(zombie.getMode() == Mode.COMBAT) { //If the player is walking, the speed of his animation should match the speed of his movements if(zombie.getState() == State.WALK) { //Find the time scale of the zombie's animations by finding how much faster the zombie walks than usual. animationState.setTimeScale(1); } //Else, if the zombie is not walking, set his time scale to default else { //Make the zombie's animations go at normal speed if the zombie is not walking back to his original position animationState.setTimeScale(1); } } } }