v1.4.0: implemented a new hero select UI for landscape, needs polish

This commit is contained in:
Evan Debenham
2022-09-29 17:57:33 -04:00
parent a70e278745
commit 3c34c27909
3 changed files with 236 additions and 102 deletions
@@ -55,7 +55,6 @@ import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfLullaby;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfMagicMapping; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfMagicMapping;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRage; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRage;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.exotic.ScrollOfDivination;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfMagicMissile; import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfMagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Dagger; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Dagger;
@@ -205,6 +204,10 @@ public enum HeroClass {
return Messages.get(HeroClass.class, name()+"_desc"); return Messages.get(HeroClass.class, name()+"_desc");
} }
public String shortDesc(){
return Messages.get(HeroClass.class, name()+"_desc_short");
}
public HeroSubClass[] subClasses() { public HeroSubClass[] subClasses() {
return subClasses; return subClasses;
} }
@@ -248,43 +251,6 @@ public enum HeroClass {
} }
} }
public String[] perks() {
switch (this) {
case WARRIOR: default:
return new String[]{
Messages.get(HeroClass.class, "warrior_perk1"),
Messages.get(HeroClass.class, "warrior_perk2"),
Messages.get(HeroClass.class, "warrior_perk3"),
Messages.get(HeroClass.class, "warrior_perk4"),
Messages.get(HeroClass.class, "warrior_perk5"),
};
case MAGE:
return new String[]{
Messages.get(HeroClass.class, "mage_perk1"),
Messages.get(HeroClass.class, "mage_perk2"),
Messages.get(HeroClass.class, "mage_perk3"),
Messages.get(HeroClass.class, "mage_perk4"),
Messages.get(HeroClass.class, "mage_perk5"),
};
case ROGUE:
return new String[]{
Messages.get(HeroClass.class, "rogue_perk1"),
Messages.get(HeroClass.class, "rogue_perk2"),
Messages.get(HeroClass.class, "rogue_perk3"),
Messages.get(HeroClass.class, "rogue_perk4"),
Messages.get(HeroClass.class, "rogue_perk5"),
};
case HUNTRESS:
return new String[]{
Messages.get(HeroClass.class, "huntress_perk1"),
Messages.get(HeroClass.class, "huntress_perk2"),
Messages.get(HeroClass.class, "huntress_perk3"),
Messages.get(HeroClass.class, "huntress_perk4"),
Messages.get(HeroClass.class, "huntress_perk5"),
};
}
}
public boolean isUnlocked(){ public boolean isUnlocked(){
//always unlock on debug builds //always unlock on debug builds
if (DeviceCompat.isDebug()) return true; if (DeviceCompat.isDebug()) return true;
@@ -302,16 +268,7 @@ public enum HeroClass {
} }
public String unlockMsg() { public String unlockMsg() {
switch (this){ return shortDesc() + "\n\n" + Messages.get(HeroClass.class, name()+"_unlock");
case WARRIOR: default:
return "";
case MAGE:
return Messages.get(HeroClass.class, "mage_unlock");
case ROGUE:
return Messages.get(HeroClass.class, "rogue_unlock");
case HUNTRESS:
return Messages.get(HeroClass.class, "huntress_unlock");
}
} }
} }
@@ -25,7 +25,6 @@ import com.shatteredpixel.shatteredpixeldungeon.Badges;
import com.shatteredpixel.shatteredpixeldungeon.Chrome; import com.shatteredpixel.shatteredpixeldungeon.Chrome;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.GamesInProgress; import com.shatteredpixel.shatteredpixeldungeon.GamesInProgress;
import com.shatteredpixel.shatteredpixeldungeon.Rankings;
import com.shatteredpixel.shatteredpixeldungeon.SPDSettings; import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass;
@@ -56,6 +55,7 @@ import com.watabou.noosa.PointerArea;
import com.watabou.noosa.ui.Component; import com.watabou.noosa.ui.Component;
import com.watabou.utils.DeviceCompat; import com.watabou.utils.DeviceCompat;
import com.watabou.utils.GameMath; import com.watabou.utils.GameMath;
import com.watabou.utils.PointF;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -66,10 +66,14 @@ import java.util.TimeZone;
public class HeroSelectScene extends PixelScene { public class HeroSelectScene extends PixelScene {
private Image background; private Image background;
private RenderedTextBlock prompt; private Image fadeLeft, fadeRight;
private IconButton btnFade; //only on landscape
//fading UI elements //fading UI elements
private RenderedTextBlock title;
private ArrayList<StyledButton> heroBtns = new ArrayList<>(); private ArrayList<StyledButton> heroBtns = new ArrayList<>();
private RenderedTextBlock heroName; //only on landscape
private RenderedTextBlock heroDesc; //only on landscape
private StyledButton startBtn; private StyledButton startBtn;
private IconButton infoButton; private IconButton infoButton;
private IconButton btnOptions; private IconButton btnOptions;
@@ -100,21 +104,30 @@ public class HeroSelectScene extends PixelScene {
background.x = (Camera.main.width - background.width())/2f; background.x = (Camera.main.width - background.width())/2f;
background.y = (Camera.main.height - background.height())/2f; background.y = (Camera.main.height - background.height())/2f;
if (landscape()){
//FIXME static offset and UI width falls apart at high resolutions
//Should try not always having the UI stuck to the left, perhaps occupy the left 20% or so instead
//Should also really try to have more variable spacing between UI elements so it fills things in a bit more
background.x += 40;
}
background.visible = false; background.visible = false;
PixelScene.align(background); PixelScene.align(background);
add(background); add(background);
if (background.x > 0){ fadeLeft = new Image(TextureCache.createGradient(0xFF000000, 0xFF000000, 0x00000000));
Image fadeLeft = new Image(TextureCache.createGradient(0xFF000000, 0x00000000)); fadeLeft.x = background.x-2;
fadeLeft.x = background.x-2; fadeLeft.scale.set(3, background.height());
fadeLeft.scale.set(4, background.height()); add(fadeLeft);
add(fadeLeft);
Image fadeRight = new Image(fadeLeft); fadeRight = new Image(fadeLeft);
fadeRight.x = background.x + background.width() + 2; fadeRight.x = background.x + background.width() + 2;
fadeRight.y = background.y + background.height(); fadeRight.y = background.y + background.height();
fadeRight.angle = 180; fadeRight.angle = 180;
add(fadeRight); add(fadeRight);
if (landscape()){
float fadeLeftScale = 47 * (80 - background.x)/80f;
fadeLeft.scale = new PointF(3 + Math.max(0, fadeLeftScale), background.height());
} }
startBtn = new StyledButton(Chrome.Type.GREY_BUTTON_TR, ""){ startBtn = new StyledButton(Chrome.Type.GREY_BUTTON_TR, ""){
@@ -142,7 +155,11 @@ public class HeroSelectScene extends PixelScene {
@Override @Override
protected void onClick() { protected void onClick() {
super.onClick(); super.onClick();
ShatteredPixelDungeon.scene().addToFront(new WndHeroInfo(GamesInProgress.selectedClass)); Window w = new WndHeroInfo(GamesInProgress.selectedClass);
if (landscape()){
w.offset(40, 0);
}
ShatteredPixelDungeon.scene().addToFront(w);
} }
@Override @Override
@@ -154,28 +171,81 @@ public class HeroSelectScene extends PixelScene {
infoButton.setSize(20, 21); infoButton.setSize(20, 21);
add(infoButton); add(infoButton);
HeroClass[] classes = HeroClass.values(); for (HeroClass cl : HeroClass.values()){
int btnWidth = HeroBtn.MIN_WIDTH;
int curX = (Camera.main.width - btnWidth * classes.length)/2;
if (curX > 0){
btnWidth += Math.min(curX/(classes.length/2), 15);
curX = (Camera.main.width - btnWidth * classes.length)/2;
}
int heroBtnleft = curX;
for (HeroClass cl : classes){
HeroBtn button = new HeroBtn(cl); HeroBtn button = new HeroBtn(cl);
button.setRect(curX, Camera.main.height-HeroBtn.HEIGHT+3, btnWidth, HeroBtn.HEIGHT);
curX += btnWidth;
add(button); add(button);
heroBtns.add(button); heroBtns.add(button);
} }
int btnWidth = HeroBtn.MIN_WIDTH;
float curX;
if (landscape()){
btnWidth = HeroBtn.MIN_WIDTH + 15;
int cols = (1 + heroBtns.size())/2;
curX = (Camera.main.width - btnWidth * cols + (cols-1))/2f;
} else {
curX = (Camera.main.width - btnWidth * heroBtns.size())/2f;
if (curX > 0) {
btnWidth += Math.min(curX / (heroBtns.size() / 2f), 15);
curX = (Camera.main.width - btnWidth * heroBtns.size()) / 2f;
}
}
float curY;
if (landscape()){
curY = (Camera.main.height-HeroBtn.HEIGHT)/2f + 5;
} else {
curY = Camera.main.height-HeroBtn.HEIGHT+3;
}
if (landscape()){
int count = 0;
for (StyledButton button : heroBtns){
button.setRect(curX, curY, btnWidth, HeroBtn.HEIGHT);
align(button);
curX += btnWidth+1;
count++;
if (count >= (1+heroBtns.size())/2){
curX -= btnWidth*count + count;
curY += HeroBtn.HEIGHT+1;
if (heroBtns.size()%2 != 0){
curX += btnWidth/2f;
}
count = 0;
}
}
} else {
for (StyledButton button : heroBtns){
button.setRect(curX, curY, btnWidth, HeroBtn.HEIGHT);
curX += btnWidth;
}
}
if (landscape()){
heroName = renderTextBlock(9);
add(heroName);
heroDesc = renderTextBlock(5);
heroDesc.align(RenderedTextBlock.CENTER_ALIGN);
add(heroDesc);
btnFade = new IconButton(Icons.COMPASS.get()){
@Override
protected void onClick() {
uiAlpha = 0;
updateFade();
}
};
btnFade.icon().originToCenter();
btnFade.icon().angle = 270f;
btnFade.visible = btnFade.active = false;
add(btnFade);
}
optionsPane = new GameOptions(); optionsPane = new GameOptions();
optionsPane.visible = optionsPane.active = false; optionsPane.visible = optionsPane.active = false;
optionsPane.layout(); optionsPane.layout();
optionsPane.setPos(heroBtnleft, 0); optionsPane.setPos(heroBtns.get(0).left(), 0);
add(optionsPane); add(optionsPane);
btnOptions = new IconButton(Icons.get(Icons.PREFS)){ btnOptions = new IconButton(Icons.get(Icons.PREFS)){
@@ -201,7 +271,7 @@ public class HeroSelectScene extends PixelScene {
return Messages.get(HeroSelectScene.class, "options"); return Messages.get(HeroSelectScene.class, "options");
} }
}; };
btnOptions.setRect(heroBtnleft + 16, Camera.main.height-HeroBtn.HEIGHT-16, 20, 21); btnOptions.setRect(heroBtns.get(0).left() + 16, Camera.main.height-HeroBtn.HEIGHT-16, 20, 21);
updateOptionsColor(); updateOptionsColor();
btnOptions.visible = false; btnOptions.visible = false;
@@ -218,16 +288,20 @@ public class HeroSelectScene extends PixelScene {
add( btnExit ); add( btnExit );
btnExit.visible = btnExit.active = !SPDSettings.intro(); btnExit.visible = btnExit.active = !SPDSettings.intro();
prompt = PixelScene.renderTextBlock(Messages.get(this, "title"), 12); title = PixelScene.renderTextBlock(Messages.get(this, "title"), 12);
prompt.hardlight(Window.TITLE_COLOR); title.hardlight(Window.TITLE_COLOR);
prompt.setPos( (Camera.main.width - prompt.width())/2f, (Camera.main.height - HeroBtn.HEIGHT - prompt.height() - 4)); if (landscape()){
PixelScene.align(prompt); title.setPos( (Camera.main.width - title.width())/2f, (Camera.main.height - title.height())/2f - 20);
add(prompt); } else {
title.setPos( (Camera.main.width - title.width())/2f, (Camera.main.height - HeroBtn.HEIGHT - title.height() - 4));
}
PixelScene.align(title);
add(title);
PointerArea fadeResetter = new PointerArea(0, 0, Camera.main.width, Camera.main.height){ PointerArea fadeResetter = new PointerArea(0, 0, Camera.main.width, Camera.main.height){
@Override @Override
public boolean onSignal(PointerEvent event) { public boolean onSignal(PointerEvent event) {
resetFade(); if (event != null && event.type == PointerEvent.Type.UP) resetFade();
return false; return false;
} }
}; };
@@ -259,22 +333,81 @@ public class HeroSelectScene extends PixelScene {
background.visible = true; background.visible = true;
background.hardlight(1.5f,1.5f,1.5f); background.hardlight(1.5f,1.5f,1.5f);
prompt.visible = false; if (landscape()) {
float dX = title.left();
float dY = title.top();
title.setPos((100 - title.width())/2f, (Camera.main.height - 140)/2f);
align(title);
dX = title.left() - dX;
dY = title.top() - dY;
if (dX != 0 || dY != 0) {
for (StyledButton button : heroBtns) {
button.setPos(button.left()+dX, button.top()+dY);
}
}
startBtn.text(Messages.titleCase(Messages.get(HeroSelectScene.class, "start")));
} else {
title.visible = false;
startBtn.text(Messages.titleCase(cl.title()));
}
if (landscape()){
heroName.text(Messages.titleCase(cl.title()));
heroName.hardlight(Window.TITLE_COLOR);
heroName.setPos((100 - heroName.width() - 20)/2f, heroBtns.get(heroBtns.size()-1).bottom()+5);
align(heroName);
heroDesc.text(cl.shortDesc());
heroDesc.maxWidth(80);
heroDesc.setPos(10, heroName.bottom()+5);
align(heroDesc);
//TODO don't like the positioning of this
btnFade.setRect(0, title.top()-10, 100, 10);
btnFade.visible = btnFade.active = true;
}
startBtn.visible = true; startBtn.visible = true;
startBtn.text(Messages.titleCase(cl.title()));
startBtn.textColor(Window.TITLE_COLOR); startBtn.textColor(Window.TITLE_COLOR);
startBtn.setSize(startBtn.reqWidth() + 8, 21); startBtn.setSize(startBtn.reqWidth() + 8, 21);
startBtn.setPos((Camera.main.width - startBtn.width())/2f, startBtn.top()); if (landscape()) {
startBtn.setPos((100 - startBtn.width())/2f, heroName.bottom()+40);
} else {
startBtn.setPos((Camera.main.width - startBtn.width())/2f, startBtn.top());
}
PixelScene.align(startBtn); PixelScene.align(startBtn);
infoButton.visible = true; if (landscape()){
infoButton.setPos(startBtn.right(), startBtn.top());
btnOptions.visible = !SPDSettings.intro(); infoButton.visible = true;
btnOptions.setPos(startBtn.left()-btnOptions.width(), startBtn.top()); infoButton.setPos(heroName.right(), heroName.top() + (heroName.height() - infoButton.height())/2f);
align(infoButton);
optionsPane.setPos(optionsPane.left(), startBtn.top() - optionsPane.height() - 2); btnOptions.visible = !SPDSettings.intro();
PixelScene.align(optionsPane); btnOptions.setPos(startBtn.right(), startBtn.top());
optionsPane.setPos(btnOptions.right(), btnOptions.top() - optionsPane.height() - 2);
align(optionsPane);
} else {
infoButton.visible = true;
infoButton.setPos(startBtn.right(), startBtn.top());
btnOptions.visible = !SPDSettings.intro(); //TODO is it visible when nothing is usable?
btnOptions.setPos(startBtn.left()-btnOptions.width(), startBtn.top());
optionsPane.setPos(heroBtns.get(0).left(), startBtn.top() - optionsPane.height() - 2);
align(optionsPane);
}
updateOptionsColor();
} }
private float uiAlpha; private float uiAlpha;
@@ -287,25 +420,59 @@ public class HeroSelectScene extends PixelScene {
for (Object v : members){ for (Object v : members){
if (v instanceof Window) resetFade(); if (v instanceof Window) resetFade();
} }
if (GamesInProgress.selectedClass != null) { if (!PixelScene.landscape() && GamesInProgress.selectedClass != null) {
if (uiAlpha > 0f){ if (uiAlpha > 0f){
uiAlpha -= Game.elapsed/4f; uiAlpha -= Game.elapsed/4f;
} }
float alpha = GameMath.gate(0f, uiAlpha, 1f); updateFade();
for (StyledButton b : heroBtns){
b.alpha(alpha);
}
startBtn.alpha(alpha);
btnExit.icon().alpha(alpha);
optionsPane.alpha(alpha);
btnOptions.icon().alpha(alpha);
infoButton.icon().alpha(alpha);
} }
} }
private void updateFade(){
float alpha = GameMath.gate(0f, uiAlpha, 1f);
title.alpha(alpha);
for (StyledButton b : heroBtns){
b.enable(alpha != 0);
b.alpha(alpha);
}
if (heroName != null){
heroName.alpha(alpha);
heroDesc.alpha(alpha);
btnFade.enable(alpha != 0);
btnFade.icon().alpha(alpha);
}
startBtn.enable(alpha != 0);
startBtn.alpha(alpha);
btnExit.enable(alpha != 0);
btnExit.icon().alpha(alpha);
optionsPane.active = optionsPane.visible && alpha != 0;
optionsPane.alpha(alpha);
btnOptions.enable(alpha != 0);
btnOptions.icon().alpha(alpha);
infoButton.enable(alpha != 0);
infoButton.icon().alpha(alpha);
if (landscape()){
background.x = (Camera.main.width - background.width())/2f + (40*alpha);
float fadeLeftScale = 47 * (80 - background.x)/80f;
fadeLeft.scale.x = 3 + Math.max(fadeLeftScale, 0)*alpha;
fadeLeft.x = background.x-4;
fadeRight.x = background.x + background.width() + 4;
}
fadeLeft.x = background.x-5;
fadeRight.x = background.x + background.width() + 5;
fadeLeft.visible = background.x > 0 || (alpha > 0 && landscape());
fadeRight.visible = background.x + background.width() < Camera.main.width;
}
private void resetFade(){ private void resetFade(){
//starts fading after 4 seconds, fades over 4 seconds. //starts fading after 4 seconds, fades over 4 seconds.
uiAlpha = 2f; uiAlpha = 2f;
updateFade();
} }
@Override @Override
@@ -354,7 +521,11 @@ public class HeroSelectScene extends PixelScene {
if( !cl.isUnlocked() ){ if( !cl.isUnlocked() ){
ShatteredPixelDungeon.scene().addToFront( new WndMessage(cl.unlockMsg())); ShatteredPixelDungeon.scene().addToFront( new WndMessage(cl.unlockMsg()));
} else if (GamesInProgress.selectedClass == cl) { } else if (GamesInProgress.selectedClass == cl) {
ShatteredPixelDungeon.scene().add(new WndHeroInfo(cl)); Window w = new WndHeroInfo(cl);
if (landscape()){
w.offset(40, 0);
}
ShatteredPixelDungeon.scene().addToFront(w);
} else { } else {
setSelectedHero(cl); setSelectedHero(cl);
} }
@@ -139,6 +139,12 @@ public class WndHeroInfo extends WndTabbed {
} }
@Override
public void offset(int xOffset, int yOffset) {
super.offset(xOffset, yOffset);
talentInfo.layout();
}
private static class HeroInfoTab extends Component { private static class HeroInfoTab extends Component {
private RenderedTextBlock title; private RenderedTextBlock title;