Search Unity

[RELEASED] Turn Based Strategy Framework

Discussion in 'Assets and Asset Store' started by michal-zetkowski, Jul 2, 2019.

  1. Sergeyshall

    Sergeyshall

    Joined:
    Sep 3, 2013
    Posts:
    4
    Hi @michal-zetkowski!
    I found an interesting issue that doesn't know how to resolve. I am using the `CounterableAttackAbility` for the counter-attack that you proposed previously. Still, when I updated the TBS framework to v2.2 I saw that the AI player was stuck when the counterattack destroys the AI unit. My assumption is that it might be related to the unfinished coroutine when the Unit was destroyed before the coroutine was finished.
    Probably the problem is here:

    Code (CSharp):
    1.  public override IEnumerator Execute(Player player, Unit unit, CellGrid cellGrid)
    2.         {
    3.             unit.GetComponent<AttackAbility>().UnitToAttack = Target;
    4.             yield return StartCoroutine(unit.GetComponent<AttackAbility>().AIExecute(cellGrid)); <-- this coroutine unfinished when Unit has a `CounterableAttackAbility` and counterattack kills the AI unit
    5.             yield return new WaitForSeconds(0.5f);
    6.         }
    seems like it is because of pauses in Act of CounterAttack
    Code (CSharp):
    1. public override IEnumerator Act(CellGrid cellGrid)
    2.     {
    3.         // Base unit attack
    4.         yield return base.Act(cellGrid);
    5.  
    6.         // Counter attack if unit is not dead and able to attack
    7.         if (UnitToAttack != null && UnitToAttack.IsUnitAttackable(UnitReference, UnitToAttack.Cell))
    8.         {
    9.             // Pause for a while
    10.             yield return new WaitForSeconds(0.5f);
    11.  
    12.             UnitToAttack.AttackHandler(UnitReference);
    13.             yield return new WaitForSeconds(0.5f);
    14.  
    15.             // Counter attack should not use actions. Return action point spent on attack
    16.             // Return ActionPoints spent in AttackHandler
    17.             UnitToAttack.ActionPoints = UnitToAttack.TotalActionPoints;
    18.         }
    19.  
    20.         yield return 0;
    21.     }
    when I set the pause before the unit destroyed by the CounterAttack the AI goes further
    Code (CSharp):
    1.  
    2.     protected override void OnDestroyed()
    3.     {
    4.         Cell.IsTaken = false;
    5.         Cell.CurrentUnits.Remove(this);
    6.         StartCoroutine(DestroyAfterAnimation());
    7.         Destroy(gameObject, 2f);
    8.     }
    But it produces a buggy behavior of units when, for instance, the unit disappears after the next unit already started its move.

    The question - is it possible to have a `CounterableAttackAbility` with pauses during attack and counter-attack but without breaking AttackAIAction Execute Coroutine that produces the AI move stuck?
     
  2. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hmm, I discussed a similar issue with someone else on Discord, but we didn't manage to fix it yet... Could you export the project and send it to me by email? (crookedhead@outlook.com)
     
  3. Sergeyshall

    Sergeyshall

    Joined:
    Sep 3, 2013
    Posts:
    4
    Yes. Just sent it.
     
    michal-zetkowski likes this.
  4. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    For anyone interested, change AttackAIAction.Execute like this:
    Code (CSharp):
    1. public override IEnumerator Execute(Player player, Unit unit, CellGrid cellGrid)
    2. {
    3.     unit.GetComponent<AttackAbility>().UnitToAttack = Target;
    4.     StartCoroutine(unit.GetComponent<AttackAbility>().AIExecute(cellGrid));
    5.     yield return new WaitForSeconds(1f); //Adjust the value accordingly
    6. }
     
    Last edited: Aug 5, 2022
  5. pzolla

    pzolla

    Joined:
    Apr 21, 2020
    Posts:
    19
    Does this code need to be updated for 2.2? If so, can you provide some insight. Is there now a better way to have the unit turn to face its move direction for each grid?
     
  6. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Yes, it needs updating. The current version uses Unity default rotation, which should make things simplier. I tried it and the code below seems to be working for 3D scenes.

    Code (CSharp):
    1. protected virtual IEnumerator MovementAnimation(List<Cell> path)
    2.         {
    3.             IsMoving = true;
    4.             for (int i = path.Count - 1; i >= 0; i--)
    5.             {
    6.                 var currentCell = path[i];
    7.                 Vector3 destination_pos = FindObjectOfType<CellGrid>().Is2D ? new Vector3(currentCell.transform.localPosition.x, currentCell.transform.localPosition.y, transform.localPosition.z) : new Vector3(currentCell.transform.localPosition.x, currentCell.transform.localPosition.y, currentCell.transform.localPosition.z);
    8.                 while (transform.localPosition != destination_pos)
    9.                 {
    10.                     transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination_pos, Time.deltaTime * MovementAnimationSpeed);
    11.                     var _direction = (destination_pos - transform.position).normalized;
    12.                     if (_direction != Vector3.zero)
    13.                     {
    14.                         var _lookRotation = Quaternion.LookRotation(_direction);
    15.                         transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime * MovementAnimationSpeed);
    16.                     }
    17.  
    18.                     yield return 0;
    19.                 }
    20.             }
    21.  
    22.             IsMoving = false;
    23.             OnMoveFinished();
    24.         }
     
  7. pzolla

    pzolla

    Joined:
    Apr 21, 2020
    Posts:
    19
    So the internets seems to have a constituency that evangelize regarding the overuse of Time.deltaTime in certain situations. If I understand the argument, Time.deltaTime moves towards the end value but may never get there. In my case the above code was 'not getting there', meaning it was not fully facing intended cardinal direction. I did switch to Time.time and that resolved. The speed variable could always be exposed so that if different machines were too fast or slow the end-user could adjust. Not sure this is the 'right' solution, just reporting back. Otherwise it appears to work based on limited testing.
     
  8. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Cool, thanks for sharing :)
     
  9. gferrari

    gferrari

    Joined:
    Jan 14, 2015
    Posts:
    135
    Hi! I'm new and just to ask if it will be very hard to use this framework to do a game like Overland game. I want some scenes where I don't have enemies and search for some lots in the grid. I think i have to change the dominationcondition or something like that?
     
  10. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey! I'm not familiar with the game, let me know what features you need so I can give a more detailed answer :) Correct, you would need to change the game end condition (should be trivial). You won't need turn transitions (just don't give a option to call CellGrid.EndTurn) and you'll need to figure out how to give the player unit unlimited movement points (setting it to int.max_value could be a temrprary workaround...)

    Let me know if you have more questions
     
  11. MoFaShi

    MoFaShi

    Joined:
    Oct 25, 2015
    Posts:
    43
    Hi, I am using the SpeedTurnResolver and I found if I add a new unit after a battle is started, the newly added unit can't do anything. it looks like the SpeedTurnResolver does not deal with newly added units.
     
  12. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Oh, indeed that is the case. To fix it you could subscribe to CellGrid.UnitAdded event from ResolveStart. In the event handler you would need to recreate the unitsOrdered queue.
     
  13. gimozangana

    gimozangana

    Joined:
    Jun 10, 2019
    Posts:
    4
    @michal-zetkowski

    Hi, thanks for making this asset. I'm making good use of it. I have a quick question.
    When painting the prefab tile in Tile Edit Mode, is there a way to tell the new tile(prefab) to match the sorting layer order of the tile(prefab) it is replacing? For example, the current layer order is 74 for the existing tile in the grid. The prefab tile replacing it should also automatically adjust to 74. Each row of the grid is a different layer so the prefabs will match whatever layer it is painted on to.

    Hope that makes sense.

    Thanks,
    Gimo
     
  14. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, you can override CopyFields method in your Cell class and assign the sorting layer there. Remember to call the base implementation also :)
     
  15. gimozangana

    gimozangana

    Joined:
    Jun 10, 2019
    Posts:
    4

    Ah yes, I see it. I'll give this a try. Thanks for the quick answer! :)
     
  16. gimozangana

    gimozangana

    Joined:
    Jun 10, 2019
    Posts:
    4
    Hey, me again. Got another question.

    I'm currently trying to find if there is a list being populated for the cells that are painted green when the path is created. I'm simply trying to communicate with just those tiles. I found Unmark() but found that to only be called individually for just the one cell. So OnMouseExit for example would work, but only for the painted tile that my mouse cursor just left, but all the other green painted tiles would not run my method.

    Any ideas? Hope that makes sense. Thanks.
     
  17. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, currently selected path is stored in MoveAbility.currentPath, it's private though... Feel free to make it public, does that help?
     
  18. icecreamt

    icecreamt

    Joined:
    Feb 19, 2018
    Posts:
    3
    Hi,

    I just bought this and I'm testing out the scene in Example 4 (the advanced wars clone). I notice that I can't always move my units to occupy the enemy factories in the top right corner. Sometimes I can move my units on to the enemy factory, sometimes I can't. I even have some units 1 step away from the enemy factory, and I can't move it to the factory. Is this just a bug in the demo scene?
     
  19. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    If there's no unit on the opponent's factory and you have enough movement points, then it should be possible to move there. Is the cell below the factory marked as movable to? Also, the problem with Example4 is that if you want to move to a cell where there's a structure, you need to point the mouse at the cell, not the structure on top of it... Quite inconvenient, I know.
     
  20. icecreamt

    icecreamt

    Joined:
    Feb 19, 2018
    Posts:
    3
    Thanks, that was the issue. Clicking the cell works, but not if I click the structure. How can I modify example 4 so that clicking the structure will work?
     
  21. icecreamt

    icecreamt

    Joined:
    Feb 19, 2018
    Posts:
    3
    I noticed that disabling the Box Collider 2D on the factory allows me to move my unit onto the factory by clicking on the structure instead of the cell. But then I can't click on factory to recruit a new unit. Still figuring out how to make improve this UI.
     
  22. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Sorry for the delay. Right, disabling the collider does the job, but you can't sekect the structure. The problem is that you can simultanously move the unit and select other units.

    Easy fix would be to remove the modify OnUnitClicked methods of AttackAbility and MoveAbility like below, respectively (also make Cell.OnMouseDown method public):

    Code (CSharp):
    1. public override void OnUnitClicked(Unit unit, CellGrid cellGrid)
    2.         {
    3.             if (UnitReference.IsUnitAttackable(unit, UnitReference.Cell))
    4.             {
    5.                 UnitToAttack = unit;
    6.                 UnitToAttackID = UnitToAttack.UnitID;
    7.                 StartCoroutine(HumanExecute(cellGrid));
    8.             }
    9.             else
    10.             {
    11.                 unit.Cell.OnMouseDown();
    12.             }
    13.         }
    Code (CSharp):
    1.  
    2. public override void OnUnitClicked(Unit unit, CellGrid cellGrid)
    3.         {
    4.             unit.Cell.OnMouseDown();
    5.         }
    6.  
    Please not that this way you need to first deselect a unit to be able to select another unit.

    BTW, take a look at how this situation is handled in Advance Wars, maybe it will give you some ideas.
     
  23. Neil2TheKing

    Neil2TheKing

    Joined:
    Sep 14, 2017
    Posts:
    15
    Hi Michal, thanks for the framework, programming is going great. I'm about to start implementing giant characters that take up four spaces. In your opinion what's the best way to do this? Figured I'd ask in case you have experience with it and a favorite solution. Thanks!
     
  24. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, I never tried it myself, but this seems like a good start https://harablog.wordpress.com/2009/01/29/clearance-based-pathfinding/
     
    Neil2TheKing likes this.
  25. cplungis

    cplungis

    Joined:
    Sep 11, 2019
    Posts:
    6
    Hi Michal, this is a great product, but I am having some trouble figuring out the event system. I want to add a method, probably to Unit.cs where a unit will be deselected if the player right clicks. I've found methods in BaseInput like GetMouseButton(int button) that seem like they get the input I need, but I can' figure out how to access them from Unit. It seems like I should create some kind of EventHandler, or modify the UnitDeselected EventHandler which would respond to the right button press and then call OnUnitDeselected(), but I can't find the class which defines the EventHandlers to modify UnitDeselected, or write a new one getting input from BaseInput. Any help would be appreciated! Thanks, Chuck
     
  26. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, thanks, I'm glad you like it :)

    You'll probably want to add the method for deselecting the unit somewhere else than Unit script, probably to CellGridStateAbilitySelected. Basically the script handles a selected unit. Take a look at CellGridStateAbilitySelected.OnUnitClicked. The chain of calls is as follows: Unity calls a callback OnMouseDown on the unit -> Unit invokes UnitClicked event -> CellGrid recieves the event and calls CellGrid.CellGridState.OnUnitClicked.

    The question is, does the OnMouseDown gets called for the right click as well. If it does, you can check which mouse button is down in CellGridStateAbilitySelected.OnUnitClicked and it that's the right button, just assign a new CellGridStateWaitingForInput object to CellGrid.CellGridState. This basically deselects the unit.

    If it doesn't, thats a bit harder. You could add a new event to the Unit class, like OnUnitRightClicked and somehow detect the right click. You would need to listen for the event in CellGrid and extend the CellGridState interface with OnUnitRightClicked method. In the method, just assign the CellGridStateWaitingForInput object to CellGrid.CellGridState as before.

    Let me know if that helped!
     
  27. cplungis

    cplungis

    Joined:
    Sep 11, 2019
    Posts:
    6
    I think either solution really requires getting the same thing. Somehow accessing the right click event.

    It seems like PointerInputModule captures the mouse inputs, and it has a field for right clicks (public const int kMouseRightId = -2;), so it would seem the hook is there to access it somehow, but I also don't see how CellGridState or CellGridStateAbilitySelected get inputs from PointerInputModule to know that a mouse event happened to then let me determine how to check which button is pressed (basically check that public const int kMouseRightId == -2) in OnUnitClicked.

    I'm also looking at https://docs.unity3d.com/Manual/UIE-Mouse-Events.html, as it seems to be another way to get mouse events, but am stuck. I've uploaded the script using the button on this page so you can see what I am trying.
     

    Attached Files:

  28. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    CellGridState gets a callback that originates from Unit.OnMouseDown. Could you just use Input.GetMouseButtonDown(1) to determine if right mouse button was pressed?
     
  29. Mamire

    Mamire

    Joined:
    Sep 22, 2022
    Posts:
    2
    Hello ! I tried to add your discord to ask some question or see if someone already ask (about tiles 2d), but link is broken ! Is the discord still online ? Thanks for the framework !
     
  30. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, the Discord server is online and going strong :) This link works for me, try it https://discord.gg/uBJNPJHFjB
     
  31. Mamire

    Mamire

    Joined:
    Sep 22, 2022
    Posts:
    2
    I cant join the Discord :/ can i pm u my discord ID to try a direct invitation or something like that ?
     
  32. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Sure, I'll pm you my discord username
     
  33. alomoadev

    alomoadev

    Joined:
    Jun 18, 2022
    Posts:
    2
    Hi, would it be possible to limit a unit's movement in terms of and plane direction? Similar to movement in chess
     
  34. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, yes, you can define it by overriding Unit.IsCellMoveableTo method :) Let me know if you have any issues.
     
  35. alomoadev

    alomoadev

    Joined:
    Jun 18, 2022
    Posts:
    2
    Thank you so much for replying. I have another question: can the combat system be changed to a capture instead (like chess)?
    Also can abilities be made to activate once a condition is met, for example if a unit is in front of another, that unit cannot be captured since the one behind it is 'protecting' it. I understand this might be complicated to implement but I'm just wondering.
     
  36. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Definitely, this could be done. Take a look at the Ability class. Thats how all actions in the framework are implemented - like moving, or regular attacks. You could implement your capture action as ability. Take a look at different sample scenes, each has some different abilities implemented. Let me know if you have more questions.
     
    PolygonPros likes this.
  37. Creiz

    Creiz

    Joined:
    Jun 6, 2017
    Posts:
    130
    Hello @michal-zetkowski. Is support still active?

    I grabbed your framework and I'm testing it. Everything looks good so far, you built quite a solid system, here.

    I have a couple of issues, however.

    I'm making a roguelike. I don't need most of your framework, like the UI, for example. However, the AI might be extremely useful to me.

    I'm generating my own levels procedurally. I can't quite figure out how to integrate the tiles with it. I tried testing with a AdvWarsSquare(I'm using square grids) on my prefabs, but it seems my units are not quite detecting it.

    As for AI? I need it to just wander around on random adjacent cells every turn, then if the player reaches a range (for now I'm thinking of using a collider for it, unless there's a better way) then the AI starts chasing the player, until it reaches, then attacks. If the player leaves the detection range, then just start idling randomly instead. Which scripts should I see for these?

    Finally? My movement is done via keyboard buttons. W goes up, etc. The turn ends after each player action. I think I found where to change that, though. I just want to know where is the movement script so I can change the behavior to listen to keyboard presses instead of the mouse.

    Thank you for you work, Michal.
    Godspeed.
     
  38. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey! The asset and the support are very much active, I'm just a bit slower over the summer ;). Most of the community activity moved over to Discord, take a look if you're interested https://discord.gg/uBJNPJHFjB

    About the procedural generation of maps - you would still need the basic scene structure that you get when generating the map with Grid Helper. Weather you do it completly on runtime, or somehow save and reuse the basic scene setup is up to you (by the basic scene structure I mean that the CellGrid, Units, Players etc gameobjects are setup).

    Next, you would uncheck the ShouldStartGameImmediatelly flag on the CellGrid script and then you're good to generate your map. Just make sure that the generated cells will be children of the CellGrid gameobject. Once the map and the units are set up, you would call CellGrid.InitializeAndStart to start the level.

    As for the AI, check out the new AI system. You can find a chapter about it in the docs. In your particular use case, I wouldn't use any colliders. You would just have two AI actions - like "patrol" and "chase". The actions would activate based on player proximity, but no need for any colliders.

    Finally, every action in the Framework, like moving or attacking is handled in ability scripts. Movement in particular, is handled in MoveAbility script.

    If you want to talk to me directly, feel free to reach out on Discord. Let me know if you have more questions, and good luck with your game :)
     
    Creiz likes this.
  39. Untrustedlife

    Untrustedlife

    Joined:
    Apr 21, 2020
    Posts:
    78
    I finally got around to looking at the new version of this on a new side project. (DR4X is so hyper customzied at this point that upgrading is pointless), i will say things look much cleaner :D
     
  40. Untrustedlife

    Untrustedlife

    Joined:
    Apr 21, 2020
    Posts:
    78
  41. cplungis

    cplungis

    Joined:
    Sep 11, 2019
    Posts:
    6
    hello michal, noticing a funny behavior in the realDamage calc in the Scene 1 Spearman.cs script. I added a Debog.Log to find out what the calculated realDamage is, and for some reason, the calculation seems to repeat a lot. Attached is an example from TBS Framework 3.0 where for player 0, the calculation is made once, but when the AI attacks, the calculation is made lots of times.

    In TBS Framework 2.2, the calc is made lots of times for both player 0 and player 1. When you modify the script so that realDamage also includes a random number, this makes it a lot harder to figure out if you got the right realDamage value.

    Do you know what is going on?

    Thanks,

    Chuck
     

    Attached Files:

  42. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey!

    Hmm, that's a valid issue. While evaluating the situation, the AI player calls DryAttack method on various units multiple times. The DryAttack method is supposed to just return the damage the unit would inflict, without actually performing the attack. If there are random values involved, obviously that's a problem. I'm not sure why that would be the case for the human player in the v2.2...

    To fix it you would need a more rigid control over how random numbers are generated in the Unit class. Control the rng generator state from within the DryAttack method perhaps? Does that make sense?
     
  43. cplungis

    cplungis

    Joined:
    Sep 11, 2019
    Posts:
    6
    I think you're right, this only affects the AI in build 2.2. I'm going to play around a bit, but at least I now also understand DryAttack better too!
     
  44. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Yep, basically I added DryAttack specifically for the AI, so it can check the damage to evaluate moves. Please let me know if you find a good solution for this issue :)
     
  45. akatsubo15

    akatsubo15

    Joined:
    Dec 16, 2022
    Posts:
    2
    Michael, hello. I use your Asset TBS. Great job! I am a beginner programmer, I have been doing this for 1 year. I have a question not about your Framework. I saw in one of the games how the attack on (the hero, the mine, the city) is going on and 2-3 seconds pass, the battle is sent to the file. It is possible to view it there, which can last several minutes. The battle makes two AI. How is this done??? at the same time, it is possible to view the characteristics of the unit during the battle. Sorry for my English. Thanks
     
  46. akatsubo15

    akatsubo15

    Joined:
    Dec 16, 2022
    Posts:
    2
    unfortunately, I did not understand how to upload mp4. I can send it by e-mail.
     
  47. michal-zetkowski

    michal-zetkowski

    Joined:
    Mar 11, 2015
    Posts:
    282
    Hey, a video would be helpful indeed :). Would it be possible to upload it to youtube or a similar platform? If not, my email is crookedhead@outlook.com
     
  48. mttsus_unity

    mttsus_unity

    Joined:
    Mar 14, 2024
    Posts:
    1

    Hello, I need to change my server IP to play online. It stays like this, what should I do? My second problem is that when I add a new warrior to a ready-made project, I cannot use it or attack it and other warriors are also affected and their route is disrupted.
    https://ibb.co/GvTrPbV