In the last part of the series we have created a possibility for the NPC units to attack the player. This time we are going to start building a card system, so the player can fight back :)
The source code for this part can be found at: https://github.com/maciekglowka/hike_deck/tree/part_06
Card trait
Cards in our game are going to be simple entities that will let the player execute various actions. Therefore, the most basic card mechanic should produce a valid Action
object, which later can be injected into player's Actor
component.
As we did with the actions, we are going to define this shared behaviour by creating a common trait. Let's start by adding it in a new cards.rs
file of the player
module:
// /
::::*;
::::;
::::;
: +
#[derive(Component)]
;
The Card
trait defines (for now) only a single method - that is going to construct the desired Action
object. It requires two parameters: the player's entity and an optional target coordinate (eg. an attack or movement direction).
As we are going full ECS here, we are going to spawn the cards as standard Bevy entities. That's why I have also created a CardHolder
component, that will store the assigned card logic.
So let's now define our initial cards for both walk and melee actions:
;
;
As you can see the implementation is pretty simple. Just take the parameters and build the expected action object. There is one important difference between the two structs though. The MeleeCard
contains an extra numeric parameter, that defines it's damage value. This will allow us to make different attack cards (with different strengths) using the same base struct. Also in the future we can create an upgrade system that will bump this value up.
Now we can spawn some actual card entities. We are also going to create a resource to hold player's current deck:
;
Deck logic
Now that we have our deck data all set up, we can implement some card operations. They will be controlled from the Input
module, that ideally the Player
should now nothing about. So, we are going to communicate the two with an event:
;
// mark that the player is ready to execute game action
;
Apart from the DeckEvent
we also define a PlayerActionEvent
- it will be emitted by the Player
module, after an action has been chosen and the turn update loop can start.
Now we can build two systems that would handle those events on the player's side:
// register systems and events in the plugin impl
app.
.
.add_system
.add_system
.add_system;
// cut
The first, select_card
system is solely responsible for setting the active card in the deck resource.
The dispatch_card
system is slightly more complex - it prepares player's action for the execution. Once we get a valid UseCard
event, we look for the player's Entity
and Actor
component. Then we try to get the current card from the deck and query it's components. If all goes well we can create the desired action (although this system doesn't even know what this action is!). The action object is then injected into the Actor
component. Finally we let everybody interested know that the player is ready to start the turn.
Player input
In order to use the cards, we should change our Input
module so it can control the deck. We do not have any UI at the moment, so we are going to implement a very basic, hard-coded card selection system for now.
// input/mod.rs
use *;
use crate Position;
use crate;
use crate GameState;
use crate Vector2Int;
;
const DIR_KEY_MAPPING: = ;
We are almost ready to test our player attacks. The only thing left is to switch the game state into TurnUpdate
after we choose player's action. Up till now it has been controlled by the PlayerInputReadyEvent
- but we have just dropped it. Instead we are going to use the aforementioned PlayerActionEvent
. So let's import it into manager/mod.rs
and change the appropriate system registration:
.add_system
It is a small change, but it actually makes our game flow a bit more consistent. It is the actual game logic (inside of the Player
module) that pushes the turn update forward - not some possibly random events from the input handling system.
We can now run the game and try to attack the NPCs when they reach us. Note though that at the beginning no card would be active - so you have to press 1
before you move (and 2
if you want to attack). Once we create some proper UI we are going to make this more clear, but now for testing it should be sufficient :)