Jump to content

I feel like I'm hardcoding too much pixel values. Any ideas?


Aristy
 Share

Recommended Posts

Hey,

Yesterday I challenged myself to develop a Adventure Capitalist clone in a single day. I got most of the stuff to work (though pretty basic) but whenever I wanted to place some buttons/text on the screen, I felt like I was doing something wrong because I was constantly changing pixel values.

This is how my menu buttons and shop buttons look like:

fhvxDXF.png

and this is the code generating the layout of the shops section:

class AbstractShop
{
    constructor() {

        this.purchased = false;
        this.level = 1;

        this.config = {
            distanceBetweenShops: 100,
            leftSideX:  175,
            rightSideX: 530
        };

        this.styles = {
            font: {
                font: "14px Arial",
                fill: "#ccc"
            }
        };

        // We need 5 shops at left and 5 at right.
        if (this.index <= 4) {
            this.position = {
                x: this.config.leftSideX,
                y: (this.index + 1) * this.config.distanceBetweenShops
            };
        } else {
            this.position = {
                x: this.config.rightSideX,
                y: ((this.index % 5) + 1) * this.config.distanceBetweenShops
            };
        }

        // Add the shop button.
        this.button = this.game.add.button(this.position.x, this.position.y, this.asset.name);
        this.button.alpha = .15;
        this.button.scale.setTo(1, 1);

        this.indicators = {
            name: this.game.add.text(
                this.button.x,
                this.button.y - 16,
                this.game.translator.translate(this.asset.name),
                this.styles.font
            ),
            profit: this.game.add.text(
                this.button.x + 75,
                this.button.y,
                null,
                this.styles.font
            ),
            interval: this.game.add.text(
                this.button.x + 75,
                this.button.y + 15,
                null,
                this.styles.font
            ),
            level: this.game.add.text(
                this.button.x + 75,
                this.button.y + 30,
                null,
                this.styles.font
            ),
            price: this.game.add.text(
                this.button.x + 75,
                this.button.y + 45,
                null,
                this.styles.font
            ),
            alertBg: this.game.add.image(
                this.button.x,
                this.button.y + 45,
                'buy-bg'
            ),
            alert: this.game.add.text(
                this.button.x + 5,
                this.button.y + 45,
                null,
                {
                    font: "14px Arial",
                    fill: "#000"
                }
            ),
            progressBg: this.game.add.image(
                this.button.x,
                this.button.y + 69,
                'progress-bg'
            ),
            progress: new Phaser.Line(0, 0, 0, 0)
        };

        this.indicators.alertBg.scale.setTo(.6, .7);
        this.indicators.alertBg.visible = false;
        this.button.onInputDown.add(() => this.click());
    }
}

as you can see on `this.indicators` property, I'm almost hardcoding everything. x + 75, y - 30 and stuff like this.

I have a few questions regarding this:

1. Is it how it works? You basically need to make pixel-perfect adjustions yourself?
2. Does phaser support more complex button layouts such as CSS? (e.g can I add a padding to buttons so they grow in size if text is longer for example?)

3. I create a button background image and insert it into game object as an image. Then I create a text object and position it on top of the button background. Theorically it is a button but I feel like it is pretty lame. Is there a better way?

4. I created a Line using `new Phaser.Line(0, 0, 0, 0)` however there is no option in the API to increase the width of it. (not length) Is this how lines work or should I rely on something else? The blue line on my screenshot is a progress bar but I need to make it like 15px. Currently it is 1px.

5. If I don't add the following code, the Line doesn't get drawn: `this.game.debug.geom(this.indicators.progress, '#0fffff');` May I ask why do we need the "debug" thing? I'm not sure if I am doing something wrong since relying on "debug" to draw a line sounds like bad practice.

Thanks alot!

Link to comment
Share on other sites

1. Basically.

