Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411525 Posts in 69377 Topics- by 58431 Members - Latest Member: Bohdan_Zoshchenko

April 28, 2024, 03:17:20 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsTropical Square Dance Crisis (Arcade Style Puzzle)
Pages: [1]
Print
Author Topic: Tropical Square Dance Crisis (Arcade Style Puzzle)  (Read 2136 times)
ltucker
Level 0
**



View Profile
« on: June 08, 2014, 10:12:53 AM »

Tropical Square Dance Crisis

Tropical Square Dance Crisis is a fast paced puzzler, keep the fruit out of the middle kind of thing.
The main mechanic is similar to flipull/plotting or zoop, but is made for touch/mouse allowing for 2D paths.
Featuring music by the talented Mr. Tyler Walker http://tylerwalkermusic.com/






Strike fruit with the boomerang -- same color to destroy, swap by hitting a different color.


I found the name in an old notebook -- I'm certain it originally came out of VGNG.
It's mainly written in lua and released for iOS currently.
« Last Edit: November 28, 2016, 05:51:58 PM by ltucker » Logged

ltucker
Level 0
**



View Profile
« Reply #1 on: June 21, 2014, 07:30:59 AM »

This one has been on the shelf for a while, so I've been trying to take stock of where I left off, get it back up and running and get back in the habit of devoting a little time to it each day.

It's made with a fair amount of Apple stuff -- which means it's inevitably suffered some code rot in the build where Xcode has decided to change things around.  I got a build for iOS working, which I consider a small miracle.  The game itself is entirely written in Lua.  The build uses LuaJIT by means of a custom CocoaPod.  I'm avoiding 64bit iOS builds for fear of how those intersect (or don't).  Anyone using these together?

The game itself seems to be working fine, and more fun than I remember.  I was probably looking at too much.  It only has one background "level" hooked in (the tropical island) which makes it a bit monotonous and hard to tell when you're advancing.  There are bunch of half-finished backgrounds.  I want to get them all rigged in for the next version, but there are a bunch of intro/outro animations that need creating.   I like doing them, but I'm not very talented at animation, so it takes forever.

Last week I made an intro to the river level where the main character parachutes into the river.   I posted it to the Screenshot Thread.

Some of this stuff probably involved a bit too much axe-grinding.  Here's a shot of a little bezier curve editor in the development enviroment that controls nearly invisible dots that float on the river Smiley

« Last Edit: June 21, 2014, 08:29:14 AM by ltucker » Logged

ltucker
Level 0
**



View Profile
« Reply #2 on: June 10, 2016, 04:00:27 PM »

Back on the road



Working on this one again in earnest and making a push to wrap it up and ship it...

I feel like the basic game mechanics have made sense for a while, but the game has
has always had a sort of excruciating learning curve unless you play a lot of puzzlers.

