ś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

środa, 9 września 2015

Ludum Dare 33


  • What is Ludum Dare?
  • What I love about Ludum Dare?
  • What I hate about Ludum Dare?
  • Ludum Dare's survival guide
  • Ludum Dare 33: how it went
  • My entry in comments
  • Other entries
  • What's next?

What is Ludum Dare?


Ludum Dare is a contest where you are supposed to create game on given theme. There are two version of the contest: Compo and Jam. The contest takes place every 4 months.

Compo lasts 48 hours. During Compo you must do all assets yourself - code, graphics, sounds. You must work alone.

Jam last 72 hours. During Jam it is allowed to use assets provided by third parties. It is allowed to work as team.

Proposed themes must survive the "Slaughter". During "Slaughter" people vote which themes should have a shot at being a theme for current edition. The week before Ludum Dare people vote at the themes that survived "Slaughter". The people favourite becomes the theme.

What I love about Ludum Dare


Ludum Dare is big. Every edition produces 2500+ games. That's a lot! Probably more than you have friends on Facebook.

I always go for Compo contest. These are crazy 48 hours. After the end of it you don't want to turn on PC or any electronic device - you are so fed up with sitting and coding. You are exhausted but at the same time you are happy. Two days before you had nothing and now... you have a game! Ludum Dare proves how much can be done during 48 hours if you are determined enough. The limits you thought you had are not broken, they are smashed to the ground.

On the side I am really curious how people survive jam(72 hours) if I am literally destroyed after compo (48 hours). I guess the pace must be a little slower and using done assets must be helpful as well.

What I hate about Ludum Dare?


  • No honest opinion
  • Graphic over code
  • No reply

No honest opinion

I never do good at LD. It's ok, no hard feelings. Regardless of the results there is something I really dislike about LD. I hate that everybody comments the games the same way: "good game". Nobody will say "your game sucks". If it does suck just say so. No constructive criticism. Just game developers "jerking off" to each others creations. After judging time the notes comes back and it turns out your game is nowhere near being a "good game".

Graphic over code

Other thing that makes me go insane are standards of people judging LudumDare entries. For me the small web based games are about two things: idea and gameplay.

Gameplay:
If gameplay is good, if the game gives you fun it doesn't matter that it looks bad. It's ok because you receive that undefined happiness. The happiness that you felt playing tetris or mario for the first time. You don't know why is it fun - it just is.

Idea:
Most of guys competing in Ludum Dare won't be able to make games for living. They won't be able to spend big money on their games. As a consequence of that they won't be able to hire professional artists or composers to work on their game. Therefore there are bound to lose against serious game developers. So if you can't out-force your competition you need to out-smart it. Introduce a great idea. Implement a mechanic that will make people go "wow". Show that games can be more than endless pile of FPSs, RTSs and RPGs.

The problem for me is that during LudumDare community tends to give too much attention to art. You can have great game but still with weak art you are going nowhere. Make a simple game with reused idea but with above-average graphics and community will shit rainbow. The worst of the worst for me is when creators use pixel art. People will rate it high not because the art itself is good but for the sake of "pixel-art" alone.

The other aspect is how much impact the art and coding have on a game. You mess up a piece of art people probably won't notice. Mess up a piece of code and your game doesn't work. Were it to happen next twenty comments you will be receiving are hateful messages that "your game isn't working". So yes I believe that coding deserve more recognition than art. On the other hand why should I even try to do something bigger and better if I can get better result with smaller risk just by investing in better art?

No reply

Comments under your entry are for me as important as final results. Sometimes somebody gives you a longer review than "good game". Even if you disagree with some of the opinion in the review there's no reply option. It may seem as little or no pain but from developer's point of view it's important to prove to the public why you did things the way you did or why you disagree with someone's opinion.

