Page 1 of 2

[ZScript] Destructible geometry material handler

PostPosted: Fri Mar 08, 2019 2:38 pm
by dodopod


This script makes it easier to use GZDoom's new (v3.7) destructible geometry feature. It does this by providing an event handler, which assigns materials to lines, floors, ceilings and 3D floors based on texture (though they can also be assigned manually). Materials, like actors, have Health and DamageFactor properties, allowing level geometry to have strengths and weaknesses to different damage types. The event handler also defines several new events, allowing you to customize a material's behavior when it's damaged or destroyed.

GitLab repo (Includes a tutorial. Better documentation will come soon.)
Downloads:

Re: [ZScript] Destructible geometry material handler

PostPosted: Mon Mar 25, 2019 6:17 pm
by ReformedJoe
Been tinkering with it for a bit now. This is so cool.
I had an idea for a project with destructible environments that I shelved because it would have been a pain in the ass, but this opens up many new possibilities.
Great stuff.

Re: [ZScript] Destructible geometry material handler

PostPosted: Mon Mar 30, 2020 11:11 am
by Jaska
This is very great! I just don't have experience on ZScript.. I was thinking how to make a brick wall. Problem would be how to avoid bricks floating in the air or just always "falling" to ground..

Re: [ZScript] Destructible geometry material handler

PostPosted: Mon Mar 30, 2020 11:26 am
by Caligari87
Unfortunately this doesn't work for something like a brick wall as it doesn't add any sort of true dynamic geometry. The level still has to be constructed out of sectors, which cannot have their 2D geometry modified at runtime, only floor and ceiling heights. This just makes it easier to trigger sector effects based on damage from weapons.
8-)

Re: [ZScript] Destructible geometry material handler

PostPosted: Mon Mar 30, 2020 12:39 pm
by dodopod
Caligari's mostly right -- you can trigger line effects too, but that's about it. I'm not entirely sure what you're going for. The less dynamic it is, the easier it would be to do, but you might be able to pull off something more complex with some tricks.

If you just want a destructible wall, the best way to do it is the Duke Nukem way. You have a 2-sided line, which blocks everything, representing the wall. When the wall is destroyed, the texture changes to one with a big hole in it, and the blocks everything flag is turned off. The downsides are that the wall is paper thin, and that it isn't very dynamic -- it's either destroyed or not. You could get fancy with it in a number of ways: shoot out debris when the wall is destroyed; use sectors instead of a line, so the wall has some thickness; divide up the line into sections, so only parts of the wall are destroyed at a time; make the wall go through different stages of destruction; animate the wall's destruction. The limit is your ingenuity.

On the other hand, it sounds like you want to be able to destroy it brick-by-brick. That would be much more difficult. You might be able to do something along the lines of making each brick into a 3D floor. That's tedious, but might be doable. I remember there being some problems with 3D floors taking damage. Of course, like you mentioned, you would need to keep the bricks from floating. You might be able to make it so that when a brick is destroyed, it informs the brick above it. That brick then checks whether it's floating, and if so, either falls to the ground, or vanishes (by which I mean teleports beneath the ground, since 3D floors can't actually disappear).

Re: [ZScript] Destructible geometry material handler

PostPosted: Tue Mar 31, 2020 3:04 am
by Jaska
Now I'm understanding its limitations. It think this could be used as destructive walls like this:



Upper part is just lowered ceiling to get a shape so everything can't be destroyed.






Maybe change block resolution to 24 pixels and limit destructable area so it wont show that tetris-like effect. I would do the brick wall only to be destructible with explosives so it hides tetris-effect also. Change destructable walls texture to another one so player can see which can be destructed. Add spawning brick debris.. One problem will be the dig-height. Maybe putting 3D-floors on lower part.. But soon it will be too complex and tedious for larger gameplay element. One solution would be to do 5-20 pre-built walls which could be copy pasted and offsetted with "height offset" if height variation is needed..

Re: [ZScript] Destructible geometry material handler

PostPosted: Tue Mar 31, 2020 7:05 am
by Jaska
If I would like to spawn debris I think it should have been done here, see comment:

Code: Select allExpand view
class DirtHandler : MaterialHandler
{
    override void OnRegister()
    {
        health = 20;
        textures.Push("FLAT10");
        textures.Push("GRASS1");
    }

    override void MaterialFloorDestroyed(WorldEvent e)
    {
        Super.MaterialFloorDestroyed(e);

        // Dig
        int newHeight = e.damageSector.floorPlane.ZAtPoint(e.damageSector.centerSpot) - 32;
        e.damageSector.MoveFloor(1, -newHeight, 0, 1, false, true);

        // Mow the lawn
        e.damageSector.SetTexture(Sector.floor, TexMan.CheckForTexture("FLAT10", TexMan.Type_Flat));

        // Reset health so we can dig some more
        e.newDamage = (e.newDamage - e.damageSector.GetHealth(SECPART_Floor)) % health;
        e.damageSector.SetHealth(SECPART_Floor, health);
   
      //Spawn debris here
    }
}


I've coded last time like 12 years ago so I'm stuck how exactly it should be done.

Hexen pack has this code to spawn debris:

Code: Select allExpand view
class DestructableDecorationBase : Actor
{
   Default
   {
    Radius 32;
    Height 32;
   Mass 100;
   Friction 1;
   Health 100;
    +SOLID
   +PUSHABLE
   +DROPOFF
   +SLIDESONWALLS
   +CANPASS
   +ACTLIKEBRIDGE
   +FLOORCLIP;
   +SHOOTABLE;
   +NOBLOOD;
   +NEVERTARGET;
   PushFactor 0.25;
   }

   void A_BitsExplode(int ammount ,string bit)
   {
      Actor mo = null;
      int i;

      for(i = ammount; i; i--)
      {
         mo = Spawn (bit, Pos, ALLOW_REPLACE);
         if (mo)
         {
            mo.SetState (mo.SpawnState + random[Pottery](0, 4));
            mo.Vel.X = random2[Pottery]() / 64.;
            mo.Vel.Y = random2[Pottery]() / 64.;
            mo.Vel.Z = random[Pottery](5, 12) * 0.75;
         }
      }
   }
}


I can't straight call A_BitsExplode(int ammount ,string bit) because it belongs to DestructableDecorationBase.. How I could do a generic debris function?

Re: [ZScript] Destructible geometry material handler

PostPosted: Tue Mar 31, 2020 7:39 am
by dodopod
It would be almost the same, probably something like this:

Code: Select allExpand view
void EjectDebris(int amount, class<Actor> debrisCls, Vector3 pos, float maxVel)
{
    for (int i = 0; i < amount; i++)
    {
        let mo = Actor.Spawn(debrisCls, pos);
       
        mo.vel.x = frandom(-maxVel, maxVel);
        mo.vel.y = frandom(-maxVel, maxVel);
        mo.vel.z = frandom(-maxVel, maxVel);
    }
}


Then, in MaterialFloorDestroyed, something along these lines:

Code: Select allExpand view
override void MaterialFloorDestroyed(WorldEvent e)
{
    Super.MaterialFloorDestroyed(e);

    // Dig
    int newHeight = e.damageSector.floorPlane.ZAtPoint(e.damageSector.centerSpot) - 32;
   
    // btw you can solve the dig-height problem like this
    // 0 might not be the right number, but it keeps you from digging down forever
    if (newHeight < 0) return;
   
    e.damageSector.MoveFloor(1, -newHeight, 0, 1, false, true);

    // Mow the lawn
    e.damageSector.SetTexture(Sector.floor, TexMan.CheckForTexture("FLAT10", TexMan.Type_Flat));

    // Reset health so we can dig some more
    e.newDamage = (e.newDamage - e.damageSector.GetHealth(SECPART_Floor)) % health;
    e.damageSector.SetHealth(SECPART_Floor, health);

    //Spawn debris here
    Vector3 pos = (e.damageSector.centerSpot, newHeight + 16);  // center of the part just destroyed
    EjectDebris(8, "BrickDebris", pos, 64);
}

Re: [ZScript] Destructible geometry material handler

PostPosted: Tue Mar 31, 2020 8:14 am
by Jaska
Tried, got error messages

ZSCRIPT error in "material-demo-v1.0.pk3\zscript.zc", line 52. Expected preprocessor statement, const, enum or class declaraction, got <Token.Identifier (void)>.

Tried Const:
ZSCRIPT error in "material-demo-v1.0.pk3\zscript.zc", line 54. Expected =, got <Token.Identifier (EjectDebris)>.

Tried Class:
ZSCRIPT error in "material-demo-v1.0.pk3\zscript.zc", line 54. Unexpected token <Token.Identifier (maxVel)>.

Code: Select allExpand view
class void EjectDebris(int amount, class<Actor> debrisCls, Vector3 pos, float maxVel)
{
    for (int i = 0; i < amount; i++)
    {
        let mo = Actor.Spawn(debrisCls, pos);
       
        mo.vel.x = frandom(-maxVel, maxVel);
        mo.vel.y = frandom(-maxVel, maxVel);
        mo.vel.z = frandom(-maxVel, maxVel);
    }
}

Re: [ZScript] Destructible geometry material handler

PostPosted: Tue Mar 31, 2020 8:16 am
by dodopod
Oh, EjectDebris needs to be inside the DirtHandler class.

Re: [ZScript] Destructible geometry material handler

PostPosted: Tue Mar 31, 2020 8:22 am
by Jaska
Nice, works perfectly. Thank you very much!

Re: [ZScript] Destructible geometry material handler

PostPosted: Fri Apr 03, 2020 6:02 am
by Jaska
I figured out how to prevent more the tetris-like effect. When breakable wall also breaks upwards it is more better.

Code: Select allExpand view
 
   override void MaterialCeilingDestroyed(WorldEvent e)
    {
        Super.MaterialCeilingDestroyed(e);

        // Move ceiling up
        int newHeight = e.damageSector.ceilingPlane.ZAtPoint(e.damageSector.centerSpot) + 16;
      
      //Limit how far up ceiling breaks
      //if (newHeight > 128) return;
        e.damageSector.MoveCeiling(1, newHeight, 0, -1, false);

        // Change the ceiling texture to represent "broken" state
        e.damageSector.SetTexture(Sector.ceiling, TexMan.CheckForTexture("CRATOP2", TexMan.Type_Flat));

        // Reset health so a new block can be breaked
        e.newDamage = (e.newDamage - e.damageSector.GetHealth(SECPART_Ceiling)) % health;
        e.damageSector.SetHealth(SECPART_Ceiling, health);
      
      Vector3 pos = (e.damageSector.centerSpot, newHeight - 8);  // center of the part just destroyed
      EjectDebris(8, "WoodenBit", pos, 32);      
    }


BTW, where you find all the actors, classes, usable functions and methods? Some are found here:
https://github.com/marrub--/zscript-doc ... gDestroyed

I guessed "MaterialCeilingDestroyed". Also definitions and ranges for many variables would be useful, like speed etc.

Re: [ZScript] Destructible geometry material handler

PostPosted: Sat Apr 04, 2020 11:43 pm
by Jaska
[Edit, now I figured out the solution! If you do height check against adjacent sector it will work dynamically!
Code: Select allExpand view
   
       //Limit how far down floor breaks, this it keeps you from digging down forever
      Double  lowestNearFloor = e.damageSector.FindLowestFloorSurrounding();
      if (newHeight < lowestNearFloor) return;   

https://github.com/marrub--/zscript-doc/blob/8081cd640bb2805a95918c142144751a00249bf4/api/level/Sector.md


...Original post...Now mostly futile, deleted most of it.

A brickwall example:
Spoiler:

Another question. At last I learned some Blender and did that brick model.

So I spawn one brick model:
EjectDebris(1, "breakableBrick04", pos, 8);

Can I rotate the brick randomly by some code?
Or do I use just different blocks like this:

Now I have 4 different brick models. So I could just do something like:
int x = random(1,4);
and then like:
if ( x == 1) EjectDebris(1, "breakableBrick01", pos, 8);
if ( x == 2) EjectDebris(1, "breakableBrick02", pos, 8);
if ( x == 3) EjectDebris(1, "breakableBrick03", pos, 8);
if ( x == 4) EjectDebris(1, "breakableBrick04", pos, 8);

but it would look kinda ugly?

Re: [ZScript] Destructible geometry material handler

PostPosted: Sun Apr 05, 2020 12:52 pm
by Jaska
Seems like I'm not going to solve this problem.

How do I spawn debris for destroyed 3D-floor? It spawns the debris into a control sector.

Spoiler:

Re: [ZScript] Destructible geometry material handler

PostPosted: Sun Apr 05, 2020 2:37 pm
by dodopod
I think I might know how to do that. The control sector has one line that defines it as a 3D floor. That line's first argument is the tag of the sectors where that 3D floor appears. So, what you need to do is iterate over e.damageSector.lines, until you find the one where special == 160 (i.e. Sector_Set3DFloor). Then, you create a SectorTagIterator, and use that to find all the sectors where the tag is equal to args[0]. So something like this:

Code: Select allExpand view
class WoodHandler : MaterialHandler
{
    const Sector_Set3DFloor = 160;

    // ...

    override void Material3DDestroyed(WorldEvent e)
    {
        Super.Material3dDestroyed(e);
       
        int tag;
        for (int i = 0; i < e.damageSector.lines.Size(); i++)
        {
            Line l = e.damageSector.lines[i];
           
            if (l.special == Sector_Set3DFloor)
            {
                tag = l.args[0]
                break;
            }
        }
       
        let it = level.CreateSectorTagIterator(tag);
        int idx;
        while (idx = it.Next())
        {
            Sector s = level.sectors[idx];
           
            Vector3 pos = (s.centerSpot, s.floorPlane.ZAtPoint(s.centerSpot));
            EjectDebris(8, "WOODGIB", pos, 32);   //Alternate debris, use this WoodenBit
        }
       
        e.damageSector.MoveFloor(1, 1024, 0, 1, false, true);
        e.damageSector.MoveCeiling(1, -1024, 0, 1, false);
    }
}


As for rotating bricks, randomly, you can create a method in BreakableBrick to increment angle, pitch, and roll. Call that method every frame during the Spawn state. When velocity < 4, say, jump to the Death state, or whatever you want to call it, where the brick just sits there. You'll want set the angle, pitch, and roll speeds, when the brick is initialized, maybe by overriding PostBeginPlay. One other thing: with angle and roll, you can just increment them every frame. Pitch is tricky. I think it's clamped to between +/- 90 degrees. So when pitch goes over 90, you need to add 180 to the angle, and multiply pitchSpeed (or whatever you want to call it) by -1. I think you might also need to set +ROLLSPRITE -- I'm not sure if that applies to 3D models. I'm not honestly sure whether that will work, since 3D rotation is very confusing.