I've been trying to dial in a more beginner mode that's not boring and obsessing over the
tutorial mode (which is too long and I'm sure nobody will ever visit).  It's making me appreciate
a lot of the more mundane features of other games.  Aside from watching new folks play, this has been
really difficult to have intuition about after playing it so many times Smiley

Anyone have any great advice or links about difficulty tuning?


« Last Edit: June 10, 2016, 05:05:46 PM by ltucker » Logged

ltucker
Level 0
**



View Profile
« Reply #3 on: July 12, 2016, 03:38:59 PM »

Basic Pixel Art Scaling with OpenGL ES 2

Square Dance is a low resolution pixel art game and it runs on a variety imperfect screen sizes.  I've seen tactics for dealing with this here [1], [2] and elsewhere, but I didn't find quite what I was looking for, so I thought it might be interesting to collect up some of what I've tried.  My examples here are using LuaJIT and OpenGL ES 2 on iPhone 6's 1334x750 retina resolution.

Square Dance's "base" resolution is 240x160.  When a sprite is drawn, its position and size are interpreted as being blitted onto a framebuffer of this size.

Pixel Purity: Integer Scaling Only!

I think most of us would agree that integer scaling is the ideal for pixel art games -- each game-pixel is mapped to an exact square of device pixels 2x2, 3x3 etc.  To do this, pick the largest integer multiple of the base resolution that is less than or equal to the device resolution. Usually the resulting quad is centered on the device's display.  Base resolution textures can be perfectly scaled up using the GL_NEAREST for GL_TEXTURE_MAG_FILTER.

"Perfect" pixel scaling

Full Size

Here, the pixels are perfect but it results in wasted space both horizontally and vertically.  Really not too great in my case...

You might be able to ensure a good or even perfect fit for certain devices by picking a specific base resolution, but generally letter-boxing and/or pillar-boxing results on a device that is not a perfect multiple of the base resolution.  In some cases, it is also possible to use the extra pixels to display extra (non-essential) game area instead.  In this case, don't scissor and draw more than the minimal area.  If you're working in specific format (an emulator?), this may not be an option.

Code:
-- Method 1

fbsize = platform.screen_size()

device_width = fbsize.width
device_height = fbsize.height

base_width = 240
base_height = 160

width_ratio = device_width / base_width
height_ratio = device_height / base_height

-- find the largest integer scale that "fits"
scale = math.floor(math.min(width_ratio, height_ratio))

-- size of the device in base pixels
base_screen_width = device_width / scale
base_screen_height = device_height / scale

-- offset of (0,0) in base pixels
offset_x = math.floor((base_screen_width - base_width) / 2)
offset_y = math.floor((base_screen_height - base_height) / 2)

-- projection to apply to transform base pixels to screen
device_projection = glOrtho(
    -offset_x, base_screen_width-offset_x,
    base_screen_height-offset_y, -offset_y,
    1, -1
)

glViewport(0, 0, device_width, device_height)

-- letter-box / pillar-box to restrict drawing to
-- the virtual screen area
glDisable(GL_SCISSOR_TEST)
glClearColor(0,0,0,1)
glClear(GL_COLOR_BUFFER_BIT)
glEnable(GL_SCISSOR_TEST)

glScissor(
    offset_x*scale,
    offset_y*scale,
    base_width*scale,
    base_height*scale
)

-- draw frame
blit(sprite.texture,
    sprite.x, sprite.y,
    sprite.w, sprite.h,
    0, 0, 1, 1,
    device_projection
)

blit(...)
View Full Example

Accepting Imperfection

For me, the tradeoff for perfection is too steep and I don't want to adjust the basic shape of the screen. I'd like to see either letter-boxing or pillar-boxing, but never both.

On some devices, the device pixels aren't necessarily what's directly displayed on the hardware anyway and some amount of scaling/filtering is applied regardless of an integer scaling factor -- for example non-retina resolutions on retina iPhones, or any resolution on iPhone 6S [3].

iPhone 6 integer scaling with retina disabled.

Full Size

This is basically what I'm aiming for (preserve squares as perceptual squares). Unfortunately, it isn't available everywhere...

Non Integer Upscaling (Bad Ways)

To get non integer scaling, only one thing needs to be adjusted, though the results are not pretty with the default upscaling options and low resolution textures.

Code:
-- Method 2
-- ...

-- don't take the floor for a non-integer scale
-- scale = math.floor(math.min(width_ratio, height_ratio))
scale = math.min(width_ratio, height_ratio)

...
View Full Example (GL_NEAREST)
View Full Example (GL_LINEAR)

GL_NEAREST

Full Size

if GL_TEXTURE_MAG_FILTER is GL_NEAREST, things look sorta right, but some pixels come out wider than others which can be seen here, but is especially strange looking when things are moving.

GL_LINEAR

Full Size

Upscaling low resolution textures with GL_LINEAR magnification, you get a fuzzy resampling that destroys all the hard edges.  There are other undesirable edge artifacts here that I haven't bothered to clean up.

Overscaling, Then Downsampling

Although upsamping is crumby, downsampling is not so bad. The idea here is to render to an offscreen framebuffer texture that bigger than the physical screen (and a perfect integer multiple of the base resolution for perfect scaling), then downsample that to the device framebuffer (by texturing a "full screen" quad)

Overscaled Offscreen Framebuffer

Full Size

This is comparable to the result that iPhone gives you when it does scaling for you, (and it works elsewhere / other resoutions). This is a workable, but it requires allocating a fairly large texture (especially on hi-res displays or devices requiring power-of-two textures). Performing these additional large texturing operations can be a performace drag on older hardware.

Code:
-- Method 3

-- ...
device_projection = ...  -- compute device projection as before

-- record the default framebuffer (not always 0, ie iOS)
device_framebuffer = glGetIntegerv(GL_FRAMEBUFFER_BINDING)

-- offscreen framebuffer scale
-- pick the next largest integer scale that is /larger/ than
-- the device scale (well, at least as large)
over_scale = math.ceil(scale)

-- base dimensions scaled up by the overscaling factor
fb_base_width = base_width * over_scale
fb_base_height = base_height * over_scale

-- calculate the width and height of the framebuffer texture.
-- these might be larger than the requested sizes
-- due to power-of-two texture size restrictions.
fb_tex_width, fb_tex_height = next_legal_texture_size(fb_base_width, fb_base_height)

-- set up the texture
fb_texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, fb_texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fb_tex_width, fb_tex_height, 0, GL_RGB, GL_UNSIGNED_BYTE, nil)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) -- <-- this will be used since it's downscaling
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