2. Text does support padding but not all features of css. ( http://phaser.io/docs/2.4.6/Phaser.Text.html )

3. It might be lame but yeah thats what I do normally.

4. Because its a line, you can create a rectangle pretty easily. ( check the API )

5. Using debug for things you want to always show is bad practice, its used for debugging.

Link to comment
Share on other sites

Hey, thanks for the reply @rgk

4: I tried making a rectangle but I couldn't figure how to change it's length dynamically. It just kept adding new rectangles I guess, although I tried to change it's length on update method.

5: That is what the Phaser Examples did for Line. I will check if there is an alternative way but I already used like 20 minutes trying to figure it out.

 

Link to comment
Share on other sites

1 hour ago, mattstyles said:

Nope, this is canvas and not the DOM. It's not until your scene gets extremely complex that you have to worry about it, if at all.

So the final code should look something like this?

create() {
    this.graphics = game.add.graphics(100, 100);
}

update() {
    this.graphics.clear();
    this.graphics.beginFill(0xFF3300);
    this.graphics.lineStyle(10, 0xffd900, 1);
    this.graphics.lineTo(250, ++this.y);
    this.graphics.endFill();
}

The whole rectangle/line/graphic thing is a bit confusing actually.

Link to comment
Share on other sites

5 hours ago, Aristy said:

Wouldn't clearing and redrawing has a performance impact? Or was it destroy that removes the object?

It might add some draw calls, but if it's a shop which needs to be opened with a hotkey (a page), it will most likely have zero performance impact on your game. 

Have you tried moving to a DOM based approach for more complex pages? That's the beauty of creating browser based games, we can create very beautiful overlay's that look like they are part of the game.  Just saying that in case you get sick of hardcoding every single pixel later on.

Link to comment
Share on other sites

6 hours ago, WombatTurkey said:

It might add some draw calls, but if it's a shop which needs to be opened with a hotkey (a page), it will most likely have zero performance impact on your game. 

Have you tried moving to a DOM based approach for more complex pages? That's the beauty of creating browser based games, we can create very beautiful overlay's that look like they are part of the game.  Just saying that in case you get sick of hardcoding every single pixel later on.

What is a DOM based approach? Develop the GUI in HTML5?

Link to comment
Share on other sites

36 minutes ago, Aristy said:

What is a DOM based approach? Develop the GUI in HTML5?

Yeah, then just show / hide it as an overlay to your game. Take a look at http://play.treasurearena.com/'s user interface to kind of get an idea. You can blend it in pretty good within your game. You can create a lot more dynamic user interfaces this way as well. And can use all the CSS you want!

Link to comment
Share on other sites

12 hours ago, WombatTurkey said:

Yeah, then just show / hide it as an overlay to your game. Take a look at http://play.treasurearena.com/'s user interface to kind of get an idea. You can blend it in pretty good within your game. You can create a lot more dynamic user interfaces this way as well. And can use all the CSS you want!

Looks pretty cool. I'm a full stack web developer myself so I can create decent looking GUI's with plain CSS. However, it sounds like a bit bad practice still. I don't like the idea of having a wrapper on top of the canvas div and make some HTML buttons that interacts with Phaser engine.

For my first few games though, I'll definitely use it. It is a great time saving trick regardless.

This is my game in the current state, by the way: http://anilunal.com/games/pizza-clickers/index.html My Paint skills are amazing.

Link to comment
Share on other sites

It's not bad practise at all, from the outset canvas was always designed to work seamlessly with other elements on the page, which, of course, it is. Fonts and SVG's are now the thing for creating icons but if it had gone another way you could easily have seen canvas elements everywhere for those icons, with no perf issues.

HTML/CSS is great for layout, and JS is great for grabbing event streams from those DOM elements, once it hits your JS it can be right in there with the rest of your game code with no interop issues.

Don't fight it, if the UI makes sense to be in HTML then use HTML. Also don't be afraid to stack multiple canvases on top of each other, always check perf of course but it can be a very useful technique sometimes.

Imagine a game where many of the controls open modals which then expose further controls, nothing wrong with having a master canvas for your main game view, then HTML/CSS for your controls on top, then more HTML to house the modal which contains a separate canvas (maybe) for another view. Or maybe you have some sort of status or global overview overlay always present in the corner, maybe that is a separate element and a separate canvas. It's not always a good idea, but it is something in your toolkit that has equal weight to the single canvas approach. Pick and choose what makes sense for the project.

Link to comment
Share on other sites

7 hours ago, Aristy said:

Looks pretty cool. I'm a full stack web developer myself so I can create decent looking GUI's with plain CSS. However, it sounds like a bit bad practice still. I don't like the idea of having a wrapper on top of the canvas div and make some HTML buttons that interacts with Phaser engine.

For my first few games though, I'll definitely use it. It is a great time saving trick regardless.

This is my game in the current state, by the way: http://anilunal.com/games/pizza-clickers/index.html My Paint skills are amazing.

Well, to be fair it can be bad practice if you're manipulating a lot of elements. And for your Pizza game, you have a lot of timers running and progress bars. If you're doing insanely heavy UI / DOM manipulating, canvas will be a better approach. But then again, you have to then check the performance of that canvas and is it affecting your main game? If it is, you can do it via the DOM and you might achieve better performance. Or vice versa. It all kind of depends.  

I understand your reasoning why you might think it's a bad approach as you want to stick to canvas only. It does seem more appropriate, but unless you are not using Ejecta, take advantage of all the tools possible!

Edit: But then again. Some people really hate the DOM and by all means go for it. I think your pizza shop is looking petty nice actually

Link to comment
Share on other sites

46 minutes ago, WombatTurkey said:

Well, to be fair it can be bad practice if you're manipulating a lot of elements. And for your Pizza game, you have a lot of timers running and progress bars. If you're doing insanely heavy UI / DOM manipulating, canvas will be a better approach. But then again, you have to then check the performance of that canvas and is it affecting your main game? If it is, you can do it via the DOM and you might achieve better performance. Or vice versa. It all kind of depends.  

I understand your reasoning why you might think it's a bad approach as you want to stick to canvas only. It does seem more appropriate, but unless you are not using Ejecta, take advantage of all the tools possible!

Edit: But then again. Some people really hate the DOM and by all means go for it. I think your pizza shop is looking petty nice actually

Okay, thanks for the reply. I guess I'll stick to canvas only for learning purposes and try to make a button class myself. I'm not exactly sure how browsers handle padding (do I need to calculate the length of each letter or something?) but I guess I can do something hacky and rely on that class instead of hardcoding everything myself.

Perhabs, I can add a basic bootstrap modal and add some sliders to change volume on the fly and have some basic options, but game related stuff will most likely stay in canvas. :)

