bullet scripting

A place for people with an interest in developing new shmups.
Post Reply
yorgje
Posts: 67
Joined: Fri Nov 08, 2013 3:21 am
Location: Gensokyo
Contact:

bullet scripting

Post by yorgje »

Hello developers,
Is there a canonical way to script bullets and enemy spawns and just stage portion things in general? I'm working on a shmup using C and allegro5, if that's relevant.
The approach I'm currently considering is parametrically defining the path that each object should take, tied to the frame that it should enter the screen. I would write the bullet script such that all of the objects were dumped into a linked list or something, i'd sort the list by entry frame before the game starts, and then the main loop would just read across these. I'm curious to hear what other approaches have been actually used in practice though.
User avatar
n0rtygames
Posts: 1001
Joined: Thu Mar 15, 2012 11:46 pm
Contact:

Re: bullet scripting

Post by n0rtygames »

I think with the exception of BrooksBishop (who wrote Aeternum on XBLIG) most of us have been too lazy and just hard coded most of our stuff.

I'm sort of half way between - being able to specify behaviours each of which can have a bunch of instructions which might look like

NudgeSpeed(-1)
Repeat(0,5)
SpawnTargetted(0)
FadeSpeedToRankDefault(1)

This would result in a bullet that slows down then fires off a targetted projectile at default rank speed and then accelerates back to the rank based speed itself. So I could fire an arc of these and just to fuck with the player a little

However honestly unless you're going for some Ketsui organic stuff or mind bending Touhou patterns, just firing bullets and offsetting them at certain angles will do you just fine.

Don't overcomplicate it and treat is as an iterative process.


As for enemies - I just built my own editor and enemies activate when entering the screen with some logic to ensure that only the relevant ones that are likely to activate even get an update. Certain enemies with specific behaviours in mind can be placed - i.e tanks that go in a particular direction, or popcorns that I want to spawn from the edge of the screen and fly sideways etc.
facebook: Facebook
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: bullet scripting

Post by mystran »

Generally.. you want a different strategy for bullets (and score items, particles, etc) and enemies.

For the former specifically, you typically have a lot of them, so you probably want to manage them in a fixed array of state data. Basically, when an enemy decides to shoot, you probably want to call something like fireBullet(type, srcPos, velocity, ...) or some pattern helper that calls the same thing multiple times, and have it automatically pick the next free bullet from the array, set the state, and then have the main loop "updateAllBullets()" method take care of it.