Ludum Dare's survival guide


  1. Everything during LD is limited by time.

    You must be aware that once you are done you will feel like a lot of things could have been done better. One rule that you should always keep in mind is NO TIME. No time for good graphic, no time for having a well edited music, no time for epic story-telling. No time, no time is pretty much an anthem for LD.

  2. Gameplay first.

    If you have no gameplay, you have no game. Make provisional graphic in form of square, circles and rectangles. Make sure that mechanics work then think about making nice sprites.

  3. Know your tools.

    Ludum Dare is no place for learning your tools. Learning new things is generally hard. It's even harder with a time ticking. Learn your tools before LD otherwise you will be doomed.

  4. Quality in screentime.

    If you have created a pretty sprite try to reuse it as much as you can. On the other hand if something will appear only once on the screen during the whole game you don't need to bother with detailed drawing.

  5. Dreams to dust.

    As you think about making a game you think big. There will be dragons and mages, there will be crafting, spell casting, dialogues. But then the time starts passing by and you have to say farewell to most of your ideas. So if your concept turns out to be too big to be finished in given time be ready to remove some of the planned contents. Doing this may be sad but again NO TIME.

  6. Coding requires thinking.

    Once you get tired you will start doing lots of stupid mistakes that require a lot of time to be fixed. If it were to happen stop coding, do some graphic and return to the coding the next day. This pretty much says: coding first everything else later.


Ludum Dare 33: how it went


My entry:
http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=34334

Preperations:
I wanted to make my entry using Unity. I did few tutorials but I haven't done anything on my own in it. As Ludum Dare is not a place to learn something new I decided to use Stencyl instead. I consider this a bitter disappointment.

What went right:
Definitely time management. I had my game finished and working 5-6 hours before deadline which is great. I think that whole production process was doing really well as well:
  • Day 1 - gameplay mechanics, map graphic, half of character graphics
  • Day 2 - opening and closing menus (potions, upgrade, dress-up), special attack, second half of character graphics, main title screen, music, Newgrounds achievement


I'm quite happy with the result. Looking at other entries I think I'm at top when it comes to the length of code written for the game. It's the first time I have made a turn-based rpg. The fact that during this LD I have done a genre that I had no experience before is really satisfying. At the same time I must say that I had some preparation before Ludum Dare because I had written some basic turn-based rpg in order to solve problem from Stencyl forums. I haven't used any of code from that project but I had still in my mind the idea of how the code for the whole game should be implemented.

What went wrong
I don't think anything went extremely wrong. Most of these are problems along the way, natural for process of creation.
  1. Create list vs empty list

    Stencyl can't empty list that hasn't been created. I have committed this "error" before so finding it wasn't that painful but still it's a waste of precious time. I put "error" in brackets because I feel Stencyl should create a new list on its own as variable is declared instead of forcing a users to do it themselves.

  2. Werewolf's nose

    I'm not an artist but I had really hard time making a good werewolf/dog nose. Despite my squarish graphic.

  3. No combat music

    There was no problem with sfx as I used like always:
    http://jfxr.frozenfractal.com/#

    Then I was thinking about doing some music for map screen and combat screen. I open my bookmarks and... I don't find the bookmark I was looking for.

    As it later turned out the site I was looking for was:
    http://www.abundant-music.com/

    In the end I managed to find another random music generator. It was enough for map theme but no good for combat theme. Even if I had access to the site during LD I doubt if I managed to create combat music as I'm lacking in music terminology. Because of this I wouldn't be able to configure seed numbers to generate music I want. Learning what bridge, harmony and bass are is one of the position on to-be-done-before-LD34 list.

  4. Mirror image

    In my game I had models for arms, heads and bodies of character created for player. I failed to notice that if I wanted to use mirror image for enemies the graphic must have center in the center of image. This forced me to add offset after doing the mirror image => another unnecessary time-waster.

  5. No rotate around point in Stencyl

    I was shocked to find out that such basic function isn't implemented. Since LD doesn't give you time to do things nicely, using math, I made it using "works about right" logic.

  6. Given up idea - Special attack

    As time was passing by I was forced to give up idea of special attacks. I had this concept that player would have a special attack based on the head they are wearing. So if I was wearing a zombie head I would have a chance to poison enemy, vampire - drain life, werewolf - bleeding/critical damage. The problem itself was not only to implement special attacks but what was even worse I would be forced to mark status of nerfed enemies by adding icons. Another lines of code, another graphic and time is ticking.

    Enemies were supposed to use specials as well. AI of enemies was supposed to be wiser. In plans AI analyzed such things as: if I am able to kill player in this attack using .... type of attack and what's the best way to do it (chance check), how to counter player. The idea of simpler but working approach crossed the chances of smart AI.

  7. Gradation of enemy and equipment/body parts

    Probably could have been done better as both enemy and equipment gradation was done more random than rational. With enemies there was at least some idea behind it:
    • 1st enemy - dead in 2-3 attacks, no threat
    • 2nd enemy - dead in 4-5 attacks, kinda stronger but still no threat
    • 3rd enemyh - equal to player from begining of the game
    • 4+ - just stronger, no deeper thoughts

  8. Bug found

    There was one bug found but it was easy to repair.

  9. Stnecyl refused to import music

    No idea why. Stnecyl proved to be a picky b*tch in the worst possible moment.