-- make the texture the color attachment for the new offscreen framebuffer
framebuffer = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_texture, 0)

-- world -> offscreen framebuffer projection
fb_projection = glOrtho(0, fb_tex_width / over_scale, fb_tex_height / over_scale, 0, 1, -1)

-- prepare to draw to the offscreen framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer)
glViewport(0,0, fb_tex_width, fb_tex_height)
glScissor(0,0,fb_base_width,fb_base_height)

-- draw as usual, but draw onto the offscreen framebuffer

blit(sprite.texture,
    sprite.x, sprite.y,
    sprite.w, sprite.h,
    0, 0, 1, 1,
    fb_projection
)

blit(..., fb_projection)

-- finally, draw the framebuffer to the device by rendering it's texture
-- into a quad covering the screen. Since the offscreen
-- framebuffer is larger than the device framebuffer, downsampling using
-- GL_LINEAR will be used.
glBindFramebuffer(GL_FRAMEBUFFER, device_framebuffer)
glViewport(0, 0, device_width, device_height)

-- letter-box / pillar-box to restrict drawing to
-- the virtual screen area
glDisable(GL_SCISSOR_TEST)
glClearColor(0,0,0,1)
glClear(GL_COLOR_BUFFER_BIT)
glEnable(GL_SCISSOR_TEST)

glScissor(
    offset_x*scale,
    offset_y*scale,
    base_width*scale,
    base_height*scale
)

-- draw the overscaled offscreen framebuffer onto the
-- device framebuffer.
blit(fb_texture,
     0, 0, base_width, base_height,
     0, 0, fb_base_width / fb_tex_width, fb_base_height / fb_tex_height,
     device_projection
)
View Full Example


Emulated Overscaling + Downsampling

Instead of making a huge framebuffer, we can make a framebuffer that is exactly the base resolution (small) and then use shader magic to treat it as if it had been scaled up to the size of Method 3.  The algorithm used by OpenGL ES 2 to downsample is outlined in the spec in section 3.7.7 Texture Minification [4].  The idea is to implement this but treat the texture as if it were scaled by the overscale factor.  I found the performance to be better in most cases to Method 3, but with much less texture memory allocated.

Emulated Overscaling

Full Size


Code:
-- Method 4

-- ...

-- calculate the width and height of the framebuffer texture.
-- these might be larger than the requested sizes
-- due to power-of-two texture size restrictions.
fb_tex_width, fb_tex_height = next_legal_texture_size(base_width, base_height)

-- set up the texture
fb_texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, fb_texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fb_tex_width, fb_tex_height, 0, GL_RGB, GL_UNSIGNED_BYTE, nil)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) -- <-- this will be used since it's upscaling
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

-- make the texture the color attachment for the new offscreen framebuffer
framebuffer = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_texture, 0)

-- world -> offscreen framebuffer projection
fb_projection = glOrtho(0, fb_tex_width, fb_tex_height, 0, 1, -1)

-- prepare to draw to the offscreen framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer)
glViewport(0,0, fb_tex_width, fb_tex_height)
glScissor(0,0,base_width,base_height)

-- draw as usual, but draw onto the offscreen framebuffer

blit(sprite.texture,
    sprite.x, sprite.y,
    sprite.w, sprite.h,
    0, 0, 1, 1,
    fb_projection
)

blit(..., fb_projection)

-- finally, draw the framebuffer to the device by rendering it's texture
-- into a quad covering the screen as defined before.

-- ...

-- instead of blitting the framebuffer use a special
-- program to emulate downscaling
-- blit(frambuffer, ...)
downscale_blit(fb_texture,
     0, 0, base_width, base_height,
     0, 0, base_width / fb_tex_width, base_height / fb_tex_height,
     over_scale,
     fb_tex_width,
     fb_tex_height,
     device_projection
)

Code:
-- "Downscaling" shader 

-- vertex shader source
fb_v_shader_src =
[[
precision highp float;

uniform mat4 viewProjection;
uniform float superScale;
attribute vec4 vPosition;
attribute vec4 texCoord_in;

varying vec2 texCoord;

void main()
{
    texCoord = texCoord_in.xy;
    gl_Position = viewProjection*vPosition;
}
]]

