Others Abandoned The Orc's Tale [v0.04] [tl101]

4.20 star(s) 6 Votes

tl101

Newbie
Aug 30, 2021
69
180
Keep up the good work! :D

When will you show us some new version?
I think the new version will come out around the end of July, early August. After that, new versions should start coming out more frequently - once every two to three months at most. But you have to understand that it's very hard to guess a concrete date. Now the main task is to learn how to work in Blender. I can't know how quickly I'll learn it. The only thing I can say for sure right now is that I definitely won't finish everything in one month.
 

tl101

Newbie
Aug 30, 2021
69
180
Monthly progress report.
This month I converted old images to the new format and prepared compressed versions of them. Fixed a couple of bugs in the new version of the engine. The rest of the work went about the following way: to model the scene -> render the images -> check how it looks -> repeat. I plan to do this for about one more month. Then it will be necessary to prepare a release regardless of the quality of the renders as the Blender can take a very long time to learn...
 
  • Like
Reactions: rudz and newguy42

tl101

Newbie
Aug 30, 2021
69
180
Monthly progress report.
For the past month, as I said before, I've been improving my blender skills. At the moment, I have all the scenes I need for the next release as drafts. Now I need to bring them all to a clean version, fix various flaws by applying the skills I've learned. I hope to have everything done by mid-August.
 

tl101

Newbie
Aug 30, 2021
69
180
Monthly progress report.
This month I've been doing some modeling and image rendering. I decided to add x-ray to the game and, in practice, it turned out to be more complicated than I had previously thought. Plus, some things had to be reworked a bit in the engine for x-ray. Everything seemed to check out, but when starting to make a clean version, there are always unforeseen issues.... Now the work on the release is coming to an end, but I'm afraid to give any specific deadlines.
 

tl101

Newbie
Aug 30, 2021
69
180
About the release.
So... I did not have time to do anything again. Several very insidious bugs came out during testing, which I have not been able to fix yet. I apologize for another delay.
 

Dimitrii

Member
Jan 28, 2019
110
123
About the release.
So... I did not have time to do anything again. Several very insidious bugs came out during testing, which I have not been able to fix yet. I apologize for another delay.
Anything that can go wrong, will eventually go wrong, right?

You can't predict bugs and such, no worries. it happens.

A question tho, as far as I understand, you're pretty focused on features and quirks of a system you've build. When you iron those out, do you plan of focusing more on simply content?
 

tl101

Newbie
Aug 30, 2021
69
180
Monthly progress report.
Well, the bugs turned out to be much more serious than expected: the code I wrote almost a year ago turned out to be not quite compatible with the code I wrote half a year ago. Now I have to rework them, which is what I'm doing. I want to get all the technical problems completely solved before the release, and not make temporary solutions so that I have to redo them again later. So those who follow the updates I ask you to be a little more patient.

This is a disadvantage of developing your own engine - if there is some serious bug, it is really serious. But there is a plus side - with the new system it is very easy to create complex scenes and add them to the game. I think, a little later, I'll make a screen capture of how it happens to demonstrate that all this work was not in vain. If I finish the release this month, I'll record the addition of the new location for the next update. If I don't make it in time, I'll do some sort of artificial example.
 
  • Like
Reactions: rudz and Green83rry

tl101

Newbie
Aug 30, 2021
69
180
A question tho, as far as I understand, you're pretty focused on features and quirks of a system you've build. When you iron those out, do you plan of focusing more on simply content?
Yes, this was supposed to be the last long engine upgrade, after which I was planning to limit the time working on new engine features to two weeks per release. But now I'm so tired of "adding new features" that when I'm done, the next few releases will be simple content.
 

tl101

