Closed thombruce closed 10 months ago
A lot of work this one, and I haven't even started on the enemy AI. But... I think AI is going to be the easier part here (using big-brain, we just define scorers and actions, then when a score exceeds a threshold take a certain action - since the actions are turn, thrust and shoot, we already have the game logic ready).
I really thought it would be the other way around. The bullet spawning and damage systems would be easy and the AI would be a lot of work, but that's looking to be the exact opposite of the case.
Still todo:
Big refactor when this is done too. I really think separating the module into its components and systems rather than the vague shared scopes we have currently will be beneficial, particularly for systems which have system sets now and need to be executed in a certain order - having them all in one place will be of great use. Compare to the MVC architecture and directory structure of Ruby on Rails; Components are our Models, Entities are our Views, Systems are our Controllers. Our separation might look more like Components, Bundles, Systems, but we'll see. We'll continue to evolve this as we move forward.
No need to refactor before the PR is merged - in fact, prefer not to - but it can be done before or after the release of v0.0.16.
Speaking of ships despawning when health runs out... we'd also like to see two things:
Also useful to see an indicator of the enemy's health somewhere, yeah? And the player's, of course.
Maybe introduce bevy_hanabi for particle effects: https://github.com/djeedai/bevy_hanabi
We're skipping the Hanabi particle system for now. I gave it a shot, but setting it up is pretty involved and pollutes the focus of this PR.
Ships now take damage and despawn when their health reaches zero, so there's just the enemy AI left to do. For that, I'm looking at big-brain: https://github.com/zkat/big-brain
I've added the following notes to a file controlling enemy AI, but it will probably be deleted before commit:
// So, big-brain isn't gonna do quite everything I thought... I still need to write custom flight and attack AI to a degree.
// Flight can be rotate to face player and thrust towards, fire when within a certain distance and turning radius.
// (This is gonna result in the enemy colliding with the player; should prefer to aim past the player in flight, then turn and fire as passing).
// (Perhaps the scoring system above could be used to switch between Pursuit, Align, Attack, or maybe that's more low level...)
//
// What big-brain is definitely useful for is scoring the enemy response to...
// - distance from player (visibility)
// - damage received/dealt - "am I winning this fight?"
// - need for a resupply
// - general hostility (switching from Neutral to Hostile)
//
// Some states we might consider:
// - Neutral
// - Hostile
// - Fleeing
// - Tracking (?) [or maybe just Pursuing]
//
// Needed actions might be:
// - Chase (or Turn & Thrust)
// - Attack (Fire)
// - Flee (or Turn & Thrust other way)
//
// It takes two conditions to meet an attacking (firing) requirement:
// - Distance
// - Alignment
// - (Probably also hostility status)
//
// It might help to think about "Behavioural Patterns" and "Specific Actions":
// Example "Behavioural Patterns": Chasing, Attacking, Fleeing
// Example "Specific Actions": Turning, Thrusting, Firing
//
// The broader "Behavioural Patterns" would govern the selection of more fine-grained AI systems
// comprised of "Specific Actions"
Essentially, big-brain scores different behavioural choices. The actual behaviour still needs to be programmed.
I'm unsure if big-brain Thinkers can be nested, but could be an interesting approach if so.
The enemy ship will sometimes need to be taking two or more "Specific Actions" (e.g. turning and firing or thrusting and turning).
What big-brain seems to be more focused on is organising specific actions into sets of behavioural patterns. See this example:
fn drink_action_system(
mut thirsts: Query<&mut Thirst>,
mut query: Query<(&Actor, &mut ActionState), With<Drink>>,
) {
for (Actor(actor), mut state) in query.iter_mut() {
if let Ok(mut thirst) = thirsts.get_mut(*actor) {
match *state {
ActionState::Requested => {
thirst.thirst = 10.0;
*state = ActionState::Success;
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
}
}
}
}
This is a drink_action_system, invoked whenever the Actor is instructed to Drink
. In the basic case above, the "specific action" is to set a thirst.thirst
stat back to 10.0
and return the state as ActionState::Success
(which probably then removes the Drink
action). So think in terms of behaviours or manoeuvres the enemy ship might need to consider, score these and introduce systems which describe the specific actions of these behaviours/manoeuvres.
If we're being realistic, big-brain may not be needed yet. A more basic system would have the enemy always be hostile, and if it is within a certain distance of the player take aggressive actions. We could then migrate this into a broader AI system with the benefit of big-brain later...
Something to give a little consideration before I finalise an approach here. Whatever way we go, keep in mind that it probably gets converted later to a brain on an enemy "pilot" instead. The pilot might have reason to abandon control of the ship, to attend to other matters internal to the ship... 🤔 This is also true of the player, though I think the separation is more straightforward there (we just switch to a different control scheme); for enemy AIs... do they swap brains? or... does a single enemy brain swap potential control schemes dependent on a pilot status? Unsure, and we're a long way off, but the less deeply ingrained the system is at this point, the more easily we handle those considerations later.
What we want at this point in time isn't actually the high-level decision making of big-brain, but something more like "Steering Behaviours": https://code.tutsplus.com/series/understanding-steering-behaviors--gamedev-12732
I'm going to look over those guides and see if they can help me to implement something...
...and even if they don't, and I implement some other pattern instead... we still don't need big-brain to govern that at this point.
We want a simpler autonomous movement controller that pursues and targets the player. There are no decisions to make or choices to weigh (besides maybe, "am I close enough and aligned enough to shoot?")
big-brain will probably be reintroduced later to govern higher level decisions like, do we need to refuel? does the pilot need to rest or attend to injury? are we in need of ammo? are we losing this fight and ought to retreat? Stuff like that.
Enemy AI will now be handled separately. See #57
closes #53
By submitting this pull request, you agree to follow our Code of Conduct: https://github.com/thombruce/.github/blob/main/CODE_OF_CONDUCT.md
Internal use. Do not delete.