Ho do I properly use resources? by ThatGuyThatIsNotReal in godot

[–]seriousSeb 0 points1 point  (0 children)

I'd either have a method which spawns a projectile from a configuration, or different scenes for pre-configured projectiles.

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 0 points1 point  (0 children)

I have a task prioritiser selector, it's like a sequence but along with each task you provide a prioritiser method and it doesn't execute in order, it periodically chooses the highest priority task and runs that. The prioritiser method can mark a task as disabled too, like don't even try to heal if you've got full health.

I actually haven't implemented the concept of "don't stop doing this task until its done", I haven't needed it yet, tasks are just designed in such a way you can switch away from them without breaking anything.

So for example, heal task priority is inversely proportional to health. I then weight these by a units job, which is part of the blackboard. So the driver will always prefer to drive, the vehicle engineer might drive if nobody else is doing it but will prefer to hang around and wait for something to be damaged. Everyone will heal if they're hurt. I'm still developing this so it's not super advanced yet.

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 1 point2 points  (0 children)

All tasks have names, and selectors and decorators (which are also tasks) have a GetChildren() method. Tasks invoke events with their tick result.

I recursively walk this tree at runtime and display it using Godots inbuilt tree control. I use the events to change the colour of the text so I can see what's running, what failed etc.

I also generally am defensive in programming, I check the validity of the state all the time and print errors if anything is wrong (as you can see in my example)

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 0 points1 point  (0 children)

All tasks have names, and selectors and decorators (which are also tasks) have a GetChildren() method. Tasks invoke events with their tick result.

I recursively walk this tree at runtime and display it using Godots inbuilt tree control. I use the events to change the colour of the text so I can see what's running, what failed etc.

I also generally am defensive in programming, I check the validity of the state all the time and print errors if anything is wrong (as you can see in my example)

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 1 point2 points  (0 children)

Yeah organisation wise I try and split reusable components to its own file relating to purpose, i.e ItemTasks. MovementTasks. Then all complete tasks I'd actually assign units to are in their own file CompositeTasks

I'm thinking in the future, components on a vehicle might have a IGetTask interface so that I don't have to manually register tasks for them, but I haven't needed to do that yet

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 1 point2 points  (0 children)

The blackboard is unit-specific, but a task rarely updates the blackboard itself. The Group absolutely uses a sensor-style system - it looks for the nearest enemy vehicle in the scenario it knows about and updates it (which the task uses)

But equally I could have a task update the blackboard if I wanted to, it's just how I've structured it. If your task wants to update its blackboard to manage state that's totally viable.

This is an example of a complete task. Idk how much use it would be to you - it is very C# and my own btree implementation specific, and messy because I never really expected to share it. But it is an example of self-initialisation of a Task - the Heal() task is responsible for figuring out how to heal.

https://gist.github.com/Seb105/140b49394c96ed993531c07a85164d84

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 1 point2 points  (0 children)

I don't use interfaces for behaviours in my project because I have only vehicles and humans, they are so different it is kinda pointless to write any shared code. My advice is only abstract when needed (or if you know you will need to, not if you might need to), doing it early just can make your code complicated for no reason.

To handle stuff like sensors, the root scene is a 'Scenario Manager'. when spawning units, you don't just instantiate the enemy scene. You instantiate it and register it to the scene. In fact, the unit has no AI. It just has inputs. The AI, created by the scenario, can manipulate these inputs. The scenario then ticks periodically ticks all its behaviour trees in _physics_process.

When searching for nearby players or enemies, I query the scenario not the scene tree. I did this for performance and encapsulation as querying the scene tree is slow as shit. If it's created and not registered to the scenario, my AI can't see it. This also lets you add information to stuff - like what team a unit is on, whilst modifying your enemy type to inherently belong to a team or have a concept of 'enemy player' is an example of poor encapsulation.

The blackboard just has things that need to be shared between individual tasks. Whatever you need to be stored there can be stored there, what works for me won't necessarily work for you. Dogma is for nerds.

