Merrak's Isometric Adventures -- Dimetric Dilemna

merrak

  • *
  • Posts: 2738
That's good advice. Readability is the top priority. Getting tiles to line up properly in a graphics program is a hassle, but does reveal a few important things. Below is a crowded scene in 4:1 ratio.


In my level design style guide I have a rule prohibiting "floor over floor", such as in the illustration. This configuration wouldn't be possible because the player could be both on top of the bridge or below it. With a 2:1 w/h ratio, the floor graphics block much of the scene. With 4:1 w/h, actors are still visible under the bridge.

Unit positions relative to each other are still readable, which was my main concern with this ratio.

There is also one nice surprise: it's harder to create optical illustions by lining up the edges of floors of different heights. Below you can see the bridge extends slightly past the vertical line defining the doorway, making it easy to see the bridge is in front of it.


I'm thinking 4:1 may be the way to go.

The last question is if it's really worth changing the renderer code to support a new w/h ratio. It's been a while since I've looked at the derivation. Thankfully I've kept all of the notes (plus, math in this thread anyway). I'll need to study it again. I think I may have saved myself a lot of grief by using a linear algebra based solution rather than trigonometry.

Using trig, the angles of the coordinate vectors very much plays a role in the projection formulas. With a linear algebra approach, though, I should just need to change the bases and the rest of the changes resolve themselves.

merrak

  • *
  • Posts: 2738
Ratio Review. I took a couple of days to update the map editor to allow for arbitrary tile aspect ratios. It's made it a lot easier to experiment than using a graphics editor. Although I'll miss the look of the 2:1 (classic "isometric") ratio, 4:1 appears to be the best route. Here are some crude rooms. First--


This is a typical kind of room from Towers of Vallas. The 2:1 ratio would be a better fit for it, but 4:1 works. There is a good sized stage for melee combat. A technical concern is the size of the rectangles that would be produced by dividing the floor into rectangle partitions. I had a lot of issues with the renderer in TOV because of this--numerical precision problems that resulted in clipping errors. Fortunately, I think the larger resolution will help balance out the effect of the thinner rectangles.

The editor uses a different renderer than the main game's, so I won't see what's going on until I update the game's renderer.

Next:


This is closer to the kinds of structures I always imagined for Vallas. With the 2:1 ratio, overlapping floors creates a significant blind spot where the player can't see Marika. Early in the production of Towers, I wrote up a style guide that had a few rules for level design. The top rule was "no blind spots", although I made a few exceptions... such as for passages that lead to secret rooms.

Very early on I mentioned Prince of Persia as one of the inspirations for the Vallas games. I wanted the player to be able to scale floors, leap over chasms, and fall into unexplored depths.

Oddly enough, I always found Prince of Persia to be a bit dull after level 3 or so. It gets repetitive, and I'm not a big fan of timed mazes. I always thought something like an ARPG, with a deeper story, and a 3D world would add needed variety to the concept.

Here's a third structure:


This room would not be possible in Towers of Vallas or Temple of Idosra. Without lighting, the floors would "bleed" into each other, creating an Escher-like illusion. Lighting can help break the illusion, but there would still be significant blind spots. Not only could Marika be hidden by the floor above her, the wall in the back would also be hidden.

I think it's actually worse if the player can't see all of the room's geometry than the player character itself. Although I did use room geometry quirks to hide doors to secret rooms in Towers, secret rooms are a bonus. I think that's a different matter than hiding required information about the room.

I'm changing strategies for tiling, so that I can apply textures. This will make life a lot easier, since I won't have to take the time to adjust pixel offsets to line up images. I can still use hxPixels and apply a simple shear transformation.

With that upgrade in place, it would be more feasible to allow for multiple ratio options in one game. I couldn't apply textures to the actor sprites, though... so having multiple ratio options (in game) would still be a significant undertaking. Multiple ratios would be most useful in the map editor, so it might be worthwhile there.

« Last Edit: September 22, 2019, 01:59:11 pm by merrak »

merrak

  • *
  • Posts: 2738
Shaders Sunday. Here it is, all-shader supported lighting effects:


There is no image for the light itself, but it's up and to the right of the pillar.

