package com.fteams.siftrain.renderer; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.PolygonRegion; import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.fteams.siftrain.World; import com.fteams.siftrain.assets.Assets; import com.fteams.siftrain.assets.GlobalConfiguration; import com.fteams.siftrain.objects.AccuracyMarker; import com.fteams.siftrain.objects.AccuracyPopup; import com.fteams.siftrain.objects.CircleMark; import com.fteams.siftrain.objects.TapZone; import com.fteams.siftrain.util.SongUtils; public class WorldRenderer { private static final float CAMERA_WIDTH = 600f; private static final float CAMERA_HEIGHT = 400f; private World world; private OrthographicCamera cam; // textures TextureRegion circle; TextureRegion circleSim; TextureRegion circleHoldEnd; TextureRegion tapZoneIdle; TextureRegion tapZoneWarn; TextureRegion tapZonePressed; TextureRegion accBadBackground; TextureRegion accGoodBackground; TextureRegion accGreatBackground; TextureRegion accPerfectBackground; TextureRegion holdBG; TextureRegion accHitMark; TextureRegion missMark; TextureRegion badLateMark; TextureRegion badSoonMark; TextureRegion goodLateMark; TextureRegion goodSoonMark; TextureRegion greatLateMark; TextureRegion greatSoonMark; TextureRegion perfectMark; BitmapFont font; BitmapFont songFont; GlyphLayout layout; short[] triangles = {0, 1, 2, 0, 2, 3}; // extra stuff private PolygonSpriteBatch spriteBatch; //private PolygonSpriteBatch polygonSpriteBatch; private ShapeRenderer renderer; private int width; private int height; private int positionOffsetX; private int positionOffsetY; // pixels per unit on X public float ppuX; // pixels per unit on Y public float ppuY; private float time; public void setSize(int w, int h, int offsetX, int offsetY) { this.width = w; this.height = h; this.positionOffsetX = offsetX; this.positionOffsetY = offsetY; ppuX = (float) width / CAMERA_WIDTH; ppuY = (float) height / CAMERA_HEIGHT; } public WorldRenderer(World world) { this.world = world; this.cam = new OrthographicCamera(CAMERA_WIDTH, CAMERA_HEIGHT); this.cam.position.set(0f, 0f, 0f); this.cam.update(); spriteBatch = new PolygonSpriteBatch(); renderer = new ShapeRenderer(); layout = new GlyphLayout(); loadTextures(); } private void loadTextures() { TextureAtlas atlas = Assets.atlas; circle = atlas.findRegion("circle"); circleSim = atlas.findRegion("circle_sim"); circleHoldEnd = atlas.findRegion("circle_hold_release_no_black"); tapZoneIdle = atlas.findRegion("tap"); tapZonePressed = atlas.findRegion("tap_pressed"); tapZoneWarn = atlas.findRegion("tap_warn"); accBadBackground = atlas.findRegion("acc_bad"); accGoodBackground = atlas.findRegion("acc_good"); accGreatBackground = atlas.findRegion("acc_great"); accPerfectBackground = atlas.findRegion("acc_perfect"); accHitMark = atlas.findRegion("acc_mark"); holdBG = new TextureRegion(Assets.holdBG); missMark = atlas.findRegion("miss"); badLateMark = atlas.findRegion("bad_late"); badSoonMark = atlas.findRegion("bad_soon"); goodLateMark = atlas.findRegion("good_late"); goodSoonMark = atlas.findRegion("good_soon"); greatLateMark = atlas.findRegion("great_late"); greatSoonMark = atlas.findRegion("great_soon"); perfectMark = atlas.findRegion("perfect"); font = Assets.font; songFont = Assets.songFont; } public void render() { spriteBatch.begin(); renderer.setProjectionMatrix(cam.combined); renderer.begin(ShapeRenderer.ShapeType.Line); drawTapZones(); drawCircles(); drawCombo(); drawAccuracyBar(); if (!world.started) { drawTapToBeginMessage(); } if (!world.paused) { drawAccuracy(); } if (world.paused) { drawTapToContinue(); } renderer.end(); spriteBatch.end(); time += Gdx.graphics.getDeltaTime(); } private void drawAccuracyBar() { float centerX = this.positionOffsetX + width / 2; float y = this.positionOffsetY + height - height * 0.1f; float bad = (float) (SongUtils.overallDiffBad[GlobalConfiguration.overallDifficulty] * 1f); float nice = (float) (SongUtils.overallDiffNice[GlobalConfiguration.overallDifficulty] * 1f); float great = (float) (SongUtils.overallDiffGreat[GlobalConfiguration.overallDifficulty] * 1f); float perfect = (float) (SongUtils.overallDiffPerfect[GlobalConfiguration.overallDifficulty] * 1f); float zone = bad / 1000f; // draw the background (bad level) spriteBatch.draw(accBadBackground, centerX - width / 6f, y, width / 3f, height * 0.01f); // draw the background (good level) spriteBatch.draw(accGoodBackground, centerX - nice / bad * width / 6f, y, nice / bad * width / 3f, height * 0.01f); // draw the background (great level) spriteBatch.draw(accGreatBackground, centerX - great / bad * width / 6f, y, great / bad * width / 3f, height * 0.01f); // draw the background (perfect level) spriteBatch.draw(accPerfectBackground, centerX - perfect / bad * width / 6f, y, perfect / bad * width / 3f, height * 0.01f); // draw each of the 'markers' for (AccuracyMarker accMarker : world.getAccuracyMarkers()) { if (accMarker.display) { spriteBatch.setColor(1, 1, 1, accMarker.getAlpha()); spriteBatch.draw(accHitMark, centerX + (accMarker.getTime()) * (width / 6) / zone - accHitMark.getRegionWidth(), y - height * 0.01f, 3f, height * 0.03f); } } spriteBatch.setColor(1, 1, 1, 1); } private void drawTapToBeginMessage() { String tapToBegin = "Tap to begin!"; float centerX = this.positionOffsetX + width / 2; float centerY = this.positionOffsetY + height / 2 + height * 0.15f; layout.setText(songFont, tapToBegin); songFont.draw(spriteBatch, tapToBegin, centerX - layout.width / 2, centerY - layout.height / 2); } private void drawTapToContinue() { String tapToBegin = "Tap to continue!"; float centerX = this.positionOffsetX + width / 2; float centerY = this.positionOffsetY + height / 2 + height * 0.15f; layout.setText(songFont, tapToBegin); songFont.draw(spriteBatch, tapToBegin, centerX - layout.width / 2, centerY - layout.height / 2); String backToExit = "Or press back again to skip to the Results screen."; centerX = this.positionOffsetX + width / 2; centerY = this.positionOffsetY + height / 2 + height * 0.1f; layout.setText(songFont, backToExit); songFont.draw(spriteBatch, backToExit, centerX - layout.width / 2, centerY - layout.height / 2); } private void drawAccuracy() { float scale = height / GlobalConfiguration.BASE_HEIGHT; float centerX = this.positionOffsetX + width / 2; float centerY = this.positionOffsetY + height / 2 + height * 0.15f; for (AccuracyPopup popup : world.getAccuracyPopups()) { if (popup.show) { TextureRegion region = perfectMark; if (popup.accuracy == CircleMark.Accuracy.MISS) { region = missMark; } if (popup.accuracy == CircleMark.Accuracy.BAD) { region = popup.soon ? badSoonMark : badLateMark; } if (popup.accuracy == CircleMark.Accuracy.GOOD) { region = popup.soon ? goodSoonMark : goodLateMark; } if (popup.accuracy == CircleMark.Accuracy.GREAT) { region = popup.soon ? greatSoonMark : greatLateMark; } spriteBatch.setColor(1, 1, 1, popup.getAlpha()); spriteBatch.draw(region, centerX - scale * region.getRegionWidth() * popup.getSize() / 2, centerY - scale * region.getRegionHeight() * popup.getSize() / 2, scale * region.getRegionWidth() * popup.getSize(), scale * region.getRegionHeight() * popup.getSize()); } } spriteBatch.setColor(1, 1, 1, 1); } private void drawCombo() { float centerX = this.positionOffsetX + width / 2; float centerY = height / 2; if (world.combo != 0) { layout.setText(font, "" + world.combo); font.draw(spriteBatch, "" + world.combo, centerX - layout.width / 2, centerY - layout.height / 2); } } private void drawTapZones() { float centerX = this.positionOffsetX + width / 2; float centerY = this.positionOffsetY + height - height * 0.25f; float size = height * 0.2f; for (TapZone tapZone : world.getZones()) { TextureRegion region = tapZoneIdle; if (tapZone.getState(TapZone.State.STATE_WARN)) { region = tapZoneWarn; } if (tapZone.getState(TapZone.State.STATE_PRESSED)) { tapZone.touchTime = time; } final float x = centerX + tapZone.getPosition().x * ppuX - size / 2; final float y = centerY + tapZone.getPosition().y * ppuY - size / 2; spriteBatch.draw(region, x, y, size, size); float alpha = 1f - MathUtils.clamp((time - tapZone.touchTime) * 5f, 0f, 1f); if (alpha > 0) { Color c = spriteBatch.getColor(); spriteBatch.setColor(c.r, c.g, c.b, Interpolation.pow2In.apply(alpha)); spriteBatch.draw(tapZonePressed, x, y, size, size); spriteBatch.setColor(c); } } } private void drawHoldBeam(Vector2 from, Vector2 to, float orgSize, float dstSize) { Vector2 delta = from.cpy().sub(to); float w = Math.max(orgSize, dstSize); float h = delta.len(); float tw = holdBG.getRegionWidth(); float th = holdBG.getRegionHeight(); float factorScale = (tw / w) * 0.5f; float topFactor = Math.max(dstSize - orgSize, 0f) * factorScale; float botFactor = Math.max(orgSize - dstSize, 0f) * factorScale; float[] points = { topFactor, 0f, botFactor, th, tw - botFactor, th, tw - topFactor, 0f }; PolygonRegion clamped = new PolygonRegion(holdBG, points, triangles); spriteBatch.draw(clamped, from.x - w * 0.5f, from.y, w * 0.5f, 0f, w, h, 1f, 1f, delta.angle() + 90); } private void drawCircles() { float centerX = this.positionOffsetX + width / 2; float centerY = this.positionOffsetY + height - height * 0.25f; float size = height * 0.2f; for (CircleMark mark : world.getMarks()) { if (!mark.visible && !mark.hold) continue; float alpha = mark.alpha; float alpha2 = mark.alpha2; Color c = spriteBatch.getColor(); if (mark.hold && mark.holdBeamVisible) { if (mark.holding) spriteBatch.setColor(1.0f, 1.0f, 0.5f, alpha * alpha2 * 0.45f * (0.75f + 0.25f * MathUtils.sin(time * 7f + mark.accuracyHitStartTime))); else spriteBatch.setColor(c.r, c.g, c.b, alpha * alpha * alpha2 * 0.45f); float orgSize = mark.getSize2() * size; float dstSize = mark.getSize() * size; Vector2 org = mark.getHoldReleasePosition().cpy(); org.x *= ppuX; org.y *= ppuY; org.x += centerX; org.y += centerY; Vector2 dst = mark.getPosition().cpy(); dst.x *= ppuX; dst.y *= ppuY; dst.x += centerX; dst.y += centerY; drawHoldBeam(org, dst, orgSize, dstSize); spriteBatch.setColor(c.r, c.g, c.b, alpha); } if (mark.visible) { int effectMask = mark.getEffectMask(); spriteBatch.setColor(c.r, c.g, c.b, alpha); spriteBatch.draw(selectTextureForCircle(effectMask), centerX - size * mark.getSize() / 2 + mark.getPosition().x * ppuX, centerY - size * mark.getSize() / 2 + mark.getPosition().y * ppuY, size * mark.getSize(), size * mark.getSize()); } if (mark.endVisible) { TextureRegion region = circleHoldEnd; spriteBatch.setColor(c.r, c.g, c.b, alpha * alpha2); spriteBatch.draw(region, centerX - size * mark.getSize2() / 2 + mark.getHoldReleasePosition().x * ppuX, centerY - size * mark.getSize2() / 2 + mark.getHoldReleasePosition().y * ppuY, size * mark.getSize2(), size * mark.getSize2()); } spriteBatch.setColor(c); } } private TextureRegion selectTextureForCircle(int effectMask) { // TODO: Remove the holdStart texture as it's no longer used if ((effectMask & (SongUtils.NOTE_TYPE_SIMULT_START)) != 0) { return circleSim; } else return circle; } }