Link to comment
Share on other sites

There's quite a big call to say that your game would be far easier to write without canvas, just using DOM. The rationale being that canvas is very good at rendering stuff very quickly and the DOM is far easier to handle user interaction and layout, as your game does nothing taxing graphically but does handle some user interaction (lots of button presses) so its more suited to DOM. Canvas does nothing to handle user input, it is purely a rendering mechanism.

Of course, you want to learn stuff so just put up with that and carry on with canvas.

There are lots of examples of how you create clickable areas within canvas, they all involve creating some sort of scene graph and mapping screen/element click location through to your rendered elements within the canvas, both Pixi and Phaser handle all this for you and model it in a very similar way to how the DOM models events.

Link to comment
Share on other sites

On 15.04.2016 at 2:43 PM, mattstyles said:

There's quite a big call to say that your game would be far easier to write without canvas, just using DOM. The rationale being that canvas is very good at rendering stuff very quickly and the DOM is far easier to handle user interaction and layout, as your game does nothing taxing graphically but does handle some user interaction (lots of button presses) so its more suited to DOM. Canvas does nothing to handle user input, it is purely a rendering mechanism.

Of course, you want to learn stuff so just put up with that and carry on with canvas.

There are lots of examples of how you create clickable areas within canvas, they all involve creating some sort of scene graph and mapping screen/element click location through to your rendered elements within the canvas, both Pixi and Phaser handle all this for you and model it in a very similar way to how the DOM models events.

