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);
}
}
}
}