Skip to content

Commit

Permalink
Monster Attack Devlogs
Browse files Browse the repository at this point in the history
  • Loading branch information
jankrassnigg committed Oct 15, 2023
1 parent 54d75fa commit edd6ae2
Show file tree
Hide file tree
Showing 64 changed files with 404 additions and 5 deletions.
2 changes: 1 addition & 1 deletion pages/docs/xr/xr-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ for (ezXRHand::Enum hand : {ezXRHand::Left, ezXRHand::Right})
## See Also
* [Input](../input/input-overview.md)
* [XR Overview](xr-overview.md.md)
* [XR Overview](xr-overview.md)
* [XR Components](xr-components.md)
2 changes: 1 addition & 1 deletion pages/docs/xr/xr-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **NOTE:**
>
> XR support is still in development. You will need to enable *Show in Development Features* in the [editor settings](..\editor\editor-settings.md) to use it.
> XR support is still in development. You will need to enable *Show in Development Features* in the [editor settings](../editor/editor-settings.md) to use it.
*XR* stands for both *VR* (virtual reality) as well as *AR* (augmented reality) devices. Currently supported devices:
* **VR**: Windows desktop VR devices that support DX11 and OpenXR.
Expand Down
4 changes: 2 additions & 2 deletions pages/docs/xr/xr-project-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

As an example, we will use the [Testing Chambers](../../samples/testing-chambers.md) project and its **Surfaces** scene to set up XR rendering for desktop VR use. Start by opening the project and scene.

Enable *Show in Development Features* in the [editor settings](..\editor\editor-settings.md) and restart it to see XR features in the editor.
Enable *Show in Development Features* in the [editor settings](../editor/editor-settings.md) and restart it to see XR features in the editor.

To use XR in your project you must first load a plugin to support XR devices. In the [plugin selection](../projects/plugin-selection.md) dialog, select your custom XR plugin. In this case, we select the *Open XR* plugin and close the dialog.

Next is to enable XR rendering under [asset profiles (TODO)](asset-profiles.md). Select the profile you want to enable XR in and then check the *Enable XR* checkbox. You also need to select the *XR Render Pipeline* here. Currently, both the `MainRenderPipeline` and the `HololensRenderPipeline` fully support XR rendering. Let's select the `MainRenderPipeline` for this example and close the dialog.
Next is to enable XR rendering under [asset profiles (TODO)](../assets/asset-profiles.md). Select the profile you want to enable XR in and then check the *Enable XR* checkbox. You also need to select the *XR Render Pipeline* here. Currently, both the `MainRenderPipeline` and the `HololensRenderPipeline` fully support XR rendering. Let's select the `MainRenderPipeline` for this example and close the dialog.

Pressing 'Play the Game' in the scene should now already start rendering on the HMD and you can look around but it will not be very interactive.

Expand Down
19 changes: 19 additions & 0 deletions pages/samples/monster-attack/devlog-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# MA Devlog 1 - Intro

For some reason the next two weeks I have more spare time than usual. So I decided to use this time to try to make a demo that is a bit more elaborate. And I decided to mention this here, so that I have more reasons to actually continue working on it. You know, I'm more of a technical person, and doing "game development" quickly becomes boring to me. I prefer to write the systems behind the scenes. Anyway, this project is limited to two weeks anyway, so let's see how far I get.