Newbie
Aug 30, 2021
69
180
Where did I disappear to? At the end of November, when I was supposed to be writing another progress report, I got a little sick, and then a very profitable order came in at work that ate up all my time. I thought, I'll write about it tomorrow, I'll write about it tomorrow... But then I thought, I'm already very late anyway, and it would be better if instead of a small post saying that everything is postponed indefinitely, I write this big post that I had originally planned. This job involved traveling for work, and there wasn't enough time for anything (sometimes I'd come home just to sleep), so writing the big post got stretched out. Now I'm starting to get a little bit of time, but the work is not finished yet, so the update will be released at an unknown date. But it will be out someday, I haven't abandoned my work on the game. I have disappeared before and will probably disappear again, but in the future I will try to warn in advance. I won't be writing progress reports in the near future, because they will consist of one text "work on the slicer is not finalized yet". From the good news - with the money I earned I upgraded my computer, so the next renders will go faster.

Then there is a text about the addon with illustrations and screen captures. In it I talk in detail about the difficulties I encountered in its development, what I got and why all this is necessary.

About the addon.
Because of the specifics of how I develop the game, I have to render a lot of almost identical images that differ in small details. These details are directly related to internal game variables (time of day, position of objects, character pose and emotion, etc). The purpose of the addon is to render all these variations automatically. That is, suppose I have a variable A, which can take values a1 and a2, and a variable B, which takes values b1 and b2, then I need to get four images "A=a1; B=b1;.png", "A=a1; B=b2;.png", "A=a2; B=b1;.png", "A=a2; B=b2;.png". Then the slicer prepares these images in a special way, after which I can use them in the game just by switching variable values in the scenario.

A huge plus of Blender is that the work in it is very easy to automate. Everything that can be done through the interface can be done with a script.

At the beginning I tried to put all the addon settings in the user interface of blender, but the appearance of the addon turned out to be monstrous. In the end, I gave up on this idea: the only things left in the interface are the ability to add/remove the render script, specify which specific variable values should be rendered, and the ability to switch between variables.
You don't have permission to view the spoiler content. Log in or register now.
You don't have permission to view the spoiler content. Log in or register now.

Since it is still easier to describe the actions when switching variables in a separate file, I decided to move all the detailed settings there. And for good reason, as the addon evolved, the number of different options increased several times over. I can't imagine how it could be fit into the user interface.
You don't have permission to view the spoiler content. Log in or register now.

Let's take a closer look.

The addon works with python code files. One render script is one file. But there can be many render scripts in a scene. The file must contain a function setup_script, which must fill the script data. The file responsible for general environment settings will be shown in full because it is quite simple. I will show only important lines from the rest of the files.
Python:
from mvr_helper import *

def setup_script(script):
    def set_time(val):
        bpy.context.scene['time'] = val

    script['time_of_day'].short_name = 'T'
    script['time_of_day']['morning'] = wrap(set_time, 6)
    script['time_of_day']['afternoon'] = wrap(set_time, 12)
    script['time_of_day']['evening'] = wrap(set_time, 18)
    script['time_of_day']['night'] = wrap(set_time, 0) 


    def set_exposure(val):
        bpy.context.scene.view_settings.exposure = val

    script['exposure'].style = 'custom'
    script['exposure']['indoor'] = wrap(set_exposure, -3)
    script['exposure']['indoor'].custom_text = 'exposure = -3.0;'
    script['exposure']['outdoor'] = wrap(set_exposure, -4)
    script['exposure']['outdoor'].custom_text = 'exposure = -4.0;'
   

    def set_artificial_light(val):
        bpy.context.scene['artificial_light'] = val

    script['artificial_light'].short_name = 'AL'
    script['artificial_light']['on'] = wrap(set_artificial_light, True)
    script['artificial_light']['off'] = wrap(set_artificial_light, False)
This declares three variables (time_of_day, exposure and artificial_light), the values they can take, and what needs to be done to make the scene match those values. This file itself is not used anywhere, but other script files can reuse these variables and augment them.

A more specific example - the file responsible for rendering the hut background images.
Python:
def setup_script(script):
    script.name = 'Orc Hut'
    script.prefix = 'Scene! '
    script.postfix = ''
    script.shorthand_dict = 'OH shorthand'

    def on_select():
        bpy.data.scenes['Scene'].render.film_transparent = False
        bpy.data.scenes['Scene'].cycles.samples = 192
       
    def on_deselect():
        bpy.data.scenes['Scene'].render.film_transparent = True

    script.on_select = on_select
    script.on_deselect = on_deselect
At the beginning there are general settings of the rendering script: the name that we can see in the interface, prefix for file names. And also, what actions should be done when selecting this script: set the number of samples to some small value because we render background images and set the background to non-transparent. Plus specify a name for the shorthand dictionary. At some point I ran into a limit on the maximum length of the filename, so I had to use abbreviations. Instead of using the full names of variables or values in the filename, a short name can be specified for them. For example above, the variable "time_of_day" is given the short name "T", and the file name would contain T='afternoon', and the slicer would know that by "T" it means the game variable "time_of_day".

Next is the declaration of variables. The variable "scene" is responsible for the camera we are looking through.
Python:
    scene = script['scene']
    scene['OH - exit'] = wrap(set_camera, 'To exit camera')
    scene['OH - bed'] = wrap(set_camera, 'Bed camera')
    scene['OH - bed'].ignore(time_of_day='afternoon', exposure='indoor')
    scene['OH - hall'] = wrap(set_camera, 'Hall camera')
    scene['OH - hall'].ignore(exposure='indoor')
Very often it happens that we don't need all possible combinations of variables. Some values make sense only if some conditions are met. There are two ways to set these conditions. The first is above. When the camera is pointed at the bed, the image is not affected by time of day or exposure at all. So we say ignore those variables. But the variables have to equal at least something, so we specify a specific value.
Short timelapse - render images through these three cameras. Also, I manually set only 16 samples so it takes less time.
You don't have permission to view the spoiler content. Log in or register now.
You don't have permission to view the spoiler content. Log in or register now.
For the camera pointing at the bed - one file, for Hall - four files for each time of day, for the camera pointing at the exit - 7. Variables that are ignored are not specified in the file name at all. Why do we get 7 images for the outward facing camera instead of 8? Because we don't need outdoor exposure at night and this will be specified further in the second way.

So, we have declared the variable "scene". Then we reuse the variables "time_of_day" and "exposure" from the common file and set an additional condition for outdoor exposure.
Python:
    script.inherit("!env", 'time_of_day', 'exposure')

    def need_outdoor_exposure():
        if script['time_of_day'] == 'night': return False
        if script['scene'] == 'OH - exit': return True
        return False

    script['exposure']['outdoor'].cond = need_outdoor_exposure
A condition can be specified either by a function, as in the example above, or by a logical expression involving a variable (will be encountered below).

The following fragment contains several features at once:

Python:
    script['magic_orb.position'].region = region('Magic orb region i', 'Magic orb region o')
    script['magic_orb.position'].short_name = 'MO.pos'
    script['magic_orb.position']['-'].basis()
    script['magic_orb.position']['OH - kitchen'].on_select = wrap(show_object, 'Magic orb')
    script['magic_orb.position']['OH - kitchen'].on_deselect = wrap(hide_object, 'Magic orb')
    script['magic_orb.position']['OH - kitchen'].cond = scene.is_in('OH - kitchen', 'OH - worktable')

    def set_MO(color, val):
        def _set():
            MO = get_object('Magic orb')
            MO['Color'] = color
            MO['Power'] = val
            MO.update_tag()
        return _set

    script['magic_orb.m_color'].style = 'var = val;'
    script['magic_orb.m_color']['-'].basis()
    script['magic_orb.m_color']['-'].cond = script['magic_orb.position'] != 'OH - kitchen'
    script['magic_orb.m_color'].short_name = 'MO'
    script['magic_orb.m_color']['V3(0.0)'] = set_MO([1.0, 1.0, 1.0, 1.0], 0.0)
    script['magic_orb.m_color']['V3(0.0)'].cond = script['magic_orb.position'] == 'OH - kitchen'
    script['magic_orb.m_color']['V3(1.0)'] = set_MO([1.0, 1.0, 1.0, 1.0], 1.0)
    script['magic_orb.m_color']['V3(1.0)'].cond = script['magic_orb.position'] == 'OH - kitchen'
You don't have permission to view the spoiler content. Log in or register now.
1. Basis values.
Some values can be marked as basis values. This has several consequences: If a variable's value is equal to its basis value, it is excluded from the filename (but this can be turned off). Otherwise, all files would contain the position of the magic sphere, magic crystal, orc state, and other variables that have no effect on the current image.

2. Region.
If the scene changes a little - a small object is added or removed, a character's emotion changes, etc., there is no point in rendering the whole image - it is enough to render only a small region. I used to do it manually. And then in Gimp I manually blurred the borders because otherwise they would be visible. Here we have everything automated, so we need some way to tell the script where the boundaries of these areas are. For this purpose we need the region parameter. Region can be set for a variable and it will work for all values except for the basis values, as in the example above. Or it can be set for each value separately - we will encounter this option later. A region is created using the function of the same name, which has two main parameters - object names. These objects are projected onto the camera and the minimum rectangle that needs to be rendered to accommodate the object is calculated. Anything inside the inner rectangle will have 100% influence on the resulting image. Outside the inner rectangle, the influence will gradually decrease. Anything outside the outer rectangle is not different from the basis image in any way.
If there are several regions, only their intersection is rendered, because the information outside the intersection has already been rendered before, when some variables had a basis value. This can be seen in the timelapse above - when the value of Orc.state and magic_orb.position differs from the basis value, only a small fragment between them is rendered, which falls into both regions.
If the regions do not overlap, then nothing needs to be rendered at all.

For stationary stuff I create region objects manually - they are just scaled cubes. For characters, I made special procedurally generated objects that follow the characters themselves.

3. Color. Special value V3.
Variables can have different types. The default type is string. To let the slicer know that the value is of string type, it is taken in single quotes (default style var='val'; ). A variable can also have a floating point value. For example, an exposure variable somewhere above the text. The style of such variables is usually var=val; without quotes or custom (then the text must be written manually in full). The peculiarity of these variables is that they can take intermediate values in the game. And the resulting image will be a mix of two original ones. This could be observed, for example, when an orc left the hut and the exposure changed smoothly. Or a magic orb could flash two colors - white and pink. For this I needed 4 images: a pair of glow/no glow for each color. By changing the value of the "glow" variable along a sine wave it was possible to achieve the blinking effect.
Now I added a new type of variables - color: V3(0.0) - black, V3(1.0) - white.
You don't have permission to view the spoiler content. Log in or register now.

The idea is that it is possible to mix two images as with a fractional type variable, but use different mixing ratios on each channel. That way, where I need something of a customizable color, two images are now sufficient.
You don't have permission to view the spoiler content. Log in or register now.

Character Render.
Let's see how the character renders.
Python:
    def on_select():
        show_collection('Dora')
        set_shadow_catcher('Outdoor', 'Hut')
        set_pose('Dora rig', 'pose - Dora - stand')
        bpy.data.scenes['Scene'].cycles.samples = 1024

    def on_deselect():
        hide_collection('Dora')
        clear_shadow_catcher('Outdoor', 'Hut')
 
    script.on_select = on_select
    script.on_deselect = on_deselect
The first thing to do is to show the character and everything related to it, and set the shadow catcher flag for the background. You may remember a long time ago when I described an inconvenient and not quite right way to speed up rendering in Daz Studio. In Blender it is done with one button. Well, like a button... that button doesn't exist for some reason. You can set the "holdout" and "indirect only" flag for a collection, and you can set "shadow catcher" for objects only. This would be inconvenient if I did it manually, but for code there is no difference.

Next, inherit all the necessary variables from the hut file. For those values that are needed, add a new action. Now we not only switch the camera, but also set the position of the character. Unnecessary values - delete.
Python:
    script.inherit('OH - scene', 'scene', 'time_of_day', 'exposure')    

    def set_position(pos, rot, look_at, head_rot):
        dtv = lambda v: tuple(math.radians(x) for x in v)
        def _set_pos():
            get_bone('Dora rig', 'master').location = pos
            get_bone('Dora rig', 'master').rotation_euler = dtv(rot)
            get_bone('Dora rig', 'head_fk').rotation_euler = dtv(head_rot)
            get_bone('Dora rig', 'look').location = look_at
        return _set_pos

    scene = script['scene']

    pos = (0.79057, -4.2683, -0.014183)
    rot = (0, 0, 10.485)
    look_at = (1.0801, -1.8514, 0.0588)
    head_rot = (0.4234, -8.4866, 0.79031)
    scene['OH - exit']+= set_position(pos, rot, look_at, head_rot)

    del scene['OH - from outside']
Next, clothes.
Python:
    def hood_off():
        set_shape_key('Dora dress - Hooded dress', 'hood off', 1.0)
        show_object('Dora dress - Hooded dress', 'Dora dress - Panties')
        set_pose('Dora rig', 'pose - Dora - ears relaxed')

    def hood_on():
        set_shape_key('Dora dress - Hooded dress', 'hood off', 0.0)
        show_object('Dora dress - Hooded dress', 'Dora dress - Panties')
        set_pose('Dora rig', 'pose - Dora - ears pressed')

    def only_panties():
        show_object('Dora dress - Panties')
        hide_object('Dora dress - Hooded dress')
        set_pose('Dora rig', 'pose - Dora - ears relaxed')

    def naked():
        hide_object('Dora dress - Hooded dress', 'Dora dress - Panties')
        set_pose('Dora rig', 'pose - Dora - ears relaxed')

    script['Dora.dress'].multiple_set = True
    script['Dora.dress'].short_name = 'dress'
    script['Dora.dress']['main, hood on'] = hood_on
    script['Dora.dress']['main, hood off'] = hood_off
    script['Dora.dress']['main, hood off'].region = region('Dora region - Hood I', 'Dora region - Hood O', basis = 'main, hood on')
    script['Dora.dress']['naked'] = naked
    script['Dora.dress']['only panties'] = only_panties
    script['Dora.dress']['only panties'].region = region('Dora region - Panties I', 'Dora region - Panties O', basis = 'naked')
The functions of switching the outfit are pretty simple - hide/show an object, set the pose (under the hood the ears need to be pressed down, otherwise they will stick out through the clothes), change the shape of the dress. You can see the regions for the values below - the "without hood" image differs from "in hood" only in the hood area. The same goes for the panties. Meanwhile, different emotions and eye/hair colors will not be rendered for the 'only panties' case because their regions do not overlap. Another new setting is multiple_set. The default behavior, for optimization purposes, calls the set value function only when the value actually changes. Setting the variable 'Dora.dress' can change Dora's pose. Other variables can change pose too and sometimes this can cause conflicts. Setting multiple_set allows the set function to be called each time before rendering.

And last for the character - inherit the variables common to all character:
Python:
script.inherit('!female', 'Dora.eye_color', 'Dora.hair_color', 'Dora.lipstick_color', 'Dora.blink', name = 'Dora')
You don't have permission to view the spoiler content. Log in or register now.
This is how the character render looks like - the number of resulting images for this render script is a little over 3000. But, due to the use of regions, most of the images are very small and render in about 30 seconds. In general, the render time for a single character is several days. Fortunately, the addon allows to save the render progress and continue from the same position.

And the last thing is clickable objects.
The engine needs to be able to figure out somehow which objects the player can click on. In previous versions I had a table like this in my code
C:
    id["any"]             = 0x000'000'00;
    id["null"]            = 0x000'000'FF;
    id["yes"]             = 0x100'000'FF;
    id["no"]              = 0x200'000'FF;
    id["new"]             = 0x300'000'FF;
    id["save/load"]       = 0x400'000'FF;
    // Orc hut                   '010'
    id["OH - kitchen"]    = 0x000'010'FF;
    id["OH - hall"]       = 0x100'010'FF;
    id["OH - exit"]       = 0x200'010'FF;
    id["OH - bedroom"]    = 0x300'010'FF;
    id["OH - bed"]        = 0x400'010'FF;
    id["OH - dumbbells"]  = 0x500'010'FF;
    id["OH - workbook"]   = 0x600'010'FF;
    id["OH - eat"]        = 0x700'010'FF;
    id["OH - door"]       = 0x800'010'FF;
    id["OH - magic orb"]  = 0x900'010'FF;
    id["OH - DTT"]        = 0xA00'010'FF;
    // Ivy                       '020'
    id["Ivy"]             = 0x000'020'FF;
And I used to paint the clickable objects on a special layer with their corresponding color. After that, in the script I can write something like
C:
hut().when_click_on("OH - kitchen").tooltip("to kitchen");
scene.to("OH - kitchen");
Since all such work should now be automated, there are innovations for clickable objects. First, the new version of the graphical engine does not need specific numeric values of identifiers - it assigns them dynamically. Secondly, the addon provides special values to get the mask of an object (or collection). The on_select method of this value sets the rendering flags so that the desired objects are transparent, while the rest of the scene is not (it turns out to be inverted, but the slicer doesn't care in principle). The on_deselect method, accordingly, returns everything back. This is how to set the identifier for the Magic orb.
Python:
   rid = script['rid']
   rid.style = 'custom'
   Orc_idle = "Orc.pose = 'idle'"
   
   rid['OH - magic orb'] = mask('Magic orb')
   rid['OH - magic orb'].custom_text = f"rid = 'OH - magic orb'; Orc.pose = 'idle';"
   rid['OH - magic orb'].cond = scene == 'OH - worktable'
To avoid writing unnecessary stuff, I replaced this code with a small function. And identifiers of all objects are set like this:
Python:
    setup_rid('OH - DTT', mask('DTT - hall, packed'), scene == 'OH - hall', Orc_idle)
    setup_rid('OH - eat', mask_collection('Tableware'), scene == 'OH - worktable', Orc_idle)
    setup_rid('OH - workbook', mask('Workbook'), scene == 'OH - worktable', Orc_idle)
    setup_rid('OH - dumbbells', mask_collection('Dumpbells'), scene == 'OH - hall', Orc_idle)
    setup_rid('OH - bed', mask('Blanket'), scene == 'OH - bed', Orc_idle)
    setup_rid('OH - magic orb', mask('Magic orb'), scene == 'OH - worktable', Orc_idle)
You don't have permission to view the spoiler content. Log in or register now.
The custom_text field is used here to tell the slicer about an additional condition. It has another use related to identifiers: The point is that the same identifier can be rendered from different angles, but a variable (from the addon's point of view) cannot have two different identical values. So we make different values, but write the same rid in custom_text.
You don't have permission to view the spoiler content. Log in or register now.
For the character, it's simpler than that. Here's the whole file:
Python:
from mvr_helper import *

def setup_script(script):
    script.name = 'OH - Dora rid - stand'
    script.prefix = "rid! Orc.pose = 'idle'; Dora.pose = 'stand'; "
    script.postfix = ''
    script.shorthand_dict = 'Dora shorthand'
   
    def on_select():
        set_pose('Dora rig', 'pose - Dora - stand')
        mask_collection('Dora').on_select()

    def on_deselect():
        mask_collection('Dora').on_deselect()

    script.on_select = on_select
    script.on_deselect = on_deselect

    script.inherit('OH - Dora - stand', 'scene', 'Dora.dress')
When selecting the entire script, set render flags, and clear them when leaving the script. Inherit from the main file only those variables that are needed. In this case, it is the camera, through which we look, and clothes, because it changes the silhouette.
You don't have permission to view the spoiler content. Log in or register now.

Why all this is necessary.
Why would it be so complicated? Suppose I want to add a new dress to Dora. How do I do that? Naturally, I need to model it and attach it to the character - there's no way around it. Then I add a new function for changing clothes - 3-4 lines of code. And add a new value for the variable Dora.dress - 1-2 lines of code. After that I need to press the render button and I can go do other things. When the render is finished and the images are processed by the slicer, in the script I can write something like
C:
Orc.say("Put on your new dress");
Dora.say("ok");
Dora.dress = "new dress";
Dora.say("Is this better?");
And that's it. After that, the dress will change on all images in the game.
I still need to work on the graphics - improve lighting, textures, update the objects themselves. What do I need to do to update all the images in the game after that? Press the render button and run the slicer once the render is complete.

How I see my work on the game after the next release: at the beginning I make a rough scenario and rough scenes -> render them and check that I'm satisfied with everything -> bring the scenes to a clean version and start rendering them -> while rendering is going on I finish writing the scenario (the number of scenes should be chosen so that rendering takes about a month) -> the remaining time can be devoted to new features or mechanics -> when rendering is finished - game release.
 

newguy42

New Member
Feb 21, 2020
12
31
Thanks for letting us know you're still at it! I was hoping we'd hear from you again!

I still go back and play scenes from this game every now and then. Even thought there aren't that many scenes to this game yet, some of those scenes are my favorite from any game that I've played.

And the addon work looks great!
 
  • Like
Reactions: CV3
4.20 star(s) 6 Votes