Overall I'm mostly happy with the results. There are still a few issues left to resolve. The most significant--I was not able to pass an array to a shader. The original design called for one image shader per wall, which would then loop through all the lights and all of the shadows to render the final result.

I'm using the bitmap shader routines OpenFL provides, which is the same mechanism used in mdotedot's extension. In fact, I started off trying to use the extension itself, but it turned out to not mesh very well with the peculiar way "Vallas Engine" handles wall bitmaps. Instead, I took the approach that it seems the OpenFL designers intended: making classes that extend the base Shader class. At this time passing arbitrary arrays using this approach isn't supported... best I can tell from the documentation on ShaderParameter.

As an alternative, I created a separate shader for each light and each shadow and applied the array of shader filters to the wall bitmap. The problem here is if the same light casts multiple shadows against a wall, its effects are applied multiple times.

I think the best design would be to use a separate shader for each light, but each light loops through all of its shadows in the one shader. I can jury rig a solution using multiple if/then checks and hope OpenFL adds support for arbitrary arrays.

merrak

  • *
  • Posts: 2738
Lighting Models Extravaganza. After a few adjustments I now have the desired shader: One shader that accepts multiple shadows. This solves a lot of the problems I ran into, although I took the brute force approach to programming it: switch statements and multiple if/else if's. It's not an elegant solution, but it's easy to work with and debug.


It can only accept up to 10 shadows per wall, which ought to be plenty for most circumstances. I think I'm done with tests for now, and so should begin to work on the "real" lighting model for the game.

I weighed the pros and cons of software rendering vs hardware, and realized that the best fit may be a hybrid model.

Towers of Vallas uses all software rendering. Each wall is a bitmap. For most rooms, the bitmap is rendered when the level is loaded and never adjusted afterward. Dark rooms with the lamp are rendered every frame. Despite the fact that I significantly underestimated how much processing time memory management takes, I still easily achieve 60 FPS. But--Towers is only 160x144 pixel resolution. I'd be really suprised if I got 60 FPS in "Vallas 2", which is 960 x 540 pixel resolution.

Shader Pros... The obvious one is speed. The other significant advantage: memory. I don't need to store images in memory, and I don't need to bother with optimizing garbage collection if I'm not rendering walls in real time.

Shader Cons... Shaders are harder for me to debug. Getting environment variables into them is a pain.

Software Pros... It's easy to pull in all kinds of variables without bothering with some pipeline I'm still working on understanding. Debugging is easier. For static walls, it's fast enough.

Software Cons... Garbage collection isn't an issue if I'm only using software for static walls, but storing all of those images will consume a lot of RAM.

A rendering speed of 5 to 10 FPS is fine during the level loading process. I can render all the walls and use shaders to animate the lighting.

One last advantage to a hybrid model--if I drop support for dynamic wall rendering on the software side, I can take out all of the "math tricks" I threw at the problem to squeeze every bit of speed out of it. I store a lot of pre-computed values and this vastly complicates the level loading process. Every time I revisit the renderer, I have to re-learn what all I did. Taking these tricks out and reverting to simpler solutions will make the engine much easier to manage and upgrade over time.

Bombini

  • *
  • Posts: 1401
Very interesting and congrats on the progress!

merrak

  • *
  • Posts: 2738
Very interesting and congrats on the progress!

Thanks!

I now have something resembling the final lighting model. Test scene shown below, this time with the full screen showing.


I have plenty of room on stage for larger rooms, longer halls, and taller areas. The small size of rooms in Towers was the biggest constraint on map design.

The image was rendered using an updated version of the software renderer I used back in 2017. I always liked the look it produced, although it is incorrect, with regards to the physics of light, in some ways. Because the renderer makes permanent changes to the wall bitmaps, I can use shaders to add animated effects.

So now I have a dual renderer system: Software to load the map and draw the static lights, and shaders to add dynamic lights and effects on actors.

The biggest improvement is a quality-of-life one. Before I had to draw all of the walls in perspective and, in the case of the 2017 renderer, draw each wall in standard lighting (diffuse--probably a misnomer) and also in a fully lit (ambient--another misnomer) state. The renderer mixed the two images to create the final results seen in the older post I linked.