To make allocation easy, you can keep the array compacted (and in order they were spawned for consistent draw order) and just keep an index to the next free. To "garbage collect" you then need a special "dead" flag (or just a special bullet type works) which you set to whatever is no longer relevant, then once per frame scan through the whole thing, keep index where the last active bullet was, keep another index for the scan, skip past dead ones (with the "next to scan" index), and move active ones after the previous active one (and bump the last active) pointer. If your bullet state is very large, you can use indirection indexes and freelist (to avoid copying all the state data around), but .. in practice might not be worth it. After the scan is done, the "last active" becomes the new allocation pointer. This method makes it very simple to manage basically as many bullets as you can draw (which typically becomes the first bottleneck if you're collision tests are simple).

For enemies there's a bit more possibilities. You could either have a script that spawns them as certain points, or you could place them in advance to certain points in the stage and have them activate on scrolling positions or whatever. Depends on what kind of gameplay or work-flow you're going for really (scripts are easier to speed-up/slow-down either during development or dynamically on the fly, while ground enemies and such are far easier to place on map using some editor). If you have a lot of enemies placed to a map, you could also sort their activation times at stage load and just keep an array of pointers to active ones or something as an optimization if you want to keep the number of actually active ones down.

In any case, for each enemy type, you probably want specific code that knows what this particular enemy wants to do, which you call for each enemy once per frame to let them "think" .. and then that method can decide to shoot or whatever. In plain C, you can either use a switch(enemyType) to dispatch them, or you can use function pointers, but you probably want some "object oriented" thinking here. Some of them might want to use paths, some might want to move based on player position, etc..

Also, for enemies it's not necessarily a catastrophe if you just allocate them with malloc()/free() or equivalent on the fly. Those can theoretically take arbitrary time though, so don't be surprised if you get some random hickups (which is why you really REALLY don't want to use them for bullets or other things that get spawned/destroyed all the time). If you're placing them to a map in advance then just allocate them at load time, if you're using a script you could just allocate some maximum number of enemies.

But in general, you probably want to place enemies on stages (one way or another), and then have the behavior code (the update method) for each enemy handle stuff like moving and shooting bullets (or patterns, whatever).

PS bonus tip: have your bullet state contain a special "delay" flag that counts down how many frames until the bullet actually becomes active (if delay is nonzero, update just decrements it and draw is skipped) then you can fire patterns at once and have them appear over time and without having to worry about the patterns distorting if the enemy moves or whatever.. :)
rfeese
Posts: 30
Joined: Fri Nov 15, 2013 4:56 pm

Re: bullet scripting

Post by rfeese »

yorgje wrote:I would write the bullet script such that all of the objects were dumped into a linked list or something, i'd sort the list by entry frame before the game starts, and then the main loop would just read across these. I'm curious to hear what other approaches have been actually used in practice though.
I think this is actually similar to what I am doing. I have a linked list of "events" which is sorted by millisec timestamp. One type of "event" is "object spawn at a location with a path". The actual bullet firing is based on positions along paths or object animations triggering pre-defined per-object bullet patterns/sequences.

As I have tried to make things de-coupled from frame rate, this means at each frame, I need to check how much time has passed and if we have passed any event timestamps, how far beyond each triggered event we have gone. Then, the objects are spawned/initialized and positions are updated accordingly based on what they would have done in that time. I allocate/deallocate objects in an "active objects" linked list that is z-sorted for rendering.

Performance of this seems fine so far and I am actually able to process many event/update cycles per rendering frame on hardware that is a few years old. I'm also using C but with OpenGL and SDL.
yorgje
Posts: 67
Joined: Fri Nov 08, 2013 3:21 am
Location: Gensokyo
Contact:

Re: bullet scripting

Post by yorgje »

Alrighty, thanks for the advice everyone, I think I have a better sense of how I want to do this now.

mystran and n0rtygames both made reference to using editors to place enemies and stuff. This sounds terribly complicated to write? I'll probably stick with a scripting approach myself, but I'd be curious to see a little insight into how people do that.

rfeese: are you planning to include a replay function, and how would that work if everything is decoupled from frame rate?
User avatar
n0rtygames
Posts: 1001
Joined: Thu Mar 15, 2012 11:46 pm
Contact:

Re: bullet scripting

Post by n0rtygames »

An editor is not complicated to write. Just a little more time. Well worth your time to do this! It's just like writing a game except you have a mouse pointer with a hitbox and you click on things ;-)

All my editor does really - is place down icons in world space and I move the camera around using WSAD. When I hit the save button - a file is created that looks like this:-

Code: Select all

InsertEnemy(0, 222.2611, 612)
InsertEnemy(1, -149.8411, 2566)
InsertEnemy(1, 200.1589, 2416)
InsertEnemy(1, 40.15887, 2346)
InsertEnemy(1, -89.84113, 2276)
InsertEnemy(1, -209.8411, 2176)
InsertEnemy(3, -209.8411, 1976)
InsertEnemy(2, -159.8411, 1406)
InsertEnemy(2, -249.8411, 1306)
InsertEnemy(2, -179.8411, 1196)
InsertEnemy(2, -239.8411, 1096)
InsertEnemy(3, 210.1589, 1406)
InsertEnemy(1, 60.15887, 1186)
InsertEnemy(1, -49.84113, 1186)
InsertEnemy(1, 0.1588745, 1116)
InsertEnemy(1, 60.15887, 1066)
InsertEnemy(1, -59.84113, 1066)
InsertEnemy(1, 0.1588745, 1006)
InsertEnemy(1, 60.15887, 956)
InsertEnemy(1, -69.84113, 956)
InsertEnemy(1, -9.841125, 906)
InsertEnemy(1, 50.15887, 856)
InsertEnemy(1, -79.84113, 856)
InsertEnemy(1, 190.1589, 1236)
InsertEnemy(1, 130.1589, 1176)
InsertEnemy(1, 190.1589, 1126)
Then I just search and replace ")" in to ");" and copy paste this in to my codebase. On top of that I have a hotkey to drop the game out of editor mode back in to test mode (T) which just reads direct from the instruction list in memory instead of on disk. If I wanna edit again - I just restart the game in edit mode, hit F8 to load from text file and adjust accordingly.
facebook: Facebook
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: bullet scripting

Post by mystran »

Well, my old project actually doesn't use an editor (which made spline-pathing rather annoying), next project probably will. Both approaches have their advantages though, which one works better probably depends on what your game design is like. Scripts (especially relatively timed "wait this many frames" scripts) make it easy to adjust timings and rearrange sections, while editors make it far easier to see the big-picture and synchronize different things and such.
n0rtygames wrote: Then I just search and replace ")" in to ");" and copy paste this in to my codebase.
Any particular reason you don't just make your editor to add the ';' automatically? If it takes you 3 seconds to do the search/replace-all, and 5 minutes to adjust the printing/parsing to deal with the change, you are going to be saving time after about 100 iterations. :)
User avatar
n0rtygames
Posts: 1001
Joined: Thu Mar 15, 2012 11:46 pm
Contact:

Re: bullet scripting

Post by n0rtygames »

mystran wrote:Any particular reason you don't just make your editor to add the ';' automatically? If it takes you 3 seconds to do the search/replace-all, and 5 minutes to adjust the printing/parsing to deal with the change, you are going to be saving time after about 100 iterations. :)
Shut up :p

I have often asked myself this same thing and just not bothered
facebook: Facebook
rfeese
Posts: 30
Joined: Fri Nov 15, 2013 4:56 pm

Re: bullet scripting

Post by rfeese »

yorgje wrote:rfeese: are you planning to include a replay function, and how would that work if everything is decoupled from frame rate?
Yes, I have a semi-working replay function, mostly planned for an attract mode. It seems to work fairly well except for a few edge cases right now. To make it work, I break time up between screen refreshes into set bite size which gives me consistent handling of events and collisions. There is a really good article on "fix your timestep" that I think I saw referenced somewhere on this site...Here's the link:

http://gafferongames.com/game-physics/f ... -timestep/
User avatar
laxa88
Posts: 51
Joined: Mon Mar 10, 2014 10:32 am

Re: bullet scripting

Post by laxa88 »

This is how I did my stuff for now -- I'm hoping it helps; and if there's any way to improve it, do tell!

(Oh, this was done in Unity, so it may not apply to you)
1) Use the editor to position prefab instances of enemies.
2) Each enemy has a MoveScript and ShootScript component.
3) I edit each positioned enemy by giving them a move and shoot file path. (which loads the respective data when they spawn).