Since I'm bad at game design, I need something that's already designed. So I chose to do a tower defense game. More specifically, a clone of [Orcs Must Die!](https://en.wikipedia.org/wiki/Orcs_Must_Die!), a game that is a lot of fun (though I only know part 1 and 2).

The nice thing about this game is, that the core mechanic is relatively quick to implement, but then there are lots and lots of details that can be added over time.

In the past few days I've set up some basics of the project, gathered some assets (from <https://quaternius.com> and <https://kenney.nl>) and implemented the basic character controller and monster functionality. So I now have a simple level layout and some monsters that run from the start point to the end point:

<video src="media/devlog1/Navigation.mp4" width=600 controls></video>

You can also already shoot and kill the monsters:

<video src="media/devlog1/Shoot.mp4" width=600 controls></video>

## See Also

* [Monster Attack Sample](monster-attack.md)
84 changes: 84 additions & 0 deletions pages/samples/monster-attack/devlog-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# MA Devlog 2 - Spike Trap

So today was all about getting my first trap working. Here is the result:

<video src="media/devlog2/ma-dl2-Spike_Trap.mp4" width=600 controls></video>

The most flexible way to do these things, is to just use [custom C++ components](../../docs/custom-code/cpp/custom-cpp-component.md) for everything. However, I want to test out our other infrastructure as well, for example the new [visual scripting](../../docs/custom-code/visual-script/visual-script-overview.md), the [state machines](../../docs/ai/state-machine-asset.md), and so on, so the goal is to prefer those, and only use C++ for the things that really need it.

At the moment I only have two custom C++ components, one for the player logic and one for the monsters. The former mostly does [input handling](../../docs/input/input-overview.md) and forwarding to the [character controller](../../docs/physics/jolt/special/jolt-character-controller.md), the latter mainly does the path finding and steering.

So today I had to figure out how to build my first trap.

If I were (or had) an artist, I would use an [animated mesh](../../docs/animation/skeletal-animation/animated-mesh-asset.md) with multiple [animations](../../docs/animation/skeletal-animation/animation-clip-asset.md) for the different trap states. However, I only have two [static meshes](../../docs/graphics/meshes/mesh-asset.md), one with spikes, one without, and I really want to make things work with the assets as I have them. So I needed a way to do the visuals without animations.

Here is what it looks like in a close up:

<video src="media/devlog2/ma-dl2-SpikeTrapAnim.mp4" width=600 controls></video>

The trap has four states, therefore I built a [state machine](../../docs/ai/state-machine-asset.md).

![Spike Trap States](media/devlog2/ma-dl2-image1.png)

In the *Dormant* state it does nothing. This is the state where the trap is not dangerous. It takes a second and then transitions into the *Loaded* state.

In the loaded state, the spikes show up and peak through the bottom. Now when a creature walks over it, the trap enters the *Firing* state where it makes damage.

After a short time, it enters the *Retracting* state, where the spikes move down and then it starts over.

Again, there are different ways how you could achieve the animations and the behavior, but I wanted to use as much existing functionality as possible. For each *State* in the state machine you select what code it should run. And one existing type of state is the **Switch Object** state. What this will do, is it simply activates / deactivates game objects in your object structure. So you could for example use this to enable a particle effect node and thus make your object *burning* or switch to a different mesh object, so that it looks broken.

For example the *Retracting* state deactivates all objects under the *States* group, but enables the *Retracting* object:

![Retracting State](media/devlog2/ma-dl2-image2.png)

My trap prefab looks like this:

![Trap Layout](media/devlog2/ma-dl2-image3.png)

On the root node, there is my state machine component.

Directly attached to it are the base mesh (second to last node) and a trigger (last node). Inside the *States* group there are the four different groups that represent the different states. For example, the *Dormat* group is just empty.

The three other groups add the spiky mesh and the *Firing* group additionally adds area damage.

Now as I said, for the animation one should use a skinned mesh and just play animations, but to achieve the up/down movement without this, I used the [Slider component](../../docs/animation/property-animation/slider-component.md).

Also I added a new *Reset Transform* component, to make the sub-objects move back into place each time. So now the *Retracting* and *Firing* group uses these components:

![Components](media/devlog2/ma-dl2-image4.png)

All this gives you the visuals.

Now on the state machine you have *transitions* (the arrows between the states). For each transition you again have to choose the "type". The transition type determines the logic to decide whether the transition should be taken or not. So you can have complex logic here.

To start, I just used the **Timeout** transition, which would just cycle through the states with a 1 second delay.
This is fine for all transitions, except for the one from *Loaded* to *Firing*. Here we only want to transition when a creature walks into the trap.

To detect this, I decided to use a [physics trigger](../../docs/physics/jolt/actors/jolt-trigger-component.md).

First this meant that my creatures need to have some kind of physics representation, which the physics trigger can detect at all. So I added a kinematic capsule shape, which just moves along with my creature (they currently don't use a character controller).

![Creature](media/devlog2/ma-dl2-image5.png)

Now whenever the creature walks into a trap, the trigger in my trap prefab will fire an event. However, so far this event won't have any effect.

I need to hook up the event from the trigger to my state machine. And this is what [visual scripts](../../docs/custom-code/visual-script/visual-script-overview.md) are really good for.

On the root node of my trap, I added a *Script Component*:

![Script component](media/devlog2/ma-dl2-image6.png)

The script is quite trivial:

![Script](media/devlog2/ma-dl2-image7.png)

Whenever the trigger fires, the script's *OnMsgTriggerTriggered* node gets executed. We then switch over the state and only react to *Activated* events. If so, we call *FireTransitionEvent* on the sibling [StateMachineComponent](../../docs/ai/state-machine-component.md) and tell it to *Fire*.

![SM transitions](media/devlog2/ma-dl2-image8.png)

So now we have it. When a creature walks into the trigger, the visual script gets the physics trigger event, forwards that to the state machine and when the state machine happens to be in the *Loaded* state, it will transition into the *Firing* state. The state machine then changes which sub-objects in the trap are active, which in turn starts the spike movement and applies area damage to anything close by.

## See Also

* [Monster Attack Sample](monster-attack.md)
72 changes: 72 additions & 0 deletions pages/samples/monster-attack/devlog-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# MA Devlog 3 - Sound

Today I spent some time on improving and polishing what I built so far.

First I set up sound. EZ uses Fmod, so [following the documentation](../../docs/sound/fmod-overview.md) and my own tutorial video, I created a new Fmod Studio project, downloaded a couple of sounds from <freesound.org> and added sounds for footsteps, the player's projectile and the trap.

Footsteps for the player are quite easy, because the [character controller](../../docs/physics/jolt/special/jolt-character-controller.md) already supports this through [surface interactions](../../docs/materials/surfaces.md). Basically, whenever something needs to interact with a surface, for example a bullet hitting a wall, we can easily spawn a *surface interaction*. Usually polygons are linked to a surface and the surface acts as a lookup table. So if I walk over a stone surface and I want to spawn a *footstep* interaction, the stone surface defines which prefab to use, and if I walk over a metal surface, it may define a different prefab to use.

![Footstep config](media/devlog3/ma-dl3-image1.png)

Surfaces are hierarchical, so both stone and metal surfaces are built on top of the *Default* surface, and as long as they don't define an override, the value from the *Default* surface is used.

Therefore, on the character controller, all that we need to define is the name of the surface interaction, and how quickly those should be spawned when walking or running.

![Footstep config](media/devlog3/ma-dl3-image2.png)

The same system is used by the [projectile](../../docs/gameplay/projectile-component.md) to spawn a prefab when it hits something.

For my trap, all I needed to add was a sound event to the *Loaded* state, so that it makes a mechanical noise when it is ready.

![Trap Sound](media/devlog3/ma-dl3-image3.png)

The next thing I did, was to add code to the player component, so that the player can place traps. This is all done in C++.

EZ has an [abstract interface](../../docs/runtime/configuration/interfaces.md) `ezPhysicsWorldModuleInterface` which you can query from the [world](../../docs/runtime/world/world-overview.md), that gives you access to things like raycasts. To get this interface, you call

`ezPhysicsWorldModuleInterface* pPhysics = GetOwner()->GetWorld()->GetModule<ezPhysicsWorldModuleInterface>();`

This is probably one of the most important such interfaces, since physics queries are so ubiquitously useful for all sorts of game mechanics.

For now I simply use this to check where the player is looking. Meaning, I shoot a ray through the camera like this:

```cpp
ezPhysicsCastResult result;
pPhysics->Raycast(result, pCameraObject->GetGlobalPosition(), pCameraObject->GetGlobalDirForwards(), 10.0f, params);
```
Now I have the point that the player is looking at. The next step is to validate, that one can place a trap there. For now I only do very simply position snapping and some rotation, I don't yet prevent the player from placing traps where they don't belong.
Of course, while in trap placement mode you want to have a preview how things would look like, so I built a copy of my trap prefab, that has no functionality, and I add that to the scene (and move it around) to show where the trap would end up. At some point this should probably also use a [custom shader (TODO)](../../docs/graphics/shaders/shaders-overview.md) for a nice "preview effect", and some sounds when placing traps for better feedback.
<video src="media/devlog3/ma-dl3-TrapPlacement.mp4" width=600 controls></video>
At some point I noticed that when a monster dies, it doesn't dissappear and a lot of bodies where piling up. So now the }*monster component* simply puts itself into a queue when it starts playing the *die* animation to keep track of dead bodies, and I delete the oldest one when I have more than 20.
Finally, I wanted to have a second trap. My favourite trap in *Orcs Must Die* is the one that shoots arrows out of a wall. From its logic it's very similar to the spike trap, so I copied and adjusted it. This is what it looks like:
![Arrow Trap](media/devlog3/ma-dl3-image4.png)
I've added a *Dart* [projectile](../../docs/gameplay/projectile-component.md) prefab and this trap simply uses 24 [spawn components](../../docs/gameplay/spawn-component.md) to shoot a lot of those. This is it in action:
<video src="media/devlog3/ma-dl3-ArrowTrap.mp4" width=600 controls></video>
However, I wanted the trap to shoot arrows three times in quick succession. Because that's way cooler. Turns out, this was absolutely trivial to do with the state machine. All I needed to do, was to copy two of the states a few times and set up a short time delay as transitions:
![Arrow Trap](media/devlog3/ma-dl3-image5.png)
And now it behaves like this:
<video src="media/devlog3/ma-dl3-ArrowTrap2.mp4" width=600 controls></video>
And together they already create quite some mayhem:
<video src="media/devlog3/ma-dl3-Gameplay.mp4" width=600 controls></video>
Finally, in game, it looks and sounds like this:
<video src="media/devlog3/ma-dl3-Gameplay2.mp4" width=600 controls></video>
## See Also
* [Monster Attack Sample](monster-attack.md)
47 changes: 47 additions & 0 deletions pages/samples/monster-attack/devlog-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# MA Devlog 4 - Tar Trap

Today I added a simple trap that just slows down monsters when they walk over it.

<video src="media/devlog4/SlowDown.mp4" width=600 controls></video>

As always, there are many ways how one can implement this. I chose to simply do a raycast at the monster position downwards and find the physics object beneath their feet. From there I could use the [surface](../../docs/materials/surfaces.md) to determine whether to slow down the monster.

That means that my trap needs to actually contain a [physical collider](../../docs/physics/jolt/collision-shapes/jolt-collision-meshes.md) object, so that the raycast can hit anything. Therefore my trap now looks like this:

![Tar Trap](media/devlog4/TarTrap.png)

Here I use a [query shape actor](../../docs/physics/jolt/actors/jolt-queryshape-actor-component.md) rather than a static actor, because I only want raycasts to be able to hit this collider, I do not want any other objects (like my player's character controller) to collide with it.

But that also means that when I do my raycast, I need to make sure to include *Query objects*:

![Query Objects Flag](media/devlog4/ma-dl4-image1.png)

Now this kinda worked, but I ran into the problem that the raycast would often not hit the ground, but the monster itself.

To show the problem, in the video above I enabled the skeleton collider visualization for the blue monsters. Those are the animated shapes that are used for shooting the monsters. My raycast would sometimes hit that and then not detect the correct ground type.

I had to set up proper [collision layers](../../docs/physics/jolt/collision-shapes/jolt-collision-layers.md) and assign the right ones to the monster shapes, the ground shapes, and the raycast.

Unfortunately this isn't fun, because there is a limit of 32 layers and you have to be very careful how to set them up, because their number can quickly grow.

For now my setup looks like this:

![Query Objects Flag](media/devlog4/CollisionGroups.png)

For debugging purposes, I wanted to see when exactly the monsters are slowed down, so I used the `ezMsgSetColor` to just dim the monsters mesh color when slowed:

![Set Color Msg](media/devlog4/ma-dl4-image2.png)

Finally, I exposed the health and walk speed as properties, so that I could configure my two monster types slightly differently.

In C++ this macro block is used to declare which variables are configurable:

![Component properties](media/devlog4/ma-dl4-image3.png)

And then these show up in the editor:

![Property UI](media/devlog4/ma-dl4-image4.png)

## See Also

* [Monster Attack Sample](monster-attack.md)
19 changes: 19 additions & 0 deletions pages/samples/monster-attack/devlog-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# MA Devlog 5 - Level Logic

Today I've looked into doing some of the level logic. Meaning when monsters are spawned, when you win or lose, and so on. I've also added that placing traps costs money and you only have a limited amount of money.

It's all working, but just so, and I'm not happy enough with it, that I think it already makes sense to dive deep into any of it. However, I can post some teasers at least. Here is the state machine for my test level:

![State Machine](media/devlog5/ma-dl5-image1.png)

Since the game is very linear in nature, working with state machines makes a lot of sense, and makes my life indeed much easier. I can create building blocks for the different phases, and if I ever build multiple levels, they can be easily re-used in different configurations.

For example here is the visual script that is used by the *Countdown* phase:

![State Script](media/devlog5/ma-dl5-image2.png)

It's using the cool new *coroutine* feature to run a script across many frames. It literally just counts down a number from 3 to 1 and then ends. In parallel the *Update* hook makes sure to print that number to screen every frame. Quite neat.

## See Also

* [Monster Attack Sample](monster-attack.md)
Loading

0 comments on commit edd6ae2

Please sign in to comment.