[ZScript] Destructible geometry material handler

Post your example zscripts/ACS scripts/etc here.
Forum rules
The Projects forums are only for projects. If you are asking questions about a project, either find that project's thread, or start a thread in the General section instead.

Got a cool project idea but nothing else? Put it in the project ideas thread instead!

Projects for any Doom-based engine (especially 3DGE) are perfectly acceptable here too.

Please read the full rules for more details.
dodopod
Posts: 66
Joined: Wed Oct 04, 2017 2:00 pm

[ZScript] Destructible geometry material handler

Post 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:
User avatar
ReformedJoe
Posts: 179
Joined: Mon May 21, 2018 4:52 pm

Re: [ZScript] Destructible geometry material handler

Post 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.
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post 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..
User avatar
Caligari87
User Accounts Assistant
Posts: 5998
Joined: Thu Feb 26, 2004 3:02 pm
Preferred Pronouns: He/Him

Re: [ZScript] Destructible geometry material handler

Post 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-)
dodopod
Posts: 66
Joined: Wed Oct 04, 2017 2:00 pm

Re: [ZScript] Destructible geometry material handler

Post 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).
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post 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..
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post by Jaska »

If I would like to spawn debris I think it should have been done here, see comment:

Code: Select all

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 all

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?
dodopod
Posts: 66
Joined: Wed Oct 04, 2017 2:00 pm

Re: [ZScript] Destructible geometry material handler

Post by dodopod »

It would be almost the same, probably something like this:

Code: Select all

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 all

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);
}
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post 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 all

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);
    }
}
dodopod
Posts: 66
Joined: Wed Oct 04, 2017 2:00 pm

Re: [ZScript] Destructible geometry material handler

Post by dodopod »

Oh, EjectDebris needs to be inside the DirtHandler class.
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post by Jaska »

Nice, works perfectly. Thank you very much!
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post 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 all

 
   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.
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post by Jaska »

[Edit, now I figured out the solution! If you do height check against adjacent sector it will work dynamically!

Code: Select all

	
 		//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?
Jaska
Posts: 113
Joined: Tue Dec 17, 2019 5:12 am
Graphics Processor: nVidia with Vulkan support

Re: [ZScript] Destructible geometry material handler

Post 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:
dodopod
Posts: 66
Joined: Wed Oct 04, 2017 2:00 pm

Re: [ZScript] Destructible geometry material handler

Post 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 all

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.

Return to “Script Library”