brave-frontier-godot
brave-frontier-godot copied to clipboard
Battle System - implementing discussion
Refers to #16
So to create a Battle system, I was thinking on, at first, have the unit deals damage to others unit (depending which are selected).
Only a base on HP - ATK (for the start. When it's fine later, we could add the DEF into equation)
For a normal quest, we used to have at least 2 or 3 round of enemies. So if we can have those round, I would add a auto-healing to the player party. Because no healing spell or healing drop exist now. (And damn if we don't kill fast, we could loose 1 or 2 character each round)
And at the end of each attack/animation, the game would look at the hp of the attacked unit and make this unit dead or let it alive. Once it's dead, it can't move and can't be focused. (Later on, or during this development, we should add a death feature which makes the unit disappear from the stage)
If you have any suggestion or have any point of an early battle system I didn't mention, feel free to comment bellow !
For a record (no PR yet), I'm trying to update the function attack
of the file field_unit
.
It would be in this function that the unit would take any damage. And so, we would pass to this function the unit resource that is attacking AND the unit that is taking the attack.
As far as now, it looks like the Resource unit I pass to the function would be the same for every unit with the same origin. So, for the actual basics_of_battles
with the 4 Selena (basing the test on the branch turn-system
), all 4 Selena have the same stats. So if a friendly unit attacks one of them or make one get down to 0, they would all take damage/die.
This or I get the target unit wrong 🤣
Anyway, until the PR, here's a preview of the code I've update for this :
Edit : because I'm bad with formatting, I'll do a PR anyway Edit2 : See PR https://github.com/aMytho/brave-frontier-godot/pull/21 Edit3: it looks like we have an answer here ==> https://godotengine.org/qa/25521/what-does-the-local-to-scene-option-on-resources-do-mean. i'll try that, hoping everything will be good
I still don't have the answer for the shared data for now. I tried using the "local to scene" of each Resource. But didn't work ? @aMytho if you have any idea !
It sounds like it is a local-to-scene issue. I had a similar problem with something else earlier.
My understanding is that there are two flags that can share data across instances. One is "something unique" and the other is the local to scene which you mentioned. Resources are shared across instances so we do need one of those active.
Have you tried making it local on the scene itself, and on each instance? Enemies.tscn
should have each field_unit
instance(without any data) already loaded. Maybe you could make those local as well. If that doesn't work, we may need to check it on the unit resource itself.
Where do you think the battle/stat logic should be located? Initially I was thinking it would be within the battle.gd
script or in its own script.
The only thing I want to avoid is having to sync multiple copies of each unit. (ex. We don't want to have to keep a stat file with all units, field units
, and the units in battle_ui
synced together). Ideally, the logic is in some parent node and we just pass down the updated values. The child nodes don't care about the state of the unit, they just update when the parent says the state changed. Passing in a unit to lower nodes is fine, but they only keep the data that is physically displayed.
Let me know if this makes sense.
I'll get the turn-system
branch merged to main in a few minutes.
I might have found an hint about what is happening. The problem with data shared show up in 2 different way : when I click on a unit, it focus on one unit. And the same goes for when I don't focus any unit. I will try to read again my code and see if it was my mistake or not (hope it was just a simple mistake)
@aMytho For the place where we should have the stat logic, maybe in a different script so we can purrify the battle.gd without having too much line in there. Don't know if it's the better option here or not. And I might not have the visual for the Ideally part of your message, so I don't understand the problem you're thinking about.
I don't wanna edit the previous message so here is my observation (really fast) : While printing the unit who is under attack, I see it's the same Ressource for every same unit.
So it might be a local_to_scene issue, which is weird because the "local_to_scene" issue is checked approximatly EVERYWHERE. Or it might be my use of the unit. I'll try to get more information
Edit : i'll try to add a unit I already have in my team, to see what's happening with them
Ok. I think we can store the logic in its own script then. If it seems alright we can stick with that, if the communication between nodes gets too complex we can put it with battle.gd
.
The main thing I was trying to avoid was managing the same unit across multiple different nodes. We would want one single node/script to handle the stats and nothing else. The other nodes (field_unit, battle_ui, etc) simply react to events in the fight. This might be an animation, status update, or other change. However, they only display the data. They don't need to check their own stats.
Example: Unit A dies. some_uncreated_script noticed that the HP was < 0. This script emits a signal or calls methods for other scripts. Field_unit disappears (death animation), batttle_UI displays the darkened border indicating that a unit dies. These scripts reacted to the change, but neither had to check their own values to do so
The local-to-scene issue seems pretty strange. Could it possible have something to do with the value being passed through the various functions? Maybe changing it on one script also changes it in others? I'm not sure if godot passes by value or reference, or if this is even related. Just trying a few different ideas.
@aMytho (sorry for all the message and spam !)
I try to add a unit already existing in my team (Selena).
And the print in console at the load of the team selector show me 2 same Resource ID
But the settings for the unit is already set at "local to scene" checked. So I'll look a bit more inside. Maybe it's at the Lookups.get_unit_by_ID that I could have an idea of what is going on
For the part about node listening (I feel like we can consider them as listener), I understand now ! So I see now the problem you want to avoid 👍
OK I have an idea on how to fix.
For the problem with the account unit, we should use the .duplicate()
function when generating a unit. So the unit in our team shouldn't have the same Resource ID.
I'll inspect how the enemies unit are loaded to do the same fix (if it's possible)
Edit : the generation for our unit, I'm talking about the Lookups.get_unit_by_unit_number. Adding .duplicate at the line of the load make the unit have another Resource ID, so, i believe, have different stats 🤞
Many messages are fine, we are just trying to solve the problem 😄
If you can use the duplication per resource, it may be a good idea to just do that in ResourceLookups
. That way every unit loaded is unique.
Edit: nvm, you just did it.
Can you confirm for one thing : for the area selection, when you select a Zone you wanna enter, we get all the data of the stage (like we juste get all of the data of the .tres without touching a thing) when pressing a level ?
Like here :
If yes, we might have to do calls to the resource_lookups in 3 possible place :
- level_select -> loadLevel
- level_select -> _on_player_ready
- battle -> load_next_stage
I believe the battle.gd is the better option, because we can slowly load the enemies unit. And so like that, we can replace the resource unit before they are placed in the area. (not sure about the last part, need to look up all the work)
We load the enemy units from the stage. This is selected in the level-select
(technically a child scene selects it, but it is emitted up from the child to the parent level_select
). The screenshot is the scene where that happens.
The level select then uses the root game content node to load the battle scene and pass in the units. We can probably add a method to the Lookups
script that duplicates whichever unit is requested. That way we don't have to change any other code.
Alternatively, we can loop through the stages and duplicate each resource in the _on_player_ready
function. The battle scene shouldn't have to do any of this since its sole focus is dealing with combat/animations/state.
Edit: I'm currently reviewing the context switcher PR. Once that is done I can look at it more in detail.
I was trying to do something in battle.gd
(load_next_stage
) by doing something in for and outside :
enemiesList.append(Lookups.get_unit_by_unit_number(unit.unit_number)["unit"])
then
zone.Stage[stage].monsters = enemiesList
But it tells me i can't
I've assumed, in the actual stage, the monsters where stored in array, but it isn't like that. I'll move the location of what I'm doing and try to see what I need to be change
@aMytho OK I found a solution 🎆 🎆
In level_select.gd
, this is what I have done :
func _on_player_ready(units): # Stop the music get_tree().root.get_node("Game").get_child(0).emit_signal("stopPlaying") # Get the stage and load the battle! var arg_names = ["units", "zone"] for stage in zone.Stage: var enemiesList : Array[Unit] for unit in stage.monsters: enemiesList.append(Lookups.get_unit_by_unit_number(unit.unit_number)["unit"]) stage.monsters = enemiesList print(zone) var arg_props = [units, zone] emit_signal("Closed") get_tree().get_root().get_node("Game/GameContent").loadSceneWithProps("res://Battle/battle.tscn", arg_names, arg_props, true )
AND the Resource ID of the enemies unit aren't the same and we the HP we are dealing to aren't the same Yey 🎆
Edit : i'll push what is done soon so you can have a better visual
@aMytho you can see the code in the PR https://github.com/aMytho/brave-frontier-godot/pull/21 (still in draft)
I'll try to move the battle stats system in another file and make the unit dead when HP <= 0.
For the dead state, I should see after each attack of unit if the attacked unit has enought HP left. If not, they will have a "is_dead" stat at true and won't be able to be selected for an attack. (and maybe i'll change the enemies unit in the first stage because we can't stay alive against 4 Selena 😆 )
Great!
I'll take a look at it later tonight. I made 2 suggestions for #19 , and I'm currently making a PR to fix a bunch of the errors/warnings across the project. Its a little messy to look through all of the extra noise so I want to clean that part up.
Your idea for the dead state makes sense. You will probably have to update the lockout function to check for the new value as well. Currently, it only checks if a unit has attacked to determine if it is allowed to be pressed.
Hello @aMytho !
Last message of the day (sound sad writing that at soon 3am 😆)
I've made a decent amount of update in the code which is resumed (but isn't only) in the following list :
- A big update about the random targetting of a unit. I've mixed the verification of if the field_unit with if the target unit is dead or not. If the target is dead or if the target isn't a unit, we remove the child (this is LEGACY).
- The stats script has been placed in the
stats_checking.gb
file. This file is placedbattle.tscn
file and the function are called in thebattle
file. I don't know if the way I implemented it is cool by you or if I should change some things - As stated before, I've updated the way we get the allies' and enemies' resources so we can have different instance of the same character in a team
- Now, when unit (enemy or ally) have their HP at 0 or less, they can't fight and don't move in the scene. They don't lock the fight and can't be focused by random. (there is a problem with focus sadly)
What need to be done after this :
- Add new stage in the zone we currently have. So we can try the "next stage" feature and the reset of the battle scene
- Remove the force stop when it is the 3rd turn
- Use the def stat to reduce damage taken
- When focusing on an enemy's unit, show their actual health bar
- Maybe rewrite/comment/document the code updated so people wouldn't be too much lost
What problem do we have right now :
- For now, it looks like when I'm playing, I always have the last unit (last in number) on my team who can't die. It looks like I miss a verification for them, and so this unit can't take any damage. To investigate
- When a unit is dead, it appears we can focus it. Which isn't intended. So to investigate
That's all I think it has been done with the PR #21. If you have time to pass for a review at any time, I would be glad to take in your review !
Because tomorrow (I should say today now) is a closed day for work, i'll try to work a bit more on this feature during my day !
Have a good day/night !
I briefly looked through it. I have a few thoughts
- Battle.gd Line 61. Currently, it checks if a unit is dead to allow the attack. This works, but it would be better to prevent the player from attacking in the first place. You can probably dim the border and prevent the unit itself from being clicked based on the state.
-
- Possibly have the stat checker return a value to indicate if the unit is dead (
battle.gd
would call this and updatebattle_ui
) or have it emit a signal that contains theplace_id
of the dead unit.Battle.gd
could listen for that and then update thebattle_ui
. See theres://Battle/UI/unit.gd
file.
- Possibly have the stat checker return a value to indicate if the unit is dead (
- The stats script is good. I think that will work for holding the current state of the units.
- The current system should allow all stages to be playable. It won't "end" the fight, but the first entry in the first area of Mistral has 4 stages to work through. When completed, a message is logged to the console. 4 Selena's make it rather hard to reach though...
- More documentation/comments is always good 👍
Hello @aMytho !
For the signal part to make a unit die, i've push a new commit about it. You can see it here ==> https://github.com/aMytho/brave-frontier-godot/pull/21/commits/ffd4de9906ec1d922de0d038c66f1d7d18c82408
I'll now go for other parts :
- Correct the unkillable friendly unit. I don't want it to be dead, but kind of
- Make the unit sprite disappear when they die
- Add (at lease) a second stage to basics_of_battle so we can see new unit during the test
-
- At the same time, get rid of the "threee turn = fight end". Or maybe make it so the user can go back to the home menu at the end of a level
- There might be a problem with the dead unit system. Not full proof yet so i'll also see what problem there are
Have a good day/night !
Hello (again) @aMytho !
Another commit for the Battle system ==> https://github.com/aMytho/brave-frontier-godot/pull/21/commits/851a9339997a35543a49624dbe5c1badd979c01e
For this one, here's what I added :
- 2 more stage in the basics_of_battle level. So now we have 3 stage to play in there instead of one
-
- That allows me to try the code I've been producing since yesterday. It doesn't look bad I would say ? By that I mean the second stage is loaded and the fight can continue. So it works for now
- An update on how the random unit is selected.
get_target_position
has been transform intoget_target
. So it allow the game to check the selected unit ID to get the good unit that will be hurt. -
- The position is, then, retrieve in the
_unit_attack
andrun_enemy_turn
to be used
- The position is, then, retrieve in the
- When we loose a stage OR we win a level, we are imediatly transported back to the main-menu scene. See
_when_battle_end
function inbattle.gd
-
- We can add later on the loose and win screen. But now, we won't be lock in the eternal suffer of a battle 😄
- For a global note : I've updated some way to get the atk and def unit so we don't get a null value when we don't want it
What could be the issue for now :
- When an ennemi unit dies, it emit a dead signal for the battle.gd to tell the BattleUI it's dead. But I believe this shouldn't be the case because BattleUI only have friedly unit in it. Instead of blocking the enemy unit to play and say it's dead, the friendly unit at the same number is considered dead and can't attack. That blocks the turn. So we need to fix this. (pretty sure the problem is in
stats_checking
->areUnitsDead
) - When a unit die, it doesn't disapear. It's not really a bug, but something that I haven't implemented yet
That's all for what I needed to tell. If anything, you can try the code yourself and see if it works fine for you ! (for testing, I've update manually (directly in database) my unit team so I can have 4 or one unit in the team).
I'll try to write more comment/create a documentation page of what is happening during a full turn of a fight (friendly unit + enemy unit turn)
Have a good Day/Night !
Hello @aMytho !
I think it's the last commit before commenting/documenting the code ==> https://github.com/aMytho/brave-frontier-godot/pull/21/commits/824047d84b50972185e27284d68889fd7c01cb47
What is done by this very last commit :
- Make the unit with 0 or less HP disapear from the scene (using hide function). So now, the user have a better visual of what is happening in the scene and know which character are alive or dead. The unit don't have a fade animation yet
- Fix the problem of when an enemy unit was considered dead, the game would block the ID equivalent of the ally unit (yeah, it's all on me for this bug)
So now, a little summary of what is done with the PR #21 :
- Unit can use their atk stat to hurt their enemy's stat. It's a basic HP - ATK done. Nothing fancy for now
- When a unit dies, their sprite disappear from the scene and they can't be selected again to fight
- When a battle end (by winning or losing), the user is immediatly transported to the home menu (see
battle.gd
._when_battle_end
and their call for more information) - A fix about the Resource generation where the same type of unit would share the same data
Now, I don't think there'll be more commit for this one (it's pretty generous with features and have made a small base where we can continue working on), so i'll open issues on each feature we can make. I'll first list them :
- Add a certain time between each stage --> At the moment, when you kill the last unit of a stage, the game immediatly load the next unit and show the loading scene screen. But the sprite of the last enemy unit (which is the last on killed) is still remaining. Maybe we should add a time for the disappearance of the sprite + the loading screen (will be explain further in issue)
- Add the WIN and LOOSE screen --> Whenever the user win or loose a fight, it's imediatly send to the home menu. We should add a WIN or LOOSE screen to at least not have a brutal change for the user
- Add stats visual in game --> when a unit take damage, we don't know how much left HP they have. Same for the monsters. The health bar is a fake one.
- Add more base stats to units --> when in fight, the day we would implement the heal, we wouldn't be able to know where the limit HP of a unit is. We could see the same with the orbs mechanics. Having a fixed value for HP ATK DEF and REC would be cool. But this could lead in a different way to management of the unit stats (more in issue)
- Change the way the game see if the unit is dead --> for the actual implementation, the game check if the unit is dead at the end of every team turn. When User turn has end, the game look at the Enemy team and see if there are any dead unit. Reverse is done for Enemy turn. Maybe we should seek for another way during the attack
I'll create all those issues and i'll let you sort them or tell if they could be usefull, important etc.
Before finishing, do you have a good place to document how the battle mechanics works, not in commenting but in a page to have a better view of it (all the method that are called, all the action the game would take etc)
Anyway, have a good day/night ! Don't forgot to review the code ! I'll be happy to take all into consideration !
🐧 🐧 🐧
@aMytho Issues created, here're the links :
- Waiting feature between stages ==> Issue https://github.com/aMytho/brave-frontier-godot/issues/24
- Win and Loose view ==> Issue https://github.com/aMytho/brave-frontier-godot/issues/25
- Stats visual + base stats of units ==> Issue https://github.com/aMytho/brave-frontier-godot/issues/26
- Update of death check ==> https://github.com/aMytho/brave-frontier-godot/issues/27
This might be a dumb idea of mine because the PR isn't on main yet, but it's to have them listed ahead and don't forget about them
Again, have a good day/night ! 🐧
I'l take care of the comment of the PR #21 tonight and place some comment in the code at the same time
Now that the base logic is complete, we can start to add sound effects and look into adding more stats to the damage calculation. I'm not sure how damage calculation is done (its likely on the wiki), but sound effects should be pretty simple. I think each field_unit
will need its own audio node. When an attack is played, we can create the sound. The assets folder should have everything we need.
I'm currently working on the win/lose view, so feel free to try these if they seem interesting.