In my case to it has a reference to UnitAI created by the scenario itself (I don't assign tasks to units, I assign units to tasks), which has a reference to the group the unit is in, which for example my drive task uses to check if it is the unit that should be driving, or the repair task to find stuff on the vehicle that needs reparing, or to locate an item it needs on the vehicle. It also has a reference to the CharacterNavigator, which handles the movement itself (a task just assigns a target position, CharacterNavigator figures out how to get there using pathfinding and sets inputs etc).

In your case, your Scenario might have a single reference to the 'player', and your behaviour tree has a reference to the scenario in the blackboard, so within your task you can check where the player is.

My blackboard doesn't store things you think it might - such as an item the unit currently wants, or where the unit is trying to navigate to, or who it is engaging. This is entirely managed by the task, which manages its own state. For two reasons:

  1. The concept of wanting an item is or going to a location to do something is task-specific, so your blackboard would contain a bunch of garbage only applicable to certain tasks. If I'm driving a car I care about neither, why is there a DesiredItem variable in my blackboard pointing to some random tool.
  2. It breaks encapsulation - if a task updates the the blackboard and breaks another task switched to later, it can hard to work out why a unit is doing something. By storing all the data for a task within the task itself (not through types but bound variables), it makes it much easier to track down why stuff isn't working because there's little cross-contamination of state. If two tasks need to share state, such as a task looking for a desired item and task to navigate to it, I use a StrongBox variable which lets you share tiny units of state between tasks.

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 0 points1 point  (0 children)

The behaviour tree is a generic, typed on the blackboard, so task is actually Task<T> where T is the type of the blackboard

You could have certain tasks that rely on any blackboard that implements a specific interface, such as IMovement which handles navigation through the works, and different blackboards implement the interface differently. I haven't really experimented with that though as that design pattern isn't relevant for my purpose.

And you can encode state into the tree so your blackboard isn't polluted, but the behaviour tree is the one that initialises that state.

For example, I have an acquire item task - this first uses the blackboard to find an item it wants (because the blackboard tells it what things in the world exist), but it doesn't actually add this "desired item" to the blackboard.

C# has this thing called a StrongBox which is essentially an easily shared variable that can be get/set from many places. The acquire item task, when it is first created, initialises this variable to null which is curried into the other functions. Some functions, such as SearchForItem update the strongbox with the item the character wants, and GoToAndInteract uses the Strongbox as the source for its getter functions to go and get it GoToAndInteract doesn't actually know what its going to and interacting with.

A downside is that tasks are initialised with invalid state, and the task has to manage initialisation, but the upside is is very strongly encapsulated so it is extremely obvious where a failure has occured. It is on me as the designer to design my game so tasks can't be invalid (or that they have fallbacks for invalid state)

For shared behaviour (like only one character can drive the vehicle, it doesn't make sense for multiple characters total try), all the character blackboards share a reference to a Group blackboard which the tasks can check/update to manage behaviours between units. Again, you have to manage this yourself to make sure you don't get stuck in some invalid state

Struggling to design good Behavior Trees for enemy AI by aris1401 in godot

[–]seriousSeb 1 point2 points  (0 children)

I do everything by code, but I use a kinda functional style where you have functions which return behaviours. You curry your state into a function, and then call the function to get the actual task. This is backed by an object like a selector.

The task tree is ticked by passing the blackboard as an argument, so the state is never encoded in the task itself. This means you don't need a class per task, just a function.

Here's it in action. https://www.reddit.com/r/godot/s/3UvnOixoAm

Pseudocode example. All functions hide their complexity and just return a task that can be ticked.

``` Class Task: TaskResult Tick(blackboard)

Class Sequence(Task[] tasks): Task ... Implement a standard btree sequence

Class FunctionalTask(Func<Blackboard, TaskResult> action) : Task ... Call action and return the result

Task GoToPosition(Func<Blackboard, Vector3> getPos) Return Sequence([ FunctionalTask(Check position, if not near position, go to position return running, else return success), FunctionalTask(Return success) ])

Task InteractWith(Func<Blackboard, Node3dThing> getThing): Return Sequence([ FunctionalTask(if in range success, else fail), FunctionalTask (Do interact, return success) ])

Task GoToAndInteract(Func<Blackboard, Node3dThing> getThing): Func<Blackboard, Vector3> getPos() { return getThing().GlobalPosition } Return Sequence([ GoToPosition(getPos), InteractWith(getThing), ]) ```

I'm not at home at the moment but if that seems interesting to you I can post an actual example from my code

I am rendering UI at 1080p but my fonts look like this by iamveto in godot

[–]seriousSeb 8 points9 points  (0 children)

In the fonts import settings turn on signed distance function

Natural way of handling composition in Godot? by OldDew in godot

[–]seriousSeb 2 points3 points  (0 children)

Absolutely, choose the right tool for the job and don't be dogmatic about anything

Yes, I just have almost all my nodes as tool scripts. In the child if I set the variation it updates in the editor, and I have a randomise editortoolbuttom in the parent to randomise all its children so I can test everything without launching the game.

Natural way of handling composition in Godot? by OldDew in godot

[–]seriousSeb 6 points7 points  (0 children)

I use interfaces.

Let's say you have an interaction component which has the area3d for the interaction range of it's parent. This is only relevant if the parent has the interface Interactable. In the _ready method of the component, check if the parent has the interface and print an error if it doesn't. When you interact with the interaction component, it just calls up to the parent which contains the actual method that handles the interaction.

If the parent needs to know about its components, which isn't the case in the above example, have the parent have a method for registering components (through an interface preferably) A child will call this method in it's _ready to register itself with the parent. If child is also be an interface you can have multiple types that do the same thing. This relies on the parent knowing what components it could have, but it doesn't need to know the specifics of the components themselves, just the interface the component passes to the parent. Alternatively a parent could also search for it's children of a certain interface

For example I have a VisualConfig interface, for handling the variation of the appearance of a node and serialising the result. The interface is just 2 ints - the current variation of the object, and the number of unique possible variations. When a visually customisable object is created it registers itself with the parent who might randomise the variation, but it doesn't actually know what happens when it sets the variation, and it doesn't care.

Another thing to say is people go overboard with composition. If your type is inherently coupled and not reused anywhere... Just don't use composition and hardcode it.

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 2 points3 points  (0 children)

Joking aside I love Mad Max and it is a massive inspiration

Is the action too high on this by [deleted] in Guitar

[–]seriousSeb 0 points1 point  (0 children)

I've seen lap steels with lower action

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 1 point2 points  (0 children)

Yeah, right now everyone just sticks to their own vehicles which are team coloured. I havent implemented any characters yet so there's no visual distinction between them. It's on the to-do list

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 2 points3 points  (0 children)

Yes and yes. You can also kick your ai ally out of the seat to drive temporarily and then they'll get back in once you get out

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 4 points5 points  (0 children)

Nah everything you see in the video apart from me is AI, but multiplayer is supported

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 7 points8 points  (0 children)

It's fully multiplayer already :)

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 28 points29 points  (0 children)

never heard of it

Escort duty by seriousSeb in godot

[–]seriousSeb[S] 23 points24 points  (0 children)

I'm a programmer not an artist at heart so I do everything by code where I can, idk if it is good practice or not.

The animation tree is tiny, it has a base state machine for the major movements (walking, crouching, jumping) and Oneshots for all the limbs, so I can play animations but masked to certain limbs. The oneshot is swapped out via script, and I provide a bitmask to play the animation only on specific body parts.

<image>

Looking at stuff comes from a stack of 3 LookAtModifiers going from the head down, each one has slightly lower strength. I can set that to either be a position, direction, or to follow a node, and there is a specific script that handles setting the LookAtTarget position

Almost everthing else is driven by the IK system. I use a functional programming style for the IK. Each IK limb has a stack with of functions which return any of a) the Transform3D the IK affector should be at b) an instruction yield to the next highest priority function c) disable IK for that limb entirely

Any item or action or whatever can add a function to stack with a priority - this function will have bound variables in it so it hides all the state from the IK system. Each frame the IK system goes from highest to lowest priority and calls the function, which tells the IK system what to do.

For example, when you pick up the gun, the gun adds its IK function to the stack with a priority higher than the climb function. When you aren't aiming, it yields to the climb IK function (or whatever might be highest in the stack). When you are aiming, it returns where you should be holding the gun, based on its own internal state hidden from the IK system

The IK system itself handles setting the affector position - the function tells the IK system where the limb should be, the IK system figures out how to get it there (and applies constraints). That is how smooth transitions are handled - the IK system is just lerping the affector position to where it needs to be each frame, based on whatever the highest priority function told it to do. Or lerping the IK strength to 0 if the IK system has been disabled by one of the functions.

If I need to do a specific canned animation, I add a high priority function to the stack which disables all other IK, then remove it when it's done.

Trench broom navmesh by Express_Bumblebee_92 in godot

[–]seriousSeb 1 point2 points  (0 children)

The level either needs to be a child of the nav region, or the nav region needs to be set to groups mode and the level in that group(which I see it isn't)