View Full Version : The NAG CoverDVD interface post-mortem
Update: File now hosted here: http://gamedev.openhazel.co.za/filecloset/data/files/133/CoverDVD_final.zip (thanks Chippit)
I've been meaning to post this for a while but am just getting around to it now. Some people may recall the short lived interface we had on the DVDs for a while, between when it went from CD to DVD until it went to 9GB DVD. Some people mistakenly believed this was done in flash, but in reality I wrote this interface in GM6 to replace the one Miktar had previously built in GM for the CD back in the day, since we realized quite early that the interface building time for the CD was already too long and expanding it would make it nearly impossible to do in time without some kind of improved automation. One of the reason the 9GB DVD doesn't have an interface anymore is it would take approximately 2 dedicated weeks of effort to compile a 9GB DVD with it, whereas just dumping the files on and cleaning things up can be done in a few days, and we just don't have the manpower to spare someone for the extra time. Plus we ran into headaches with various hardware and software issues.
Anyway, I have the .gm6 file if anyone wants it, though I don't know where I can put it online for people to grab. I'll just post each script file in a post with a code block and then discuss it afterwards. It'll probably take me a few days to get through the entire program, but I'll work on it. The main goals for the project were as much automation as feasible and flexibility to allow for changes in the future in case we needed to change the graphical style.
Let's see, starting with the overall project, there's only one room, room0. It's set for 1024x768 at 30fps, and the game uses a custom loading image we put in, which you may remember. There's only one object in room0, and that's a coverdvd object. So basically the program start point is the coverdvd object's create event.
There are a couple of basic objects. The coverdvd object is the overall thing. It contains one or more category objects, which then contain fileobject objects. Each of these is responsible for certain details and ignores the rest. For example, the coverdvd object will arrange where the buttons of the category objects go when it is running the display (at the main menu). Once you're in a category though, that category runs the show and can do whatever the heck it wants with the display elements like the file systems and the other buttons. Each fileobject also controls things like where and how its run button is displayed, etc.
There are some notes on the overall design in the Game Information tab as follows:
How it works:
The coverdvd object will look for coverdvd.ini. If it exists, it will call ProcessINI(). The sequence looks like this:
coverdvd.ProcessINI() -- creates a ds_list of category objects and sets their basic info
|
|
category.Init_category() -- reads the rest of the category info from the ini. Calls SelectFileType() to create the correct file object type for a given file, which it reads from the ini. Creates a ds_list of fileobjects in the category.
|
|
fileobject.Init_fileobject() -- reads the rest of the file info from the ini. Creates a runbutton for execution.
Once everything is created, covercd starts up with page = "main". All the other objects take their cue for when to execute their draw events from what covercd.page is. Categories check owner.page == mypage, and set the fileobjects to draw which in turn set their runbuttons to draw. This triggers their draw events. Categories only draw their background and files if their page is selected. Otherwise, categories will draw their icon sprites. Their mouse left release events are active in this mode, and they will use that check to determine if we need to change the page. The coverdvd object behaves in the reverse. If the page isn't "main" it reverts to drawing the main menu icon, which the category objects can then position.To change the page, any object can call SetPage(). The reason for not doing this directly is in case there needs to be any cleanup on a page transition, variables reset etc. For example, an animation that exectues once on page display. We might set a variable "animated" to 1 once the animation finishes so it doesn't constantly repeat. But if we leave the page and come back it won't animate again. Calling SetPage() allows us to perform maintenance actions before the page actually changes.
File execution seems complicated but is actually very simple. Every fileobject is a specific type, such as file_exe. Every fileobject also has created a runbutton object. The runbutton object is responsible for handling the mouse release event so that it will trigger execution. The runbutton object has its owner (the fileobject) execute the RunFile() function. RunFile() contains a simple switch statement based on the object type of whatever object is calling it. The switch cases determine what happens for that given fileobject subtype. Using this system it is virtually impossible to perform the wrong execution type for a given file. It's "hard-wired" by object subclassing.
If the file coverdvd.ini DOES NOT exist, coverdvd will automatically call GenerateINI(), which will create an ini file by scanning the directory structure for the known subdirectories and then scanning their files.
Now, a bit of a note on the INI system. Originally the whole thing just built itself on the fly by looking at what files were hanging around the disc. This worked in testing, but when we applied it to the real world, it fell flat on its face. This is because some DVD drives really didn't like seeking all over the damn disk for files at high speeds, and some kind of timeout would happen and the program would crash. Sometimes DVD firmware updates would fix this, sometimes not, but it became a huge issue. So I decided to redesign things so that all that needs to load during the game's init is the INI file, which means a lot less seeking at startup.
Rather than have a separate program build the INI or build it by hand, the coverdvd object will check if coverdvd.ini exists and if not, it will create one. The INI file is designed to still be human-readable and editable so that we could tweak it by hand if necessary, but I don't think we ever had to.
dislekcia
07-08-2007, 04:23 PM
Neat, thanks for posting this. I was amazed at how Miktar got all that dynamic GM stuff going in the first version, hope it carried over :)
You can always put the file up at the Filecloset (http://www.gamedev.za.net/filecloset), that's what it's there for!
-D
I didn't feel like going through yet another goddamn signup so I found a host that doesn't require one. I'll put the link in the first post at the top. It includes the last set of menu graphics we used (the white ones).
coverdvd object:
It has 3 events, create, left released, and draw.
Create event:
page = "main";
mypage = "main";
categories = 0;
mainbutton_x = 0;
mainbutton_y = 0;
animate = true;
animate_setup = true;
animation_counter = 0;
menu_scrolloffset = 0;
// splashtext positioning
text_hoffset = 40;
text_voffset = 480;
text_width = 700;
textcolor = c_black;
// splashimage positioning
splashimage_hoffset = 655;
splashimage_voffset = 10;
x = 0;
y = 0;
// check for minmum 1024x768 resolution
if( display_get_height() < 768 || display_get_width() < 1024)
{
show_message("The DVD interface requires a minmum 1024x768 display resolution to run");
game_end();
}
if( file_exists("coverdvd.ini") )
{
ProcessINI();
}
else
{
GenerateINI();
ProcessINI();
}
myexitbutton = instance_create(x, y, exitbutton);
// exitbutton position is set by the menu function
myreplaybutton = instance_create(x, y, replaybutton);
replay_hoffset = 356;
replay_voffset = 691;
mydisclaimer = instance_create(x, y, disclaimer);
mydisclaimer.owner = id;
mydisclaimer.mytext = disclaimertext;
disc_hoffset = 585;
disc_voffset = 680;
//mychecksum = instance_create(900, 600, checksum_tester);
Yeah I know, my commenting is a bit sparse. Basically the page and mypage variables are tracking what we're displaying. Page will vary depending on what category is controlling the view, and each one will check the value vs mypage. For coverdvd itself, we want mypage to be "main", so all we have to do to return to the menu later is set that variable.
The animate and animation variables are used to control the screen transitions. I didn't want to annoy people with constant animations so the way things are set it will animate the first time you transition to a page, but not after that. This can be turned on and off by messing with these variables. You'll see them in more detail in various draw events.
The splashtext and splashimage are the front page stuff, fairly obvious, we set our own position to 0,0 so we can draw the screen, and then it's time for some error checking. First thing we do is make sure we can run 1024x768. Then we go looking for our INI file. If it's there, we process it with ProcessINI(), if not we call GenerateINI() first and then ProcessINI(), so if the ini file hasn't been built yet it will be, but if it has then we won't waste time rebuilding it. One important implication of this is any time the structure of the DVD changes, you need to delete and rebuild the INI file, otherwise it will use the old one and ignore any changes you've made.
Once that's set, we create the three front page buttons, for exit, Replay ITV's ad screen, and the disclaimer. Oh and that commented out line at the bottom is to create a checksum object. In some builds we had a button on the front page that would run an MD5 checksum program that would load a checksum file on the disc that was built at burn time, and then compare it to the current disc. This was one of the way we were trying to reduce errors because people kept complaining of bad discs and insisted it couldn't possibly be their hardware. However, it was decided having the checksum directly accessible was a bit confusing for people since they didn't know what it was for and the majority never needed it, so we decided to take the button out. Usually we'd run the checksum ourselves when we got printed samples back to make sure there wasn't some kind of master error in reproduction (this happened a few times).
Left Released event:
if( page != mypage )
{
SetPage(mypage);
}
Easy stuff here. :)
Draw event:
if( page == mypage )
{
x = 0;
y = 0;
depth = 10;
draw_sprite(title, 1, x, y);
draw_sprite(sprite_index, -1, mainbutton_x, mainbutton_y);
// draw month and date
themonth = MonthString(ini_read_real("coverdvd", "month", 1));
theyear = ini_read_string("coverdvd", "year", 0);
datecolor = make_color_rgb(157, 8, 13);
draw_set_font(font_verdana_bold);
draw_text_ext_color(text_hoffset, text_voffset - 30, themonth + " " + theyear, -1, 200, datecolor, datecolor, datecolor, datecolor, 1);
// draw splash text
draw_set_font(font_verdana);
draw_text_ext_color(text_hoffset, text_voffset, splashtext, -1, text_width, textcolor, textcolor, textcolor, textcolor, 1);
// draw splash image
//draw_sprite(splashimage, 1, splashimage_hoffset, splashimage_voffset);
// draw signature
draw_set_halign(fa_right);
draw_text_ext_color(text_hoffset+ text_width, text_voffset + string_height_ext(splashtext, -1, text_width), signature, -1, text_width, textcolor, textcolor, textcolor, textcolor, 1);
draw_set_halign(fa_left);
myreplaybutton.x = replay_hoffset;
myreplaybutton.y = replay_voffset;
mydisclaimer.x = disc_hoffset;
mydisclaimer.y = disc_voffset;
}
else
{
x = mainbutton_x;
y = mainbutton_y;
depth = -1; // need to be over scroll menu items
draw_sprite(sprite_index, -1, x, y);
myreplaybutton.x = room_width+10;
myreplaybutton.y = room_height+10;
mydisclaimer.x = room_width+10;
mydisclaimer.y = room_height+10;
}
if( animate = true )
{
MotionBlurHorizontalMenuLoad();
myreplaybutton.x = replay_hoffset;
myreplaybutton.y = replay_voffset;
mydisclaimer.x = disc_hoffset;
mydisclaimer.y = disc_voffset;
}
else
{
// draw a vertical scroll menu at the bottom
VerticalCategoryScrollMenu();
}
Ok, here's the draw event. Since we start at the page "main" this one will be the first one that executes and draws the main screen. You may notice the bigass if/else statement that this thing starts off with. That basically controls if we're in "master" mode and drawing the whole screen, or "slave" mode, and just drawing the main menu button. The coverdvd object has the main menu graphic as its sprite, so thats what it appears as when not actively filling the screen, and lets it check for left released to see if anyone has clicked on it so we need to go back to the main menu. Nifty huh?
If we're drawing the main page there's a bunch of text and buttons and the date string to draw etc. Nothing really major. In the else part of the statement, we set the depth and position to draw our sprite, and we make those extra buttons vanish off the screen, though the objects still technically exist.
Now on to drawing the menu. If animate is true, we call our desired animation function, in this case MotionBlurHorizontalMenuLoad(), or if not we just call VerticalCategoryScrollMenu(). I believe the older black interface used MotionBlurVerticalMenuLoad() to make the categories come up from the bottom. This switch is why the menu animates when you first load up the DVD but then later when you come back to main from anywhere else, it doesn't re-do the animation, since that could get annoying if you want to navigate it quickly.
Next I'll walk you guys through the INI generation and processing sequence, which goes a little deep in terms of scripts calling each other but it's really not so bad.
Cyberninja
07-08-2007, 05:43 PM
Cool. I always thought you guys created the interface in Flash/Director before I found out it was Gm. Hmm..I get a "invalid download link" error, on the file Gldm. 0-0 Is this just my interwebz going faulty again?
Miktar
07-08-2007, 05:49 PM
Cool. I always thought you guys created the interface in Flash/Director before I found out it was Gm. Hmm..I get a "invalid download link" error, on the file Gldm. 0-0 Is this just my interwebz going faulty again?
It used to be Flash, pre-2003 (before I joined). I took over the CD production when I joined.
Gazza_N
07-08-2007, 06:00 PM
Excellent! Thanks Gldm! I've always been curious as to how you guys put the interface together, and now we're being made privy to the arcane secrets of its making...
Whoooooo! :D
Ok, now for a look at how we build the INI file. You may remember CoverDVD will call GenerateINI() if it doesn't find the file. So things start there. Basically GenerateINI() will call ScanForDirectories() for each subdirectory we want to look for. That will then call ScanForFiles() and find the files in it, and write their details in the INI file.
GenerateINI()
/************************************************** **************************
Generates a new ini file for us from the local directory structure.
************************************************** **************************/
// create file
inifile = file_text_open_write("coverdvd.ini");
// start coverdvd section
file_text_write_string(inifile, "[coverdvd]");
file_text_writeln(inifile);
currentdate = date_current_datetime();
currentdate = date_inc_month(currentdate, 1);
// month
file_text_write_string(inifile, "month=" + string(date_get_month(currentdate)));
file_text_writeln(inifile);
// year
file_text_write_string(inifile, "year=" + string(date_get_year(currentdate)));
file_text_writeln(inifile);
// signature from signature.txt
sigfile = file_text_open_read("signature.txt");
file_text_write_string(inifile, "signature=");
file_text_write_string(inifile, file_text_read_string(sigfile));
file_text_writeln(inifile);
file_text_close(sigfile);
// splashimage from splashimage.bmp
//file_text_write_string(inifile, "splashimage=splashimage.bmp");
//file_text_writeln(inifile);
// splashtext from splashtext.txt
splashfile = file_text_open_read("splashtext.txt");
file_text_write_string(inifile, "splashtext=");
file_text_write_string(inifile, file_text_read_string(splashfile));
file_text_writeln(inifile);
file_text_close(splashfile);
// disclaimer from disclaimer.txt
discfile = file_text_open_read("disclaimer.txt");
file_text_write_string(inifile, "disclaimer=");
file_text_write_string(inifile, file_text_read_string(discfile));
file_text_writeln(inifile);
file_text_close(discfile);
// end [coverdvd]
file_text_writeln(inifile);
// check for directories
ScanForDirectories("demos");
ScanForDirectories("movies");
ScanForDirectories("patches");
ScanForDirectories("drivers");
ScanForDirectories("addons");
ScanForDirectories("utilities");
ScanForDirectories("cheats");
ScanForDirectories("flash");
ScanForDirectories("anime");
ScanForDirectories("fullgames");
ScanForDirectories("photos");
ScanForDirectories("gamedev");
ScanForDirectories("design");
Many people probably overlook the fact that GM has a whole slew of functions for managing INI files. It was my discovery of these that led to the attempt to do it this way, and it actually worked out really well.
First we open our coverdvd.ini and write the header, which is simply [coverdvd]. Then we get the date, increment the month by one, and store that. Why increment? Because we're working on next month's NAG now, duh. Then we get all sorts of other details we might need like the text from the signature, splash text, and disclaimer text files. That's basically it for what we need to build the main page. Then we need to handle the categories. For that, we call ScanForCategories() and pass it the name of the subdirectory we want to include. If you don't have a directory explicitly called here, the DVD won't make a category for it. Useful if you want to have say, a ynnuf directory that doesn't show up unless you browse. ;)
ScanForDirectories()
/************************************************** **************************
Scans for directories and adds them to the ini file. Argument 0 is the
directory/category name.
************************************************** **************************/
if( directory_exists(argument0) )
{
file_text_write_string(inifile, "[" + argument0 + "]");
file_text_writeln(inifile);
numfiles = ScanForFiles(argument0);
file_text_write_string(inifile, "numfiles=" + string(numfiles));
file_text_writeln(inifile);
file_text_writeln(inifile);
}
This one's pretty simple. We double check that the directory exists, which lets us ignore things like say, if we don't have a category for a specific month. We just leave the directory off the DVD and don't have to worry about modifying the code at all. If the directory exists, we write its name in the INI file surrounded by square braces, like [directory], and then we call ScanForFiles() and after that write down how many files there were. Pretty simple.
ScanForFiles()
/************************************************** **************************
Scans for files in a subdirectory, adds them to the ini file and returns
the count.
************************************************** **************************/
filecount = 0;
currentfilename = file_find_first(argument0 + "/*.txt", fa_hidden);
while( string_length(currentfilename) > 0 )
{
// open description file
descfile = file_text_open_read(argument0 + "/" + currentfilename);
// set file name in ini
file_text_write_string(inifile, "file" + string(filecount) + "_name=" + string_replace(currentfilename, ".txt", ""));
file_text_writeln(inifile);
// scan for filetype and write to ini file
filetype = FindFileType(argument0, currentfilename);
file_text_write_string(inifile, "file" + string(filecount) + "_type=" + string(filetype));
file_text_writeln(inifile);
// set file description string from txt file in ini
file_text_write_string(inifile, "file" + string(filecount) + "_desc=" + file_text_read_string(descfile));
file_text_writeln(inifile);
file_text_close(descfile);
filecount += 1;
currentfilename = file_find_next();
}
file_find_close();
return filecount;
This one's a little more complicated. First we need to find files in the directory. Have a look at find_file_first() in the GM docs sometime, it's nifty. It goes and gets a directory list of *.txt in the directory we're currently scanning. We based all our assumptions on the .txt file description for a given file. The reason for this is if we looked for .exe and such we might find something we forgot to make a description and screenshot for, which could bomb at run time. I though it was a much safer assumption that if a description file has been written for a file, it's probably got a screenshot and file to go with that.
So for each .txt file, we grab its contents and store it as the description, and also write the file name and file type. It's assumed that if there was a filename.txt and filename.filetype, a filename.bmp will be there also. All the names must match or it won't work, but it's not that hard to check in explorer. One complicated bit is finding the file type, which is going to be important later, as we handle different file types in different ways. That's what FindFileType() is for. Once we're done we return the number of files so the category can have a count to write down in the INI.
FindFileType()
/************************************************** **************************
Determines the type of file based on it's extension. Argument 1 is the name
of the file's .txt file, and Argument 0 is the path name.
************************************************** **************************/
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".exe") ) )
{
return "exe";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".avi") ) )
{
return "avi";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".mov") ) )
{
return "mov";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".wmv") ) )
{
return "wmv";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".mpg") ) )
{
return "mpg";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".zip") ) )
{
return "zip";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".pdf") ) )
{
return "pdf";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".swf") ) )
{
return "swf";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".msi") ) )
{
return "msi";
}
if( file_exists( argument0 + "/" + string_replace(argument1, ".txt", ".mp3") ) )
{
return "mp3";
}
show_message("FILE NAME MISMATCH IN " + argument0 + ": " + argument1);
Bigass switch basically. Yeah I know, a switch and cases might be a better style for this, but I did it this way for some reason. Basically we string replace the ".txt" with each file type extension that we support, and see if it exists. If it does, we return the substituted string, and that's the file type. If we don't match anything, it defaults to an error message saying there was a file name mismatch, and tells us the current directory and filename that caused it, which turned out to be useful on several occasions.
That's basically it for the INI generation. When we're done, the output should look something like this (this is an actual generated INI from this code with placeholder files in directories):
[coverdvd]
month=1
year=2006
signature=Blah blah blah, this is a test.
splashtext=this is the splashtext
disclaimer=this is the disclaimer
[demos]
file0_name=DivX 505
file0_type=exe
file0_desc=Often some of the trailers on the#CoverCD require the DivX codec.#Installing this will allow those#movies to play.
file1_name=reanimator
file1_type=swf
file1_desc=test for reanimator flash
file2_name=test
file2_type=pdf
file2_desc=test for pdf
numfiles=3
[movies]
file0_name=DivX 505
file0_type=exe
file0_desc=Often some of the trailers on the#CoverCD require the DivX codec.#Installing this will allow those#movies to play.
numfiles=1
[addons]
file0_name=NAG Sizzler August 2005
file0_type=pdf
file0_desc=Hot, hot, hot! The Sizzler returns for another round with previews not found inside the magazine itself.
numfiles=1
[utilities]
file0_name=wshell
file0_type=zip
file0_desc=test
numfiles=1
I tried to keep the INI as simple as possible, so that if we had to get our hands dirty and manually fix it for some reason, it wouldn't be utter machine code gibberish and we could get it fixed in a reasonable time. I don't think we ever actually had to do that though, it worked quite well, and solved a lot of those seek timeout issues we had on the early DVDs.
Cool. I always thought you guys created the interface in Flash/Director before I found out it was Gm. Hmm..I get a "invalid download link" error, on the file Gldm. 0-0 Is this just my interwebz going faulty again?
Hmm, the link seems to have broken almost immediately. I think I need somewhere else to put it, but I really don't want to have to fill out yet another account registration form. Anyone got any ideas?
Cyberninja
07-08-2007, 06:21 PM
Hmm, the link seems to have broken almost immediately. I think I need somewhere else to put it, but I really don't want to have to fill out yet another account registration form. Anyone got any ideas?
Why not just host the file on this site for now?: http://www.sharebigfile.com/
Then one of us can transfer it to the filecloset account later. :-)
@Miktar: Ah, I see. Yeah, I remember seeing the Macromedia logo on ancient copies of my Nag cds. It makes sense now. ;-)
Chippit
07-08-2007, 06:22 PM
Email it to me, and I'll upload it to FileCloset for you?
EDIT: Or do that ^^
Ok, it should be here for the moment then: http://www.sharebigfile.com/file/202394/CoverDVD-final-zip.html
Ok, enough with the production side, today we'll get on to the part that runs when most people run the DVD. That starts with ProcessINI(), which is basically going to be a huge nested mess of function calls running around and building objects. It was a bit tough to organize such that it could be fixed if things fell apart, but I think it managed ok.
ProcesINI()
/************************************************** **************************
Begins construction and initialization of objects from the ini file.
************************************************** **************************/
// load ini file
inifile = ini_open("coverdvd.ini");
// load basic info
month = ini_read_string("coverdvd","month", "no month");
year = ini_read_string("coverdvd", "year", "no year");
splashtext = ini_read_string("coverdvd", "splashtext", "no splashtext");
//splashimage = sprite_add(ini_read_string("coverdvd", "splashimage", "splashimage.bmp"), 1, false, false, false, true, 0, 0);
signature = ini_read_string("coverdvd", "signature", "no signature");
disclaimertext = ini_read_string("coverdvd", "disclaimer", "no disclaimer");
// create list for category objects
categories = ds_list_create();
// create categories
CreateCategory("demos");
CreateCategory("movies");
CreateCategory("patches");
CreateCategory("drivers");
CreateCategory("addons");
CreateCategory("utilities");
CreateCategory("cheats");
CreateCategory("flash");
CreateCategory("anime");
CreateCategory("fullgames");
CreateCategory("photos");
CreateCategory("gamedev");
CreateCategory("design");
Looks a bit like a mirror of GenerateINI doesn't it? Probably because I wrote both at the same time and wanted to make sure I was getting everything right, so rather than write the whole forward process and then the whole reverse process, I worked from the outside in using stub functions. Getting it all up and running was a lot less painful than anticipated, since I usually have trouble dealing with things like getting data back from strings saved in a file.
CreateCategory()
/************************************************** **************************
Creates a catetgory object. Argument 0 is the string of the category name
************************************************** **************************/
// create category objects from ini
if( ini_section_exists(argument0) )
{
// first make sure we're not wasting our time
if( ini_read_real(argument0, "numfiles", 0) > 0 )
{
switch( argument0 )
{
case "demos":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_demos));
break;
case "movies":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_movies));
break;
case "patches":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_patches));
break;
case "drivers":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_drivers));
break;
case "addons":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_addons));
break;
case "utilities":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_utilities));
break;
case "cheats":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_cheats));
break;
case "flash":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_flash));
break;
case "anime":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_anime));
break;
case "fullgames":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_fullgames));
break;
case "photos":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_photos));
break;
case "gamedev":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_gamedev));
break;
case "design":
// create category object and add to the list
ds_list_add(categories, instance_create(0, 0, cat_design));
break;
}
// By using the size of the list we can assign a dynamic index
// that accounts for some categories not being used in a given
// instance of the dvd.
index = ds_list_size(categories) - 1;
// set basic info
tempobject = ds_list_find_value(categories, index);
tempobject.owner = id;
tempobject.mypath = argument0 + "/";
tempobject.mypage = argument0;
tempobject.mysection = argument0;
// initialize category and file objects from ini
with( tempobject )
{
//show_message("start " + mysection);
Init_category();
//show_message(mysection + " OK!");
}
}
}
Here we go and create all the categories we find in the INI file. the categories ds_list is there for the reasons mentioned in the comments near the bottom. If we use a dynamic list, then if we have a DVD that skips a given category, nothing needs to be modified. In case someone left an empty directory on the DVD with no files, we check to make sure numfiles is > 0, so we don't create a category which appears in the menu but is just empty when you go to it. Notice each category has a different object type. These all inheret from the file_category object. There's a reason for this and I'll explain it in the next post on the category objects, so be patient. Some debug info is left in the bottom here from when I was making sure the Init_category() function worked and so I wanted to see when it started and ended to see if there were issues specific to any of the categories. The reason I call the init script from a with(tempobject) is just me being fussy with object oriented design. If I had built this in C++, I would have something like Category.Init() instead of Coverdvd.InitCategory(), since I want ot do my initialization within the object that's being created. So it seems a bit overcomplicated since I could do it the other way without the with() statement, but it just works better in my head this way. If you look at how the scripts are organized in the project, Init_category is filed under category but CreateCategory is filed under coverdvd. GM lets you call any script from any object, but I wanted to organize them a bit more like C++'s more rigid structure when possible just to keep myself from getting confused.
Init_category()
/************************************************** **************************
Initializes category object from the ini file.
************************************************** **************************/
// get number of files in category
numfiles = ini_read_real(mysection, "numfiles", 0);
files = ds_list_create();
// create file objects
for(i = 0; i < numfiles; i += 1)
{
// determine type of file
filetype = ini_read_string(mysection, "file" + string(i) + "_type", 0);
tempfileobject = SelectFileObject(filetype);
// set basic info and initialize
tempfileobject.owner = id;
with(tempfileobject)
{
Init_fileobject(other.mysection, other.i);
}
// create and add
ds_list_add(files, tempfileobject);
}
This one basically reads the sections of the INI file for each category, and gets the data back. The number of files, file names and types, etc. The filetype is used to figure out what kind of file object we're creating, with SelectFileObject(), and then once we have a new object we use another with and run Init_fileobject().
SelectFileObject()
/************************************************** **************************
Determines what type of file object to create, creates it and returns it.
Argument 0 is the filetype string.
************************************************** **************************/
switch(argument0)
{
case "exe":
return instance_create(0, 0, file_exe);
case "avi":
return instance_create(0, 0, file_avi);
case "mov":
return instance_create(0, 0, file_mov);
case "wmv":
return instance_create(0, 0, file_wmv);
case "mpg":
return instance_create(0, 0, file_mpg);
case "zip":
return instance_create(0, 0, file_zip);
case "pdf":
return instance_create(0, 0, file_pdf);
case "swf":
return instance_create(0, 0, file_swf);
case "msi":
return instance_create(0, 0, file_msi);
case "mp3":
return instance_create(0, 0, file_mp3);
}
Pretty simple, it's just a switch that creates a different object based on the argument we put in. Why do this? It's a neat trick I learned at school. Instead of having a switch statement elsewhere to decide what to do with a file, we're going to hard-code that decision into the object type itself. Sometimes it's useful, sometimes it's not, it depends on how complicated your program is. I was a bit worried this thing was going to start adding features and changes in the future, so I wanted to try and reduce the potential number of mistakes in advance.
Init_fileobject()
/************************************************** **************************
Initializes a file object from the ini file. Argument 0 is the section in
the ini to read. Argument 1 is the file designator to read the info for.
************************************************** **************************/
// get file name
myname = ini_read_string(argument0, "file" + string(argument1) + "_name", 0);
// get file description
mydesc = ini_read_string(argument0, "file" + string(argument1) + "_desc", 0);
// set path equal to category path
mypath = owner.mypath;
// get screenshot sprite
myicon = sprite_add(mypath + myname + ".bmp", 1, false, false, false, true, 0, 0);
object_set_sprite(id, myicon);
// create execution button, don't forget to move it with us in draw!
mybutton = instance_create(x, y, runbutton);
// set our runbutton's owner to us
mybutton.owner = id;
Basically just sets a lot of properties based on the INI file. Since the file objects contain things like their own description and dynamically grab their screenshot sprites, it makes it pretty easy for the category object to just throw them around in any pattern it desires. The fileobjects don't care what order or layout they're in, they just display themselves in the sized patch they're told to. They also each create a runbutton object, which is what we use to check for left release so that we know when the file should be executed. Notice pretty much every object has an owner variable. I use this a lot in case I need to know what category the file is in, or what file is being run when the button is hit, etc.
Next up I'll go into the objects themselves a bit more.
Chippit
08-08-2007, 01:29 PM
The source file now has a permanent home on the FileCloset:
http://gamedev.openhazel.co.za/filecloset/data/files/133/CoverDVD_final.zip
Cyberninja
08-08-2007, 06:24 PM
@Gldm and Miktar: You guys should go through ALL of that, just to get the dvd to look nice for us? Damn. My hats off to both of you. RESPECT! http://img510.imageshack.us/img510/3223/bowingus5.gif (http://imageshack.us) (I will no longer derail the thread, promise. http://img66.imageshack.us/img66/2112/iconwinkfo3.gif (http://imageshack.us))
Miktar
08-08-2007, 06:32 PM
Well - we did. Now it's just some folders with files in em, and some bmps that scroll to show you want's on the DVD. :P The GM interface worked great for CD, okay for DVD4 but DVD9 was just too many files to enter each by hand.
Cyberninja
08-08-2007, 06:51 PM
Yeah I know. http://img524.imageshack.us/img524/3889/iconwinkjs5.gif (http://imageshack.us) That must have been really hectic to sort that stuff out, back in the day. The dvd is just fine as it is imo. Functionality first, flash later. :P Dammit! I broke my promise...I phail. http://img444.imageshack.us/img444/8582/iconredfaceta2.gif (http://imageshack.us)
Nandrew
08-08-2007, 11:18 PM
Just stumbled here, this lewks awesome. :)
Gonna read it phully nowz ...
Glad people are reading it!
Now let's see, where was I? Oh yes, need to cover the remaining objects.
Category Object
You may notice there are also subclasses of this, namely file_category and then the specific ones like cat_demos which come from there. The reason for this is due to functionality we never added. It would be entirely possible to have say, one layout style for your demos, another for photos/screenshots, another for videos, etc. That way if we wanted to change the presentation we could just override the defaults in subclasses. But we never got around to a photo_category or such and never had to do any manual adjustments on the individual ones.
Anyway, category has 4 events. Create, Step, Left Released, and Draw. The main reason for a step here is to handle the scrolling and up/down arrow system, which took a bit of trial and error to figure out how to stop it from doing silly things like scrolling infinitely or displaying arrows when you can't go anywhere. The event code is here:
Create Event
owner = 0;
mypath = "none";
mypage = "none";
mysection = "none";
numfiles = 0;
files = 0;
drawtint = make_color_rgb(255, 255, 255);
selecttint = make_color_rgb(255, 48, 48);
list_scrolloffset = 0;
list_hoffset = 10;
list_voffset = 5;
list_hspacing = 390;
list_vspacing = 125;
list_columns = 2;
list_scrollmin = 0;
list_scrollmax = 0;
myuparrow = instance_create(782, 39, uparrow);
myuparrow.owner = id;
mydownarrow = instance_create(782, 672, downarrow);
mydownarrow.owner = id;
Fairly simple create method. Note the top variables like mypath and numfiles and such will be set by our init functions that read them from the INI file. The drawtint and selecttint are color values that adjust the color of the sprite. I decided to go with grayscale sprites for the most part and then use tinting so that if we wanted to change the DVD color scheme it wasn't so hard to do. It also lets you do neat things with effects like the motion blur fade thingy by just copying the sprite and adjusting the tint. The various list_ variables control how the list of files is going to be displayed, such as the spacing and number of columns, the size of the elements, etc. Finally we have our arrow objects, which get created and will handle scrolling for us.
Step Event
// draw the category list if we need to
if( owner.page == mypage )
{
// clamp scroll limits to min/max
list_scrollmax = (ceil(ds_list_size(files) / list_columns) * list_vspacing) - room_height;
if( list_scrolloffset >= list_scrollmax )
{
list_scrolloffset = list_scrollmax;
instance_deactivate_object(mydownarrow);
}
else
{
instance_activate_object(mydownarrow);
}
if ( list_scrolloffset <= list_scrollmin )
{
list_scrolloffset = list_scrollmin;
instance_deactivate_object(myuparrow);
}
else
{
instance_activate_object(myuparrow);
}
// draw the list
for(i = 0; i < numfiles; i += 1)
{
tempfile = ds_list_find_value(files, i);
tempfile.x = list_hoffset + (i mod list_columns) * list_hspacing;
tempfile.y = list_voffset + floor((i / list_columns)) * list_vspacing - list_scrolloffset;
tempfile.draw = true;
}
}
else
{
instance_deactivate_object(myuparrow);
instance_deactivate_object(mydownarrow);
for(i = 0; i < numfiles; i += 1)
{
tempfile = ds_list_find_value(files, i);
tempfile.draw = false;
}
}
The step event is a bit complicated, but it's mostly dealing with scrolling the list. It would be kinda silly to limit the number of files to what could fit on one screen or change screens, so we had to handle scrolling. It sounds easy, but when you get things like knowing when to stop involved, it's not as easy. First we calculate the size of the scrolling area (list_scrollmax) based on the number of items in the list and their size. Then we check to see if we're within the range to scroll or not, and this controls whether or not the arrow object is activated. This is how we get the up or down arrow to disappear when you're at the end of the list. Once we know where we are, it's a matter of using those offsets to place the file objects. The nice thing about GM is you don't need to do nuts and bolts graphics work. You just position your object and if it's visible or partially visible it gets drawn, no need to worry about it. The last else case at the end here deactivates the arrows and tells all our fileobjects not to draw. This is basically for when it's not the category's page that is currently active, so it won't mess up the display.
Left Released Event
SetPage(mypage);
A triumph of design! :P This is all the code it takes to change from viewing one category to another or the main menu. Why's it even a function instead of just like owner.page = mypage? Because that lets us do things like animated transitions, which will be covered a bit later.
Draw Event
if( owner.page == mypage )
{
tempx = x;
tempy = y;
x = 0;
y = 0;
depth = 10;
draw_sprite(background, 1, x, y);
draw_sprite_ext(sprite_index, 1, tempx, tempy, 1, 1, 0, selecttint, 1);
}
else
{
depth = 0;
draw_sprite_ext(sprite_index, 1, x, y, 1, 1, 0, drawtint, 1);
}
Here's our draw code. Those who aren't skimming this are probably going "Wait, what? Where's the rest of the drawing?" about now. The thing is most of that work is delegated to the fileobjects. All the category worries about drawing is its sprite and/or background. The rest is handled by fileobjects, which it controls by adjusting their position, and the arrow objects, which do likewise. We could easily modify each category or category group for a different background if we wanted to, but we never implemented that either. Note the background also uses tint so we could skin it to match the sprites easily.
Uparrow and Downarrow
I might as well put these here as they're small and only used by the category object anyway. The uparrow and downarrow are identical except for a +/- sign.
Create Event
owner = 0;
scrollstep = 8;
Owner is obviously going to be set by the category that creates it. Scrollstep should be obvious, it's how many pixels to scroll each time we click the arrow.
Left Button Event
owner.list_scrolloffset -= scrollstep;
This one's from uparrow. Downarrow has a +=, which seems backwards but that's how it works in GM's coordinate system. Note the event is left button, not left released. This means it goes off continuously if you hold the button down, instead of once per click like most of our other objects.
I wasn't sure if I should go into the animations called by SetPage() here or not, but I think it's better to leave them until after fileobject is covered, since they're not really critical and I'll probably want to talk a bit about them and how they work.
Fileobject
Here's the last of the big 3 in the project design. Coverdvd does our global organization, category handles layout and scrolling, and fileobject handles the dirty stuff like screenshots and descriptions. It's not really as complicated as you might think though, the only events are create and draw. Why no step event? Where is the execution code? Runbutton handles that and will also be covered below in this post. :) But first, fileobject!
Create Event
owner = 0;
myname = 0;
mydesc = 0;
myicon = 0;
mydesc = 0;
mypath = 0;
draw = false;
animate = true;
animate_setup = true;
// run button positioning
runbutton_hoffset = 110;
runbutton_voffset = 70;
// text positioning
text_voffset = 0;
text_hoffset = 165;
text_width = 220;
titlecolor = make_color_rgb(237, 79, 42);
textcolor = c_black;
bgcolor = c_white;
Whoops! You can see I messed up at some point and mydesc is in there twice. Doesn't break anything but I probably missed it when I was working on this thing at some point. Things like myname and mypath and mydesc should probably be "null" not 0 for style reasons (you want to indicate they're strings), but I was a bit sloppy here. Animate flags also make a reappearance since fileobjects also animate. The rest of it is things like where the runbutton goes, where the text goes (so if you wanted your text on a different side of the screenshot you'd change it here), and colors.
Draw Event
if( draw == true )
{
if( animate == true )
{
ProgressiveScanFileAnimation();
}
else
{
// update button position
mybutton.x = x + runbutton_hoffset;
mybutton.y = y + runbutton_voffset;
// tell our runbutton to draw
mybutton.draw = true;
// draw screenshot
draw_sprite(myicon, 1, x, y);
// draw the text, it will auto-flow
draw_set_font(font_verdana_bold);
draw_text_ext_color(x + text_hoffset, y + text_voffset, myname, -1, text_width, titlecolor, titlecolor, titlecolor, titlecolor, 1);
draw_set_font(font_verdana);
draw_text_ext_color(x + text_hoffset, y + text_voffset + string_height_ext(myname, -1, text_width), mydesc, -1, text_width, textcolor, textcolor, textcolor, textcolor, 1);
}
}
else
{
// make sure the button doesn't draw either
mybutton.draw = false;
}
Do we draw? Do we animate? Such decisions! Hehe. Note the if/else on the call to ProgressiveScanFileAnimation() means that nothing else is happening if we're calling that function. This means the fileobjects can't run their respective files (because they can't show their runbutton objects) while animating. This is another reason the animation is flagged to run only once, since a long animation effect could make this annoying. Note that the runbutton's coordinates are updated relative to the fileobject's own x and y. This means if the downarrow changes the category's list_offset, it will change the fileobject's x and y, and the fileobject will change the runbutton x and y to stay in sync. Putting it in draw instead of step gets rid of nasty lag errors where the button might lag a step behind in drawing.
Near the end is the code to draw the filename, and its description. The use of # characters in the description files denotes line breaks, which is one thing we still had to do manually. Fortuantely GM handles translating # to new line for us in its internal text functions. Oh and notice draw_text_ext() takes all those color arguments? We could do nifty things with that, but it's yet another casualty to the "never got around to it" feature list. Oh and the last thing is to tell the runbutton to stop drawing if its fileobject doesn't draw.
Runbutton
Runbutton's not a difficult object, but in addition to create, draw, and left released, it has an alarm event. Why? I'll show you right now.
Create Event
draw = false;
reset = true;
Hmm, what's this reset variable for?
Alarm0 Event
reset = true;
Oh, it's what the alarm controls. But why?
Draw Event
if( draw == true )
{
draw_sprite(sprite_index, 1, x, y);
}
Hmm, no it's not there... Why is this done manually? So fileobject can turn it off!
Left Released Event
// the run button was clicked so tell our owning fileobject to execute
if( draw == true && reset == true )
{
with(owner)
{
RunFile();
}
reset = false;
alarm[0] = 30;
}
Oh, we test for it here? Yes, and this is where RunFile() is, so we're gonna actually execute something soon! What's the deal with reset and alarm0 then? Well, how do you interact with an object with a left released event in GM? You click on it. How do you execute a file in Windows? You double-click on it. Guess what happened a lot in the early DVD interface tests? ;) That's right, two videos, two zip files, two demo installers, ugh! So reset is a trap so that we only capture one left click in a second and only execute once in case the runbutton is double-clicked!
Cool, so how do we actually execute files?
RunFile()
/************************************************** **************************
Performs file execution based on the type of file object.
************************************************** **************************/
// execution command is determined by fileobject subclass
switch( object_index )
{
case file_exe:
execute_shell(mypath + myname + ".exe", 0);
break;
case file_avi:
execute_shell(mypath + myname + ".avi", 0);
show_message('NOTE: If movie does not play, you may not have Quicktime or DivX installed. Installations for both can be found under Utilities.');
break;
case file_mov:
execute_shell(mypath + myname + ".mov", 0);
show_message('NOTE: If movie does not play, you may not have Quicktime or DivX installed. Installations for both can be found under Utilities.');
break;
case file_wmv:
execute_shell(mypath + myname + ".wmv", 0);
show_message('NOTE: If movie does not play, you may not have Quicktime or DivX installed. Installations for both can be found under Utilities.');
break;
case file_mpg:
execute_shell(mypath + myname + ".mpg", 0);
show_message('NOTE: If movie does not play, you may not have Quicktime or DivX installed. Installations for both can be found under Utilities.');
break;
case file_zip:
tempfile = file_bin_open(mypath + myname + ".zip", 0);
filesize = string(file_bin_size(tempfile));
file_bin_close(tempfile);
show_message("NOTE: The zip file needs to copy to a temporary directory on C:\. This may take some time. If there is an error check that you have at least " + filesize + " bytes free on C:\. Click OK to begin copying.");
file_copy(mypath + myname + ".zip", temp_directory + myname + ".zip");
execute_shell(temp_directory + myname + ".zip", 0);
break;
case file_pdf:
execute_program("FoxitReader.exe", mypath + myname + ".pdf", 0);
break;
case file_swf:
execute_program("SWFplayer.exe", mypath + myname + ".swf", 0);
break;
case file_msi:
execute_shell(mypath + myname + ".msi", 0);
break;
case file_mp3:
execute_shell(mypath + myname + ".mp3", 0);
break;
}
Now here's a reasonably meaty chunk of code! Note that our switch is based on the type of object, not its contents. So it's really really hard to get the file extension type wrong here, since if we had an unknown filetype, the object would never have even been created! So no decision errors for us, which is one less thing to worry about here. As you can see, most filetypes use execute_shell(), which works reasonably well for most things. The video formats get a little disclaimer that pops up too and says basically if it isn't working now, something's wrong. Usually a lack of codec. I don't think .wmv or .mpg really need it, but it comes from cut and paste most likely. I could probably clean this up with some code reuse by putting all the video filetypes in one group or something, but there aren't enough to really make it worth the effort. If we had 30 formats, I'd probably do that.
Now why is .zip such a pain in the ass? Because Windows puts its TEMP and TMP directories on C:\, and some people don't have a lot of room left on that drive. So a really big zip file will try to write all its temp data there, and can bomb. So what we do is grab the file itself with file_bin_open, read its size with file_bin_size and save it as a string. Then we close it. This lets us use the string in our little warning message box saying if it dies check for X free space on C:\ and gives us an OK so that people can at least read it before the file throws up errors if there's not enough room.
Some of the other filetypes use execute_program instead of execute_shell, because they need compatible reading programs. Particularly .swf and .pdf. One reason we like foxitreader is you can just toss it a filename and it will open it quickly without a big fuss.
So anyway, that's where the magic happens. The rest of the code is mostly fluff like animations, which I'll cover in its own post in a bit.
Nandrew
10-08-2007, 04:49 PM
... finished up to here :D
Tr00jg
12-08-2007, 02:23 PM
Great job! I am really impressed! I remember seeing the GM red-ball on the DVD. Awesome stuff.
Ok, about time I finished this up. The only thing left really is the animations and such. They're not too complicated really. There's a kind of mishmash of functions that I'm just going to group under here.
SetPage()
You probably remember this one from when it changes from one category to another. The code is fairly simple:
/************************************************** **************************
Sets the page of the interface to display. The main point is to allow us
to do any cleanup or setup needed before a page transition. Argument 0 is
the target page to change to.
************************************************** **************************/
newpage = argument0;
// perform any cleanup needed for page transition here
// if you want to have an animation perform each time a page is loaded
// this is a good place to reset the animation flags
switch(newpage)
{
case "main":
break;
case "demos":
break;
}
// page transition function goes here
// use SimplePageChange as default
WhiteFadePageChange(newpage); // this is a special effect transition
What's with the useless switch? Basically it's just a stub. I had once thought of doing a different transition animation for each category, but never implemented it. So that's what would have gone in the switch. If I wanted to really drive people crazy and had a lot of ideas I could even have made it check the page it was coming from as well as going to and have a unique one for each case. Since animations only fire once, it'd probably have driven people crazy trying to figure out how to reproduce each one. :)
Notice the part at the top that talks about doing cleanup and resetting flags. It was never needed though I think I had an animation in mind that would but I never wrote it. That's what that separate animate setup variable was.
Anyway, the only one really made was the WhiteFadePageChange, which I thought looked pretty nifty.
WhiteFadePageChange()
/************************************************** **************************
A special effect page transition function that causes the page to fade to
white and then from white into the new page. It creates a fadetowhite object
which will then create a fadefromwhite object in its own destructor.
Argument 0 is the target page we're changing to.
************************************************** **************************/
// create fade object
myfade = instance_create(0, 0, fadetowhite);
// this controls how long the fade takes on each phase
myfade.steps = 15;
// set variables required by fade object
myfade.owner = id;
myfade.newpage = argument0;
To do the fade, I decided to create a new object that could take over the display and handle doing the effect. That way changing effects just means a new effect object, instead of adding code into category or coverdvd somewhere. Let's look at the object:
fadetowhite object
Create Event
counter = 0;
steps = 0;
owner = 0;
Boring!
Step Event
counter += 1;
if( counter > steps )
{
instance_destroy();
}
Basically a countdown that controls the effect level and when to stop.
Draw Event
depth = -100;
draw_set_alpha(counter/steps)
draw_set_blend_mode(bm_add);
draw_set_color(c_white);
draw_rectangle(0, 0, 1024, 768, 0);
draw_set_blend_mode(bm_normal);
draw_set_alpha(1);
Pretty simple. We draw a white box in bm_add mode over the entire 1024x768 screen. The alpha level depends on the counter vs the number of steps to run, so it fades from zero (clear) to white (opaque). Just don't forget to reset your blend mode and alpha at the end or rrazy things might happen. :)
Destroy Event
SimplePageChange(newpage);
temp = instance_create(0, 0, fadefromwhite);
temp.steps = steps;
temp.owner = owner;
The destroy event here changes the page for us, and creates another object. I split the transition animation in half so I could do other things with it if I wanted to, but never did.
SimplePageChange()
/************************************************** **************************
The default page transition function, it simply sets the page name in
coverdvd. All alternate page transition functions need to do this somewhere.
************************************************** **************************/
// set new page
with( coverdvd )
{
page = other.newpage;
}
Easy enough. You'll note SetPage goes right here if we're not doing animations.
FadeFromWhite Object
Create Event
counter = 0;
steps = 0;
Yawn.
Step Event
counter += 1;
if( counter > steps )
{
instance_destroy();
}
Pretty similar to the last one.
Draw Event
depth = -100;
draw_set_alpha((steps - counter)/steps)
draw_set_blend_mode(bm_add);
draw_set_color(c_white);
draw_rectangle(0, 0, 1024, 768, 0);
draw_set_blend_mode(bm_normal);
draw_set_alpha(1);
Same thing.
MotionBlurVerticalMenuLoad
/************************************************** **************************
A special effect. Slides the categories up one by one with an alpha decaying
motion blur trail.
************************************************** **************************/
// menu positioning
menu_hoffset = 858;
menu_voffset = 50;
menu_hspacing = 0;
menu_vspacing = 50;
menu_width = 0;
menu_height = 600;
trail_offset = 50;
numtrails = 5;
movestep = 10;
mainbutton_x = menu_hoffset;
mainbutton_y = 0;
myexitbutton.x = menu_hoffset;
myexitbutton.y = 700;
// do pre position setup
if( animate_setup = true )
{
for(i = 0; i < ds_list_size(categories); i += 1)
{
tempcategory = ds_list_find_value(categories, i);
tempcategory.x = menu_hoffset;
tempcategory.y = room_height;
}
animate_setup = false;
}
// move categories into position
if( animation_counter < ds_list_size(categories) )
{
tempcategory = ds_list_find_value(categories, animation_counter);
tempcategory.x = menu_hoffset;
// set final target position
targety = menu_voffset + animation_counter * menu_vspacing;
if( tempcategory.y > targety )
{
// draw trails and move up
for( j = 0; j < numtrails; j += 1)
{
draw_sprite_ext(tempcategory.object_index.sprite_i ndex, 1, tempcategory.x, tempcategory.y, 1, 1, 0, tempcategory.drawtint, (j / numtrails ));
tempcategory.y -= movestep;
// break early if we reach the target position during the loop
// otherwise it will overshoot and look silly
if( tempcategory.y <= targety )
break;
}
}
else
{
// once done, increment to the next animation
tempcategory.y = targety;
animation_counter += 1;
}
}
else
{
// once we're done, animation is turned off.
// this must be manually reset by another process if the animation
// needs to happen again. Otherwise it only runs once.
animate = false;
animation_counter = 0;
}
Here's one I was quite proud of. This is what made all the categories zoom up from the bottom in the black DVD interface. There Horizontal version is the one called in this white interface, but it shouldn't be hard to change them. The code just differs by position calculation so I won't post the second one. Note animate_setup is actually used here!
It's commented enough that I think it speaks for itself.
ProgressiveScanFileAnimation
// perform pre-animation setup
if( animate_setup == true )
{
screenblank_top = 0;
textlength = 0;
animate_setup = false;
}
// set animate stop condition
if( screenblank_top >= sprite_get_height(myicon) && textlength >= string_length(mydesc) )
{
animate = false;
}
else
{
// draw screenshot
draw_sprite(myicon, 1, x, y);
// draw occluding rectangle
draw_set_color(bgcolor);
draw_rectangle(x, y + screenblank_top, x + sprite_get_width(myicon), y + sprite_get_height(myicon), 0);
screenblank_top += 4; // controlls screen draw speed
// setup temp string.
// by deleting to the end of the string and incrementing our start
// index each time, we can show a string being progressively typed.
tempstring = string_delete(mydesc, textlength, (string_length(mydesc) - textlength + 1));
// draw the text, it will auto-flow
draw_set_font(font_verdana_bold);
draw_text_ext_color(x + text_hoffset, y + text_voffset, myname, -1, text_width, titlecolor, titlecolor, titlecolor, titlecolor, 1);
draw_set_font(font_verdana);
draw_text_ext_color(x + text_hoffset, y + text_voffset + string_height_ext(myname, -1, text_width), tempstring, -1, text_width, textcolor, textcolor, textcolor, textcolor, 1);
textlength += 4; // controls text type speed
}
Another one I was pretty proud of. This is what the fileobjects run to make the screenshot draw and the text to type itself out. Trying to figure out how to only draw part of the screenshot turned out to be silly. It was easier to just draw a black rectangle over the part that I didn't want displayed yet, and make it smaller each step. Likewise, the string just takes the description and chops off the end of it. How much gets cut off reduces each step, until the whole thing is drawn.
I thought it looked nifty.
I think that covers the animations for the most part.
So that pretty much wraps up the CoverDVD. Questions? Comments? Rotten produce?
Nandrew
16-08-2007, 03:58 PM
I do have these tomatoes that have been sitting in the back of my fridge for a while. Will those do?
I'm not sure if I've mentioned this already, but I really did learn about some cool file IO and INI stuff. Some parts I tended to glance over ... y'know, any of the more "general knowledge" bits.
Thanks for all the stuffs, GLDM. Eye saloot u!
Powered by vBulletin® Version 4.2.4 Copyright © 2019 vBulletin Solutions, Inc. All rights reserved.