VivaMortis - post Mortem

Started by iWasAdam, April 17, 2019, 07:44:40

Previous topic - Next topic

iWasAdam

OK. I thought I would do a post mortem for those who find this stuff interesting. I'll go over some of the concepts and some of the working. but feel free to ask for more detail about how and why things are done a certain way :)

I'm not going to say "do it this way", just "I did it this way.. and why"!

First off is my language choice:
MX2
Mx2 is my fork of Monkey2. I found there were some things missing from Monkey2 (yep I tried to have them integrated into Monkey2, but no luck there). So felt I need to fork it to add my own stuff.
What did I add?
Lots of colors, lots of additional graphics commands, font sprite support, added lots of strange little helper functions that I considered 'missing". Overhauled the sound system. lots of wierd little stuff to extend some of the base functions and shader support.

Next is my tools:
Generally I use the tools I code myself apart from some photoshop.
Here is a quick list of my tools and what they do
TED21 editor. It is a fork (not again) of the Ted2 editor which came with Monkey2. Visually and operationally it's very different and very fast (for me) to work with. Line numbers, code overview, colors are all displayed, module compile and lots of other odd little things I add as I need them.

PaletteEd - to create the palettes
FontSprite - this is a sprite editor which specialises in a 16x16 (256) grid of sprites. a FontSprite. These can be any size and you are not limited to single characters. It uses palettes from PaletteEd (although you can modify existing ones as well). Most of the graphi work is done here. It's fast and fit in very well with my workflow. There are multiple fontSprites used with different resolutions. the core being 16x16 pixels. Here is the 8x8pixel fontmap with the character set and UI maps stuff:

FontMap - this takes fontsprites and palettes and allows you to make maps. very similar to Tiled. a 'map' can have different layers each with it's own resolution. Each layer is just a set of x.y cells with a cell being a char and a color from the palette. How you use the layers and data is up to you. VivaMortis uses 7 layers:
4 layers are the basic room layouts. in each layer are 9 room types; corridors, big room, room with decoration, junctions, etc

1 layer is the map itself. the characters show the room and the color the 'zone'

2 layers are then used for monsters, items, etc. Again the characters and colors giving different results

layers can be hidden and shown together, so it makes 'seeing' the results a very quick operation:


One thing I would like to say is 'I think visually'. If I can see it... it makes sense. hence visual data and not text data for the maps, etc. But I can also see code that way too - code has a visual look and I suppose to me it all becomes pictures in some way? But my brain is wired differently from 'other' people.

OK. The last thing to speak about is the sound. This is completely custom written by me. It's taken sever versions to get to this point (this being version 4/5). It uses a custom editor called QasarBeach - this is a complete reproduction of the Fairlight Series IIx and then some. The only way to think of it is 'sound lego'. I' have lots of little block that I can join together. the order they are joined gives a different sound and each block has many inputs.
QasarBeach is a set of 16 block (voices) organised in a particular way and accessed through the Fairlight UI.
It has sound editing

sequencing

and individual control for each voice

The underlying sound system was written to support many different sample formats from old 80/90's synths and samplers
QasarBeach is a very complex, but fast system and was written as a copy of the old Fairlight system (a lot of communication was had with the original people who created the Fairlight as well, and also with those who keep the machines alive).
VivaMortis is the first time the technology has been seen outside of the lab. It was also designed to be portable for me to use directly in games and other things...

OK. Thats all for now. Lots of images (I know - but I think in pictures) and lots to take in. If there is anything you would like to know more about. just ask :)

Derron

Ok so this was about "assets" and their creation.

What about the game logic itself, screen handling, idea finding...


bye
Ron

iWasAdam

oky-doke... screen handling it is then ;)

Lets get the distinction about window and screen:
The window is how the os sees things. could be a floating window or could be fullscreen. You don't really know the size of the window when you give the user flexibility to resize it (unless you specify it and lock it down), so a window could be 640x480 on one system and 1024x960 on another or even 2000x1500 on another with a retina display.

We now have a window render size Width and Height. But how do we treat this as screen with a fixed 256x192? We uses images/canvas objects.
In essence a canvas is a bitmap or image of a fixed size that we do all our drawing to. then we just draw this bitmap to the window (letting the language handle the stretching). Some pixels will get stretched in lines here and there - but in general it works.)

We are now dealing with a fixed 256x192 resolution and can treat that just like the original sized (spectrum) display.

So how do I get from 16 color to 1 color?
More involved as it uses a shader.
the shader takes everything that isn't black and makes it the input color, so you just set the input color run the shader and your 16 colors instantly becomes mono...