For shoot pattern data, I use bulletML, so that's one thing less I worry about. They will execute automatically whenever the enemy spawns.

For move pattern data, I use a text file to read as well, like this:

Code: Select all

# Currently I only use "angle" move_type, but there's "position" which I use for boss fights
# move_type,angle,duration,movespeed,direction_type,direction_ease

# move in
angle,180,0.5,100,absolute,linear
# curve
angle,15,0.4,100,relative,linear
angle,15,0.4,100,relative,linear
angle,15,0.4,100,relative,linear
angle,15,0.4,100,relative,linear
angle,15,0.4,100,relative,linear
angle,-90,2,100,absolute,linear
angle,-15,0.4,100,relative,linear
angle,-15,0.4,100,relative,linear
angle,-15,0.4,100,relative,linear
angle,-15,0.4,100,relative,linear
angle,-15,0.4,100,relative,linear
angle,0,1,100,relative,linear


Although the above is what I've done so far, I plan to change it to the following:

1) Don't use the editor to place the enemy instances.
2) Use a text file to read the enemy spawn data, like this:

Code: Select all

# enemy_id, milliseconds_to_appear, x, y
1, 2000, 280, 500
1, 2500, 150, 820
1, 2500, 180, 820
1, 2500, 220, 820
3) The enemies are generated when the level is loaded (rather than instanced at the moment they are supposed to appear, because it's a slower process).
4) The enemies are disabled by default. When the milliseconds_to_appear is reached, they're activated.
5) The enemies id will determine which move and shoot patterns they will execute when they spawn.
Doodle/development tumblr : http://wyleong.tumblr.com
Art (rarely updated) : http://animenifestor.deviantart.com
User avatar
BrooksBishop
Posts: 63
Joined: Sun Aug 05, 2012 4:39 am
Location: San Diego, CA
Contact:

Re: bullet scripting

Post by BrooksBishop »

Aeternum's bullet scripting system is probably the one thing I put the most work into in the entire game.

The scripts themselves are XML, based heavily on Kenta Cho's BulletML, but highly refined and adapted to my own needs.
Here's an example, the bullet of the popcorn cats of stage 1:

Code: Select all

<?xml version="1.0" encoding="utf-8" ?>
<XnaContent xmlns:Escapement="WastedBrilliance.Escapement.Ballistics"
            xmlns:Action="WastedBrilliance.Escapement.Ballistics.BulletAction">
  <Asset Type="Escapement:BulletDefinition">
    <Name>MinionCat01Primer</Name>
    <Owner>Enemy</Owner>
    <Radius>0.16</Radius>
    <Color>FFFF22CC</Color>
    <Index>1</Index>
    <Direction>Aim,0</Direction>
    <Speed>Absolute,6+Rank*2</Speed>
    <Actions>
      <Item Type="Action:PlaySound">
        <Cue>Bullet01</Cue>
      </Item>
      <Item Type="Action:Repeat">
        <Count>rank*3</Count>
        <Actions>
          <Item Type="Action:Fire">
            <Name>MinionCat01</Name>
            <Owner>Enemy</Owner>
            <Radius>0.16</Radius>
            <Color>FFFF22CC</Color>
            <Index>1</Index>
            <Direction>Relative,0</Direction>
            <Speed>Sequence,0.4+rank*0.2</Speed>
          </Item>
        </Actions>
      </Item>
    </Actions>
  </Asset>
</XnaContent>
This XML maps directly to a "BulletDefinition" class, and gets pre-compiled by XNA's awesome Content Pipeline system.