Matt, thanks for the replies so far. I did a bit of testing on the game you linked to me.

Here is an image: MBnZT1m.png

Whenever I change the screen size, the divs on the DOM change with targeted values. I have a feeling that Phaser makes the scaling itself, but since DOM is independent, they rely on "transform: scale3d(X,Y,Z);" feature to resize the overlay so it matches the size of the canvas.

That's the trick behind it or is there a different approach?

Link to comment
Share on other sites

It was wombat who linked the game, but, yeah, looks like that is the way they are going.

They've made things simple for themselves by just scaling up and down the main container (#screen) using JS to work out the max dimensions of the container so that it maintains the aspect ratio they are using.

The just scaling up and down maintaining aspect is probably the simplest approach, although its the least effective for the user. The alternative is that your game changes its layout based on available screen size, and that is far from a trivial task. If you know most of your users are using screens within a small set of ranges then scaling up and down and accepting letter-boxing or pillar-boxing simply saves you bucketloads of work, both design and coding work.

I'm not sure if this approach is performant or not, I guess the steps might be that you draw all the pixels for a 1280x720 canvas (which could be a large overdraw), then there are some calculations that distort all those pixels to fit within a different size thanks to the scale and finally composite that layer on to the screen (none of this you worry about, its all handled by the browser when you whack the transforms on), all of this sounds more expensive than just drawing into whatever size screen you currently have but I doubt things are that straight forward, again, its a case of test it and see.

 

Link to comment
Share on other sites

@Aristy Yeah, that's one approach as Matt said if you want your game's UI to scale with the screen size. They simply have a onresize event listener and it updates the css for the dom element.  

This is only if you want your game to run at any resolution though. Which is how treasureArena is designed from the ground up, they have a custom scaling property in their source as well (I've been naughty and looked at other stuff too :P)

But, I recommend fixed resolutions. For example, opening a inventory window. The CSS overlay is positioned absolutely to the right of the screen. So it's always going to be there regardless. At a fixed width.

It's all up to you. But, honestly, just add a basic div positioned absolutely with a fixed height/width and play around with it. You'll start to fall in love with it over canvas UI's

Edit: But, doing the canvas UI approach is not like a sin or anything, just a personal preference and harder for me, personally.

Edit2: Also, if you do DOM ui's make sure you use display:none to hide, not opacity or something else. You want to stop the rendering (I'm sure you know this, but just saying)

Link to comment
Share on other sites

On 22.04.2016 at 11:19 PM, mattstyles said:

It was wombat who linked the game, but, yeah, looks like that is the way they are going.

They've made things simple for themselves by just scaling up and down the main container (#screen) using JS to work out the max dimensions of the container so that it maintains the aspect ratio they are using.

The just scaling up and down maintaining aspect is probably the simplest approach, although its the least effective for the user. The alternative is that your game changes its layout based on available screen size, and that is far from a trivial task. If you know most of your users are using screens within a small set of ranges then scaling up and down and accepting letter-boxing or pillar-boxing simply saves you bucketloads of work, both design and coding work.

I'm not sure if this approach is performant or not, I guess the steps might be that you draw all the pixels for a 1280x720 canvas (which could be a large overdraw), then there are some calculations that distort all those pixels to fit within a different size thanks to the scale and finally composite that layer on to the screen (none of this you worry about, its all handled by the browser when you whack the transforms on), all of this sounds more expensive than just drawing into whatever size screen you currently have but I doubt things are that straight forward, again, its a case of test it and see.

 

Yesterday I gave it a try. Used SHOW_ALL feature of Phaser, added an absolutely positioned overlay div on top of the canvas. For testing purposes, I only drew a 50x50 red square to top-left of canvas screen using basic CSS. Actually, I'm still newbie at Phaser so I don't exactly know if some of the features I need is handled by Phaser, or if I should develop the functionality myself.


