# Conflicts:
#	gradle/wrapper/gradle-wrapper.properties

Also added Solarized Dark theme.
This commit is contained in:
Oliver Jan Krylow 2017-05-08 19:15:18 +02:00
commit a4e9936a85
64 changed files with 1157 additions and 511 deletions

View file

@ -21,6 +21,8 @@ public class Klooni extends Game {
public static Theme theme;
public Skin skin;
public ShareChallenge shareChallenge;
public static boolean onDesktop;
private final static float SCORE_TO_MONEY = 1f / 100f;
@ -32,6 +34,12 @@ public class Klooni extends Game {
//region Creation
// TODO Possibly implement a 'ShareChallenge'
// for other platforms instead passing null
public Klooni(final ShareChallenge shareChallenge) {
this.shareChallenge = shareChallenge;
}
@Override
public void create() {
onDesktop = Gdx.app.getType().equals(Application.ApplicationType.Desktop);

View file

@ -0,0 +1,87 @@
package io.github.lonamiwebs.klooni;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.PixmapIO;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.ScreenUtils;
import java.io.File;
public abstract class ShareChallenge {
// Meant to return the file path to which the image will be saved
// On some platforms it might be as simple as Gdx.files.local().file()
abstract File getShareImageFilePath();
// Meant to share the saved screenshot at getShareImageFilePath()
public abstract void shareScreenshot(final boolean saveResult);
// Saves the "Challenge me" shareable image to getShareImageFilePath()
public boolean saveChallengeImage(final int score) {
final File saveAt = getShareImageFilePath();
if (!saveAt.getParentFile().isDirectory())
if (!saveAt.mkdirs())
return false;
final FileHandle output = new FileHandle(saveAt);
final Texture shareBase = new Texture(Gdx.files.internal("share.png"));
final int width = shareBase.getWidth();
final int height = shareBase.getHeight();
final FrameBuffer frameBuffer = new FrameBuffer(Pixmap.Format.RGB888, width, height, false);
frameBuffer.begin();
// Render the base share texture
final SpriteBatch batch = new SpriteBatch();
final Matrix4 matrix = new Matrix4();
matrix.setToOrtho2D(0, 0, width, height);
batch.setProjectionMatrix(matrix);
Gdx.gl.glClearColor(Color.GOLD.r, Color.GOLD.g, Color.GOLD.b, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(shareBase, 0, 0);
// Render the achieved score
final Label.LabelStyle style = new Label.LabelStyle();
style.font = new BitmapFont(Gdx.files.internal("font/x1.0/geosans-light64.fnt"));
Label label = new Label("just scored " + score + " on", style);
label.setColor(Color.BLACK);
label.setPosition(40, 500);
label.draw(batch, 1);
label.setText("try to beat me if you can");
label.setPosition(40, 40);
label.draw(batch, 1);
batch.end();
// Get the framebuffer pixels and write them to a local file
final byte[] pixels = ScreenUtils.getFrameBufferPixels(0, 0, width, height, true);
final Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
BufferUtils.copy(pixels, 0, pixmap.getPixels(), pixels.length);
PixmapIO.writePNG(output, pixmap);
// Dispose everything
pixmap.dispose();
shareBase.dispose();
batch.dispose();
frameBuffer.end();
return true;
}
}

View file

@ -59,7 +59,7 @@ public class SkinLoader {
}
public static Texture loadPng(String name) {
final String filename = "ui/x" + bestMultiplier + "/" + name + ".png";
final String filename = "ui/x" + bestMultiplier + "/" + name;
return new Texture(Gdx.files.internal(filename));
}
}

View file

@ -24,19 +24,21 @@ public class Theme {
private int price;
public Color background;
public Color foreground;
public Color emptyCell;
public Color currentScore;
public Color highScore;
public Color bonus;
public Color bandColor;
public Color textColor;
private Color[] cells;
private Color[] buttons;
public static Skin skin;
public NinePatch cellPatch;
public Texture cellTexture;
// Save the button styles so the changes here get reflected
private ImageButton.ImageButtonStyle[] buttonStyles;
@ -84,6 +86,20 @@ public class Theme {
return new Theme().update(handle);
}
// Used to determine the best foreground color (black or white) given a background color
// Formula took from http://alienryderflex.com/hsp.html
// Not used yet, but may be useful
private final static double BRIGHTNESS_CUTOFF = 0.5;
public static boolean shouldUseWhite(Color color) {
double brightness = Math.sqrt(
color.r * color.r * .299 +
color.g * color.g * .587 +
color.b * color.b * .114);
return brightness < BRIGHTNESS_CUTOFF;
}
//endregion
//region Theme updating
@ -105,8 +121,9 @@ public class Theme {
price = json.getInt("price");
JsonValue colors = json.get("colors");
background = new Color( // Java won't allow unsigned integers, we need to use Long
(int)Long.parseLong(colors.getString("background"), 16));
// Java won't allow unsigned integers, we need to use Long
background = new Color((int)Long.parseLong(colors.getString("background"), 16));
foreground = new Color((int)Long.parseLong(colors.getString("foreground"), 16));
JsonValue buttonColors = colors.get("buttons");
buttons = new Color[buttonColors.size];
@ -125,6 +142,7 @@ public class Theme {
highScore = new Color((int)Long.parseLong(colors.getString("high_score"), 16));
bonus = new Color((int)Long.parseLong(colors.getString("bonus"), 16));
bandColor = new Color((int)Long.parseLong(colors.getString("band"), 16));
textColor = new Color((int)Long.parseLong(colors.getString("text"), 16));
emptyCell = new Color((int)Long.parseLong(colors.getString("empty_cell"), 16));
@ -135,8 +153,7 @@ public class Theme {
}
String cellTextureFile = json.getString("cell_texture");
cellPatch = new NinePatch(new Texture(
Gdx.files.internal("ui/cells/"+cellTextureFile)), 4, 4, 4, 4);
cellTexture = SkinLoader.loadPng("cells/"+cellTextureFile);
return this;
}

View file

@ -65,9 +65,11 @@ public class Band extends Actor {
scoreLabel.setBounds(x + scoreBounds.x, y + scoreBounds.y, scoreBounds.width, scoreBounds.height);
scoreLabel.setText(Integer.toString(scorer.getCurrentScore()));
scoreLabel.setColor(Klooni.theme.textColor);
scoreLabel.draw(batch, parentAlpha);
infoLabel.setBounds(x + infoBounds.x, y + infoBounds.y, infoBounds.width, infoBounds.height);
infoLabel.setColor(Klooni.theme.textColor);
infoLabel.draw(batch, parentAlpha);
}

View file

@ -182,6 +182,7 @@ public class MoneyBuyBand extends Table {
}
}
setColor(Klooni.theme.bandColor);
infoLabel.setColor(Klooni.theme.textColor);
super.draw(batch, parentAlpha);
}

View file

@ -1,5 +1,6 @@
package io.github.lonamiwebs.klooni.actors;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
@ -19,17 +20,21 @@ public class SoftButton extends ImageButton {
//region Constructor
public SoftButton(int styleIndex, String imageName) {
public SoftButton(final int styleIndex, final String imageName) {
super(Klooni.theme.getStyle(styleIndex));
this.styleIndex = styleIndex;
image = Theme.skin.getDrawable(imageName);
updateImage(imageName);
}
//endregion
//region Public methods
public void updateImage(final String imageName) {
image = Theme.skin.getDrawable(imageName);
}
@Override
public void draw(Batch batch, float parentAlpha) {
// Always update the style to make sure we're using the right image.
@ -40,6 +45,7 @@ public class SoftButton extends ImageButton {
Klooni.theme.updateStyle(style, styleIndex);
style.imageUp = image;
getImage().setColor(Klooni.theme.foreground);
super.draw(batch, parentAlpha);
}

View file

@ -1,5 +1,6 @@
package io.github.lonamiwebs.klooni.actors;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
@ -28,11 +29,11 @@ public class ThemeCard extends Actor {
public float cellSize;
//endregion
//region Static members
private final static double BRIGHTNESS_CUTOFF = 0.5;
private final static int colorsUsed[][] = {
{0, 7, 7},
{8, 7, 3},
{8, 8, 3}
};
//endregion
@ -48,7 +49,7 @@ public class ThemeCard extends Actor {
priceLabel = new Label("", labelStyle);
nameLabel = new Label(theme.getDisplay(), labelStyle);
Color labelColor = shouldUseWhite(theme.background) ? Color.WHITE : Color.BLACK;
Color labelColor = Theme.shouldUseWhite(theme.background) ? Color.WHITE : Color.BLACK;
priceLabel.setColor(labelColor);
nameLabel.setColor(labelColor);
@ -69,21 +70,14 @@ public class ThemeCard extends Actor {
batch.setColor(theme.background);
batch.draw(background, x, y, getWidth(), getHeight());
// Do not draw on the borders (0,0 offset to add some padding), colors used:
// 0 7 7
// 8 7 3
// 8 8 3
Cell.draw(theme.getCellColor(0), batch, x + cellSize, y + cellSize, cellSize);
Cell.draw(theme.getCellColor(7), batch, x + cellSize * 2, y + cellSize, cellSize);
Cell.draw(theme.getCellColor(7), batch, x + cellSize * 3, y + cellSize, cellSize);
Cell.draw(theme.getCellColor(8), batch, x + cellSize, y + cellSize * 2, cellSize);
Cell.draw(theme.getCellColor(7), batch, x + cellSize * 2, y + cellSize * 2, cellSize);
Cell.draw(theme.getCellColor(8), batch, x + cellSize * 3, y + cellSize * 2, cellSize);
Cell.draw(theme.getCellColor(8), batch, x + cellSize, y + cellSize * 3, cellSize);
Cell.draw(theme.getCellColor(8), batch, x + cellSize * 2, y + cellSize * 3, cellSize);
Cell.draw(theme.getCellColor(3), batch, x + cellSize * 3, y + cellSize * 3, cellSize);
// Avoid drawing on the borders by adding +1 cell padding
for (int i = 0; i < colorsUsed.length; ++i) {
for (int j = 0; j < colorsUsed[i].length; ++j) {
Cell.draw(theme.cellTexture, theme.getCellColor(colorsUsed[i][j]), batch,
x + cellSize * (j + 1), y + cellSize * (i + 1), cellSize);
}
}
nameLabel.setBounds(x + nameBounds.x, y + nameBounds.y, nameBounds.width, nameBounds.height);
nameLabel.draw(batch, parentAlpha);
@ -112,19 +106,4 @@ public class ThemeCard extends Actor {
}
//endregion
//region Private methods
// Used to determine the best foreground color (black or white) given a background color
// Formula took from http://alienryderflex.com/hsp.html
private static boolean shouldUseWhite(Color color) {
double brightness = Math.sqrt(
color.r * color.r * .299 +
color.g * color.g * .587 +
color.b * color.b * .114);
return brightness < BRIGHTNESS_CUTOFF;
}
//endregion
}

View file

@ -37,7 +37,7 @@ public abstract class BaseScorer implements BinSerializable {
// The board size is required when calculating the score
BaseScorer(final Klooni game, GameLayout layout, int highScore) {
cupTexture = SkinLoader.loadPng("cup");
cupTexture = SkinLoader.loadPng("cup.png");
cupColor = Klooni.theme.currentScore.cpy();
cupArea = new Rectangle();

View file

@ -2,6 +2,7 @@ package io.github.lonamiwebs.klooni.game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Interpolation;
@ -105,11 +106,18 @@ public class Cell implements BinSerializable {
//region Static methods
// TODO Use skin atlas
public static void draw(Color color, Batch batch,
float x, float y, float size) {
// Default texture (don't call overloaded version to avoid overhead)
public static void draw(final Color color, final Batch batch,
final float x, final float y, final float size) {
batch.setColor(color);
Klooni.theme.cellPatch.draw(batch, x, y, size, size);
batch.draw(Klooni.theme.cellTexture, x, y, size, size);
}
// Custom texture
public static void draw(final Texture texture, final Color color, final Batch batch,
final float x, final float y, final float size) {
batch.setColor(color);
batch.draw(texture, x, y, size, size);
}
//endregion

View file

@ -125,6 +125,7 @@ class GameScreen implements Screen, InputProcessor, BinSerializable {
if (!gameOverDone) {
gameOverDone = true;
saveMoney();
holder.enabled = false;
pauseMenu.showGameOver(gameOverReason);
if (Klooni.soundsEnabled())
@ -150,13 +151,7 @@ class GameScreen implements Screen, InputProcessor, BinSerializable {
// Save the state, the user might leave the game in any of the following 2 methods
private void showPauseMenu() {
// Calculate new money since the previous saving
int nowScore = scorer.getCurrentScore();
int newMoneyScore = nowScore - savedMoneyScore;
savedMoneyScore = nowScore;
Klooni.addMoneyFromScore(newMoneyScore);
// Show the pause menu
saveMoney();
pauseMenu.show();
save();
}
@ -275,6 +270,14 @@ class GameScreen implements Screen, InputProcessor, BinSerializable {
//region Saving and loading
private void saveMoney() {
// Calculate new money since the previous saving
int nowScore = scorer.getCurrentScore();
int newMoneyScore = nowScore - savedMoneyScore;
savedMoneyScore = nowScore;
Klooni.addMoneyFromScore(newMoneyScore);
}
private void save() {
// Only save if the game is not over and the game mode is not the time mode. It
// makes no sense to save the time game mode since it's supposed to be something quick.

View file

@ -1,8 +1,11 @@
package io.github.lonamiwebs.klooni.screens;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Interpolation;
@ -13,7 +16,10 @@ import com.badlogic.gdx.scenes.scene2d.actions.RunnableAction;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import java.io.File;
import io.github.lonamiwebs.klooni.Klooni;
import io.github.lonamiwebs.klooni.ShareChallenge;
import io.github.lonamiwebs.klooni.actors.Band;
import io.github.lonamiwebs.klooni.actors.SoftButton;
import io.github.lonamiwebs.klooni.game.BaseScorer;
@ -31,8 +37,10 @@ class PauseMenuStage extends Stage {
private final ShapeRenderer shapeRenderer;
private final Klooni game;
private final Band band;
private final BaseScorer scorer;
private final SoftButton playButton;
//endregion
@ -40,6 +48,7 @@ class PauseMenuStage extends Stage {
// We need the score to save the maximum score if a new record was beaten
PauseMenuStage(final GameLayout layout, final Klooni game, final BaseScorer scorer, final int gameMode) {
this.game = game;
this.scorer = scorer;
shapeRenderer = new ShapeRenderer(20); // 20 vertex seems to be enough for a rectangle
@ -90,8 +99,7 @@ class PauseMenuStage extends Stage {
});
// Continue playing OR share (if game over) button
// TODO Enable both actions for this button? Or leave play?
final SoftButton playButton = new SoftButton(2, "play_texture");
playButton = new SoftButton(2, "play_texture");
table.add(playButton).space(16);
playButton.addListener(new ChangeListener() {
@ -143,6 +151,16 @@ class PauseMenuStage extends Stage {
}
void showGameOver(final String gameOverReason) {
if (game.shareChallenge != null) {
playButton.updateImage("share_texture");
playButton.addListener(new ChangeListener() {
public void changed(ChangeEvent event, Actor actor) {
game.shareChallenge.shareScreenshot(
game.shareChallenge.saveChallengeImage(scorer.getCurrentScore()));
}
});
}
band.setMessage(gameOverReason);
show();
}
@ -166,7 +184,9 @@ class PauseMenuStage extends Stage {
// This is the only place where ShapeRenderer is OK because the batch hasn't started
Gdx.gl.glEnable(GL20.GL_BLEND);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(1f, 1f, 1f, 0.3f);
Color color = new Color(Klooni.theme.bandColor);
color.a = 0.1f;
shapeRenderer.setColor(color);
shapeRenderer.rect(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
shapeRenderer.end();
}