środa, 7 października 2015

#Stencyl: Turn-based strategy


Difficulty: (5/10)
Estimated time of work: 6-8 hours


Original request:


There was no original request per se. The game was made for Ludum Dare 33. I think that this tutorial is a good starting point for anyone who will be trying to create turn based strategy. Tutorial puts focus on battle itself omitting other things like: buying/selling potions, equipping/customizing player and others.

Video of finished project




Play me


You can play finished version on Newgrounds:
http://www.newgrounds.com/portal/view/662557

Download me


You can download finished project from Ludum Dare entry:
http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=34334

You can download post-LD debugged version here:
http://wikisend.com/download/342412/Frankenstein_30_09

Project in bullets


  • Player picks action: attack or potion use.
  • There are 3 types of attacks: Normal, Strong, Special.

    Normal attack deals standard damage within +/- 25% range.
    Strong attack is like normal attack but it deals 150% damage of what normal attack would deal. Strong attacks has 75% hit chance (25% for missing and dealing 0 damage).
    Special attack consumes 15 energy to perform attack. Special attack has 25% to deal double damage otherwise its standard attack.

  • If attack is chosen: player moves toward enemy and once he is close enough he hit them dealing damage based on attack type. After attack player returns to his starting position If enemy survives he attacks.
  • Stats of enemy - health and attack power are based on level picked by player.
  • Player and enemy attack each other in one-then-switch fashion until one of them dies. After this happens a pop-up menu appears informing about result of the battle
  • On the screen after attack appears text informing how much damage has been dealt, was special successful etc.

Project review


Project isn't really that difficult but it's rather big. Main problem lies in big number of attributes. As there is a lot of things to be coded this project requires to be kept clean otherwise it will turn into huge mess.

Gameflow



Diagram shows a flow of the battle

The battle scene was created first so it operates on local attributes. After creating the scene: all buttons, characters etc. battle scene needs to import game attributes into local attributes. Game attributes are set up in map scene. Imported game attributes involve:

  • head - type of the head picked for the player
  • arm - type of the arm picked for the player
  • body - type of the body picked for the player
  • potion_health_owned - number of health potions owned
  • potion_energy_owned - number of energy potions owned
  • potion_attack_owned - number of attack potions owned
  • player_energy - maximum energy of player
  • level - level currently being played/type of enemy
  • player_health - maximum health of player
  • player_power - maximum power of player

With so many attributes it's good to underline the rule of attribute naming. With smaller project you would name attributes naturally for example: <health_potions_owned>. However if you do that while dealing with bigger projects at some point you will find yourself in front of the huge list of attributes and then you will realize you forgot how you named your attribute.

So instead use something called "reversed notation". This way you will quickly find the attribute you are looking for: <something with potion?> → <potion> → <what potion?> → <potion_health> → Oh! I named it <potion_health_owned>

Building the scene

Events: → combat_scene_behavior → when_created → combat_scene_behavior → scene_set_up → combat_scene_behavior → animate → combat_scene_behavior → level → combat_scene_behavior → enemy

Actors in the scene on the start are created in event <scene_set_up>. Second thunder actor, marking enemy energy, can be easily removed from the code. It's leftover from the time when enemies would have used special attacks.

Parts of player and enemy: head, body, arm, circle after being created are stored as actor attributes.

Next we need to set initial state of boolean attributes and deal with importing information from game attributes into the <combat_scene>.


<when_created> handles scene creation

Attributes:
  • <in_combat> - out-of-use
  • <menu_open> - boolean; false; true if there is open menu (choice of attack or potion)
  • <player_turn> - boolean; true; true if it's player turn, must be true if you try to open action menu
  • <tips> - text; <empty_text>; displays info about last player's action
  • <enemy_text> - text; <empty_text>; displays info about last enemy's attack

Trigger events:
  • <animate> - triggers <animate_2>, give enemy(each body part) proper look based on the <level> game attribute
  • <level> - animate player, <animate_part> animates actor <temp_actor> based on value of <helper_1> what allows to animate single body part using proper game attribute(head, arm, body). Trigger event <calculate> from behavior <calculate> calculates total health and attack after body parts bonuses. There is a minor mistake in <calculate> event (attack is switched with health) and it's fixed by swapped assignment of combat and health in <level> event.
  • <enemy> - set player health and damage based on <level> game attribute

<level> trigger event

Attributes in <created>:
  • maximum_enemy_damage - maximum damage enemy can deal
  • maximum_enemy_damage - maximum damage player can deal
  • minimum_enemy_damage - minimum damage enemy can deal
  • minimum_player_damage - minimum damage player can deal
  • strong_multiplier - how much strong attack is stronger from normal attack; 1.5=150%
  • strong_chance - how much chance does strong attack have to be successful; 75=75%
  • special_cost - energy cost of special attack
  • energy_enemy - local attribute; set to <max_energy_enemy> from <enemy> event
  • energy_player - local attribute; set to <max_energy_player> from <level> event
  • hp_enemy - local attribute; set to <max_hp_enemy> from <enemy> event
  • hp_player - local attribute; set to <max_hp_player> from <level> event
  • special_chance - chance that special attack will do double damage instead of standard damage; 25 = 25%
  • special_enemy_cost - leftover from the time when enemies were meant to have special attacks

On-click states


There are two ways how to trigger a on actor click trigger, both are used in the project:
  • <on actor click> event
  • <on mouse pressed> event + mouse location check

In order to control a gameflow following attributes are used:
  • menu_open - boolean; true if menu (attack or potion) is open, prevents from opening menu when one is open, prevents opening menu in the middle of attack
  • menu_mode - number; says which menu is open (1=attack, 2=potion, 3=to map, 4=win/lose)
  • playerturn - boolean; true if it's player's turn. False during enemy's turn, prevents from opening action menu during enemy's turn.

Gameflow as seen through mouse clicks
Events: → combat_scene_behavior → when_the_mouse_is_pressed → combat_scene_behavior → open_potions → combat_scene_behavior → open_attack → combat_scene_behavior → clean_up → combat_scene_behavior → menu_open → button_behavior → animate_me


Opening attack or potion menu;

Creating menu is triggered by clicking on screen and if player clicked on one of the buttons (attack,item or to map) a proper menu pops up. <when_the_mouse_is_pressed> handles that.

In case of clicking attack or item button an event is triggered <open_attack> or <open_potions>. Each of these events sets up <menu_open> (1 for attack and 2 for item) and then triggers <menu_open>. <menu_open> creates 4 buttons: 3 action buttons + cancel. Button actors are saved in <menu_list>. <menu_open> while creating buttons sets <number> for them so that the actor knows which button is he then triggers <animate_me> which sets up animation for actor based on <menu_mode> and <number>. Menu can only be created when there is no other menu on the screen so there is <menu_open> check in <open_attack> and <open_potions>. Menu can be only created during player's turn so there is additional <playerturn> check.

If player were to pick <to_map> button a proper pop-up would be created and <menu_mode> would be set to 3.

<to_map> menu as it's simple is handled by <combat_scene_behavior> in <when_the_mouse_is_pressed>. For potion and attack game uses <is_pressed_on_self> to move on with game logic.

Cancel button is important. Firstly because it allows player to change his mind. What is even more important that without it game can get stuck. This would happen if player picked potion option and had none to be used. If player pressed the cancel button <clean_up> triggers killing all buttons located in <menu_list>. Then <menu_open> is set to true meaning is it's ok for player to open menu since cancel button closed last one.

Battle (Player attack)

Events: → button_behavior → is_pressed_on_self → puppet_behavior → attack → puppet_behavior → deal_damage

Let's say we have attack/potion buttons open and we click on one of them. This triggers <is_pressed_on_self> of <button_behavior>. In case of attack we send information which attack has been chosen to <puppet_behavior> by setting up <number> attribute. If special attack was chosen we need to check if player has enough energy to perform special attack. For all types of attacks if everything went ok <is_pressed_on_self> triggers <attack> of <puppet_behavior> which handles player's attack. <clean_up> is triggered as well to kill menu actors.

In case of pressing potion firstly behavior checks if there is potion to be drunk. If yes action associated with potion drinking is performed. Number of potion is decremented since potion has been drunk. Information about successful potion drinking is given to player by setting up <tips> text attribute in <combat_scene_behavior>. Since player took his turn it's time for enemy to attack. This is done by triggering <enemy_turn> in <puppet_behavior>.

Warning: during writing this post bug has been found. After successfully drinking potion <player_turn> must be switched to false. Otherwise player would be able to open action menu in the middle of enemy attack.


<is_pressed_on_self> with comments and after bug fix

Player picked one of attacks and <attack> has been triggered. <attack> set <playerturn> to false and prevents player (human playing) from taking any more action until player and then enemy finishes their turns. <attack> triggers <move_player> which handles actual player attack.

Phases of <move_player>:
  • Save starting position of body parts
  • Rotate arm so it point at the enemy
  • Stop rotation
  • Move player towards enemy
  • Deal damage to enemy and move back
  • Reposition player at starting position
  • Triggering enemy

It's important to reposition player at starting position after attack. Otherwise player could be doing "small steps" which could eventually make him walk out of screen. Breaking attack into many phases allows to deal damage as player is close enough to enemy.

Dealing damage is handled by triggering <deal_damage> event from <move_player>. Based on <attack_mode> attack set previously from <is_pressed_on_self> of attack button <deal_damage> event can decide which attack has been chosen. For normal attack its straightforward: random number between damage minimum and maximum damage is picked as damage. Number of damage is subtracted from enemy health. Text with information how much damage has been dealt is drawn on screen by setting <tips> text attribute in <combat_scene_behavior>. <combat_scene_behavior> behavior handles drawing the text.

When attack require chance test such as special or strong attack, chance test is first checked. Based on test result (passed or failed) <tips> text is set and proper amount of damage is dealt.

Battle (Enemy attack)

Events: → puppet_behavior → enemy_turn → puppet_behavior → to_map → puppet_behavior → reward → puppet_behavior → move_enemy → puppet_behavior → deal_damage_enemy → puppet_behavior → end_of_turn

Last phase in player attack is triggering <enemy_turn> which handles enemy_attack.
First enemy checks if it is alive. If enemy is dead <to_map> triggers with <win> equal 1. <to_map> creates end of battle menu and uses <win> to animate it ether as won or lost battle menu. If the battle has been won <max_level> can be updated. Game attribute <max_level> keeps as number biggest number of level that has been completed. <max_level> shouldn't be updated if player has finished already finished level. Winning level triggers <reward> event which updates <money> game attribute based on what level player has completed.

If enemy is still alive after player's attack <move_enemy> triggers handling enemy's attack. Logic of moving is the same as for player but different actors are moving (enemy body parts instead of player). Instead of <deal_damage> enemy triggers <deal_damage_enemy> which works as player's normal attack. <deal_damage_enemy> updates <enemy_text> text attribute which informs player about how much damage enemy has dealt. <move_enemy> ends with triggering <end_of_turn>.

<end_of_turn> checks if player is alive after enemy's attack. If player is dead <to_map> triggers with <win> equal 0 creating a lost battle menu. If player is alive <playerturn> is set to true allowing player to start next turn. Action toolbar switches animation so that attack and item buttons are visible. Information about last turn attacks is removed by setting <tips> and <enemytext> to empty text.

Help


My tutorials are intended for people who finished crash course of Stencyl. If any part requires deeper explanation be sure to ask in comments. Remember that there might be other people with similar problems to yours so by asking questions you are not only helping yourself but others as well.

If I were to ignore you for longer period of time PM on Stencyl forums or newgrounds.

This post might be a little mess since it's my second one using html and not Google editor. I'm new to html so if you think there might be a word missing in the text be sure to let me know about it.

If you have any suggestions put them in comments. I will take them into consideration for the future posts.

Thank you for reading.

Tags: Stencyl, Frankenstein, Turn-based, Strategy, RPG, LD33, LudumDare, Ludum, Dare, Monster