My entry in comments


Biggest complains:
  1. Bad art - I must say I like my art for this game. Squarish characters were intended and I'm satisfied with their looks. Only exception being Scientist which really could have been done better. Probable some animations would be fine such as character blinkings.

  2. Make different animations for different attacks - I don't know how it could have been done. I wanted my monsters to look like mechanical puppets and I think I achieved that. Should I make a player uppercut the enemy if the player chose "strong attack"?! - the idea sounds extremely stupid.

  3. No combat music - I fully agree with people complaining.

  4. No story ending - this was single complain but I fully agree with it. I had a nice storyboard which introduced a protagonist and villian. I should have done similar one for the game ending.

Other entries


Most popular idea among entries involved being a godzilla-style monster and killing something. Fun if you play 2-3 entries but after 10th one it gets boring.

Some entries wanted to take moral approach but in most cases I don't think they did it good enough.

Most common monsters: undeads(zombies, vampires), infected, dragons, godzillas (big monsters), extraterrestrials, demons, killers

For best entries after 50 go to:
http://ludumdare.com/compo/2015/08/26/you-should-play-this-after-50-games/

I have played 75 games so at this point I can add these entries to the best-of list:

Slasher:
http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=8745

Lil' Drake
http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=18768

Eat Sheep & Die
http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=40797

What's next?


I will be describing my LudumDare entry because I think it's good starting point for anyone considering doing a turn based combat.

I hope to do a non-jam game as I hadn't done anything of sort in a while. As art has always been a source of complain I decided to start learning blender. Regarding the music I know I will need to catch up with music terminology and play around with music generators but I am not sure when I will exactly do it.

poniedziałek, 10 sierpnia 2015

#Stencyl: Zerg terrain infection

Part 4: Spreading the infection

Topics:
  • Observation of infection
  • Algorithm of infection spreading
  • Implementation of algorithm
  • Infection over time
  • Extras: counter-infection algorithm
  • Extras: making infected area more interesting

Behaviors used in part IV

Behavior Events:
  • infection_behavior (scene behavior)
    • infect
    • infection_step
    • when <a> pressed
    • when <f> pressed
    • do every <1> seconds

Main variables:
  • infection_behavior
    • infected_list - List; stores as elements infected tiles actors that make edge of infected area
    • temp_list - List; stores the copy of <infected_list>
    • checked_tile - Actor; element of <temp_list>
    • auto_infect - Boolean; true if infection should spread over time

 

Observation of infection spreading


Before jumping to algorithm itself we need to make some observations about the infection process
  • Only edge of infected area is able to infect further tiles. There is only need to track tiles that make edge.
  • After infecting edge tile becomes a tile inside infected area surrounded from four sites by infected tiles (since there are no tiles which can resist the infection).

Edge tile(green) infects tiles around it(blue). Right: after infection tile is surrounded from all sides by infected tiles.


Algorithm for infection spreading


Algorithm for infection (single iteration):
  • Make two lists. First list <temp_list> stores the currently infected edge tiles(pre infection). Second list <infected_list> stores the newly infected edge tiles (post infection).
  • For each tile of current edge try to infect tiles around it. If you encounter uninfected tile infect it and add tile to the list of newly infected edge tiles.
  • Copy elements from newly infected edge tiles to currently infected edge tiles.

Implementation of algorithm
       -> infection_behavior -> infection_step
       -> infection_behavior -> infect
       -> infection_behavior -> animate
       -> infection_behavior -> when <a> pressed

Trigger event <infection_step> does a single iteration of infection. It is called when you press <a>.

At first we set pre-infection edge tile list <temp_list> and post-infection edge tile list <infected_list>. <infected_list> is empty at the beginning.

Then we go through each tile actor of pre-infection edge tile list. Set that actor's animation to animation of being surrounded by 4 infected tiles. Send information to <infect> trigger event that you would like to infect tiles around you. We need to set <checked_tile> before every <infect> trigger event because <checked_tile> is changed upon execution of <infect> trigger event.

