/** * GladiatorBrawler is a 2D swordfighting game. * Copyright (C) 2015 Jeasonfire/Allexit * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.saltosion.gladiator.systems; import com.badlogic.ashley.core.ComponentMapper; import com.badlogic.ashley.core.ComponentType; import com.badlogic.ashley.core.Engine; import com.badlogic.ashley.core.Entity; import com.badlogic.ashley.core.EntitySystem; import com.badlogic.ashley.core.Family; import com.badlogic.ashley.utils.ImmutableArray; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.Vector2; import com.saltosion.gladiator.components.CAI; import com.saltosion.gladiator.components.CCombat; import com.saltosion.gladiator.components.CParticle; import com.saltosion.gladiator.components.CPhysics; import com.saltosion.gladiator.components.CRenderedObject; import com.saltosion.gladiator.gui.nodes.GUINode; import com.saltosion.gladiator.gui.properties.ImageProperty; import com.saltosion.gladiator.gui.nodes.TextNode; import com.saltosion.gladiator.gui.properties.TextProperty; import com.saltosion.gladiator.util.AppUtil; import com.saltosion.gladiator.util.AudioLoader; import com.saltosion.gladiator.util.Global; import com.saltosion.gladiator.util.Name; import com.saltosion.gladiator.util.SpriteLoader; import com.saltosion.gladiator.util.SpriteSequence; import java.util.ArrayList; import java.util.List; public class RenderingSystem extends EntitySystem { private final ComponentMapper rom = ComponentMapper.getFor(CRenderedObject.class); private final ComponentMapper pm = ComponentMapper.getFor(CPhysics.class); private final ComponentMapper cm = ComponentMapper.getFor(CCombat.class); private final ComponentMapper aim = ComponentMapper.getFor(CAI.class); private final ComponentMapper pam = ComponentMapper.getFor(CParticle.class); private ImmutableArray entities; private SpriteBatch batch; private BitmapFont font; private ShapeRenderer debugRenderer, particleRenderer; private OrthographicCamera camera, fontCamera; public float aspectratio; public int screenHeight = 0; public int screenWidth = 0; private boolean debug = false; private final Color debugColor = new Color(0, 1, 0, 1), debugAIColor = new Color(1, 0, 0, 1); private float deltaDelay = 0; private double deltaAvgSum; private long deltaAvgTimes; private String deltaString = "0"; private List drawableText; private Sprite[] healthbar; private boolean healthbarLoaded = false; private float xMin = -15, xMax = 15; @Override public void addedToEngine(Engine engine) { updateEntities(engine); batch = new SpriteBatch(); font = new BitmapFont(Gdx.files.internal("fonts/font.fnt")); font.setUseIntegerPositions(false); debugRenderer = new ShapeRenderer(); particleRenderer = new ShapeRenderer(); camera = new OrthographicCamera(); camera.setToOrtho(false, 1, 1); fontCamera = new OrthographicCamera(); fontCamera.setToOrtho(false, Global.FONT_SCALE, Global.FONT_SCALE); drawableText = new ArrayList(); } public void setViewport(int width, int height) { camera.setToOrtho(false, width, height); fontCamera.setToOrtho(false, width * Global.FONT_SCALE, height * Global.FONT_SCALE); } @Override public void update(float deltaTime) { camera.update(); fontCamera.update(); Gdx.gl.glClearColor(0, 0, 0, 0); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); updateEntityAnimations(deltaTime); renderEntities(deltaTime); renderParticles(); renderDebug(); renderGUI(new Vector2(0, 0)); if (debug) { drawString("FPS: " + Gdx.graphics.getFramesPerSecond(), new Vector2(camera.position.x - 12, camera.position.y + 8)); drawString("Delta (ms): " + getDeltaWithDelay(deltaTime, 0.1f), new Vector2(camera.position.x - 12, camera.position.y + 7)); } renderFont(fontCamera); } /** * A debugging function for easier performance logging. * * @param deltaTime The delta of the current frame * @param delay The delay between deltaString updates * @return A string that has the delta formatted */ private String getDeltaWithDelay(float deltaTime, float delay) { this.deltaDelay += deltaTime; this.deltaAvgSum += Gdx.graphics.getDeltaTime() * 1000; this.deltaAvgTimes++; if (this.deltaDelay >= delay) { this.deltaDelay = 0; this.deltaString = String.format("%.2f", this.deltaAvgSum / this.deltaAvgTimes); this.deltaAvgSum = 0; this.deltaAvgTimes = 0; } return deltaString; } private void updateEntityAnimations(float deltaTime) { for (int i = 0; i < entities.size(); i++) { updateEntityAnimation(entities.get(i), deltaTime); } } private void updateEntityAnimation(Entity entity, float deltaTime) { CRenderedObject ro = rom.get(entity); CPhysics po = pm.get(entity); CCombat co = cm.get(entity); if (ro == null || po == null || co == null) { return; } boolean moving = false, combat = false; String dirMove = po.movedLeftLast ? "Left" : "Right"; String dirSwing = ""; if (po.movingLeft || po.movingRight) { moving = true; } if (co.swingCdCounter > 0) { combat = true; switch (co.getSwingDirection()) { default: case RIGHT: dirSwing += "Right"; break; case LEFT: dirSwing += "Left"; break; case UP: if (dirMove.equals("Left")) { dirSwing = "Left-"; } else { dirSwing = "Right-"; } dirSwing += "Up"; break; case DOWN: if (dirMove.equals("Left")) { dirSwing = "Left-"; } else { dirSwing = "Right-"; } dirSwing += "Down"; break; } } // Play animations & play sounds if (po.stepCD > 0) { po.stepCD -= deltaTime; } if (moving && combat) { ro.playAnimation("torso", "Torso-Combat-" + dirSwing); ro.playAnimation("legs", "Legs-Run-" + dirMove); tryToMakeStepSound(po); } else if (combat) { ro.playAnimation("torso", "Torso-Combat-" + dirSwing); ro.playAnimation("legs", "Legs-Idle-" + dirMove); } else if (moving) { ro.playAnimation("torso", "Torso-Run-" + dirMove); ro.playAnimation("legs", "Legs-Run-" + dirMove); tryToMakeStepSound(po); } else { ro.playAnimation("torso", "Torso-Idle-" + dirMove); ro.playAnimation("legs", "Legs-Idle-" + dirMove); } if (!po.isGrounded()) { ro.playAnimation("legs", "Legs-Jump-" + dirMove); } } private void tryToMakeStepSound(CPhysics po) { if (po.stepCD > 0 || !po.isGrounded()) { return; } po.stepCD = 0.3f; AppUtil.jukebox.playSound(AudioLoader.getSound(Name.SOUND_STEP), AppUtil.sfxVolume / 3 * 2); } private void renderEntities(float deltaTime) { if (AppUtil.player == null) { return; } CPhysics playerPhys = pm.get(AppUtil.player); batch.setProjectionMatrix(camera.combined); batch.begin(); for (int i = 0; i < entities.size(); i++) { CPhysics physics = pm.get(entities.get(i)); CRenderedObject renderedObject = rom.get(entities.get(i)); if (renderedObject == null) { continue; } // Draw entity for (String channel : renderedObject.getChannels()) { SpriteSequence currSequence = renderedObject.getSequence(renderedObject.getCurrentSequence(channel)); int currFrame = (int) Math.floor(renderedObject.getCurrentFrame(channel)); Sprite currSprite = currSequence.getSprite(currFrame); int spriteHeight = currSprite.getRegionHeight(); int spriteWidth = currSprite.getRegionWidth(); currSprite.setPosition(((physics.getPosition().x - spriteWidth / 2) + getCameraOffset(playerPhys, physics).x), (physics.getPosition().y - spriteHeight / 2) + getCameraOffset(playerPhys, physics).y); currSprite.draw(batch); float nextFrame = renderedObject.getCurrentFrame(channel) + deltaTime * currSequence.getPlayspeed(); renderedObject.setCurrentFrame(channel, nextFrame % currSequence.frameCount()); } // Draw healthbars CCombat combat = cm.get(entities.get(i)); if (combat != null) { if (!healthbarLoaded) { loadHealthbarSprites(); } float spriteWidth = healthbar[0].getWidth(); float spriteHeight = healthbar[0].getHeight(); float hp = (float) combat.getHealth() / (float) combat.getMaxHealth(); healthbar[0].setPosition(((physics.getPosition().x - spriteWidth / 2) + getCameraOffset(playerPhys, physics).x), (physics.getPosition().y - spriteHeight / 2 + 2.5f) + getCameraOffset(playerPhys, physics).y); healthbar[0].draw(batch); healthbar[1].setPosition(((physics.getPosition().x - spriteWidth / 2) + getCameraOffset(playerPhys, physics).x), (physics.getPosition().y - spriteHeight / 2 + 2.5f) + getCameraOffset(playerPhys, physics).y); healthbar[1].setSize(spriteWidth * hp, spriteHeight); healthbar[1].draw(batch); healthbar[2].setPosition(((physics.getPosition().x - spriteWidth / 2) + getCameraOffset(playerPhys, physics).x), (physics.getPosition().y - spriteHeight / 2 + 2.5f) + getCameraOffset(playerPhys, physics).y); healthbar[2].draw(batch); } } batch.end(); } private void renderGUI(Vector2 rootPosition) { batch.setProjectionMatrix(camera.combined); batch.begin(); renderGUINode(AppUtil.guiManager.getRootNode(), rootPosition); batch.end(); } private void renderGUINode(GUINode node, Vector2 position) { if (!node.isVisible()) { return; } position.add(node.getPosition()); if (node instanceof ImageProperty) { Sprite s = ((ImageProperty) node).getImage(); s.setPosition(position.x * AppUtil.VPHEIGHT_CONST * aspectratio - s.getWidth() / 2 + camera.position.x, position.y * AppUtil.VPHEIGHT_CONST - s.getHeight() / 2 + camera.position.y); s.draw(batch); } if (node instanceof TextNode) { drawString(((TextProperty) node).getText(), new Vector2(position.x * AppUtil.VPHEIGHT_CONST * aspectratio + camera.position.x, position.y * AppUtil.VPHEIGHT_CONST + camera.position.y)); } for (GUINode child : node.getChildren()) { renderGUINode(child, new Vector2(position)); } } private void renderParticles() { if (AppUtil.player == null) { return; } CPhysics playerPhys = pm.get(AppUtil.player); particleRenderer.setProjectionMatrix(camera.combined); particleRenderer.begin(ShapeType.Filled); for (int i = 0; i < entities.size(); i++) { CParticle particle = pam.get(entities.get(i)); if (particle == null) { continue; } particleRenderer.setColor(particle.getColor()); particleRenderer.rect(particle.getPosition().x - particle.getSize().x / 2 + getCameraOffset(playerPhys).x, particle.getPosition().y - particle.getSize().y / 2 + getCameraOffset(playerPhys).y, particle.getSize().x, particle.getSize().y); } particleRenderer.end(); } private void renderDebug() { if (debug) { if (AppUtil.player == null) { return; } CPhysics playerPhys = pm.get(AppUtil.player); debugRenderer.setProjectionMatrix(camera.combined); debugRenderer.begin(ShapeType.Line); for (int i = 0; i < entities.size(); i++) { CPhysics physics = pm.get(entities.get(i)); if (physics == null) { continue; } float x0 = physics.getPosition().x - physics.getSize().x / 2 + getCameraOffset(playerPhys, physics).x; float x1 = physics.getPosition().x + physics.getSize().x / 2 + getCameraOffset(playerPhys, physics).x; float y0 = physics.getPosition().y - physics.getSize().y / 2 + getCameraOffset(playerPhys, physics).y; float y1 = physics.getPosition().y + physics.getSize().y / 2 + getCameraOffset(playerPhys, physics).y; debugRenderer.setColor(debugColor); debugRenderer.line(x0, y0, x1, y0); debugRenderer.line(x1, y0, x1, y1); debugRenderer.line(x1, y1, x0, y1); debugRenderer.line(x0, y1, x0, y0); CAI ai = aim.get(entities.get(i)); if (ai == null) { continue; } float x = physics.getPosition().x + getCameraOffset(playerPhys, physics).x; float y = physics.getPosition().y + getCameraOffset(playerPhys, physics).y; debugRenderer.setColor(debugAIColor); debugRenderer.circle(x, y, ai.getReactDistance()); } debugRenderer.end(); } } /** * This is the main method that actually _renders_ the text. Use * "drawString(str, pos)" method to add a string to a list that will be * rendered here. * * @param camera */ private void renderFont(Camera fontCamera) { batch.setProjectionMatrix(fontCamera.combined); batch.begin(); for (TextObject obj : drawableText) { font.draw(batch, obj.text, obj.position.x * Global.FONT_SCALE, obj.position.y * Global.FONT_SCALE); } drawableText.clear(); batch.end(); } public void drawString(String text, Vector2 position) { drawableText.add(new TextObject(text, position)); } public void updateEntities(Engine engine) { entities = engine.getEntitiesFor(Family.getFor(ComponentType.getBitsFor(), ComponentType.getBitsFor(CPhysics.class, CParticle.class), ComponentType.getBitsFor())); } public void loadHealthbarSprites() { healthbarLoaded = true; healthbar = new Sprite[3]; healthbar[0] = SpriteLoader.loadSprite(Name.HEALTHBARIMG, 0, 0, 32, 8); healthbar[1] = SpriteLoader.loadSprite(Name.HEALTHBARIMG, 0, 1, 32, 8); healthbar[2] = SpriteLoader.loadSprite(Name.HEALTHBARIMG, 0, 2, 32, 8); } public boolean getDebug() { return this.debug; } public void setDebug(boolean debug) { this.debug = debug; } public Vector2 getCameraLocation() { return new Vector2(this.camera.position.x, this.camera.position.y); } private Vector2 getCameraOffset(CPhysics playerPhys) { Vector2 offset = new Vector2(Math.max(xMin + camera.viewportWidth / 2, Math.min(xMax - camera.viewportWidth / 2, -playerPhys.getPosition().x)) + camera.viewportWidth / 2, -playerPhys.getPosition().y + camera.viewportHeight / 3); return offset; } private Vector2 getCameraOffset(CPhysics playerPhys, CPhysics currPhys) { Vector2 offset = new Vector2(Math.max(xMin + camera.viewportWidth / 2, Math.min(xMax - camera.viewportWidth / 2, -playerPhys.getPosition().x)) / currPhys.getZParallax() + camera.viewportWidth / 2, -playerPhys.getPosition().y / currPhys.getZParallax() + camera.viewportHeight / 3); return offset; } public float getXMin() { return xMin; } public RenderingSystem setXMin(float xMin) { this.xMin = xMin; return this; } public float getXMax() { return xMax; } public RenderingSystem setXMax(float xMax) { this.xMax = xMax; return this; } public void dispose() { batch.dispose(); debugRenderer.dispose(); particleRenderer.dispose(); font.dispose(); } private class TextObject { public String text; public Vector2 position; public TextObject(String text, Vector2 position) { this.text = text; this.position = position; } } }