But you have the UI that is multi colored as well?

Yep. but think of it like this:
layer/bitmap 1 is the ui and everything that is not to be converted by a shader
layer/bitmap 2 is just the map

when you come to render, you do the following:

draw bitmap 1 to the window

if we want spectrum mono
draw bitmap 2 with mono shader to the window
else
draw bitmap 2 normally to the window

(on top of bitmap 1)


Here's the actual code (where canvas is the window, canvasImage is the UI, canvasImage2 is the map ):
canvas.Color = Color.White
canvas.DrawRect( 80, 40, Width-160, Height-80,  canvasImage )

If _spectrum Then
canvas.Color = _palette.GetRGBA( _roomCol )
canvas.DrawRect( 80, 40, Width-160, Height-80,  canvasImage2, _myShader )
Else
canvas.DrawRect( 80, 40, Width-160, Height-80,  canvasImage2, _myShader2 )
End If


Monkey2 need to be recoded to allow different shaders to be used on images. Hence MX2

iWasAdam

idea finding - ohh a hard one.

My first concept was for a character based scrolling shootem sort of like a big sci-fi ghouls and ghosts. but on seeing how the entries were coming along I thought this is going to serious competition. I need to rethink and play to the specs to make something that would stand out.

My first thought was for an isometric version of SabreWulf. but this was shot down as the graphics used were considered no be too close to knighttlore, I.E. Ripped off. Do I needed to go and work on some variations of the graphics.

How did mortis appear?
My first attempts were to cowel the figure I had. and the face was great, but removing it was even better (whilst I was drawing it). So the cowel without a face became something to aim for. And I then thought about what if the cowel was taken off and it was a skull. which became mortis once I had redraw the character.

Firing was something that I wanted, and a fireball was originally used - but I thought that was a bit derivative, so again the skull became your weapon.

at this stage the general concept - running around, firing and collecting runes was there. I needed some way to bring them all together and I thought Day of the dead... Pinata.... Skulls. and suddenly it all tied up.

Next up was to find a mexican day of the dead style. and Googling I saw a poster for Viva Mexico. and I thought 'ooh cool'... Viva.. viva, Viva Mortis. live death and it all fit together.

Now I had a concept, a general game and an overriding theme...

Derron

Thanks for the insights..

Render2Texture is what I would use too when it comes to proper virtual resolutions. For now there is no proper r2t which works with vanilla and NG. I already nagged (our fellow forum user) col to update his great render2texture code to work with NG too (so sdl.glmax2D and sdl.gl2sdlmax2D). Why is that needed? BlitzMax contains "VirtualResolution" but while this works fine for DrawImage and DrawRect() commands it does ignore the scaling for simple "Plot()", "DrawLine()" and "DrawOval()". This is why for GenusPrime all stars are no simple "pixels" but "DrawRect(x,y,1,1)" commands.

Using shaders is also no (trivial) option for NG (now) so the color stuff would need to be handled "in software" here. This is why I wrote some stuff to map any RGB color to a given palette - it uses a cieLab-color-distance which is no strict value but a perceived color distance. I think a similar approach could be of use for you if you eg had a game which allowed multiple palettes so you could switch automatically (of course it might need adjustments here and there). Also this could allow to automate color animation effects - with an NES or SNES Palette (or 256 colors) this might become even more powerful.


@ screens
You name them "pages". Screen transitions and so: reused existing stuff / framework?


bye
Ron

iWasAdam

aha pages - You've been looking at the Source...

One thing you will notice looking at my source is it is verbose (lots of words).
This is to make reading it very simple.
Everything in indented exactly the same way. Generally the variables all follow similar conventions. simple counters and for loops are usually j,k,l but everything is defined before use.
In monkey2 and some other languages you can define variables as you use them and the compiler will sort of give them a type it thinks is correct. E.G.
for k:=1 to 7
this automatically gives k the type as int
or
local myColor:=oldColor
this will give myColor the type that oldColor is

My personal recommendation is NEVER USE AUTOMATICS!!! For anything... EVER!
Why do I say this?
The answer is simple. In the above two examples you have no idea of what type the variables will be.
The color one is a terrible programming style as you have no concept (at a glance) what the variables myColor and oldColor are.
Are the int's and references to colors? Are the Colors or are they something else? You have no idea and then need to backtrack and find out what oldColor is and work backwards. What is oldColor was defined in another file or a library hidden away somewhere?

What happens if you assume that myColor is an int and it turns out the be something else?