-- fragment shader source
fb_f_shader_src =
[[
precision highp float;

varying vec2 texCoord;
uniform sampler2D tex;
uniform float superScale;
uniform float pixelWidth;
uniform float pixelHeight;

void main()
{
    // pretend there are this many texels ...
    float wt = superScale * pixelWidth;
    float ht = superScale * pixelHeight;

    // texture minification from section 3.7.7
    float u = wt * texCoord[0];
    float v = ht * texCoord[1];

    float i0 = floor(u - 0.5);
    float j0 = floor(v - 0.5);
    float i1 = i0 + 1.0;
    float j1 = j0 + 1.0;

    float a = fract(u - 0.5);
    float b = fract(v - 0.5);

    vec4 t00 = texture2D(tex, vec2(i0/wt, j0/ht));
    vec4 t10 = texture2D(tex, vec2(i1/wt, j0/ht));
    vec4 t01 = texture2D(tex, vec2(i0/wt, j1/ht));
    vec4 t11 = texture2D(tex, vec2(i1/wt, j1/ht));

    gl_FragColor = (1.0 - a)*(1.0-b)*t00 + a*(1.0-b)*t10 + (1.0-a)*b*t01 + a*b*t11;
}
]]
_fb_program, _fb_vshader, _fb_fshader = init_program(fb_v_shader_src, fb_f_shader_src)
FB_ARG_VPOSITION = glGetAttribLocation(_fb_program, "vPosition")
FB_ARG_TEXCOORD = glGetAttribLocation(_fb_program, "texCoord_in")
FB_ARG_TEX = glGetUniformLocation(_fb_program, "tex")
FB_ARG_PIXEL_WIDTH = glGetUniformLocation(_fb_program, "pixelWidth")
FB_ARG_PIXEL_HEIGHT = glGetUniformLocation(_fb_program, "pixelHeight")
FB_ARG_SUPERSCALE = glGetUniformLocation(_fb_program, "superScale")
FB_ARG_VIEWPROJECTION = glGetUniformLocation(_fb_program, "viewProjection")


-- emulated downscaling blit
function downscale_blit(tex, x, y, w, h, tx, ty, tw, th, super_scale, pixel_width, pixel_height, projection)
    glUseProgram(_fb_program)
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, tex)
    glUniformMatrix4fv(FB_ARG_VIEWPROJECTION, 1, GL_FALSE, projection)
    glUniform1f(FB_ARG_PIXEL_WIDTH, pixel_width)
    glUniform1f(FB_ARG_PIXEL_HEIGHT, pixel_height)
    glUniform1f(FB_ARG_SUPERSCALE, super_scale)

    glVertexAttribPointer(
        FB_ARG_VPOSITION, 2, GL_FLOAT, GL_FALSE, 0,
        _quad(x,y,w,h)
    )
    glEnableVertexAttribArray(FB_ARG_VPOSITION)

    glVertexAttribPointer(FB_ARG_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0,
        _quad(tx,ty,tw,th)
    )
    glEnableVertexAttribArray(FB_ARG_TEXCOORD)

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
end
View Full Example

Evaluation vs iPhone Scaling

These are (inverted) image diffs between the screenshots above. Methods 3 and 4 are pretty darn close to what iPhone's scaling does. They show an error along the left-hand side and subtle differences at color boundaries if you look closely. iPhone may align the first lefthand pixel with a device pixel.  I'm too lazy to see if this accounts for the difference.

Method 3 vs iPhone Diff

Full Size

Method 4 vs iPhone Diff

Full Size

Method 3 vs Method 4

Full Size

Method 3 and Method 4 do not differ significantly as far as I can tell.


Other Options Not Explored

Another option is to oversize all the textures so that they are all larger than the largest supported scaling factor and apply GL_LINEAR for GL_TEXTURE_MIN_FILTER.  This works also, but possibly wastes even more texture memory than method 3, requires preprocessing textures and isn't as general (what's the maximum size screen again?)

Method 4 could be applied as textures are rendered to the device framebuffer rather than writing to an offscreen framebuffer.  I did experiment with this a little but I found it a bit more finnicky -- creating difficult small errors on the edges of each sprite rather than on the very edges of the screen.  It also probably wants premultiplied alpha in the textures as they require a slightly more complex resampling to combine that I'll leave as an exercise to the reader -- see [5] Smiley

Method 4 might be improved by removing "Dependent Texture Reads"[7]. I'm not sure if the 4 texture coordinates can be accurately replaced by "varyings" in the vertex shader. Dependent textures do slow down older devices quite a bit.

Other complex reconstruction algorithms (thanks bdsowers) [8]