What kind of algorithms can I rely on to scale my overlay div? Something like the following would work?

.overlay {
    transform: scale3d(
         currentGameScreenHeight / initialGameScreenHeight,
         currentGameScreenWidth / initialGameScreenWidth,
         1
    );
}

This is the part I must get right from the ground up to prevent future headaches. I can develop the game, but all the scaling, keeping aspect ratios, keeping to overlay intact is pretty confusing. :)

Link to comment
Share on other sites

Yes, the Phaser SHOW_ALL handles this by maximising the size of the canvas, but Phaser only knows about its own stuff, any UI you draw with DOM/CSS is outside of the domain of Phaser, so you'll have to replicate how Phaser maximises the screen. I'm also not sure if a Phaser canvas resizes based on the window, or of its parent, in any case you'll probably want to turn the Phaser rescaling off and use your own method on a parent container.

My games don't have this sort of scaling but, off the top of my head, what you'd need to do is work out which is smaller out of horizontal or vertical size and create a scale factor out of that, then apply that scale factor to both width and height:


let container = document.querySelector( '.js-app' )

const WIDTH = 800
const HEIGHT = 600
let vw = window.innerWidth
let vh = window.innerHeight

let scale = Math.min( vw / WIDTH, vh / HEIGHT )

container.style.transform = `scale( ${ scale } )`

This assumes that you are scaling to the window, you might want to add another wrapper and scale to that, then you could additionally set min or max-width in CSS for that overlay. I think the container will probably also need a `transform-origin: top left` applied via CSS to it as well.

So, given a screen of 1200x800, your scaling factor would become 1.33, so your new container size would be ~1066 x 800, of, if your viewport became 768x1024 then your scale factor would become 0.96 so your new screen size would become 768x576.

I've not tested any of it, there might be more to it, but I think something along those lines will work, you can probably get smarter by using one aspect ratio variable rather than a width and height. You'll also find that if your aspect is 1.5 (landscape) but your viewport is portrait then your game will be anchored at the top of the screen, this is simply because vertical positioning is far harder with CSS, I'd recommend adding a wrapper element and then applying CSS to centralise it on screen, this would work:

html,
body {
  min-height: 100vh;
  min-width: 100vw;
}

.wrapper {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate( -50%, -50% );
}

Alternatively, flex-box would probably work very nicely.

Link to comment
Share on other sites

1 hour ago, mattstyles said:

Here, I made a fiddle

https://jsfiddle.net/mattstyles/f5Lm6acx/

Is this sort of thing what you need?

Not sure if this would handle screens with different DPI's, you'd have to test it out, probably an easy fix if it doesnt using devicePixelRatio to alter the scaling.

Wow, thanks alot!

This is what I did after hours of test&trial:

import RainbowText from 'objects/RainbowText';

class GameState extends Phaser.State {

    create() {
        let center = { x: this.game.world.centerX, y: this.game.world.centerY }
        let text = new RainbowText(this.game, center.x, center.y, "- phaser -\nwith a sprinkle of\nES6 dust!");
        text.anchor.set(0.5);

        this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
        this.game.scale.pageAlignHorizontally = true;
        this.game.scale.setResizeCallback(() => this.resize());
        window.addEventListener("resize", () => this.resize());

        this.resize();

        this.game.time.events.loop(Phaser.Timer.SECOND, () => this.fixMargins());
    }

    update() {

    }

    fixMargins() {
        let scaleWidth = (this.game.scale.width / this.game.settings.width).toFixed(2);
        let scaleHeight = (this.game.scale.height / this.game.settings.height).toFixed(2);

        document.getElementById("overlay").style.height = this.game.scale.height + "px";
        document.getElementById("overlay").style.width = this.game.scale.width + "px";
        document.getElementById("overlay").style.marginLeft = this.game.scale.margin.left + "px";

        document.getElementsByClassName("box")[0].style.transform = "scale(" + scaleWidth +"," + scaleHeight + ")";
    }

    resize() {
        let aspectRatio = this.game.scale.aspectRatio.toFixed(2);
        let sourceAspectRatio = this.game.scale.sourceAspectRatio.toFixed(2);
        let windowAspectRatio = (window.innerWidth / window.innerHeight).toFixed(2);

        // Force the aspect ratio
        if (windowAspectRatio >= sourceAspectRatio) {
            return;
        }

        // Don't allow scaling so scroll bars will always be hidden.
        this.game.scale.setMinMax(1, 1, window.innerWidth, window.innerHeight);
    }
}

export default GameState;

and the CSS is:

html {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  margin: 0px;
  padding: 0px;
}
*, *:before, *:after {
  box-sizing: border-box;
}

body {
  height: inherit;
  width: inherit;
  margin: 0px;
  display: block;
  background: #222;
}

#screen {
  position: relative;

  #overlay {
  position: absolute;
  width: 100%;
  height: 100%;
  text-align: center;

    .box {
      position: absolute;
      display: block;
      border: 1px solid rgba(177, 24, 24, .2);
      bottom: 20%;
      left: 35%;
      right: 35%;
      top: 20%;

      &:after {
        content: "(MENU)";
      }
    }
  }
}

HTML:

    <div id="screen">
        <div id="overlay">
            <div class="box"></div>
        </div>
        <div id="canvas"></div>
    </div>

and it was still pretty buggy. Scaled terribly, game started in a huge screen and only worked properly when I resized the canvas, menu was scaling in a weird way etc.

Your fiddle is exactly what I was after. It scales up and down, keeps the aspect ratio and also scales DOM elements, wow! You saved me hours of development time mate, thanks alot!

I'll read your code and check what I did wrong.

Edit: Okay, adapted your code into my game and it works flawlessly. I have once concern though. Right now it only works if I put it in my GameState. I'll eventually get multiple states such as Boot, Loader, Menu etc. What would you suggest in this case? Should I add a wrapper class such as StateHandler and call scaling stuff on constructor once?

Ps. Would you be available for paid mentorship in the future?

Link to comment
Share on other sites

1 hour ago, Aristy said:

Edit: Okay, adapted your code into my game and it works flawlessly. I have once concern though. Right now it only works if I put it in my GameState. I'll eventually get multiple states such as Boot, Loader, Menu etc. What would you suggest in this case? Should I add a wrapper class such as StateHandler and call scaling stuff on constructor once?

 

Glad its working out.

Not sure about the state thing, this all happens pretty much outside of Phaser i.e. anything Phaser does with states is independent of the scaling, this is good, as it has separated out your scaling concerns from the rest of your code.

If you think about it from a functional point of view, the resize code simply takes in a window resize event, and spits out a few mutations to the DOM i.e. the application of scale based on screen & required dimensions. Phaser works in a separate domain.

You'll probably want to change your UI based on state though, and this is where things could get a little trickier as you now have 2 systems in play: one for Phaser and one for your DOM UI. It's not so difficult to change things though.

Think about it from a top-down perspective, i.e. you want to change state, so, have a state handler that calls both the Phaser state change function and whatever DOM UI state change function that you're going to create. You want to have a function which is called when the state change, that actions a state change for both of your systems, that way they are guaranteed to be in sync.

It's more work to have these 2 systems in play, but the advantage of UI in DOM is considerable for many projects so I'd say its worth it.

I expect Phaser exposes a callback for state changes, but, if you use that you're going to couple Phaser with the UI, which might be more intuitive to you but it creates a hard coupling. This might be preferable though, its up to you. You could then have the code that creates and destroys your UI elements within your Phaser state create/destroy functions.

Lots of different ways of doing it.

Quote

Ps. Would you be available for paid mentorship in the future?

Ha, thanks for the offer, if its just advice I'm happy to give it for free, either publicly here or PM me if you need more privacy or for something more specific. No need for cash to get in the way!

 

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...