The new renderer can now apply textures, so I can draw walls flat. This will make it a lot easier to use Aseprite's Tiled Mode to draw seamless tiles.

I also only need to draw the diffuse wall image. Ambient is computed using a formula:

Code: [Select]
                // Red: 2.3204 t - 1.73792 t^2 + 0.405156 t^3
                a_r = 2.3204 * a_r - 1.73792 * Math.pow( a_r, 2 ) + 0.405156 * Math.pow( a_r, 3 );

                // Green: 1.24935 t + 0.591609 t^2 - 0.832233 t^3
                a_g = 1.24935 * a_g + 0.591609 * Math.pow( a_g, 2 ) - 0.832233 * Math.pow( a_g, 3 );

                // Blue: 0.960621 t - 0.0537841 t^2 + 0.0957061 t^3
                a_b = 0.960621 * a_b - 0.0537841 * Math.pow( a_b, 2 ) + 0.0957061 * Math.pow( a_b, 3 );

I hard-coded the coefficients to match the same effect as in the 2017 renderer, but I can also make these coefficients a property of the tile. This will let me adjust the lighting to get different material effects: shiny, glossy, matte, etc. I can still set different colored lights, too.

The next step will be to make some more tiles and some new materials. I also need to implement actor shaders. Here's a mock-up I created in GIMP using the curves tool with the same curve the renderer applies to the walls. It's very dark, but there's only one dim light in the room... not shown because it has no sprite.


merrak

  • *
  • Posts: 2738
Helper App Monday. One nice thing about developing with Stencyl is how quick it is to develop 'helper apps' to solve the little problems.

Here's one I put up with for far too long: Drawing tile images. Each tile is represented by a 3D coordinate. I then have to compute what screen coordinate the 3D point maps to. But I can't draw the image at that point, because the (x,y) coordinate of an image is its upper left corner. I need to subtract from the x and y position to shift the image so that it lines up with the tile correctly.


For simple shapes, like pillars that take up the entire tile box, it is easy to know how to shift the image. For other structures like staircases, windows, and the like, this has always been a lot of guesswork. Here's what happens when the shift is miscalculated:


One of the reasons these coordinates are complicated is because how each plane that makes up a tile is represented. The first coordinate in the plane's defining polygon is its origin, but the origin could be anywhere inside the tile's bounding box.

So today I finally decided enough is enough and made a small program that lets me adjust the position of an image and compute the offset coordinates.



I had a nice variety of tiles for Towers of Vallas: walls, stairs, windows, doors, water... 28 3D structures in total. I was dreading re-configuring all of them for the new 4:1 w/h pixel ratio. This app will make it a lot easier to port them over and make more 3D structures to put in the sequel.

merrak

  • *
  • Posts: 2738
Fleshing Out the Lighting Model

The new lighting model is coming along nicely. While not complete, I do have a working materials customization. Below is the test scene, with updates applied.


Getting this working turned out to be quite a bit more involved than I originally anticipated... but that's the way these things usually go. My initial plan was to have a simple .xml file where I can store materials definitions. Each material is given a name, and once loaded, I can apply a specified property to a tile.


The first hurdle was a rather dumb oversight: tiles consist of multiple tile faces, the individual planes that make up a tile, which may have different materials. Windows are a good example. Some window tiles consist of a glass plane and also a floor, which may be stone, brick, wood, metal, etc. Obviously I don't want both of these faces to have the same material.

The second hurdle was quite a bit more complex. As many of you are aware, Stencyl optimizes tile collision boxes by merging the individual tile bounding boxes into large polygons. "Vallas Engine" does something similar with tile faces. I'm still using the same process I outlined way back in 2017. So this is how Vallas Engine sees the walls:


The problem is the wall renderer needs the material data to be associated with the wall, not the tile faces that make up the wall. Copying the data over is simple enough once I ensure that I don't group tile faces with different materials into the same wall.

This optimization process is extremely complicated, so I spent the better part of a day re-learning how it works. I feel it could be made a lot simpler, but it has always worked very well against anything I've thrown at it, so it's hard to justify sinking time into making unnecessary updates.

But now it's all working! There are two, maybe three, items still on my wish-list of features which will give me a few more things to keep me busy.

