EDIT: I've been too lazy to update this post, so it's gotten rather out-of-date. Please check the latest posts for up-to-date information.
Okay, so it seems that a few people think I should make a "better version of Danmakufu".
Stuffman is apparently even volunteering to pay me
"... TWENTY BUCKS maybe!" (:V), so I figured I'd sit down and start to think out how it would be done.
Please note:As of right now I have not started any actual work on this. Like I said before, I do want to go ahead with this, but I need to make sure I can devote time to it - I wouldn't want to leave you guys with a half-finished program
.
First Item: Things that could be improved from Danmakufu:
- No power items
- No easy way to modify the game frame (the graphics outside the play area)
- (IMO) Tasks
- Program freezes when a script infinite-loops
- Inability to specify what layer when drawing things
- Difficulty of putting together a stage or full game
- RGB(0,0,0) as transparency causes usability issues
- No easy way to do good looping music
- No easy way to enumerate enemies
- Pause music when game pauses; separate music on gameover/continue screen, resume music on continue
- Make player scripting easier (looking for specifics, if possible)
- Store dialog (and other?) text in a separate file, to facilitate making translations
- No way to change playfield size (low priority issue IMO)
- Compiled format to distribute scripts in (low priority issue IMO)
Feel free to contribute your thoughts to this list - it is one of the main driving lists for the feature set of the proposed new program.
Second Item: Structure:
Unlike Danmakufu, I would want to make this a purely event-oriented setup. Here's how it would work:
1) We have several
object types: player, enemy, enemy bullet, etc.
2) Each
object type has several events, with script code for each of them. These include:
- Initialize - script run once when an object of this object type is created
- Tick - script run once each frame for each object of this object type that is active (much like Danmakufu's @MainLoop, when you're not using tasks)
- Draw - script run whenever we need to draw each object of this object type
- Finalize - script run once, when an object of this object type is being deleted
- Collide - script run whenever an object of this object type collides with another object.
- We can specify different Collide functions for colliding with different object types
It should be noted that ...
- Several of these can be pre-made (for instance, player collides with enemy shot = player dead X_X) and even hard-coded into the program itself.
- You can create bullets with custom behavior (like Danmakufu's CreateShotA or Object shots) simply by defining a new bullet object type and giving it event scripts to behave as you want it to.
3) With the above setup, each object we create will have an
object type assigned to it, and thus will have its behavior scripted by those events.
(do you hate the color
yellow yet?
)
Third Item: Code:
Overall, I like the C-style syntax that Danmakufu has, so the target code structure would probably be similar to that in many ways. Suggestions for specifics are welcome at this point, since I don't have any finalized vision of how it'll be yet.
One idea I have is to contain the script for each
object type in its own file. This allows a good segmentation of functionality, although with a complicated script it may start to get messy with the number of files.
EDIT: The general consensus seems to be that being able to group together
object types in the same file is better. So that's how I'll make things.
Internally, the script will be converted into a binary code (or something similar) when the file is read in, to simplify execution. I'm basically picturing this internal code to be much like machine/assembler code - each command is a single action, simple and straightforward. In other words, if you had a formula "
a = (b + 7) * 3 + c", it would translate to something like:
- calculate b + 7, and store it in temp1
- calculate temp1 * 3, and store it in temp2
- calculate temp2 + c, and store it in a
(Where
temp1 and
temp2 are internal temporary variables to store intermediate results)
The long-term goal is that script-writers won't have to even know about the internal code, although I may allow access to it in scripts anyways ...
Also, related to the code is what data-types the code will support. I think we can get away with the following:
- Number
- String
- Array
- Boolean (true/false)
Fourth Item: Implementation:
As I mentioned previously, this would be implemented using
SDL.NET and
OpenGL in
C#. The main advantage of this is that it should be entirely
cross-platform portable (without even recompiling!) using
Mono.
To note:
As part of the imlpementation of the script parser, I plan on adding a feature to kill infinite loops. The idea is that, if any single event script executes over, say, a million commands, it's up to no good and we need to kill things. Exact limit is TBD, of course.
Fifth Item: Program's Name:
I'm thinking 無数の弾幕 ("Musuu no Danmaku" - "Countless Barrages") ... any objections/recommendations?
Sixth Item: An Idea for Bullet Graphics:
Know how right now we have a fixed set of bullet colors in Danmakufu? And how you need a new image file if you want a different color? I've got an idea that can simplify this a bit ...
First - by my observation, the (vast majorify of) bullets in Touhou and Danmakufu consist of the bullet's color, white, and shades between these two. So, my plan is to color-code the image file:
- Use the
green channel to indicate how strongly the bullet's color will be used at that pixel
- Use the
red channel to indicate how strongly the white color will be used at that pixel
- Perhaps use the
blue channel for a second color (do we have any two-color bullets?)
This way, we can use one bullet image and create any color of bullet with that shape! Of course, to aid with easy operation, values for "regular" colors, such as red, orange, etc., will be included in the program.
Of course, there should still be a way to draw a image file normally for a bullet.
I could also write a quick program to take a normal bullet graphic, along with it's color, and generate a "generic" color-coded graphic like I describe above from it.
Seventh Item: Misc. Notes:
It is important to realize that this program will
not be script-compatible with Danmakufu. IMO, if we try to keep such compatibility, it'll stifle the ability for this program to rise above Danmakufu's limitations and quirks.
Though, it would certainly be possible to create a "script converter" that would be able to, at least in the case of simpler scripts, automatically convert them to this program's script language. But I doubt it would be able to do anything that gets too complex.
It could be possible to implement a Danmakufu script interpreter within this program. This would probably have to be implemented as an entire separate branch of execution from the execution engine described above, though, and I doubt it'd be top priority ...
Eighth Item: A Sample of How a Script Could Look:
Here's a
sample of how a simple boss script could look - this script would just fire a bullet at the player every 1/2 second. Please note that, as mentioned above, I don't have the syntax finalized in any way, so this is all speculative.
enemy_boss_object_type "Stupid_Boss"
{
// typical counter variable
count = 60;
// holds the image file name for easier reference
graphic = "teh_boss.png"
Initialize
{
// Initialize necessary stuff
SetLife(100);
MoveTo(GetCenterX(), 100);
SetCollisionSize(24);
LoadGraphic(graphic);
}
Tick
{
count = count - 1;
if (count <= 0)
{
count = 30;
// Fire a blue shot at the player every 1/2 second
FireShot(GetX(), GetY(), GetAngleToPlayer(), 2.0, Shot_Type_1, Blue);
}
}
Draw
{
// Draw the boss
DrawGraphic(graphic, GetX(), GetY());
}
Finalize
{
// Create some point items for the player at the end
i = 0;
while (i < 10)
{
CreateItem(Point_Item, GetX() + rand(-50, 50), GetY() + rand(-20, 20));
i = i + 1;
}
}
}
And here's a speculative custom bullet script, which flies forward for a second, then stops and turns towards the player, then starts moving again:
enemy_shot_object_type "Silly_Shot"
{
// Typical counter variable
count = 60;
// set to true once the bullet has stopped, so we won't keep stopping and restarting
let already_stopped = false;
Tick
{
count = count - 1;
if (count <= 0)
{
if (!already_stopped)
{
spd = GetSpeed();
if (spd > 0.1)
{
// We're still decelerating
SetSpeed(spd - 0.1);
}
else
{
// We've stopped.
SetSpeed(0);
already_stopped = true;
}
}
else if (GetSpeed() == 0)
{
// We've stopped, and haven't restarted yet. Turn towards the player.
ang_diff = GetAngleToPlayer() - GetAngle();
if (abs(ang_diff) < 0.1)
{
SetAngle(GetAngleToPlayer());
SetSpeed(2.5);
}
else if (ang_diff < 0)
{
SetAngle(GetAngle() + 0.1);
}
else
{
SetAngle(GetAngle() - 0.1);
}
}
}
}
}
Once again, please don't put too much value on the exact particulars of the scripts. This is mainly meant to demonstrate what I mean by this whole "event-oriented" business.
The enemy boss script defines all of the necessary functions for our boss - in this case, it lines up exactly with Danmakufu's
@Initialize,
@MainLoop,
@DrawLoop, and
@Finalize. There is no need for any
Colliside handlers because the game already knows what to do when the player or a player shot hits the boss, and we're not defining any other interactions.
The bullet script only has a
Tick handler - there is no need for
Initialize or
Finalize handlers, and it is using a default
Draw handler that just draws the bullet's assigned graphic at the appropriate angle and position. And, just like the boss script, there aren't any
Collide definitions because we're only using the default collision behavior of the bullet (that is, kill the player on contact).
Ninth Item: Game Script:
One idea brought up is that, in Danmakufu , it is a bit of a pain to create a full game, especially things like menus and such. So, my idea is to set up a "game script" file, which allows the scripter an easy way to define an entire game.
Here's an example of what I'm thinking of:
game "t3h hardest danmakuu evar lulz"
{
// allow selection of difficulty for the main game
Main_Game_Difficulty_Select(true);
// define images for the difficulty selections
Main_Game_Difficulty_Images("grade_school.png", "normal.png", "hard.png", "wtf.png");
// enable the extra game
Extra_Game(true);
// allow selection of difficulty for the extra game
Extra_Game_Difficulty_Select(true);
// define difficulties for the extra game, in increasing order
Extra_Game_Difficulty_List(["Extra", "Phantasm", "lulz", "OMEGA"]);
// define images for the difficulty selections in the extra game
Extra_Game_Difficulty_Images("extra.png", "yukari.png", "⑨.png", "Ω.png");
// enable the music room
Music_Room(true);
// list of music for the music room
music_files_list = ["01.ogg", "02.ogg", "03.ogg", "04.ogg", "etc.ogg"];
music_names_list = ["Menu - Stuff Go Boom", "Stage 1 - Your Face", "Stage 2 - WTF Is This Shit?!",
"Final Stage - ZOMGWTFBBQ", "Extra Stage - lulz", "Credits - You'll Never Hear This"];
Music_Room_Info(music_files_list, music_names_list);
// disable spellcard practice. Only n00bs use it anyways :V (Cheese: wait what?)
Spellcard_Practice(false);
// define the stages for the main game
main_game_scripts = ["stage1_script.txt", "stage2_script.txt", "stage3_script.txt"];
Main_Game_Scripts(main_game_scripts);
// define the stages for the extra game
Extra_Game_Scripts(["lulz.txt"]);
// use EoSD-style power-up mechanics
Power_Style(EOSD_Type);
// define the in-between stages results screen image
Mid_Game_Results_Screen("point_totals.png");
// define the main game post-game script
Main_Game_Ending("its_ovar.png");
// define menu background image
Menu_Background("lulz_title.png");
// define menu option images
Menu_Item_Images("new_game.png", "extra_game.png", "", // blank string for spellcard practice, since its disabled
"music.png", "highscores.png", "stage practice", "replay.png", "quit.png");
}
The idea with this is that the program will use this script to load the necessary resources for the menu, and then run the menu using its own built-in code, up until the point where it starts the actual game. This can yield advantages, including:
- Automatic handling of spellcard practices and high score records.
- Replays will start where the actual game starts, not at the menu (plus, with the above structure, we could have replays selectable from the actual game's title screen, even).
- With the proposed structure above, it'll automatically handle stage transitions with a results screen.
- Defines game-wide behavior, such as how power-up items work (options include "No power up items" and "EOSD-Style" ... other suggestions? How about a "Gradius Style" (hit once and you're back to zero )?)
- Of course, easier creation of menus for larger scripts.
Update:A better idea came up - create a separate "menu script" language which defines each menu the game needs.
Each menu can has a set of options, including images for selected/not selected, enabled/disabled, etc. Each option can lead to either another menu or start up an actual game.
Like I said above, I need to make sure I can devote some time into this before I start. In the meantime, please comment on what I've spec'd out, make suggestions, etc.
Once again, on an unrelated note, I would like to ask that, if anybody has musical talent they'd like to lend to what might be my last remaining Danmakufu epic, I'd appreciate it.
Holy crap longpost is long.