<infect> trigger will determine if tile is infected or uninfected. If it's uninfected it will infect it - set proper value in matrix <ground_list> (matrix responsible for keeping status of tile; Part 3) and add the newly infected tile to <infected_list>.

After infection process is done we need to update animation for post-infection edge tiles based on their surroundings. It is done by <animate>.

We add event <when <a> pressed> so that we can call manually single iteration of infection.

Infection over time
       -> infection_behavior -> when created
       -> infection_behavior -> do every <1> seconds
       -> infection_behavior -> when <f> pressed

Infection over time is nothing more than calling <infection_step> once every <some time>. We add <do every <1> seconds> to achieve it. Since we don't want infection to spread until we tell it to do so we add <auto_infect> condition. In when created we set value of <auto_infect> to false. Value of <auto_infect > can be changed to <true> upon pressing <f>.

Events allowing infection over time

Extras: counter-infection algorithm

Sometimes we want for ground to regain lost ground. For example if the infecting race lost supply that allowed the infection. The biomass on the infected tile is dying so neutral ground regains it. The algorithm for this is very similar to that of infection.

Algorithm for counter-infection (single iteration):
  • Make two lists. First list <temp_list> stores the currently infected edge tiles(pre counter-infection). Second list <infected_list> stores the newly infected edge tiles (post counter-infection infection).
  • For each tile of current edge. See if around it there is a tile that has four sides infected. If yes and it's not in <infected_list> add it.
  • After you are done with creating <infected_list> for each tile of currently infected edge set it to neutral.
  • Copy elements from newly infected edge tiles to currently infected edge tiles.
  • Animate the edge tiles based on their surroundings.

Extras: making infected area more interesting

We have infected area but once it gets big we will end up with big boring purple plane. If infected area is big enough we can build some objects inside infected area. The objects themselves can be build up from tiles.

This will require checking once in a while values in <ground_list> to answer the question if we have enough infected tiles to build. Since it's only graphical effect with no influence on gameplay we don't need to do it often.

Infected area with added pools of green goo(sized 3x3) and grouped infected tiles(2x2).

To choose other part follow the link:


czwartek, 6 sierpnia 2015

#Stencyl: Zerg terrain infection

Part 3: Creating tiles, infecting first tiles

Topics:
  • Adding new matrices
  • Adding tiles actors
  • Marking tiles occupied by buildings
  • Infecting tiles around buildings
  • Changing animation of infected tiles
  • Extras: Is it ok to place building using tile logic
  • Extras: Infect area outside of screen 

Behaviors used in part III

