# BenchGunner
BenchGunner is a gun that fires on its own, the way a player holding the trigger does: `rate` shots
a second, each one a muzzle flash at the gun, a tracer down its aim and an impact where the round
lands. The gun aims along its entity's forward (-Z). It holds one effect of each kind for as long as
it exists, and a shot plays each of them again from its start.
The bench_mixed scene uses it. Its build script spawns eight guns, `BenchGun1` to `BenchGun8`, as
children of `BenchRange`, each with `rate = 11` and `distance = 30`, to stand in for eight players
holding the trigger.
The three effects are procGraph assets played through Generator components, by default
`effects.combat.muzzleFlash`, `effects.combat.tracer` and `effects.combat.impact`.
## Fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `rate` | number | 11 | Shots per second. |
| `distance` | number | 30 | How far down the aim the rounds land, in metres. |
| `muzzleFlash` | assetRef (procGraph) | `effects.combat.muzzleFlash` | The flash at the gun on each shot. |
| `tracer` | assetRef (procGraph) | `effects.combat.tracer` | The round's trail down the aim. |
| `impact` | assetRef (procGraph) | `effects.combat.impact` | What a round does where it lands. |
All fields are `Sync`.
## Lifecycle
- `start()`: spawns one temporary child entity per effect field that names an asset, each with a
Generator set to that graph and `playOn = "manual"`. `muzzle` and `tracer` stand at the gun's
origin, and the tracer's Generator gets the parameter `distance`. `impact` stands at
`{ 0, 0, -distance }` in the gun's space, turned to face back up the aim toward the gun.
- `update(dt)`: while `rate` is above 0, it accumulates `dt * rate` shots, held at most 2, and fires
one shot per whole shot owed. A long frame fires at most one shot more than a short one. A shot
calls `seek(0)` and then `play()` on each of the three Generators.
- `onDestroy()`: despawns the child effects.
## Usage
```luau
local gun = entity.spawn("BenchGun1")
gun.component.add("BenchGunner", { rate = 11, distance = 30 })
```
# rock_field
A field of rough rocks strewn over a 24 m square: one rock shape (a subdivided octahedron with
noise displaced along its normals) copied onto points across the ground, each copy with a turn,
lean and size of its own.
```lua
local e = entity.spawn("rock_field", { position = { 0, 0, 0 } })
e.component.add("Generator", { graph = "rock_field", params = { seed = 3, count = 400 } })
```
| Input | Default | What it does |
|---|---|---|
| `seed` | 1 | Re-rolls where the rocks lie and how they sit. |
| `count` | 160 | How many rocks (0 to 5000). |
The output `rocks` is the rock copies with their stone material.
# valley
A small valley: 256 m of rolling ground at 2 m a cell with a valley cut down through it,
weathered, with a graded floor and a stream along it, meadow on the gentle slopes and crag on the
steep ones.
```lua
local e = entity.spawn("valley", { position = { 0, 0, 0 } })
e.component.add("Generator", { graph = "valley", params = { relief = 30 } })
```
| Input | Default | What it does |
|---|---|---|
| `relief` | 18 | How high the hills roll, in metres (0 to 120). |
| `cut` | -30 | How deep the valley runs, in metres (-120 to 0). |
| `weather` | 60 | How long the erosion runs (0 to 2000). |
The output `land` is the landscape.
# BenchRegenerator
BenchRegenerator keeps a generator re-cooking the way a game that edits its world does. Every
`every` seconds it sets the graph input named `input` on the Generator of the entity named
`target`, stepping through `values` and starting over at the end. A value the generator has not
held before is a new cook.
The bench_mixed scene uses it twice. `BenchRocksReroll` re-rolls the rock field on `BenchRocks`
(`input = "seed"`, `values = { 1 }`, `every = 2`, `stepping = true`), and `BenchValleyEdit` edits the
valley on `BenchValley` (`input = "cut"`, `values = { -60 }`, `every = 5`, `stepping = true`). With
`stepping` on, each of these counts up from its single value, so every re-cook is a new cook.
It looks up the target by name and reads that entity's `Generator` component.
## Fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `target` | string | `""` | The name of the entity whose Generator is re-cooked. |
| `input` | string | `"seed"` | The graph input set on each re-cook. |
| `values` | table | `{ 1, 2, 3, 4 }` | The values the input steps through, in order, starting over at the end. |
| `every` | number | 2 | Seconds between re-cooks. |
| `stepping` | bool | false | Counts up from the last value once `values` is used up, so no value repeats and every re-cook is a cook the generator has not made before. |
All fields are `Sync`.
## Lifecycle
- `update(dt)`: while `every` is above 0, it adds `dt` to a timer. Each time the timer passes
`every`, it finds the first entity named `target`, copies its Generator's `params`, sets
`params[input]` to the next value and assigns the copy back to the Generator's `params`.
The next value is the next entry of `values`. Past the last entry, with `stepping` on and a numeric
last entry, it is the last entry plus the number of steps taken past it; otherwise the sequence
starts again at the first entry.
## Usage
```luau
local reroll = entity.spawn("BenchRocksReroll")
reroll.component.add("BenchRegenerator", {
target = "BenchRocks", input = "seed", values = { 1 }, every = 2, stepping = true,
})
```
# impact
A round striking a hard surface: a flash, a spray of sparks thrown back off it
that fall and cool, a burst of dust and grit that billows and settles, and a
flicker of light on the surface.
Fire it where the round lands, with the effect's -Z turned along the normal of
the surface it struck, and let it go when it ends:
```lua
-- `graph` is this graph's reference: the AssetRef `asset.resolve` or a search returns for it.
local function hitEffect(position, normal)
local e = entity.spawn("impact", { position = position, temporary = true })
e:lookAt({ position[1] + normal[1], position[2] + normal[2], position[3] + normal[3] })
e.component.add("Generator", { graph = graph, playOn = "load", ending = "hold" })
e.component.get("Generator").events.onFinished:connect(function()
e:despawn()
end)
return e
end
```
| Input | Default | What it does |
|---|---|---|
| `size` | 1 | How big the strike is: 1 a rifle round on concrete, 3 a heavy round, 0.4 a pistol. |
| `light` | 1 | Strength of the light it throws, as a multiple of the default. |
Set them with the Generator's `params`, e.g. `params = { size = 3 }`. It plays
66 frames at 60 fps (1.1 s). Every play with the same params plays the frames
the first one cooked.
# tracer
The glowing streak of a round in flight: a white-hot streak leaving the muzzle
along -Z and burning out `distance` metres on, shedding embers that fall and
cool behind it.
Fire it at the muzzle with its -Z turned toward what the round hits, and
`distance` the range to it:
```lua
-- `graph` is this graph's reference: the AssetRef `asset.resolve` or a search returns for it.
local function tracer(from, to)
local dx, dy, dz = to[1] - from[1], to[2] - from[2], to[3] - from[3]
local e = entity.spawn("tracer", { position = from, temporary = true })
e:lookAt(to)
e.component.add("Generator", {
graph = graph, playOn = "load", ending = "hold",
params = { distance = math.sqrt(dx * dx + dy * dy + dz * dz) },
})
e.component.get("Generator").events.onFinished:connect(function()
e:despawn()
end)
return e
end
```
| Input | Default | What it does |
|---|---|---|
| `distance` | 40 | How far the round flies before it burns out, in metres: the range to what it hits. |
| `speed` | 240 | How fast it flies, in metres per second. |
| `width` | 0.04 | Width of the streak, in metres. |
The streak runs back from the round for as far as it flies in a sixtieth of a
second, and never behind the muzzle. It plays 45 frames at 60 fps (0.75 s).
Each `distance` is its own set of frames: plays at one range share what the
first one cooked.
# muzzleFlash
The bloom at a barrel the instant a weapon fires: a ragged star of light and a
tongue of flame down the bore, gone within three frames; a spray of burning
powder that streaks out, falls and cools; a few puffs of smoke that swell, rise
and thin; and a light that throws the flash on whatever stands near.
The bore runs along the effect's -Z. Stand it on a child of the weapon at the
muzzle, so it follows the weapon, and play it from the start on each shot:
```lua
-- `graph` is this graph's reference: the AssetRef `asset.resolve` or a search returns for it.
local function mountFlash(weapon, muzzleOffset)
local muzzle = entity.spawn("muzzle", { parent = weapon.id, position = muzzleOffset })
muzzle.component.add("Generator", { graph = graph, playOn = "manual", ending = "hold" })
local flash = muzzle.component.get("Generator")
return function()
flash:seek(0)
flash:play()
end
end
```
| Input | Default | What it does |
|---|---|---|
| `size` | 0.28 | Width of the flash in metres: a pistol at 0.12, a rifle at 0.28, a tank gun at 1.2. Every layer scales with it. |
| `light` | 1 | Strength of the light it throws, as a multiple of the default. |
It plays 54 frames at 60 fps (0.9 s). Played again, it plays the frames its
first play cooked, so a weapon firing on full auto cooks it once.
# effectShowcase
The procedural effects of the Procgen Effects Showcase world. Each one is a `.procGraph` that plays
through a `Generator`, and every shader, material and module a graph draws with sits beside it in
this package, so the package carries everything its graphs need. Each graph's README says what it
makes, how to place it and what each of its inputs changes.
## Contents
| Folder | Graphs | What they make |
|---|---|---|
| `combat/` | `impact`, `muzzleFlash`, `tracer` | A round striking a hard surface, the bloom at a barrel as a weapon fires, and the glowing streak of a round in flight. They draw with `fxFlash`, `fxSmoke` and `fxSpark` in `combat/shaders/`. |
| `blast/` | `wall_blast` | A charge blowing a breach through a stone block wall, on a 9.6 s loop. |
| `storm/` | `storm`, `tower` | A thunderstorm around a lone tower: wind-driven rain, lightning striking on the schedule `schedule.module` keeps, and sparks and smoke where strikes land. `tower` is the stone keep it stands around. |
| `rift/` | `arcane_rift` | A rift tearing open in the ground, its motes and rubble moved by the compute shaders in `rift/compute/`. |
| `dragon/` | `dragon_breath` | A dragon's fire breath sweeping a courtyard: a flame jet, rolling smoke and drifting embers. |
| `launch/` | `launch` | A heavy-lift rocket liftoff on one looping timeline, with the heat-haze material `rk_haze_mat`. |
| `waterfall/` | `wf_env`, `wf_water`, `wf_spray`, `wf_foam`, `wf_mist` | A waterfall built in parts: the cliff, basin and boulders, the falling and still water, the droplets, the foam where the falls land, and the mist with its rainbow. Its water materials are in `waterfall/materials/`. |
| `shaders/` | | `fx_noise`, the noise functions the waterfall and wall blast shaders include. |
## Playing one
A graph plays on a `Generator` component bound to it:
```lua
-- `graph` is the graph's reference: the AssetRef `asset.resolve` or a search returns for it.
local e = entity.spawn("impact", { position = { 0, 1, 0 } })
e.component.add("Generator", { graph = graph, playOn = "load" })
```
Set a graph's inputs with the Generator's `params`, e.g. `params = { size = 3 }`.