[ZScript] Destructible geometry material handler
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.
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.
[ZScript] Destructible geometry material handler
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:
- ReformedJoe
- Posts: 180
- Joined: Mon May 21, 2018 4:52 pm
Re: [ZScript] Destructible geometry material handler
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.
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
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..
- Caligari87
- Admin
- Posts: 6174
- Joined: Thu Feb 26, 2004 3:02 pm
- Preferred Pronouns: He/Him
- Contact:
Re: [ZScript] Destructible geometry material handler
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.
Re: [ZScript] Destructible geometry material handler
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).
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
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..
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
If I would like to spawn debris I think it should have been done here, see comment:
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:
I can't straight call A_BitsExplode(int ammount ,string bit) because it belongs to DestructableDecorationBase.. How I could do a generic debris function?
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
}
}
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;
}
}
}
}
Re: [ZScript] Destructible geometry material handler
It would be almost the same, probably something like this:
Then, in MaterialFloorDestroyed, something along these lines:
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);
}
}
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);
}
Re: [ZScript] Destructible geometry material handler
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)>.
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);
}
}
Re: [ZScript] Destructible geometry material handler
Oh, EjectDebris needs to be inside the DirtHandler class.
Re: [ZScript] Destructible geometry material handler
Nice, works perfectly. Thank you very much!
Re: [ZScript] Destructible geometry material handler
I figured out how to prevent more the tetris-like effect. When breakable wall also breaks upwards it is more better.
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.
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);
}
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
[Edit, now I figured out the solution! If you do height check against adjacent sector it will work dynamically!
...Original post...Now mostly futile, deleted most of it.
A brickwall example:
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?
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
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
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.
How do I spawn debris for destroyed 3D-floor? It spawns the debris into a control sector.
Spoiler:
Re: [ZScript] Destructible geometry material handler
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:
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.
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);
}
}