1. I have a switch that lets me choose between shader-supported lighting and software-supported. I need to finish the code that lets the two modes work together: software for light from static sources, and shaders for moving lights, accents, and illuminating actors.

2. Normal maps for static light sources. I can do this in software.

3. Normal maps for dynamic light sources. I haven't figured out how to get the required data passed to the shader. Getting this working will let me use Sprite Lamp to enhance the psuedo-3D sprites.

merrak

  • *
  • Posts: 2738
Delightful! "DLight" Shader.

Here is the result of the "Bouncy Ball Test" for accent lighting. What is that? Read on!  8)

<a href="https://www.youtube.com/v/gXupRSlWRIU" target="_blank" class="new_win">https://www.youtube.com/v/gXupRSlWRIU</a>

Earlier in the week I managed to get a basic software/shader hybrid rendering system up and running. This means I can use software to draw walls in their default lighting state, then use shaders to modify them in real time. There are definite pros and cons to this setup. I posted a simple test showing a flickering lamp to Discord. I just now finished the "DLight" (dynamic light) shader that extends that test shader to include the rest of the lighting features (custom material, brightness, etc).

The biggest pro is I can pull in all kinds of variables to draw walls, but still not burden the CPU with redrawing them. This works very well for accent lights, like the fireball glow in my video.

It's not a great solution for primary lights, like the player's lamp in Towers of Vallas. The problem here is that the two rendering routines have to work multiplicatively. In a totally dark room, the base wall renderer zeroes out the pixel values.  Since the shader works by multiplying existing RGB values, it won't be able to light the room back up.

Here is the room with only default lighting (software rendered), and ambient level 0. My lighting model includes an ambient level, which defines how lit up a pixel is when no lights are touching it. It's used to simulate points that could be lit up by light reflecting off of other surfaces. Setting ambient to 0 means only points in direct view of a light source will be lit at all.

The point of interest is the bit of wall behind the pillar, which is completely in the shadow.


Now look what happens when I add a dynamic light.


The room lights up more, as expected, but the totally dark area stays dark. Since the DLight shader multiplies pixel values, any pixel with RGB of 0 stays at 0.

The shadow of the pillar actually is recomputed. If the light were working correctly, the shadow would update as expected. This problem can be avoided if there was an easy way to additional pixel data into the shader. Supposedly there is, but I haven't gotten it to work.

But--this will work for what I want it for. I have another shader I can use for totally dark rooms, and while there are other limitations with that approach, the renderer is designed to be configurable.

merrak

  • *
  • Posts: 2738

Next Steps. I think the lighting model is pretty much done for now. I recently found all my old scene screenshots from the original "Thief of Vallas", my very first Stencyl game. I used to take the screenshots from the scene editor, hand-draw lighting effects in GIMP, then export the lighting effects layer to use as a scene foreground.

In fact, there are three staples of the Vallas games that have persisted all this time: atmospheric lighting, spike traps, and fireball shooters.  Now I finally have the lighting system I wanted.

So what's next?... fireballs and spikes, obviously! Towers of Vallas has a terrible object and NPC loading system. The original design called for a "reset system", which is what you'd find on DIKU-based MUD servers. A "reset" is a script of sorts that loads an object or NPC in the world.

This system solves many problems you'd normally encounter changing scenes... the most notable of which is persistence. When an item has been collected, it stays collected.

Towers uses a jury-rigged extension of the StencylJam 16 game Temple of Idosra, which is itself a jury-rigged extension of nothing that I threw together in less than a day. It's a terrible system to program with. Coding something as simple as quitting out of the main menu, then returning to the game introduced all kinds of weird bugs.

This is the time to tear out all of the old code and implement the proper system. Tearing out the old code is going to be a hassle, which is why I'm putting off getting started by writing this journal entry. But I suppose it's time to stop procrastinating and get to work...  :o

merrak

  • *
  • Posts: 2738
Test Map Tuesday. Revisions to the "reset system" are going well so far. The worst offender is the Map Special sub-system. This is the system that lets me configure tiles and objects in the map.

"Map Specials" are modifiers that attach to a particular tile row,col,level coordinate. Unfortunately, they are programmed to modify whatever is at the specified coordinate, regardless of what is there.

It's not such a good system where load/save games are a thing. Example--if I want to customize a chest to contain some contents, I add a Map Special that basically says "put these contents in the object at this coordinate." Later, if the chest is moved, triggering the same "Map Special" will throw an error. To prevent this, I have a complicated system of marking Map Specials as "spent". But some really should be reused, such as those that initialize doors. So now I also have exceptions, and exceptions to exceptions...  :'(

This system made sense for Temple of Idosra, which was put together in pieces on different computers, designed to be merged together at a later date. I suppose "Vallas Engine" is still feeling the effects of Hurricane Matthew.

But now I'm looking forward... by looking backward  :o

Here is the entire Level 1 from Thief of Vallas, way back to 2013. I can't even load the project anymore. It was last opened back on Stencyl 2. But since I exported all of the scene screenshots to draw the lighting, I do have the maps... not that they're great examples of level design.


I think a good test of the redesigned map systems would be to remake this level. The original game had ghosts and skeletons, from back before I really knew what I wanted to do with it... so it wouldn't be canon. It's also a 2D level... but then I can load my sprites from the StencylJam 18 game and at least have a full working set of character animations to test the game. Eventually I'm going to want to update all of the character artwork, but that's far off from now.

merrak

  • *
  • Posts: 2738
Turkey Thursday. It's been about a month since my last update... so what's new? Not as much as I'd like. I don't have as much free time in October/November--and most of my progress has been "quality of life" updates that don't have a visible face.

I did complete one nice upgrade to the tiling system, though. I wouldn't expect anyone to be surprised that making tile textures that blend together seamlessly is much more difficult with the larger resolution. On top of that, even when I do get seamless tiles, the lack of variety is very noticeable.

Dirt and rocks were particularly challenging. For reference, here is my cavern wall texture.


Tile textures are 32x64 px, so this is what it looks like in the editor.


Pretty bad. The obvious solution is to make more tiles using the rest of the texture. Placing those tiles would be a pain. The larger texture is seamless, but if I break it up into multiple tiles, those little tiles wouldn't be seamless with each other.

This problem can be solved by careful drawing, but I thought of another solution. Older versions of the "Vallas Renderer" supported tiles with multiple images. The idea was that I'd draw each tile under one of 32 lighting conditions, then the renderer picks the appropriate image to display. This is how the first generation of the engine rendered scenes, although it used 7 light levels instead of 32, as seen below. The chunky light gradient is most noticeable at the top, just left of middle.


The code that supports this system is still there. I never took it out, so it was simple to repurpose it to pick one of several from a larger texture based on position. The result is that I can now load larger textures into the game and the tile renderer can pick a 32x64 section of the texture to use. I don't just have to use this to tile larger textures. I can also pick sections at random: automatic variation!

Here's the result.


And here's the entire room. You can see what a difference this makes by comparing the walls to the floor. The floor has no larger texture array. The floor is the same image repeated over and over.


vicevicebingo

  • Posts: 80
<a href="http://static.stencyl.com/games/30916-0.swf" target="_blank" class="new_win">http://static.stencyl.com/games/30916-0.swf</a>

Your bad art looks better than your minecraft art
a lots, in hilarious way.

Fayabella

  • Posts: 239
Quote
making tile textures that blend together seamlessly is much more difficult with the larger resolution.
Hey, this may not help any (as it seems you've found a better solution) but if you ever do need seamless tiles, Gimp has a filter that automatically makes any image seamless. The results may vary depending on the image you put in, but it usually looks alright. Just import an image and then go to Filters > Map > Make Seamless.


merrak

  • *
  • Posts: 2738
Hey, this may not help any (as it seems you've found a better solution) but if you ever do need seamless tiles, Gimp has a filter that automatically makes any image seamless. The results may vary depending on the image you put in, but it usually looks alright. Just import an image and then go to Filters > Map > Make Seamless.

That's what I used, but thanks for the tip just the same :)

The problem wasn't just making the tiles seamless, but also adding enough variety so that it wasn't obvious the same pattern was repeating itself over and over.

"The Jigsaw Problem" would've been a good name for it. Take a larger image and break it up into pieces. Even if the larger image is seamless, the little pieces only fit together one way.