Any better ways you've heard of? Better write-ups? Let me know!

Further Reading and References

[1] https://forums.tigsource.com/index.php?topic=36020.0
[2] https://forums.tigsource.com/index.php?topic=5185.0
[3] https://www.paintcodeapp.com/news/iphone-6-screens-demystified
[4] https://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf#page=90
[5] http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
[6] http://www.david-amador.com/2013/04/opengl-2d-independent-resolution-rendering/
[7]https://developer.apple.com/library/ios/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/BestPracticesforShaders/BestPracticesforShaders.html#//apple_ref/doc/uid/TP40008793-CH7-SW15
[8]https://en.wikipedia.org/wiki/Pixel_art_scaling_algorithms
« Last Edit: July 14, 2016, 10:51:02 AM by ltucker » Logged

bdsowers
Level 3
***



View Profile WWW
« Reply #4 on: July 13, 2016, 07:46:07 AM »

Have you checked out the Wikipedia article on scaling pixel art? https://en.wikipedia.org/wiki/Pixel_art_scaling_algorithms

The hgnx family of algorithms can produce some pretty cool results.

There are a ton of algorithms that do different things beyond the simple linear/nearest neighbor type algorithms. Some of them are a little complicated. And they all might be overkill for what you're trying to do, but still may want to take a look.
Logged

ltucker
Level 0
**



View Profile
« Reply #5 on: July 13, 2016, 08:25:36 AM »

Have you checked out the Wikipedia article on scaling pixel art? https://en.wikipedia.org/wiki/Pixel_art_scaling_algorithms

The hgnx family of algorithms can produce some pretty cool results.

There are a ton of algorithms that do different things beyond the simple linear/nearest neighbor type algorithms. Some of them are a little complicated. And they all might be overkill for what you're trying to do, but still may want to take a look.

Nice, thanks!  Yeah, might be interesting as a follow up to run some of these as well.  For this game I'm just looking to preserve the low resolution pixels as perceptual squares on purpose, which (I think) is a simpler problem (though likely less interesting hah) -- I feel like a lot of these are trying to get a reconstruction of what the image might look like if it were actually drawn in high resolution -- but maybe worth a second look at the details as there are quite a few Smiley

« Last Edit: July 13, 2016, 08:52:39 AM by ltucker » Logged

ltucker
Level 0
**



View Profile
« Reply #6 on: July 14, 2016, 10:12:26 AM »



Messing around with a stage select.  It's a bit funky since you have to hit "Start" on the bottom.
Most people seem to try and just tap an image of a level at first.
Logged

ltucker
Level 0
**



View Profile
« Reply #7 on: July 16, 2016, 08:44:01 AM »



Fast fill control in operation -- it also makes all animations run faster.
This GIF doesn't do it much justice, but the rows jiggle when they're full.
Logged

ltucker
Level 0
**



View Profile
« Reply #8 on: July 19, 2016, 08:25:11 AM »



adding little demo animations to try and reiterate visually.
« Last Edit: July 19, 2016, 08:30:24 AM by ltucker » Logged

ltucker
Level 0
**



View Profile
« Reply #9 on: July 30, 2016, 11:37:43 AM »



Spending some time rigging in contact links.
Hopefully this will force me to actually make a website Wink
Logged

ltucker
Level 0
**



View Profile
« Reply #10 on: November 14, 2016, 05:33:47 PM »

Woo.  Launched a crumby little website finally: https://halfsour.io/

If anyone feels like kicking the tires and letting me know if there are any problems, it would be greatly appreciated.

I went with a static site generator (hugo + gulp pipeline) hosted on AWS S3+CloudFront -- to hypothetically minimize the amount of moving parts, maintenance, upgrading and security issues.  We'll see if that pans out -- there is still a lot of software in mix and there is a fairly amazing a amount of glue and popsicle sticks that seem to be used to boil CSS these days.
Logged

ltucker
Level 0
**



View Profile
« Reply #11 on: November 23, 2016, 05:37:04 PM »

Putting together a little press kit and other doo-dads while I wait for an app store approval.  I got one version approved, but before I launched it I realized that the Game Center app evaporated since the last time I was paying any real attention and there was no way to view achievements :/ Apple rot.  In the mean time, I tossed a trailer video onto youtube (you can see it at the top) -- eh let me know if that makes any sense hah.
Logged

ltucker
Level 0
**



View Profile
« Reply #12 on: November 28, 2016, 05:55:07 PM »

After a lengthy Thanksgiving app review... the first version is released for iOS! Enjoy!  Smiley

Download on App Store
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic