diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2019-07-12 15:19:55 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2019-07-12 15:19:55 +0000 |
| commit | f0b8e51fb9cac674d6f892db116e9d37524f6b16 (patch) | |
| tree | 589a0be3c16e452d12fe167a181f58c96f9571c0 /root | |
| parent | fix sassc mismatch (diff) | |
| download | mmm-f0b8e51fb9cac674d6f892db116e9d37524f6b16.tar.gz mmm-f0b8e51fb9cac674d6f892db116e9d37524f6b16.zip | |
add aspect ratios
Diffstat (limited to 'root')
9 files changed, 359 insertions, 0 deletions
diff --git a/root/blog/aspect_ratios/date: time$iso8601-date b/root/blog/aspect_ratios/date: time$iso8601-date new file mode 100644 index 0000000..1f5943f --- /dev/null +++ b/root/blog/aspect_ratios/date: time$iso8601-date @@ -0,0 +1 @@ +2019-07-12 diff --git a/root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon b/root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon new file mode 100644 index 0000000..d348f4b --- /dev/null +++ b/root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon @@ -0,0 +1,92 @@ +assert MODE == 'CLIENT', '[nossr]' + +import CanvasApp from require 'mmm.canvasapp' +import div from require 'mmm.dom' + +class UIDemo extends CanvasApp + width: nil + height: nil + new: () => + super! + + @canvas.width = nil + @canvas.height = nil + @canvas.style.width = '100%' + @canvas.style.height = '100%' + @canvas.style.border = '2px solid var(--gray-dark)' + + @node = div @canvas, style: { + position: 'relative' + resize: 'horizontal' + overflow: 'hidden' + + width: '576px' + height: '324px' + minWidth: '324px' + maxWidth: '742px' + + margin: 'auto' + padding: '10px' + boxSizing: 'border-box' + } + + -- match size of current parent element and return it (for interactive resizing in the demo) + updateSize: => + { :clientWidth, :clientHeight } = @canvas.parentElement + @canvas.width, @canvas.height = clientWidth, clientHeight + @canvas.width, @canvas.height + + -- set up a coordinate system such that the virtual viewport + -- of size (w, h) is centered on (0,0) and fills the canvas + -- returns remaining margins on the two axes + fit: (w, h) => + width, height = @updateSize! + @ctx\translate width/2, height/2 + + -- maximum scale without cropping either axis + scale = math.min (width/w), (height/h) + @ctx\scale scale, scale + + -- calculate remaining space on x/y axis + rx = (width/scale) - w + ry = (height/scale) - h + + -- margins are half of the remaining space + rx / 2, ry / 2 + + strokeRect: (cx, cy, w, h) => + lw = @ctx.lineWidth / 2 + @ctx\strokeRect cx - w/2 + lw, cy - h/2 + lw, + w - 2*lw, h - 2*lw, + +class Box + new: (@cx, @cy, @w, @h) => + + rect: => + @cx, @cy, @w, @h + +class Example extends UIDemo + click: => + if @naive + @naive = false + else + if @show_boxes + @naive = true + @show_boxes = not @show_boxes + + text: (box, text, align='center', my=.5) => + mx = .1 + @ctx.textAlign = align + + if align == 'left' + @ctx\fillText text, box.cx + mx - box.w/2, box.cy + my + else if align == 'center' + @ctx\fillText text, box.cx, box.cy + my + if align == 'right' + @ctx\fillText text, box.cx - mx + box.w/2, box.cy + my + +{ + :UIDemo + :Example + :Box +} diff --git a/root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon new file mode 100644 index 0000000..499f9aa --- /dev/null +++ b/root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon @@ -0,0 +1,17 @@ +=> + assert MODE == 'CLIENT', '[nossr]' + + import UIDemo from @get '_base: table' + + class FitDemo extends UIDemo + draw: => + @fit 16, 9 + @ctx.fillStyle = 'red' + @ctx\fillRect -8, -4.5, 16, 9 + + @ctx.fillStyle = 'black' + @ctx.font = '6px Arial' + @ctx.textAlign = 'center' + @ctx\fillText '16:9', 0, 2 + + FitDemo! diff --git a/root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon new file mode 100644 index 0000000..38aeac1 --- /dev/null +++ b/root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon @@ -0,0 +1,36 @@ +=> + assert MODE == 'CLIENT', '[nossr]' + + import UIDemo from @get '_base: table' + + arr = (args) -> + with js.new js.global.Array + for i in *args + \push i + + class PerforateDemo extends UIDemo + draw: => + @fit 16, 9 + + @ctx.lineWidth = 0.15 + @ctx.strokeStyle = 'green' + @strokeRect 0, -3.5, 16, 2 + @strokeRect 0, 3.5, 16, 2 + + @ctx\setLineDash arr { 0.4 } + @ctx\beginPath! + @ctx\moveTo 0, -4.5 + @ctx\lineTo 0, -2.5 + @ctx\moveTo 0, 2.5 + @ctx\lineTo 0, 4.5 + @ctx\stroke! + @ctx\setLineDash arr {} + + @ctx.strokeStyle = 'blue' + @strokeRect 0, 0, 16, 5 + + @ctx.font = '4px Arial' + @ctx.textAlign = 'center' + @ctx\fillText '16:5', 0, 1.5 + + PerforateDemo! diff --git a/root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon new file mode 100644 index 0000000..2017e20 --- /dev/null +++ b/root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon @@ -0,0 +1,48 @@ +=> + assert MODE == 'CLIENT', '[nossr]' + + import Box, Example from @get '_base: table' + + class Sidebar extends Example + draw: => + margin_x, margin_y = @fit 16, 9 + if @naive + margin_x, margin_y = 0, 0 + + @ctx.font = '1.5px Arial' + sidebar = Box -7 - margin_x, -1, 2, 7 + @text sidebar, 'A', 'center', -1.8 + @text sidebar, 'B', 'center', -0.3 + @text sidebar, 'C', 'center', 1.2 + @text sidebar, 'D', 'center', 2.7 + + bottom_l = Box -4 - margin_x, 3.5 + margin_y, 8, 2 + bottom_r = Box 4 + margin_x, 3.5 + margin_y, 8, 2 + + @text bottom_l, 'levelname', 'left' + @text bottom_r, 'info a b c', 'right' + + main = Box 1, -1, 14, 7 + @ctx.lineWidth = 0.1 + @ctx.strokeStyle = 'black' + @ctx\beginPath! + for x=-5.5, 7.5 + @ctx\moveTo x, -4.5 + @ctx\lineTo x, 2.5 + + for y=-4, 2 + @ctx\moveTo -6, y + @ctx\lineTo 8, y + @ctx\stroke! + + if @show_boxes + @ctx.lineWidth = 0.1 + @ctx.strokeStyle = 'green' + @strokeRect sidebar\rect! + @strokeRect bottom_l\rect! + @strokeRect bottom_r\rect! + + @ctx.strokeStyle = 'blue' + @strokeRect main\rect! + + Sidebar! diff --git a/root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon new file mode 100644 index 0000000..f108e0f --- /dev/null +++ b/root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon @@ -0,0 +1,30 @@ +=> + assert MODE == 'CLIENT', '[nossr]' + + import UIDemo from @get '_base: table' + + arr = (args) -> + with js.new js.global.Array + for i in *args + \push i + + class TearDemo extends UIDemo + draw: => + margin_x, margin_y = @fit 16, 9 + + @ctx.lineWidth = 0.15 + @ctx.strokeStyle = 'green' + @strokeRect -4 - margin_x, -3.5 - margin_y, 8, 2 + @strokeRect 4 + margin_x, -3.5 - margin_y, 8, 2 + + @strokeRect -4 - margin_x, 3.5 + margin_y, 8, 2 + @strokeRect 4 + margin_x, 3.5 + margin_y, 8, 2 + + @ctx.strokeStyle = 'blue' + @strokeRect 0, 0, 16, 5 + + @ctx.font = '4px Arial' + @ctx.textAlign = 'center' + @ctx\fillText '16:5', 0, 1.5 + + TearDemo! diff --git a/root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon b/root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon new file mode 100644 index 0000000..dae4dfb --- /dev/null +++ b/root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon @@ -0,0 +1,57 @@ +=> + assert MODE == 'CLIENT', '[nossr]' + + import Box, Example from @get '_base: table' + + class VTK extends Example + draw: => + margin_x, margin_y = @fit 16, 9 + if @naive + margin_x, margin_y = 0, 0 + + levelname = Box -4 - margin_x, -3.5 - margin_y, 8, 2 + settings = Box 4 + margin_x, -3.5 - margin_y, 8, 2 + infobar = Box -4 - margin_x, 3.5 + margin_y, 8, 2 + exit = Box 4 + margin_x, 3.5 + margin_y, 8, 2 + + main = Box 0, 0, 16, 5 + + @ctx.font = '1.5px Arial' + @text levelname, 'levelname', 'left' + @text infobar, 'info a b c', 'left' + @text settings, 'settings', 'right' + @text exit, 'exit', 'right' + + @ctx.lineWidth = 0.2 + @ctx.strokeStyle = 'black' + @ctx\beginPath! + @ctx\moveTo -8 - margin_x, -2.5 - margin_y + @ctx\lineTo 8 + margin_x, -2.5 - margin_y + @ctx\moveTo -8 - margin_x, 2.5 + margin_y + @ctx\lineTo 8 + margin_x, 2.5 + margin_y + @ctx\stroke! + + @ctx.lineWidth = 0.1 + @ctx.strokeStyle = 'gray' + @ctx\beginPath! + for x=-7.5, 7.5 + @ctx\moveTo x, -2.5 + @ctx\lineTo x, 2.5 + + for y=-2, 2 + @ctx\moveTo -8, y + @ctx\lineTo 8, y + @ctx\stroke! + + if @show_boxes + @ctx.lineWidth = 0.1 + @ctx.strokeStyle = 'green' + @strokeRect levelname\rect! + @strokeRect settings\rect! + @strokeRect infobar\rect! + @strokeRect exit\rect! + + @ctx.strokeStyle = 'blue' + @strokeRect main\rect! + + VTK! diff --git a/root/blog/aspect_ratios/text$markdown.md b/root/blog/aspect_ratios/text$markdown.md new file mode 100644 index 0000000..dea0249 --- /dev/null +++ b/root/blog/aspect_ratios/text$markdown.md @@ -0,0 +1,77 @@ +Dealing with different screen sizes and formats can be annoying. +On desktop PCs, 16:9 is the most common aspect ratio these days, but depending on where you are rendering your content, +it might still be a different format, for example when a task bar, broswer navigation bar or similar is slicing away some of your screen real-estate. +For mobile games the situation is a bit worse, because there are simply more different aspect ratios out there +and different systems may require extra space for on-screen navigation keys, statusbars etc. + +In this post I want to present a simple technique for layouting **simple UIs for games** that can work across a range of similar screens. +If you are looking for a way to design a UI-heavy app, or something that needs to work across very different environments (like phones, tablets and desktop PCs), this is probably not what you are looking for. +In any case, if you just want to take a quick look you can jump down to the [examples](#examples) at the bottom of the page as well. + +## step one: `fit` +The most trivial solution is to simply choose the aspect ratio you would like to work with, +and then fit a rectangle with that ratio into whichever space is available. +Depending on the screen, this may either be a perfect fit, leave space on the horizontal axis, or leave space on the vertical axis: +(drag the lower right corner to see this approach react to different screen sizes) + +<mmm-embed path="interactive" facet="fit" nolink></mmm-embed> + +This is pretty trivial to accomplish in code. +You can calculate the scales in relation to your reference grid on the x and y axis separately, and simply use the one with a lower value. +Establishing a reference grid and knowing the scale to translate between it and the physical screen sizes will also help +designing the layout in general, as we will see later. +Here is a simple example in JS: + +```js +// measured from somewhere +const width = 2000; +const height = 1080; + +const sx = width / 16; +const sy = height / 9; + +const scale = Math.min(sx, sy); +const offset_x = width * (1 - scale); +const offset_y = height * (1 - scale); + +// the biggest 16:9 rectangle you can fit is (scale*width, scale*height) large +// and it's top-left corner is at (offset_x/2, offset_y/2) +``` + +The problem with this is that it simply doesn't look very good. When the ratio is a perfect match there is of course no problem, +but otherwise the empty space makes the screen look empty (especially if there are system UI elements next to it). + +## step two: `perforate` +So how can this be imroved? We would like to use the unused space on the x or y axis, but we can't just scale everything up, +or we would start cropping important pieces of UI. Stretching the game to fill the screen also doesn't work for obvious reasons. + +To proceed, the UI has to be 'perforated' into different sections that are independent from each other. +This is where establishing a reference grid becomes useful to orient ourselves in the layout. +In my example I am splitting the screen into a main content section that is 16:5 units large, as well as a top and bottom bar. +The two bars are also split in half vertically in the middle, for reasons that we will see in the third step. + +<mmm-embed path="interactive" facet="perforate" nolink></mmm-embed> + +It's imortant to note that how you divide the screen up depends completely on your game/interface of course. +The layout I am using here is just an example; at the end of this post you can find another one with a different layout as well. + +## step three: `tear` + +Now that the sections are defined, in the last step we can 'tear' the sections apart and decide how they should react to the +left-over space calculated in step one individiually. +In my layout, I stretch the top and bottom bars to fill the screen completely horizontally. +By dividing the bars into separate sections in the last step, I know how much space is guaranteed to be available on each side. +If there is room left on the vertical axis, I move the bars out from the center to give the content some visual space: + +<mmm-embed path="interactive" facet="tear" nolink></mmm-embed> + +Once again, how you make the pieces behave depends a lot on what elements your UI has in the first place, and how you want it to look and feel. + +# examples + +Finally, here are two examples with a bit more visual coherence to show how this actually ends up working. +You can click on these to cycle between the normal view, showing the frames used to subdivide the canvas, and viewing `fit` only for comparison. + +<mmm-embed path="interactive" facet="vtk" nolink></mmm-embed> +<mmm-embed path="interactive" facet="sidebar" nolink></mmm-embed> + diff --git a/root/blog/aspect_ratios/title: text$plain b/root/blog/aspect_ratios/title: text$plain new file mode 100644 index 0000000..464c7ef --- /dev/null +++ b/root/blog/aspect_ratios/title: text$plain @@ -0,0 +1 @@ +Aspect-ratio independent UIs |
