poniedziałek, 10 sierpnia 2015

#Stencyl: Zerg terrain infection

Part 4: Spreading the infection

  • 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

  • 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: 

    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:

    niedziela, 2 sierpnia 2015

    #Stencyl: Matrix behavior

    • Download
    • Behavior's history
    • Matrix in matrix behavior
    • Using matrix behavior
    • List of trigger events
    • Storing in matrix something different than numbers
    • Implementation details

    <Matrix behavior> can be downloaded together with <Zerg terrain infection> project:

    Behavior's history
    There are many cases when people need 2D lists or matrix for their project. Stencyl does not support in any way such problem.

    My first prototype for matrix behavior was created when I need to make a map for generating maze.

    Generating maze project:

    The behavior worked but whole set-up was rather chaotic. As I went on with Zerg terrain infection project I decided to make matrix handling more coherent.

    Zerg terrain infection project

    The behavior still needs some cosmetic upgrades such as more intuitive arguments names. In current version the behavior is fully functional. I plan to develop matrix behavior in future in order to make it more user-friendly. The next version of behavior will come with presentation in form of Stencyl project.

    If you need a function and it hasn't been implemented let me know.

    Apparently there is another 2D list extension but I haven't used it and I doubt if there is any documentation on how to use it:

    Matrix in matrix behavior

    Matrix is 2D list made for storing number and numbers only. It has x and y direction. Set of entries with the same y and different x is called record. Matrix is limited in x direction and unlimited in y direction meaning you can add any number of records you like. Position in both x and y direction are indexed starting from 0.

    Picture of matrix. Red numbers represent single record.

    As we create matrix we name it. There can't exists two matrix with the same name - name must be unique. The newly created matrix is the added to the list of matrices. That means we can refer to matrix either using its name or using index which holds reference to the matrix. Most trigger events are available in two options _by_name or _by_index. Using _by_index is quicker but is less user friendly.

    Matrix knows:
    • its name
    • its index in list of matrices
    • its number of records
    • limit in x-direction

    Picture shows list of matrices with 3 matrices.

    Using matrix behavior

    Communication with <Matrix behavior> is based around setting attributes for <Matrix behavior> using block: <for this scene, set <> to <> for behavior <Matrix behavior> >. Then we trigger event according to what we want to do.

    If what we did should return something, for example value at (x,y) position we can get it using block: <for this scene, get <> from behavior <Matrix behavior> >.

    In <Matrix behavior> there is a special inactive <when created> event that allows quick copy-paste from <Matrix behavior> to other behaviors.

    List of trigger events

    General rule is that _by_index triggers don't check if input values makes sense. They assume you know what you are doing. _by_name triggers tend to check input values and print proper error to the console if they find something wrong.

    IN - attributes you are ought to change while triggering the event
    OUT - attributes you can get after you trigger the event

    In <Matrix behavior> the following trigger events has been implemented:

    • add_list - create new matrix
      • Additional information:
        • You can't trigger scene events in <when created> events of scene bahavior. You need to add do after <small time> if you want to use trigger event in <when created> event of scene bahavior.
      • IN:
        • name - name of matrix; must be unique
        • size - size of record in x direction; must be bigger than 0

    • add_record_by_name - add record to the matrix with given name
      • IN:
        • name - name of matrix
        • input_list - list containing record you want to add

    • add_record_by_index - add record to the matrix with index
      • IN:
        • helper_2 - index of matrix; must be unique
        • input_list - list containing record you want to add

    • get_data_by_name - returns info(index, number of records, limit in x-direction) about matrix with given name
      • IN:
        • name - name of matrix
      • OUT:
        • size - limit in x-direction
        • number_of_records - number of records in the matrix
        • matrix_index - index of matrix in list of matrices

    • get_record_by_index - returns record from matrix with given index
      • IN:
        • input_1 - index of matrix in list of matrices
        • input_2 - index of record (index in Y direction)
      • OUT:
        • input_list - list containing a record

    • get_value_by_index - gets value at (x,y) position from matrix with given index
      • IN:
        • input_1 - index of matrix in list of matrices
        • input_2 - X position
        • input_3 - Y position
      • OUT:
        • output - value at (X,Y) in matrix with given index
    • set_value_by_index - sets value at (x,y) position from matrix with given index
        • Additional information:
          • It's forbidden to set value in record that doesn't exist.
        • IN:
          • input_1 - index of matrix in list of matrices
          • input_2 - index in x direction
          • input_3 - index in y direction
          • input_4 - value to be set

        • fill_by_name - matrix with given name is changed into matrix of <n> identical records. Each record is filled with the same number.
          • Additional information:
            • requires list_behavior
          • IN:
            • input_1 - number that should fill matrix
            • input_2 - number of records that should fill matrix

        • print_matrix_names - prints into console all names of matrices along with their indexes

        • print_matrix - prints into console matrix with given index
          • IN:
            • input_1 - index of matrix in list of matrices

        Storing in matrix something different than numbers

        Matrix behavior does not allow to put into matrix something else than numbers. In order to store in matrix other type than numbers we need to do it indirectly. We create additional list with objects of type we want and in matrix we keep indexes to the objects.

        Picture shows indirectly placing objects of chosen type into matrix.

        Implementation details

        In reality <Matrix behavior> keeps matrices as lists. X and Y coordinates of matrix are translated to index of list using limit in x direction. If X and Y are coordinates then index [i] in list is calculate as:

        [i] = Y * <limit in x direction> + X

        Matrix as in matrix behavior and matrix as list.

        Matrices are stored in list called <ultra_list>. Attributes of matrix under index [i] in <ultra list> are stored as elements [i] in lists:
        • name - <name_of_lists>
        • number of records - <number_of_rows>
        • limit in x-direction - <size_of_list>

        <Matrix behavior> uses one local trigger event <find_list>. <find_list> searches for matrix with <name> in list <name_of_lists> and returns index of matrix in <ultra_list> as helper_2. If matrix with given name doesn't exist helper_2 is returned as -1.