The bullet objects are a pre-allocated pool of pure state data (position, direction, speed, age, color, etc.). When a bullet is "fired" it stores a pointer to a BulletDefinition, and iterates through the definitions actions, applying whatever those actions say to do to itself. The bullets all have a tiny instruction pointer and stack to accommodate this.

Also important to making this really easy on myself, was that I wrote an entire Expression parsing system. For example, you can see the initial speed is "6+Rank*2". This gets parsed at compile time into a stack of token objects. Then at run time that stack can be evaluated, including all variables and function calls (sin, floor, sqrt, etc.). First of all, this makes it really easy for me to write bullet behavior in terms of mathematical functions. But it also lets me add a lot of dynamics to the bullets. Like the "Rank" variable which is used in Aeternum as the difficulty slider, going from 0.0 on Easy to 1.0 on Lunatic. This way I could actually quantify the difficulty curve and easily adjust bullet effects if they weren't at the difficulty I wanted.

All bullets themselves inherit from the same common "BulletSource" interface as characters, which you can see in the example with the "Fire" action nested inside a "Repeat" action, which is what lets bullets fire bullets, and is the basis for much of the advanced scripting of my bullet patterns.


When I got around to doing levels, I ended up doing things a little less robust though, lol. My stages are all subclasses of a Stage base class, which is a common interface to a coroutine engine.
Coroutines are really great for doing scripting in a game, and C#'s native inclusion of them made it really easy. So rather than "position based" or "time/frame based" enemy spawns like I think a lot of games do, Aeternum's enemy spawns are all based on relative timing, with "WaitForSeconds" coroutine calls in between. So basically when it runs a stage script it spawns enemies along with instructions on how to move and when to fire bullets and sends them off on their own coroutine to execute on that. The stage then waits an exact amount of time before spawning the next wave of enemies, and on and on. The benefit for me in this method is that it's a lot easier to adjust stages for timing and flow by inserting or removing waves of enemies which in turn automatically moves up or pushes back everything that comes after.

Bosses use this same system, but since it's all just C# code, they're written to include while loops that last until their phases are over by either timing out or running out of health.


I definitely would say there is no canonical way of doing bullets. The description of writing parametric equations is something I also considered, and can work quite well for specific types of patterns. I just ended up going with an "imperative program" style of script, since I'm generally more comfortable with that.
Aeternum a bullet hell on PC and Xbox Live Indie Games - @BrooksBishop - Aeternum OST
User avatar
Formless God
Posts: 671
Joined: Fri Mar 12, 2010 7:46 am

Re: bullet scripting

Post by Formless God »

Are those coroutines accomplished via enumerators and yield return?
RegalSin wrote:Then again sex is no diffrent then sticking a stick down some hole to make a female womenly or girl scream or make noise.
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: bullet scripting

Post by mystran »

I've been working on a somewhat similar bullet scripting thing to what Brooksbishop describes, ATM it's implemented as C++ templates, but closures or regular objects would work as well. The whole thing is a bit work in process, but initial experiments suggests that it's quite powerful so maybe worth sharing:

The basic idea is, you have a basic emit(type, src, dir, speed, ... ) implemented by type Emit and then "pattern composers" that each take some design parameters and then one or more sub-patterns. So you could do (please excuse the ugly syntax):
Circle<15, OffsetDir<64, AimPlayer< Speed< 3, Type< FooBullet, Arc< 5, 20, Emit > > > > > >
to create a "circle" of 15 directions, offset each one 64 units forward, then aim each one at player, set the speeds to 3, types to FooBullet, and create an Arc of 5 (out of circle division of 20) bullets from each one (ie middle one for odd-number keeps the direction, rest go rotated around).

Then there's similar composers (some with actual state) for layering/looping/repeated whip/spear type things, over-time rotation, etc (so eg throw Loop<30, 60, pattern> around something to have it emit every 60 frames with initial delay of 30 frames). Internally, each type just has a single emit() method which does it's thing and calls the sub-patterns' emits one (or zero in case of time-delays) or more time with adjusted parameters, and eventually the actual bullets get dumped to the active-bullets pool. So it's sort of "take every bullet up to this point, and then do this thing with all of them" type of thing.

Then actually using the pattern is just a matter of calling the top-level emit() once per frame from the enemy update code (with enemy position, player position, etc set in a state structure so they are available for composers).
User avatar
BrooksBishop
Posts: 63
Joined: Sun Aug 05, 2012 4:39 am
Location: San Diego, CA
Contact:

Re: bullet scripting

Post by BrooksBishop »

Formless God wrote:Are those coroutines accomplished via enumerators and yield return?
Yep. So I took things that would need to pause a script, like waiting for time to elapse, waiting for an enemy to path from one point to another, waiting for player button presses (for the tutorial) and wrapped them up into classes that defined a condition of completion. So whenever the script needed to wait for something, it would yield return one of those classes, and the script manager would handle checking their statuses and determining when to continue executing the script.

Here's a phase from stage 1 of Aeternum from the script

Code: Select all

yield return Wait[5f];

#region ### WAVE FOUR - one big cat, center ###
{
    Enemy m = Session.AddEnemy<MajinNekoWhite>(bigCatHealth, new WaypointPath(
        Ease.CubicInOut,
        new Vector2(14f, 0f),
        new Vector2(10f, 0f),
        new Vector2(14f, 0f)
    ));

    Session.ScriptManager.Execute(new Behavior1(m, 0f, 4f, 6f, 0f, minionCat03));
}
#endregion

yield return Wait[5f];
So you can see it yields to a Wait object for 5 seconds, then creates a new cat enemy which it spins off into its own script in the script manager, then goes back to waiting. The Behavior1 script in this case defines how fast to move, and what bullet to fire.

I based my script engine on Nick Gravelyn's "The Magic of Yield" blog post, which has unfortunately been taken down. This description is fairly close: C# Coroutines at Holy Donut Games
Aeternum a bullet hell on PC and Xbox Live Indie Games - @BrooksBishop - Aeternum OST
User avatar
Formless God
Posts: 671
Joined: Fri Mar 12, 2010 7:46 am

Re: bullet scripting

Post by Formless God »

Ah, I see. You also mentioned sending enemies "off to their own coroutine". Is this like calling GetEnumerator() every time an enemy is created and how do you deal with the generated garbage?
RegalSin wrote:Then again sex is no diffrent then sticking a stick down some hole to make a female womenly or girl scream or make noise.
User avatar
BrooksBishop
Posts: 63
Joined: Sun Aug 05, 2012 4:39 am
Location: San Diego, CA
Contact:

Re: bullet scripting

Post by BrooksBishop »

Formless God wrote:Is this like calling GetEnumerator() every time an enemy is created and how do you deal with the generated garbage?
It's more like externally storing a reference to the script's current Enumerator (in my case scripts yield IEnumerator<IInstruction>), and then calling MoveNext() on it in a controlled manner, and checking whether it's finished or not.

As far as garbage, I wrote about this more in depth on my blog a while ago: Lessons in XNA From the Trenches and Memory Usage in Aeternum.
But basically, the core engine (the bullet engine, the particle effects, the drawing systems, etc.) are all heavily engineered to generate no garbage whatsoever. It's not too hard, in my opinion. You just have to actually have a grasp of the differences between reference types and value types, and know how to properly utilize objects of each type, like when to pool, when you can safely "new" etc.

Also PROFILE, PROFILE, PROFILE. I can't stress it enough. Learn to profile your application to see what you're doing with memory when and where, and use that information to track down any problems you may be having.

So, since the engine itself was garbage free, I just kinda let go on the scripts. The overhead of using yield and coroutines in my case was miniscule.
I just forced a garbage collection at the end of the allocation step of every stage, after all the image, bullet, and dialogue assets had been loaded. Between the already black screen stage transitions and the other loading being done, you don't really notice it. So any garbage the scripts threw out over the course of the stage never got to the point where it required a collection during the stage, and thus was never a problem.

Here's the Run method of my "Command" object which is the heart of my script system. The Run method just gets called every frame on each active script.

Code: Select all

public interface IInstruction
{
    bool IsComplete { get; }
    void Update(float dt);
    void OnComplete();
}

IEnumerator<IInstruction> scriptEnumerator = null;

public bool IsComplete { get; set; }

internal void Run(float dt)
{
    if (IsComplete) return;

    if (scriptEnumerator == null)
    {
        scriptEnumerator = script();
    }

    if (scriptEnumerator.Current == null || scriptEnumerator.Current.IsComplete)
    {
        do
        {
            IsComplete = !scriptEnumerator.MoveNext();
        } while (!IsComplete && scriptEnumerator.Current != null && scriptEnumerator.Current.IsComplete);
    }
    else
    {
        scriptEnumerator.Current.Update(dt);
    }
}
Aeternum a bullet hell on PC and Xbox Live Indie Games - @BrooksBishop - Aeternum OST
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: bullet scripting

Post by mystran »

For those not familiar with .NET/CLR garbage collection (which is fortunately quite predictable, at least in theory): After every collection, the runtime reserves an area for new allocations known as "free space" and whenever you allocate, it just picks a chunk of this area for you to use. So allocation is very fast, up until the point where a new object can no longer fit into the free space. Once that happens, everything stops and a collection is performed, a new free space region is reserved and then things resume. [that's the simplified version at least]

So what BrooksBishop refers to is that essentially, if you manually request garbage collection just before you start a stage (to maximize the free space size), then a bit of garbage here and there doesn't really matter as long as you don't use up all the free space during a stage (which would trigger a collection and result in a potential hickup).

A similar strategy can also be used in a language like C/C++ by allocating a large enough chunk of raw memory (a "memory pool" basically), then putting all your temporary objects there during a stage. Then as long as the pool never gets full, you can treat those objects as you would treat objects in a garbage collected language, except if you know all of them are garbage when the stage ends, then you don't need to collect anything, you can just free the whole thing (or simply re-use for the next stage).
User avatar
Formless God
Posts: 671
Joined: Fri Mar 12, 2010 7:46 am

Re: bullet scripting

Post by Formless God »

So I cooked up something quick based on The Magic of Yield and I ended up with something resembling the code BB posted. It works great! Feels good to be able to write very readable code like this,

Code: Select all

//definition
IEnumerator<int> BarrageComponent(float direction, int times, int rate)
{
    float speed = 0.5f;
    while (times > 0)
    {
        times--;
        Marker.Create(200, 200, speed += 0.1f, direction += 0.02f);
        yield return rate;
    }
}

//...

engine.Execute(BarrageComponent(1, 2, 3));
Fire a component every 8 frames at an increasing angle:

Image

There are allocations, though. The wrapper objects are already pooled, but the profiler shows a d__0 object being created for every call to the above method. Good news is these are (20 + size of parameters) bytes big, so it takes about a couple 万 of them to exceed the 1MB limit. The number of calls can be reduced by cramming a lot of stuff into them so that one script = one complete pattern and reserve all of them for the important enemies only (bosses, midbosses, fairies of doom).

Oh, and about freeing up space at load time (for .NET), it's simply calling the GC's Collect method after loading everything, right? Are there any additional checks/safety measures required?
RegalSin wrote:Then again sex is no diffrent then sticking a stick down some hole to make a female womenly or girl scream or make noise.
User avatar
n0rtygames
Posts: 1001
Joined: Thu Mar 15, 2012 11:46 pm
Contact:

Re: bullet scripting

Post by n0rtygames »

This is in fact also exactly how my trigger zone and event management works in some Unity projects I've got kicking around. Less twitch based performance needed there though as they're a bit more adventure game like (RE style prototypes, first person games etc). Interesting stuff to read here regarding your optimizations for bringing this much more human readable approach over to a shmup.

Good shit, all of you! :-)
facebook: Facebook
User avatar
BrooksBishop
Posts: 63
Joined: Sun Aug 05, 2012 4:39 am
Location: San Diego, CA
Contact:

Re: bullet scripting

Post by BrooksBishop »

Formless God wrote:Oh, and about freeing up space at load time (for .NET), it's simply calling the GC's Collect method after loading everything, right? Are there any additional checks/safety measures required?
Yep, "GC.Collect();" is all it takes. Just make sure you use it sparingly, use it after all loading code (XNA's ContentManager load function tends to throw some stuff especially on images for example) , and know that it will hang for a second when you use it.
But also keep in mind, forcing a garbage collection/compaction is really kind of an advanced bonus optimization. It's far more important to understand the differences between value types and reference types, and to use that knowledge to effectively manage your memory first.
For C# especially, I found that instances of boxing occurring without your knowledge can be a huge killer. Profiling will show you when this happens easily.
Aeternum a bullet hell on PC and Xbox Live Indie Games - @BrooksBishop - Aeternum OST
User avatar
Formless God
Posts: 671
Joined: Fri Mar 12, 2010 7:46 am

Re: bullet scripting

Post by Formless God »

Yup, I'm very paranoid about memory so the first thing I did was getting that garbage-free system up and running. Although I can already do complex behavior in it, the code is at least 3x as long and scattered across several different documents and you don't have those neat persisting local variables, that's why I have such a huge boner for coroutines. The main reason I'm worried about garbage is because these are the only guys making new objects in an otherwise garbage-free system, but it seems combining the two would work.
RegalSin wrote:Then again sex is no diffrent then sticking a stick down some hole to make a female womenly or girl scream or make noise.
User avatar
n0rtygames
Posts: 1001
Joined: Thu Mar 15, 2012 11:46 pm
Contact:

Re: bullet scripting

Post by n0rtygames »

Could this be placed in the useful threads thingy?
facebook: Facebook
User avatar
BPzeBanshee
Posts: 4857
Joined: Sun Feb 08, 2009 3:59 am

Re: bullet scripting

Post by BPzeBanshee »

It most certainly could good sir.
User avatar
MsK`
Posts: 42
Joined: Tue Jun 28, 2016 12:36 pm
Location: Bordeaux, France
Contact:

Re: bullet scripting

Post by MsK` »

The way I did it in shogun rise of the renegade is with, what I call, "micro VMs" called actors. Actors are used for both bullets and enemies. Each actor is running a very simple virtual machine that has 3 special registers (speed, angle and orientation) and a bunch of general purpose registers. To script that actor, I had a very simply asm-like language, with a syntax inspired by an HP calculator where instead of writing stuff like "add r0, 3", you write a more C-like "r0 += 3".

The actors move in polar coordinates. So to make an actor move forward you just write "speed = 42" and boom, it moves 42 pixels each frame. If you want it to move to another direction, just write "angle = 42°" and you're done. Want to move while looking at another direction? Like the player, for taunting? just write "target orientation, playerX, playerY", it will set the orientation (which doesn't act on the movement but just on the rotation of the sprite) to the angle to the given X and Y coords. The other instructions are the classics add, assign, sub, multiply, divide, cos, sin, aim, aim (which is the same thing as target but relative to angle instead of orientation), etc. I don't really remember, it was a while back x)

What makes the thing really powerful is two instructions : fire and wait. "fire" is inspired by unix's fork(), a new instance of an actor is created and the full content of the current actor is copied onto the new one. This is really fast because all the actors are actually one big array of structs containing the registers and the position of each actor. So it essentially finds an empty slot and does memcpy(). This is also a powerful way of sending information from one actor to another. The only parameters to the fire command are the new script to be executed in place of the current one, and an offset given in polar coordinates.

The "wait" instruction is one of the two instructions that makes the virtual machine stop running. It has only one parameter: the number of frames to wait before resuming execution of the script. The other instruction that makes the machine stop running is "die" of course, which kills the actor entirely.

So technically, it's only an array of structs containing limited data (that fully fits in 2 or 3 CPU cache lines!) and around ~20 instructions with 2 that makes the thing incredibly powerful.

For Pawarumi, I have basically the same thing with unity coroutines. Every enemy inherits from a base enemy class that has the speed, angle and orientation. The FixedUpdate() makes the movement and the coroutines just play with the parameters. For bullets it's different though, I get a new bullet from the manager and start a coroutine on it, passing the info with just a vararg like any other function call, but the bullets themselves also have the same speed, angle and orientation are all updated in one go from the FixedUpdate of the manager. And I extrapolate the movement in the renderer for smoother framerate. (I also did that in shogun so that I could play scripts at half the speed when the weapon menu was open)
Dodge the bullet, save the world.
Working on Pawarumi, check it out!
My twitter | Manufacture 43's Twitter
Image
Vestpocket
Posts: 1
Joined: Sun Apr 01, 2018 4:42 am

Re: bullet scripting

Post by Vestpocket »

In DoDonPachi, they don't use any kind of script. Bullets are spawned directly in enemy frame routines and, at best, there might be a lookup table for speeds and positions to spawn a burst of bullets. There are no special purpose functions for this, however.

To the point of the thread, there is indeed an enemy action script beyond level layout spawning. The script is optimized for angle and velocity scripting. That is to say, the primary purpose of the script will give frame based timing information for changing the angle -- this could make an enemy perform a curve, do a figure 8, and so on -- and non-timing based velocity changes.

Secondarily, it implements a really simple command set. There's a simple "repeat last X instructions" command, a command to set the current animation, a command to set a generic user variable (one intended as a general purpose signal to the code), and then a general purpose command that writes raw data to any property based on its offset in the enemy's memory slot. That's it.
Last edited by Vestpocket on Sat Apr 07, 2018 3:57 pm, edited 1 time in total.
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: bullet scripting

Post by mystran »

My latest approach (from a project I might eventually try to make into something publishable) is to use a few macros in C++ directly to implement co-routines. It turns out it's actually pretty easy to do. The result looks something like this (as a snipped from inside some random enemy class):

Code: Select all

    $script(Shoot)
    {
        Vec2    aimDir;
        int     i;

        $begin(Mob * m)
        {
            while(!m->onGameArea()) $wait(1);
            while(true)
            {
                $wait(30);

                aimDir = game.getAim(m->sPos);

                if(m->sPos.y > 80) break;

                for(i = 0; i < 10; ++i)
                {
                    if(i < 6)
                    {
                        Vec2 rot = Vec2::rotationDeg(15);
                        game.shoot(m, BT_LASER_B, m->sPos, 2*aimDir.cMul(rot));
                        game.shoot(m, BT_LASER_B, m->sPos, 2*aimDir.cMulConj(rot));
                        game.shoot(m, BT_LASER_B, m->sPos, 2*aimDir);
                    }
                    $wait(6);
                }
            }
        }
        $end
    } shooting;

    void updateShooting()
    {
        shooting(this);
    }
The way this works is that the $script-macro creates a wrapper class to hold the "current line" for the script and any local variables that need to be preserved from frame to the next (which need to be defined before $begin), then $begin-macro defines operator() with a switch-statement that dispatches to a case label based on the "current line" of this script, $wait macro defines a loop (for multi-frame wait) that uses __LINE__ preprocessor directive to set the "current line" then generate a matching case-label right after a return-statement.

This way one can write more or less arbitrary C++ code in more or less natural way (just preserved locals need to be moved to the top) and it compiles to a switch-based state-machine. While the above is simple, for things like boss-scripts one can put multiple phases after each other and let loop conditions check for whether to move forward to the next phase, etc. Since the code can do anything (not just spawn bullets) it can also do things like trigger bullet clears or change the sprite of the mob, or whatever.

Implementation looks like this (ugly macros, but it's worth it):
Spoiler

Code: Select all

// base class for co-routines
class ScriptedRoutine
{
protected:
    int _line, _wait;
public:
    ScriptedRoutine():_line(0) {}
    int __end_of_script() const; // defined by $end
    void restart() { _line = 0; }
};

// implement scripts internally as a class
#define $script(NAME) struct NAME : public ScriptedRoutine

// $begin defines operator(...)
// the __VA_ARGS__ is C99 but implemented by MSVC05 and GCC3
#define $begin(...) bool operator()(__VA_ARGS__) { \
                    switch(_line) { case 0:;

// $end must be placed at the end of the script
// we do bunch of magic in this script to add a few methods
#define $end    case __LINE__:; } _line = __LINE__; return false; } \
    bool done() const { return _line == __LINE__; } \
    int __end_of_script() const { return __LINE__; }

// $wait(n) will yield n times
#define $wait(nWait)    \
    do {\
        _line=__LINE__;\
        _wait = (nWait); while(_wait--) { return true; case __LINE__:; }\
    } while (0)

// $stop() will jump to the end
#define $stop() do { _line = __end_of_script(); return false; } while(0)
User avatar
Sai'ke
Posts: 46
Joined: Tue Feb 06, 2018 10:20 am

Re: bullet scripting

Post by Sai'ke »

In the game I'm writing I'm using lua as a bind-in language for enemy/bullet/boss behaviors. It seems fast enough for my purposes and the GC hasn't given me that many headaches yet. Lua gives you pretty good control over the GC. Don't GC when you know that a lot of particle FX are taking up your time.

In Lua it's really easy to setup a system where you use composition to bind in behaviors. I prefer this a lot over inheritance and find this to be extremely flexible. Also, the ability to update code without recompilation is very nice (recompiling a lua script is pretty much instantaneous).

Whenever something seems to slow I move it over to C++ and use memory pooling on that side. But the cases where I've actually had to do this were rare so far (Mostly particle FX).

Typically, for each script, I have a routine that gets called when the script is created (object.onCreate). This object then registers whatever callbacks (registerTimeCallback( func, time, args ), registerCollisionCallback( func ), registerDeathCallback( func ) etc) it needs. These functions always get the object that registered them passed as a first argument, so that when the callback is called, it has all the fields in the table representing the object.

Typically, the onCreate also binds in behaviors via composition (e.g. physicsObject for collisions / forces or behaviors.spline, behaviors.target etc).
FALCONER
Posts: 2
Joined: Sat Jun 30, 2018 11:23 am

Re: bullet scripting

Post by FALCONER »

For a shmup I was working on that never made i out we built this language/engine:
https://github.com/furusystems/Barrage

Code: Select all

# Comments are prefixed with pound sign

# A Barrage has a starting Action that results in bullets being created
# Actions can have sub-actions
# Bullets can trigger actions
# Actions triggered by bullets use the bullet's position as origin

# Barrage root declaration
barrage called waveburst
	# Define and name a bullet.
	# This bullet initially moves backwards, but accelerates towards a positive velocity
	bullet called offspring
		speed is -100
		acceleration is 150
		# This bullet action waits a second before turning towards the player
		do action
			wait 1 seconds
			set direction to aimed over 1 seconds

	# This is our base bullet
	bullet called source
		speed is 100
		do action
			# This action immediately triggers a sub-action
			do action
				# fires 6 360 degree spreads of 11 bullets, one every quarter second
				wait 0.25 seconds
				# Math expressions in parentheses are evaluated to constants at build time 
				fire offspring in aimed direction (360/10*0.5)
				do action
					fire offspring in incremental direction (360/10)
					repeat 10 times
				repeat 6 times
			# wait for the sub-action to complete..
			wait (6*0.25) seconds
			# then die 
			die

	# Barrage entry point. Every barrage must have a start action 
	action called start
		# Fire a source bullet directly towards the player 
		fire source in aimed direction 0
It's basically declarative bullet scripting + particle governor. It's not done and needs a ground up refactor, but this+a visual editor made complex patterns designable with flat iteration time. I think a good language and a good preview is integral to making intricate patterns.

As for fixing timestep, gaffer's article fixes the physics/game update to a known timestep so it should have zero impact on replays. It just decouples rendering from your game step which is smart to do anyway. IMO a fixed timestep makes everything easier and more intuitive to work with. Euler integration adds pointless complexity for games like these.
Post Reply