Open ern2150 opened 2 years ago
Part of making a DVD image that doesn't just house a movie and kinda point at it with a menu are things called commands.
If you're putting together a menu and you want it to preserve a choice the user has made, for example, you can rely on some memory registers that the dvd spec exposes to you.
A menu has pre and post commands, and as you "leave" the menu (to play the movie, for example), you'd invoke the post commands. To remember the last button selected (for example, a particular chapter/bookmark in the movie's runtime), you could access the read-only register for "selected button", and then store it in one of the read/write registers, as part of the post commands. (What you'd actually do with this information isn't relevant for now.)
One of the limitations of the dvd spec is that a dvd menu can only have so many commands assigned to it. If you get an error about exceeding this limit, it might look like ERR: Can only have 128 commands for pre, post, and cell commands.
. That error might come as a surprise if you haven't implemented, through whichever software you use, hundreds of commands you could obviously point to and count. This usually happens when there is some hidden multiplier effect.
Any register/variable math counts as a command, whether it's part of a parenthetical phrase or is explicitly on its own and separated (with a semicolon usually). Conditional statements do have room in their command space to use variable math, but the way you express your conditional can lead to unintentional extra commands being counted. For whatever reason, the command space is more forgiving of comparing two registers/variables than it is of comparing a variable to a constant. If you already have a variable set to a constant, then you can compare that variable to another and it only counts as one command, but if you compare a variable to a constant, that counts as two behind the scenes. This can contribute to the hidden multiplier effect.
One workaround to that particular wrinkle is to find a way for a variable being assigned to a constant to happen outside your menu command structure. In the "remember which chapter I picked" scenario above, you'd be assigning one variable to another, which still counts as a command, and has to happen within that menu's command space. When examining that variable later, however, if you were trying to see if it was the button corresponding to chapter 6 versus the button for chapter 7, you would be able to temporarily assign those constants (button number for chapter 6) to a register whenever you wanted, as long as it was before the comparison was made. You could do this as part of the "cell command" space of the movie itself as it plays through that chapter, and then if there is logic (in a post command for that chapter, for example) to jump to another menu to test the "button pressed", that menu wouldn't suffer the "constant comparison multiplier" problem.
How to Score More Points in Nintendo Games -- I mean how can you make DVD games where you "keep score"?
This is a bit of an emotional roller coaster. If you've never experienced an interactive DVD menu / game, you might just flatly assume it isn't possible. SURPRISE! It is. If you have experienced one and just assumed there was some simple/repetitive path-based logic to arrive at a "win" condition that would be the same every time, you're partially correct. You may have assumed a DVD game where the outcome is different each time isn't possible. SURPRISE! It is. The simple rebuttal to this is that the commands you can execute in different contexts can use the random(n)
method to make the game less deterministic.
There's a more complex answer to the question "How do you keep score?", however. When the score you want to present to the user is more complex than just Win or Lose, you usually need to rely on the read/write registers (or global variables if you like) to preserve some kind of game "state". These registers end up containing a numeric value that represents that state. This is similar to how home console games that didn't have removable storage (memory cards, etc) would give you Save Codes to write down and then come back and enter later. Those alphanumeric codes usually translated to a series of values in the game's state data that would take you to a particular level you got to when you'd saved and equip your character with items you'd already collected (or if you stole them from a cheat book or a friend).
Keeping in mind the limits of the spec, you can, through commands, do many manipulations of these registers throughout the DVD "game" to arrive at some final state / score. You also need to care how normal navigation (using the buttons on the DVD remote) can invoke those commands independent of your "on rails" navigation and produce unintended values. For example, you may expect users to select menu buttons to take them through the various game paths in a maze, but if a particular menu that is the Win/Exit or Lose/Dead End state is defined as "root" or "chapter", you can't forget to test what happens if they get there by hitting those buttons on their remote directly. Is it a fun Easter Egg / cheat you should reward? Can you detect them being clever and direct them to a surprise ending? As with many things, starting simple and coming back and revising as logic gets more complex is advisable.
Once the "players" have arrived at a part of the game where you want to show them the score, you get back into emotional rollercoaster territory. You may have assumed you could take one of these register's literal numeric values and put them on the screen through a dynamically created text field, like you can with most programmable interfaces. SURPRISE! You can't. At least not that directly. You may have also assumed that you could create a movie (through some other software) of a counter going from 1 or 100 up to some maximum score by ones quickly, and then through some other conditional logic, play this movie but pause it where their score shows up in that movie's timeline. SU--well actually you can totally do this. It's the conditional pausing part that is an annoying surprise. Depending on your software, of course, you may find you are laboriously repeating the same statements over and over again because you cannot "jump"/navigate/pause a particular movie at a point referred to by a variable / register.
In DVDStyler, in particular (and I'm assuming by extension dvdauthor and the spec itself), you have to always be explicit and use a constant when referring to a destination of automatic navigation. You can't say "jump to the chapter/cell number/timecode that is in this register." Instead you have to say "jump to chapter 7 or cell number 1" (and you can't, bizarrely, specify a timecode to jump to) or, and this might help, but not as much as you think, "jump to the previous cell / next cell / last cell." If, for example, this Counter Movie is a frame inside a menu, again especially in DVDStyler, the amount of that movie that displays in that menu is an explicit, constant value you have to define -- a number of seconds. Another limitation to this example is that commands only run at the end of a particular DVD structure - a chapter or cell - so that knowing when to pause, ie knowing when to execute a comparison command, happens explicitly/statically, and leads to laboriously repeating / copy-pasting the same command over and over again. This can also run you pretty close to your 128 command limit.
You can "keep score", but how do you get around those limitations to "show score"?
We'll stick to the Counter Movie example, playing as part of a DVD Menu, and get into more complex ways to conditionally show multiple items in a menu later.
DVD Menus are structurally similar to movies/episodes/features or "titles" as the spec calls them, because they are made up of smaller parts called Cells. The simplest kind of part to think of is a Chapter, but it is essentially a special kind of Cell. So just as you can execute a command at the end of a title that is a tv episode to jump to the next title that is an episode, you can execute commands at the end of each Cell (or Chapter) to compare and/or update registers and/or navigate elsewhere. DVD Menus are just like this, but it's a little weird to think of a Menu having a timeline, and therefore Chapters, if you've made virtually any other kind of interactive software. Because of how much more interactive (and therefore complex) DVD Menus are intended to be versus playable Titles, they get the expanded 128 command space to use. They have a "pre" command that can execute as you've navigated to this menu, a "post" command that executes as you "leave" this menu, and then a whole host of possible Cell (or chapter if you like) commands, which again execute at the end of each Cell.
In DVDStyler, you can define multiple Cells at the menu level. Each cell has Type, Start Time, End Time, Pause Time, and Command(s) fields. Multiple commands can be executed in the Command field of each cell, but the implication is that it's simpler to read one line of command(s) per cell at a glance. These cells do not have to have a command at all, and in fact in some cases you won't want one. When talking about this Score Counter Movie example, however, you'll want to be able to progress through the timeline of the counter and then stop at the point that corresponds to the score. This means that each Command field might have a comparison of the register that holds the Score to a register that holds an explicit number as a threshold. That threshold might be different for every cell of the menu, so that if you want to show Score 100, which is at 100 seconds in the movie, you'd want to compare the Score register to the Threshold register, which would be set to 100 at that point, and if they match, stay in this cell.
"Stay in this cell" could be implemented a few ways, but the simplest is to "jump" to this cell's number. How does the Threshold register keep getting set to the appropriate timecode as the time increases naturally? You have to increment it as part of your cell Commands. Does this add to the "command multiplier problem" worse than just a direct comparison to a constant each time? Good question. If the answer is Yes, then you'd just directly compare to a constant and be ok with potentially hitting that command limit ceiling faster than you'd wanted, but not as fast as the other way. If the answer is no, then you'd need to make sure it was part of each cell command set. Ideally you would find a way for the only explicit constant in a cell command to be the navigation destination.
Either way, once the menu has reached its destination, you can stay looping on that cell forever, and let the player stop/eject/reset the player themselves -- but is that the best experience?
Once you "show score" - what's next?
You can make parts of DVD navigation loop forever. Specifically, in a DVD Menu cell, you can tell it to jump back to this cell. But there is that "pause" attribute, which lets you pause at the end of a cell runtime for an additional amount of seconds. The commands then execute after this pause. So it's probably a better experience for the player if, after the score is stable and shown/paused for a certain period of time, say 20-30 seconds, that the navigation moves on to something else where they can choose what to do next. This would then give you the freedom of isolating this score logic into its own menu, and then jumping from each cell to some common point that then jumps off to the appropriate next part of the game -- another menu, another title, whichever.
How else can you express "score" besides a simple linear timer/counter?
Video games that are designed to be replayed often focus on "achievements" - generically, moments of a similar nature you collected along the way. This might be equipment your player's character literally collects, or it may be locations or characters on non-mission-critical paths they've visited. You could flatly express these as an overall numerical score, but perhaps it's more fun to show them in a kind of trophy case at the end of the game?
Especially in games where there's more than one possible path through to the end, you can reward players for hitting those more obscure points by playing them back to them at the end. There are probably more fancy ways to do this, but let's stick to the Trophy Case idea. If you have 9 possible "trophies" across the entire game, but it's possible that you'd only collect a subset of those because of your choices, how do you conditionally display these in an end-game menu?
In addition to the DVD menu itself having a concept of a timeline and executing commands at different points of that timeline, menu "elements" such as buttons, frames, and text are all time-sensitive as well. In DVDStyler, these are referred to as "animations." Generically, you can change certain attributes of these elements at different points throughout that menu's timeline. One of the simplest things you can change is the element's "opacity", or how (in)visible it is at a given time.
If we have Trophies A,B,C,D,E,F,G,H, and I, we could assign specific points in the timeline to make sure each individual one was highlighted/visible only for that set of seconds, and the rest are dark/invisible. One of the read/write registers could contain a number that lets us know which trophy was collected, say, 1-10 for A, 11-20 for B, and so on. This could conveniently correlate to the timeline, so that checking the "trophy score" against a constant that matched the timeline of a menu cell would pause the menu on that particular set of seconds, say 71-80 for Trophy H. What you would want to do is go into each menu element (a symbol that represented the achievement/location as an image, a still frame of the video where you collected it, or simply text that said "H") and alter its Animations to only be visible at the right time.
In DVDStyler, the Animations dialog is similar to the Menu Cells dialog, in that there are rows for different parts of the timeline. Each row contains Attribute, Values, and a Begin time and Duration. In the Trophy H example, we would want the Opacity (visibility) of the H to be zero for all times except 71-80. To cover most cases, you could set the default attributes of that element to be Opacity 0 (invisible), and only set the exception in the animations. For that exception, you'd set Opacity as the Attribute, Values to be just "1" (to mean 100% opaque/visible), and Begin at 71 with Duration of 9. Instead it might be easier (because default values affect how you see the element in the design menu) to leave the defaults alone, and make sure the First Animation row covers the default. That would be setting Attribute to Opacity, Values to just "0" (invisible), and Begin at 1 with Duration of 9. What's nice about this is as long as the menu gets to that first second (1) of animation, this element will stay invisible until you need it to be visible. If for whatever reason the timeline of the menu might jump around, it's a safe bet to zero-out the other Begin/Duration pairs. This all works but there's a more compact way to do this also.
In the Values field, you can (as it says) specify multiple values separated by semicolon, and it will divide those value changes evenly across the whole Duration you specify. So instead of nine separate entries for the Off/On moments for Trophy H, you could just have one single row. It would Begin at 1 with Duration 90, and then you'd add your Values of "0;0;0;0;0;0;0;1;0" for the Opacity Attribute. Duration of 90 seconds divided by the 9 entries in the values gives each Value 10 seconds of screen time. If the menu cells play through all 90 seconds without stopping, you'd see nothing in Trophy H's spot until about halfway through the 60s where it would start to fade in, and then it would start fading out at second 80 and be gone around halfway through the 80s. Providing more Values provides better granularity/speed to the fading between desired states.
Extending this to all the trophies in this case, each would have its own unique opacity/visibility values pattern, but each would be defined under its element's Animations in DVDStyler. If you kept the Menu Cell logic the same as the Score Counter Movie, you'd see each trophy fade in and out, in order, until you got to the "score" that corresponded to the appropriate trophy, then pause, and move on to whatever is next.
What if I want to only show the achievement(s) the player actually accomplished, and not give away how many other ones there are until they find them?
We'll get to the plural achievements/trophies idea later, but let's focus on only showing what's relevant for a single trophy. The simplest way to do this is to target which menu cell corresponds to which segment of time where that trophy element is visible (opacity 100% / "1"). You can then, in earlier cells' commands, check the "trophy score" against some threshold value, and jump deliberately to the target cell. This does mean re-numbering/re-timing things from what was described earlier. This probably ends up looking like it "wastes" menu cell rows, but that's less of a concern than the command limit, which also shouldn't be drastically affected by this strategy. It also "wastes" time, but you can obscure this through some "score loading" animation video that is ok to repeat / interrupt at any time. At time of writing, it's not 100% clear what the minimum time needed is for a cell to execute a command, or how disastrous a long if/else chain would be in a single cell.
This might end up looking like:
Menu cells Start | End | Pause | Commands | Comment |
---|---|---|---|---|
0:00 | 0:01 | 0 | if (trophyScore == trophyAThreshold) jump cell 11; | cell 1 |
0:01 | 0:02 | 0 | if (trophyScore == trophyBThreshold) jump cell 12; | cell 2 |
0:02 | 0:03 | 0 | if (trophyScore == trophyCThreshold) jump cell 13; | cell 3 |
0:03 | 0:04 | 0 | if (trophyScore == trophyDThreshold) jump cell 14; | cell 4 |
0:04 | 0:05 | 0 | if (trophyScore == trophyEThreshold) jump cell 15; | cell 5 |
0:05 | 0:06 | 0 | if (trophyScore == trophyFThreshold) jump cell 16; | cell 6 |
0:06 | 0:07 | 0 | if (trophyScore == trophyGThreshold) jump cell 17; | cell 7 |
0:07 | 0:08 | 0 | if (trophyScore == trophyHThreshold) jump cell 18; | cell 8 |
0:08 | 0:09 | 0 | if (trophyScore == trophyIThreshold) jump cell 19; | cell 9 |
0:09 | 0:10 | 0 | (jump to some location to express the trophy score being "out of bounds") | cell 10 |
0:10 | 0:20 | 10 | (allow trophy A to be "visible" via its animation during pause, then jump to a post-trophy-display menu/area) | cell 11 |
0:20 | 0:30 | 10 | (allow trophy B to be "visible" via its animation during pause, then jump to a post-trophy-display menu/area) | cell 12 |
... etc
Using this strategy also means knowing when / how to set the Trophy Score as you navigate through the DVD and its titles. As described before, this value would be stored in one of the read/write registers. In this simple single-achievement/trophy mode, you would overwrite this register with whatever the appropriate value was for that single trophy when you needed to, not caring what the value was previously.
You could choose to overwrite this value as part of a title cell command right when it happens in the movie, so at a glance in the DVDStyler menus, for example, you'd see a snapshot of that scene in the Chapters menu, click on it, and see the Cell Command field where the relevant register was being overwritten. There would need to be one of these for each trophy.
An alternative to this is to set the value when whatever navigation choice is made to take you to the scene that contains the acheivement/trophy. If, for example, there are no other choices that could be made in the meantime (and with the usual caveats that the user could hit a button on the remote to skip the relevant scene entirely), you could make the scoring decision to "award" the point when the navigation choice was made. This might simplify having to assign the register yourself if, for example, you take advantage of DVDStyler's ability to "Remember last selected button" in a menu that provides that navigation choice, and then refer back to the register it uses to store that choice. This becomes less simple when you read that value back later, and I'll dedicate an entire section to WHY REMEMBERING BUTTONS IS TRICKY later.
How do handle multiple achievements?
Luckily much of the above logic still applies. You want to be comparing as few values as possible in your menu cells to decide which part of the timeline to show as always. You want to only have the appropriate achievements or trophies visible when the time is right, and to do this you change the pattern of the values accordingly. The difference for an individual element is it’s likely you’ll have the same trophy show up in some kind of repeating pattern instead of only one set of values. Across multiple elements you may not notice much of a difference when editing these values, but the intent is more than one trophy would be visible to the player at one time.
How do you limit the number of variables being compared? You can find a clever way to use binary math to update a single register in different ways for different trophies. For example, the writeable registers in the DVD spec are 16-bit. If you only have 16 trophies, and any one of them, or some combination of them, or all of them could be collected, you simply flip each bit to indicate this. During your menu cell conditional statements, mapping these bits to an easily repeatable decimal pattern, and therefore timeline pattern and value pattern might be uncomfortable at first. You could store a quick lookup table in your spreadsheet software of choice for quicker calculations. You’ll still be repetitively plugging the values into the DVD software interface, but you may discover automation shortcuts along the way.
Eventually you’ll discover the real fun of this kind of achievement math — testing!
How do you test this stuff? By playing the game!
Simply put, you use the DVD software to generate a local VIDEO_TS folder full of .ifo, .vts, and .vob files, and point your DVD-image-capable player (eg VLC) at the folder that contains it. You then use that player’s controls to navigate the disc structure like a physical player would. Ideally you’d have a good way to remind yourself of which choices you need to make to see the relevant trophies (again a spreadsheet helps here).
The main drawback to this kind of testing, and really DVD registers in general, is there’s not much (if any) free software out there to help you monitor them while playing through the disc. Instead it’s literally playing through the game in real time hoping you’ll get the right outcome. In some players like VLC you can speed up playback on normal titles, which helps but can sometimes skip past menu choices you meant to make.
WHY REMEMBERING BUTTONS IS TRICKY
In DVDStyler, when you add buttons to a menu, they retain their sequential ID, even if you delete elements in between others. When you ask the software to “remember last button pressed” and assign that to a specific register, it will do what you ask, but with a large caveat. Because there’s no explicit way to declare which button should be considered first, second, etc., it just keeps them in the order you created them.
For example, if you started the menu with six buttons but delete numbers two and four, it won’t try to remember the state of those two unreachable / deleted buttons, which makes sense. What it will do instead is assign the third created button as Button Two, the fifth as Button Three, and then the sixth as Button Four.
Both of these factors will require you to make adjustments. Conforming the elements you think of as First, Second, etc to what the software considers “right” is the path of least resistance. There is, however, one more complicating factor to understand.
This is documented elsewhere, and as I understand it comes more from the DVD spec itself, but there are read-only registers as well as the ones you can update. One of these is “last button highlighted”, and it takes the button number and multiplies it by 1024. When you ask DVDStyler to “remember”, it stores this multiplied value in the register you requested.
If, for example, you decide to use these remembered values later to make some score calculation, you need to keep that multiplier in mind. In a 16-bit number, multiplying by 1024 affects higher bits than multiplying by a lower number, understandably. It might make sense then , if you’re taking multiple remembered “chosen button” values, to divide each one by a different factor of 1024 before adding them all together.
This might seem trivial if the amount of “trophies” you’re dealing with fit comfortably/sensibly into one or several 16-bit numbers. But there are other “choose your path” game layouts where only ever having a binary choice is unsatisfying, so being able to choose from four or more options may change the way you “pack” these numbers into a final score.
Another thing to consider is if your choice menu is on a timer and makes a default choice on behalf of the user (by jumping to another structure), that choice won't automatically become part of that math -- you'll have to set the "last button pressed" value as part of that default command.
TRICKY: Automatic Navigation
This is documented elsewhere (including the spec itself) but the language might be confusing. You can have more than one movie/episode/feature on a DVD. Usually each movie is its own Title, and usually there’s only one Titleset that contains them. You can also have more than one Menu, and while those are usually in a Titleset as well, there’s also usually, but less obviously, a menu space outside of that called a VMGM, where at least one Menu sits. This VMGM usually just re-routes to the first Titleset menu, and might simply play a warning or intro video before jumping to the first Titleset menu.
So if you’re making an interactive game, that VMGM space is good for an overall game intro movie, and maybe some basic instructional text/video menus, but then it would take you to the first truly interactive menu, inside a Titleset. Making this jump automatically is fine, and can be expressed a few ways, but explicitly would look like jump titleset 1 menu 1
.
That Titleset menu can then jump to any cell/chapter of any Title it contains. This direct of a jump cannot be done from any of the VMGM menus, nor from any other Titleset. There are clever tricks to make that work that can be discussed later as well as their tradeoffs, but the explicit jump title 1 chapter 1
command only works inside that Titleset.
Let’s say that during playback of a title, you want to jump to an interactive segment, a menu. For whatever reason, this is considered a call
instead of a jump. When you call a menu, though, you cannot refer to its number, but instead its Label, such as “root”, “chapter”, or “angle.” This is where another odd limitation pops up.
Menus can only have Six kinds of Labels inside a Titleset. It appears like a given menu can have all six labels selected, but is unclear how you would phrase the call command to address a menu uniquely that had multiple labels. Instead, it appears that you can only usefully navigate to 6 specific Titleset Menus, by their Label, from within a cell/chapter of any of that Titleset’s titles. You do so like so: call menu entry root
.
From these 6 menus, as stated previously, you can jump into any chapter/cell within the Titleset, or as needed, off to another Titleset or VMGM menu.
TRICKY: Menu Cell timing maximum has to have matching Element Animation timing
At least in DVDStyler (and maybe by extension dvdauthor and the spec itself), you cannot specify a jump destination to a menu cell that doesn't have a reason to exist yet. If there's no Element (button/frame/text) on this Menu that has Animation instructions for the seconds that contain the end of that menu cell's duration, the "build" of the VIDEO_TS folder will fail. Worse, it will wait until it has almost finished building everything else to tell you this. The error message is usually something unhelpful like cannot jump to cell 13 because only 12 exist
. Clearly you have specified a cell 13, it's just that when DVDStyler optimizes the timings for the menu, it doesn't see a need for that cell to exist (because nothing changes during that timing), so it just deletes it and moves on. This can be frustrating when the timing of showing multiple items in a menu is complex and you're working in layers/iterations and get ahead of yourself.
TRICKY: Converting video to DVD quality ... repeatedly
Another time-waster (at least in DVDStyler, but this would apply at some level to any DVD-making software) is that the software wants your video titles in DVD format. Makes sense, of course, but you would think it would be more efficient about it and offer to convert once and then never again. Making new chapter markers or having menus get a slideshow of a portion of your movie doesn't require the movie to be transcoded again, so why does it try to do this?
Luckily you can pre-convert all your video assets using ffmpeg -i input.mp4 -target ntsc-dvd output.mpg
. Unfortunately you still have to tell DVDStyler "no convert! only show!" through multiple dialogs and checkboxes, but you should only have to do this once.
TRICKY: Video "background" in a menu needs manual audio de-muxing
This one makes sense eventually but it's still annoying it isn't offered as part of DVDStyler. If you want to have an interactive portion of your video, you essentially have to fake it. You do this by creating a Menu, and then have the segment of your movie that would normally be playing set as the Video Background.
You then need to make a separate copy of the audio from that video so you can use it as the menu’s Audio Background. That’s easy enough, but the tricky part is making sure they sync for the duration of the video that will be on screen in the menu. For whatever reason DVDStyler doesn’t give you an interface to start that audio at a particular time, so you have to trim it yourself. This means changing it every time you change the underlying video start time.
if (regsister<constant) jump cell x;
builds fine
129 cells 127 with commands
failed - 128 commands
if (regsister<constant) jump cell x;
post command to jump to a cell
remember last selected button on-autofailed - can only have 128 commands
What weird boundaries does DVDStyler appear to have?
What of these are actually because of the underlying cli for dvdauthor?
What of those are just part of the DVD standard?