It is much better and simpler to do the following:

local k:int
for k = 1 to 7
next

local myColor:Color = myColor

You are now in no doubt what types the variables are. You know the myColor is of type Color. and you know that oldColor should also be this type. If not then you will get an error and you 'know' what is wrong :)


let's assume we have a game. it has one screen: play the game and nothing else.

so our basic game loop would something like be
renderGame()
getInput()
GameCode()

if we wanted to have a title screen. We would need to add this to our existing game code.
So lets have some Const definitions

const PAGE_GAME:int = 0
const PAGE_TITLE:int = 1

field _page:int = PAGE_GAME


You can see here I'm using a particular style. Const are all capitals separated with _. So at a glance I know when seeing one of them. It is a const. And I generally keep the constants towards the top of the program or top of the variable block.

As you can see this is becoming a 'state-machine'.

We now need to  add this 'state into each of the three core parts (we'll look at just the renderGame):

Method RenderGame()
  select _page
    Case PAGE_GAME
      DrawGame()
    Case PAGE_TITLE
      DrawTitle()
  End Select
End Method


So you can see that we now have separate 'states' when rendering

All main repeated parts of the game can now use this _page system.

Let's assume we want to have some form of transition from the title to the game:
1st we would add a new page const:

const PAGE_TITLETRANSITION:int = 3

We need to draw/render the title and then (some form of) transition to the game drawing. because we are not needing to access the input or the game loop
in those sections we would do something like

Method GetInput()
  select _page
    Case PAGE_GAME
      GetGameInput()
    Case PAGE_TITLE
       //we are not interested in getting input for the title
    Case PAGE_TITLETRANSITION
      //again we are not interested in input
  End Select
End Method


usually input is some form of readkey and/or readjoy. so you would read the values and then make the decision about the state.
Here is an extract from VivaMortis source dealing with Keypress OnKeyDown( KeyDown:Key ):

Select KeyDown
Case Key.Escape
SetPage( PAGE_MENU )

Case Key.Key1
Select _page
Case PAGE_CONTROLS
_optionOver = 0
Case PAGE_MENU
If _menuOver = 0 Then
_spectrum = Not _spectrum
End If
_menuOver = 0
End Select


and in the render we would add a new state:

Method RenderGame()
  select _page
    Case PAGE_GAME
      DrawGame()
    Case PAGE_TITLE
      DrawTitle()
    Case PAGE_TITLETRANSITION
      DrawTitle()
      'add some form of transition - maybe the game render has an alpha parameter?
      DrawGame()
  End Select
End Method



Derron

So your procedural approach requires to handle all individually possible screen transitions
title->game
game->title
menu->title
title->menu
... ?

Why not have a page manager which stores the current page and on transition renders this current page first and then the neext page. Once transition is done set the next page to be the current page and clear the next page variable then.

That way you could transition from every screen to every other screen without having to add a new "case" and a new screenTransitionKey.



@ explanation
While I think it might help beginners to know about variables, definitions and potential mistakes I am not sure if those beginners are helped with such advanced projects. And all other users here might have read about these issues. So maybe it's not needed to be that elaborative in these topics.
Do not feel offended - just want to help you save some time writing stuff not too many are interested in.

Exception is of course if you write these articles in a dev log too.


bye
Ron

iWasAdam

Yep. Im certainly not saying what I do is the right or wrong way. it's just the way I personally have found to be the quickest and simplest.

@Page Transitions. Again there are lots of ways to approach this. but (in this case) it was a retro approach, so things like alpha blending, etc are not relevant.

@Variables - again I thought it better just to say what and why I did things and in that case what to avoid. Yep we all know what to do, but sometimes if you are given a good example of why something was done it might just encourage you to think differently...
On a private project, it only needs to be readable by you. but if you want others to see it, then you will need to prepare that the code can be 'generally' readable.

VivaMortis is a personal project, but I hope that others may find something of use in there too?  ;D

iWasAdam

@Render2Texture
In NG there is a version of the Mojo renderer.
modules/mky/mojo2/examples/rendertoimage

That is the general system that monkey2 uses and also vivamortis with the canvas.

Derron

Thanks for the heads up ... I do not really like the "canvas" idea (for simplicity).
With Col's code you can keep your "well known" BlitzMax commands - you just set a target before.

SetRenderImage(myImage)
DrawImage(img, 10,10) 'gets drawn on the "myImage"-canvas
DrawText(...)
SetRenderImage(null)

And this works (for now) with DX and OGL - with SDL it would mean to work even for other targets.

bye
Ron