Behavior Events:
    •  main scene behavior (scene behavior)
      • when created
    •  tile_behavior (actor behavior)
      • index_declare
    •  building_behavior (actor behavior)
      • placed_on_map
    •  matrix_support_behavior (scene behavior)
      • rectangle_fill_by_index
    •  infection_behavior (scene behavior)
      • infect
      • animate
      • animate_me

    Main variables:
    •  main scene behavior (scene behavior)
      • tile_list - List; as elements contains tiles actors
      • tile_size - Number; width/height of tile, often used to translate position in pixels to position in matrix coordinates
    •  matrix_behavior (scene behavior)
      • ground_list - Matrix; contains value which corresponds to status of tile (uninfected, occupied by building, infected)
      • tile_list - Matrix; contains indexes to list <tile_list> in <main_scene_behavior>, indirectly stores tiles actors
    •  tile_behavior (actor behavior)
      • index - Number; actors knows that he is being stored in list <tile_list> in <main_scene_behavior> as <tile_list[index]>
    •  infection_behavior (scene behavior)
      • x - Number; X position in <ground_list> matrix
      • y - Number; Y position in ground_list matrix
      • return - Number; status of tile before the infection
      • infected_list - List; stores as elements infected tiles that make edge of infected area
      • top, down, left, right - Number; status of neighbouring tile, needed for choice of animation
      
    Adding new matrices
           -> main_scene_behavior -> when created

    We will need two more matrices: <ground_list> matrix and <tile_list> matrix.

    Values in <ground_list> matrix will correspond to status of tile
    • 0 - tile is uninfected and free
    • 1 - tile is occupied by building (we consider them to be infected as well)
    • 2 - tile is infected

    <tile_list> matrix will indirectly contain tiles actors. In <tile_list> matrix as elements there will be indexes pointing to list <tile_list> of <main_scene_behavior>. <tile_list> of 
    <main_scene_behavior> is the list that will contain tiles actors themselves.

    If you find indirectly placing actors into matrix to be confusing read section "Storing in matrix something different than numbers" from "Matrix behavior" post: 
    http://t4upl.blogspot.com/2015/08/stencylmatrix-behavior-topics-download.html

    We add matrices in <when created> of <main_scene_behavior>. We fill them both with 0s so that we can use <set_value_by_index> later on. Size of places in matrices to be filled is equal to number of tiles.

    We wrap whole creating of matrices in <do after> because Stencyl doesn't allow to use trigger events of scene behavior in <when created> of scene behavior.

    Filling matrix with 0s is required because:
    • It's good to have some initial state, it makes debugging easier
    • <set_value_by_index> of <matrix_behavior> requires that record exists before it will set specific value in that record

    <when created> of <main_scene_behavior> completed with part of code adding <tile_list> and <ground_list> matrices.

    Adding tiles actors
           -> main_scene_behavior -> when created
           -> tile_behavior -> index_declare
    In <when created> we create tile actors so that they cover the screen. As we create them we add them to list <tile_list> of <main_scene_behavior>. We let the tile actor know under which index is he hold in the list by setting <index>.

    The if statement allows us to go to the next row while creating tile actors.

    If you find placing actors tiles to be confusing read section "Creating a matrix of cards" from "Match game" post:


    <when created> of <main_scene_behavior> completed with part of code responsible for creating tiles actors.

    After adding the tile we force it to update <tile_list > matrix with its index by triggering <index_declare>.

    Information about index which holds the tile actor is given to tile so that it knows it. Since it's the last actor added to the list it's index = Length of List - 1.


    Tile actor calculates where he should put information about its index in the matrix based on it's <X position>, <Y position>, <tile size>.


    Tile actor puts the information into the matrix since it knows everything it needs to do it.


    Trigger event <index_declare>. Tile actor using information about its position and <tile_size> learns where to put its index into the matrix. The tile actor then puts the index into the matrix.


    Marking tiles occupied by buildings
           -> building_behavior -> placed_on_map
           -> matrix_support_behavior -> rectangle_fill_by_index

    After we placed the building we need to update <ground_list> so that it will mark the tiles covered by building. We already have a trigger event that is executed as building is placed called <placed_on_map>. We add part responsible for matrix update.

    The added code sends information about corners of the building as matrix positions to <matrix_support_behavior>. Upon triggering <rectangle_fill_by_index> information is used to fill matrix with 1s in rectangle pattern - this way marking in matrix the tiles covered by building.


     Send information from <placed_on_map> about corners of the building as matrix coordinates to <matrix_support_behavior>


    Trigger event <rectangle_fill_by_index> fills matrix <ground_list> with 1s in area described by corners.

    Infecting tiles around buildings
    -> building_behavior -> placed_on_map
    -> infection_behavior -> infect

    After placing the building we want to infect tiles around the building. To do this we add another part to <placed_on_map> trigger event. We go through each tile covered by the building and if this tile makes edge of the building it tries to infect one of the tiles around it (left,right, above, or down of it depending on edge).

    Trying to infect means sending crucial information (in this case position in <ground_list> matrix) to infection_behavior which will do infection upon triggering <infect>.


    Building checks its tiles. If they are edge tiles they try to infect tiles around it. Back of the arrow shows the source tile on the edge and head of the arrow points the tile that is being infected.


    <placed_on_map> with added part that checks tiles covered by building in order infect tiles around it.

    <placed_on_map> doesn't infect tiles. It just sends request for tile at <x>, <y> position in <ground_list> matrix to be infected. The infection itself is handled by <infect> of < infection_behavior>.


    <infect> of < infection_behavior> - trigger event responsible for handling the infection of tile at <x>, <y> position.

    Before going to infecting tile itself let's make one thing clear: we don't want to infect tile that's already infected because it doesn't make sense. The second thing is that we assume that tiles covered by building are infected. If player doesn't see them it doesn't matter if they are infected or not so it's ok to assume such thing.

    As first thing we assume that tile we are willing to infect is already infected (<return>=1). We ask if position of tile we want to infect is inside boundaries of the map. If the tile position is outside of the boundaries <return> remain equal to 1 and nothing happens because later if requires <return> = 0.

    If tile position is inside the boundaries of the map we ask what's the status of the tile by asking the value in <ground_list>. If it's equal to 0, meaning uninfected, (in code: if <return> = 0) we proceed with infecting it.

    We want to know which index of the list <tile_list> of <main_scene_behavior> stores the tile actor we are about to infect. We learn it by asking <tile_list> matrix of <matrix_behavior> what value it holds at <x>, <y> position. Now we can get newly infected tile actor as element under the index we learned in <tile_list> of <main_scene_behavior>.

    We change animation of the newly infected tile actor to <Animation 0> which is one of the infected tiles animations (fully infected square). We add newly infected tile actor to <infected_list> - we will need this list later. Since we have infected the tile we need to mark the fact that tile has changed status from uninfected to infected. We do it by setting value in <ground_list> matrix at <x>, <y> position to 1.
     
    Changing animation of infected tiles
           -> infection_behavior -> animate
           -> infection_behavior -> animate_me

    What we want to do now is add some variety to our infected tiles. We will do it by changing animation of the tile based on its surrounding. There are some basic states that tile can have. Once we have basic states all other can be achieved by rotating the basic states.


    States that tile can have based on its surrounding. Brown - uninfected, purple - infected.
    (1) 4 sides uninfected, (2) 1 side infected (3) 2 sides infected on opposite sites (4) 2 sides infected on neighbouring sites (5) 3 sides infected (6) 4 sides infected

    For animating tile actor we will use <animate> from <infection_behavior >.

    Side note: if tile has as its neighbour tile outside of the screen we assume that this tile out-of-boundaries is infected.
      
    Trigger event <animate> from <infection_behavior >.

    The big while at the beginning let us do animation change for each element of <infected_list>. This means we will perform following code for each edge tile of infected area.

    The neighbourhood of tile is represented by variables <top>,<down>,<left>,<right>. They are 0 if neighbouring tile in given direction is uninfected and 1 if it's infected. There is a variable <sum> which says how many tiles around current tile are infected. <sum> makes choosing animation simpler but is not obligatory for solving the problem itself.

    Once we have chosen the edge tile we ask if neighbour in chosen direction is on map. If not we consider it to be infected and set proper direction variable to 1. At the same time we add 1 to <sum>. If neighbouring tile is on the map we ask its status by getting value from <ground_list> of <matrix_ behavior>. The tile is infected if value in list equals 1(covered by building) or 2(infected but not covered by building). If statement ( >0) checks both of the possibilities. If the tile is infected we set proper direction variable to 1 and we add 1 to <sum>.

    After we get all info about status of neighbourhood we trigger <animate_me> which will set proper animation to the tile based on values of <top>,<down>,<left>,<right>.


    Trigger event <animate_me> uses values of <top>,<down>,<left>,<right> to set animation to edge infected tile.

    Extras: Is it ok to place building using tile logic

    In part II we learned how to place the building. The approach we used didn't use tiles. This part presents alternative approach to problem of checking if it's ok to place building using tiles and <ground_list> matrix.


    Example of trying to place building(green frame) over building already on map. On right it is visible how the <ground_list> matrix looks like as we try to place the building; 0 - free place, 1- covered by building

    In alternative approach we check if in area we are about to place the building (green) are any occupied places (represent by 1). If yes that means we can't place building. If no it's ok to place building and we place it.

    Pros: The approach from part II is ok if our buildings are rectangles or we can assume that they are more or less of that shape. This generally works. By using getting values in matrix we can place buildings that are triangles or other more exotic shapes.

    Cons: As we place building in this approach we will need to associate with each type of building a shape equivalent in matrix. Both setting the building and checking if its ok to place are therefore more difficult.

    Extras: Infect area outside of screen

    We decided that area outside of screen is considered infected. This is ok but we can do it better by not assuming anything. All we need to do is add 2 additional columns and 2 rows to the matrix so that they can represent tiles outside of screen. This however creates offset between actor position and its position in matrix what can be painful to use.


    Left picture shows map with building placed in top left corner of the map. Middle - matrix as used in project. Right - matrix with added record to cover outside of screen, this causes (1,1) offset.

    To choose other part follow the link: