D36, a Deckbuilding Roguelike (Live Changelog)

August 06, 2020

This is a changelog, as well as a future goal list. Started June 05 (2020).

This is a roguelite deckbuilder with a twist! It's played on a gameboard with the extra dimension of movement.


Progress to Date


  • World Generation System

    • Implemented based on Unity Roguelike Tutorial, flexible grid system with placement of obstacles, custom objects (enemies)
  • Card System

    • Functions for adding, destroying, shuffling, managing cards
    • Card functions fully integrated in several classes
    • Database of cards and future ideas in the Airtable deck
    • Card template placeholders sprited
    • Card Scriptable object setup
    • Dynamic layout (orientation and transform) algorithm implemented
    • Deck -> Hand -> Discard loop fully implemented
  • Combat System

    • Zoom on active enemy implemented, plus other smooth camera LERP for translation
    • Enemy hitmarker animation prefab implemented
    • Enemy Scriptable Object setup
    • Two loam enemies sprited
    • Basic greedy pathfinding and attack sequence implemented
    • Bomb counters (using turn counters) implemented
    • Status effects on enemies implemented with UI and gameplay impacts
  • Currency and Shop System

    • Shop scene (+ relevant scene transition rehooks for future Main Menu/NPC village interaction) created with template assets
    • Loot system + Card choices fully implemented
  • Player System

    • Health, Shields and AP fully implemented though lacking visual cues
    • Madness UI/code implemented, but unused
  • Random Event System

    • Modal generated with templates, dynamic buttons/layout, flavour + reward text implemented
    • RandomEvent class implemented
  • Inventory System

    • Item UI and dynamic layout generated
    • Item Tooltip UI set up
    • Item Scriptable Object generated
    • Item collection linked to Shop + Looting systems

05-06-2020


Main changes - Partially implemented random event consequence behaviour

  • Combat System

    • Enemy movement restricted to a cross, via crosscounter for loop iteration restriction to even indices
  • Random Event System

    • Conditional reward text generation based on button pressed and current event, class for StatRewards implemented
    • Currently only templates with no gameplay impact (Says "+ gold" but nothing is added)

Next:

  • Probability display for choices
  • Implementation of consequences (application of results to gameplay variables)
  • Implementation of Item Effects

    • Including additional consideration of player stats
  • Difficulty Escalation subsystem

    • Part of the Worldgen system, factoring into the algorithm how many enemies to create
  • A trap system
  • Boss enemy
  • More enemies
  • More cards

    • Weighed probability card draw system
    • Card UI redesign
  • The Death System
  • The Madness Subsystem

    07-06-2020


Main changes - Bugfix, Movement/attack confinement ,visual effects for action/turns

  • Combat System

    • Added turn marker chevron sprites above enemy
    • Changed all attack and movement to cross based, or circular (radius R) drawing tiles with altered algorithm for drawing a circular area. This is applied equally to players and enemies.

      public void DrawCircleOverlay(int type, int range){
      ...
      if(new Vector2(i,j).magnitude <= range){ //circle drawing, range = radius
          overlayTileMap[i + (int)overlayOrigin.position.x, j + (int)overlayOrigin.position.y].SetStatus(type);
      }
      ...
      }
      
  • Added Turn Effect Renderer - Flexible backend for drawing sprite swappable effects on top of gameplay pieces:

    public void Draw...Effect(Transform parent){ //-extend to EffectType for spriteswap
        Instantiate(effect, parent);
    }
    
  • Random Event System

    • Corrected misindexing of choice/consequence pairing -> bidx0 should be shop access, not leave and other such instances

-Graphical Improvements'

  • Working on respriting the Card design to a final resolution, with applicable masks. Video screencap recorded on OBS.
  • Partial card sprite refitting

Next:

  • Complete card sprite refitting -> New paradigm: Set the rarity in the SO as a character, and then when drawing, refer to the Adamant.CardManager sprite arrays using a switch statement found in the CardInstance.CardDisplay

    • Finish writing the switch statements, redo the animation
  • Make preaction delayed, for camera lerp time

11-06-2020


  • Card system

    • Completed respriting and reconfiguration of cards in main, but not deck display
  • WorldGen System

    • Added difficulty scaling and enemy queuing via resource random paradigm:

      float resources = 3.5f*Mathf.Log(level+1)+1;
      print("resources = " + resources);
      Queue<Enemy> enemyQueue = new Queue<Enemy>();
      while(resources>=1){ //greater than the cost of the cheapest enemy
      Enemy testEnemy = enemies[Random.Range(0,enemies.Length)];
      if(testEnemy.resCost <= resources){
      enemyQueue.Enqueue(testEnemy);
      resources-=testEnemy.resCost;
      numEnemies++;
      } 
      }
      
  • Content

    • Added the Slughoul enemy, with high HP, and attack. Idea of making <1 AP enemies, who move every 1/AP turns or done via probability

Next:

  • Think about boss and final level
  • Level indicator UI
  • Shop UI broken when adding to nonexistent inventory
  • Shop redesign

12-06-2020


Trap System [New!]

  • Added trap prefab
  • Added traps to generation list

Next

  • Trap animation control more precisely → Set up a spawn animation like monsters

14-06-2020


Trap System

  • Spiketrap fully implemented with appropriate damage and animation cycling from idle -> warn (with warning UI icon) -> activate using animation triggers:

    if (turnCount%chargeFreq == 0){
            transform.GetChild(0).gameObject.SetActive(false);
            a.SetTrigger("triggerTrap");
    
            //trap triggered - Detect Collision
            Collider2D [] hitObjects = Physics2D.OverlapCircleAll(transform.position, 0.5f);
            foreach (Collider2D c in hitObjects){
                if(c.tag == "Enemy"){
                    c.GetComponent<EnemyInstanceManager>().TakeDamage(trapDamage);
                }
                else if (c.tag == "Player"){
                    GameManager.g.gameObject.GetComponent<PlayerStats>().TakeDamage(trapDamage);
                }
            }
    
  • Variable trap damage and frequency implemented, though different types of traps might make the prefab difficult to abstract clearly, particularly due to animation trigger naming. However, the warning sprite can be all purpose used

Combat System

  • Added Azorite enemy SO w/ animations and spawns, not balanced -> Potential boss!

Bug

  • Many more cards than usual spawn with a loamwalker and slughoul on the board, both stacking on the bomb causing bombcontroller to malfunction ; not sure how to reproduce

    • Current Solution → Unlink turn counting from turncounter (global) and do all turn computations locally, per entity for bombs & traps

Next

  • Special hitmarker effect/extensibility with same for trap damage

15-06-2020


Combat System

  • Added variable hitmarker effects and new parameter (char type) to spawn specific effects, by spawning a generic hiteffect gO and setting the animation, similar to enemy:

    • s/default = slash | k = spike | u = skull | r = reversal | b = bomb
  • Modified turn processing order to Coroutine based and sequential

    public IEnumerator TurnParseSequence(){
        yield return StartCoroutine(enemyManager.EnemyMove());
        yield return StartCoroutine(TriggerProps());
       InitializeTurn(); 
    }
    

Card System

  • FIX: Deck preview now refitted with new cards
  • Added card: Autodash - Moves you adjacent to the exit for free, with proper distance checks, drawing and discarding is a bit iffy though

Worldgen System

  • Added map diagnostic text file that autorefreshes on build and logs turns with character key and header:
d36 Dungeon Display
----- KEY -----
. = empty    B = bomb 
E = enemy    P = player 
R = rock     T = trap
X = exit

----- Turn #0 -----
. . . . . . 
. . . . . R 
R X . E R . 
R . . . . R 
. P R . R . 
. T . . R . 
  • Changed mapreference to char2D instead of int2D, for easier references, but this may have unintended errors in the future due to int/char ambiguity not throwing an error later
  • Adjusted difficulty scaling to 3.5sqrt(l+1) rather than via log.

Other

  • Added Cheats:

    • F1 - Add 100 AP
    • F2 - Add 1 madness
    • F3 - Randomly Kill an enemy (reuse of dark sacrifice card code)
    • F4 - Skip 10 levels

17-06-2020


Card System

  • Added Card Art:

    • Slash, Bomb, Pierce, Soulsiphon, Move, Jump, Shield Dash, Autodash, Fortify, Bandage

Combat System

  • Added healthbars to enemies with complete spriting through HP/MaxHP mapping. (instant, slow hpbar to be added later). Positioning and scale are hardcoded.
hpFill.localPosition =  Vector3.Lerp(new Vector3(-0.4402f, 0.03f, 0),Vector3.zero+0.03f*Vector3.up,hp/mhp);
hpFill.transform.localScale = Vector3.Lerp(new Vector3(0,1,1),Vector3.one,hp/mhp);

18-06-2020


Worldgen System

  • Added chest, animations but not rigged up

20-06-2020


Card System

  • Refactored card drawing system, added position/orientation LERP targets for smooth animation multiplied by animspeed timestep factor
  • Card Destruction and Creation now handled by directly passing "selected card" Card object from CardDisplay button to TurnParser, rather than a reference to the hand list
  • BUG - CardManager.cs ln57??? Array out of bounds/nonnegative??? idk

Next

  • Enemy differentiation for more interesting strategic gameplay, including countering from card types/damage types? More AI complexity than just moving / attacking based on resource points like CIV VI - Special unit effects
  • Implement more cards!

30-06-2020


Graphics

  • New exit sprite + open animation, updated to 32x32 resolution
  • New player sprite + idle animation, updated to 32x32 resolution
  • New cult symbols
  • New Flooring - Ground Tileset w/ 5 Tile Variations, updated to 32x32 resolution

Combat System

  • Runes can be drawn with the CustomParticleSystem Component, which can burst runes or vertically display them, with preset velocity, number, lifespan, colour - All fadeout to crgb 0.1.0.0 (clear red)
  • Rune component attached to enemy on hit, but looks messy so disabled.
  • Rune component attached to exit gate, to show a "locked" rune on scene entrance and an "open" rune on scene exit

Other

  • New CustomParticleSystem component → Architecture to generate custom particles with a simpler system

    • Test addition to the EIM component, on the TakeDamage function
  • Thought → How to generate enemy differentiation? More vectors than Health, AttackRange, Damage, MoveRange

    • Instead, generate different functions with possible synergizing enemies, like a bomber, splitter, necromancer, medic, thief, engineer, card-stealing, energy-stealing, bomb-defusing and other enemy archetypes
    • Enemies should interact with all the game mechanics, not just the HP one - They can remove cards, destroy bombs, alter the board, change other enemies, and have other effects!

Next

  • Lerp Movement?

01-07-2020


Player System

  • Player movement has been unified between the PlayerMovement and DrawPlayer scripts and the player's movement is now controlled by LERP Targeting
  • The DrawPlayer Script has been eliminated for redundancy, reference rehook has been established to PlayerMovement in the SceneReloader script
  • Public movefactor has been added - could be edited for quick movement like in Civ

Enemy System

  • Enemy movement has been lerped
  • An additional nullcheck has been added to prevent nulref error after kill by bleed
  • Bug - Lerping results in weird warps to origin/0 and enemy crossstack

Graphics

  • Bomb has been resprited and resolution updated to 32x32

Combat System

  • Azorites have new attack - Firecross - Spikes of fire instantiated in a squared cross around the enemy with custom range

    Instantiate(azorFireSpike, new Vector3(pos.x+i, pos.y+j,0), Quaternion.identity);
    azorFireSpike.GetComponent<CustomAttackEffector>().setDamage(dmg);
    

Other

  • Added Insert Datestring KBshortcut for easier devlogs - Ctrl+Shift+I - to VSCode Extensions

20-07-2020


Major Change

  • Swap to isometric tile system, requiring significant overhauls of movement, levelgeneration, collision and pathfinding code
  • Built
  • Still quite a few bugs left with level generation, movement, spawning outside of level, vertical displacement by origin location, etc - tiles should have origin moved down
  • Setup two transformation utility functions of ISOtoXY and XYtoISO in the boardmanager script:

    public Vector3 IsoToXY(Vector3 v){
        return new Vector3(0.5f*v.x - v.y, 0.5f*v.x + v.y, 0);
    }
    public Vector3 XYToIso(Vector3 v){
        return new Vector3(v.x + v.y, 0.5f*(v.y-v.x), 0);
    }
    

21-07-2020


Bugfixes

  • Sorted out several problems with Isometric system overhaul

    • Movement, Attacks encoded properly via XY/iso
    • Enemy spawning seems to work

Graphics

  • Added level loading effect by scale/float in with LERP targets
  • Reorganized the BoardInitialization function with most things being placed in the CreateBoard() coroutine rather than in the end of script.
  • Redesigned the Trap asset, with an overlaid warning tile rather than a hovering icon (timing is bugged)

25-07-2020


Graphics

  • Changed enemy sprites to placeholder.
  • Resprited exit tile

Next:

  • Exit tile should instance an (onexit) collider upwards on end of scene
  • Enemy code architecture should be restructured to use inheritance and OOP principles

    • This way enemies may be separated into different classes rather than trying to fit them into the same generic class.

PUSHED TO REPO

31-07-2020


Enemies

  • BFS pathfinding is implemented for enemy movement:

    • However, it stops all enemies from moving if the player is surrounded - some solutions are:
    • Give alternate targets if player is blocked off
    • Move as close to player as possible - Ignore enemies in path but break out of step loop, though this can create other problems - Spiralscan around player for nearest unnoccupied "target" position close the real target.
    • For now - move to a random adjacent unoccupied position

      try //throws keynotfound exception if the path is blocked/impossible
      {
          while (currentStep != initialPos)
          {
              path.Add(currentStep);
              currentStep = cameFrom[currentStep];
          }
      
          path.Remove(boardManager.IsoToXY(target.position));//to prevent going on the player
          path.Reverse();
      }
      catch
      {
          // if the path is impossible, go in a random valid direction
          print("no valid path");
          Vector3[] adjacents = { initialPos + Vector3.left, initialPos + Vector3.up, initialPos + Vector3.right, initialPos + Vector3.down };
          List<Vector3> validAdjacents = new List<Vector3>();
          foreach (Vector3 v in adjacents)
          {
              if (boardManager.isValidAndEmptyPosition(v))
              {
                  validAdjacents.Add(v);
              }
          }
          //if there are no valid adjacent paths
          if (validAdjacents.Count == 0)
          {
              path.Add(Vector3.zero);
          }
          //if there are valid adjacent paths, add a random position to one.
          else{
              path.Add(validAdjacents[Random.Range(0, validAdjacents.Count)]);
          }
      }
      
  • This is a Failed Trycatch - enemies teleport to player
  • Refactored status effect parsing, now uses strings rather than an array of switches - This way, multiple instances of "bleed" can be passed, one is thrown out and processed each round.

    private int ParseStatus()
    {
        // List<string> tempStatusEffects = statusEffects;
        if (statusEffects.Count > 0)
        {
            statusImage.SetActive(true);
        }
        else
        {
            statusImage.SetActive(false);
        }
    
        //could nest this
        if (statusEffects.Contains("bleed"))
        {
            statusEffects.Remove("bleed");
            TakeStatusDamage(1, 'k');
        }
    
        return 0;
    }
    
  • ParseStatus is called correctly, but ideally there should be a chain of events -> damage parsing -> status infliction -> status effect with noticable delay
  • Added Engineer Enemy

    • After 3 turns:
    • Moves towards player - linear move
    • Has an 80% chance of spawning an enemy
    • Has a 20% chance of spawning another engineer
    • This needs visual indicators!

Bugs

  • Trap doesn't work as expected
  • Gamebreaking - Switching scenes breaks handmanager reference?

01-08-2020


Cards

  • Added armored dash card
  • Trying to fix scene reloading issue to no avail - I will dismantle and rebuild this card system.

02-08-2020


Cards

  • Handmanager is deprecated - Variables and Functions are moved into the CardManager script
  • Card destruction functionality has been removed from carddisplay script
  • Scene reloading issue is resolved

    • New bug - Cards not spawning until turn 2, has to do with order script loading
  • Implemented new dragging interface!

Enemy

  • Working out "nearest" pathfinding rule

Other

  • To fix hitdetection - bomb "fire" instances should do the damage with small raycasts, rather than the bomb creating an elliptical raycast

03-08-2020


Cards

  • Added Recant, Devotion cards for synergy

Player System

  • Added madness mechanic and redid UI - Just a number, instead of a bar

Cheats

  • Altered cardspawning + key indicator for input reception + array of inputs

    if (Input.GetKeyDown(KeyCode.Keypad7)){
                GameManager.g.gameObject.GetComponent<CardManager>().AppendCardToHand(cardsToAdd[0]);
            }
            if (Input.GetKeyDown(KeyCode.Keypad8)){
                GameManager.g.gameObject.GetComponent<CardManager>().AppendCardToHand(cardsToAdd[1]);
            }
            if (Input.GetKeyDown(KeyCode.Keypad9)){
                GameManager.g.gameObject.GetComponent<CardManager>().AppendCardToHand(cardsToAdd[2]);
            }
    

Other

  • Improved roadmap for graphical overhaul + boss battle

07-08-2020


World Generation

  • Setup new TileInstance.cs script for all map tiles - handles tile swapping with the SwapTile method, which exchanges gameobjects from normal - trap - blight tiles, initializes them (sets parent and inserts in tilemap array BMgr) and sets correct positioning

    public void SwapTile(string swaptype)
    {
        GameObject instance = gameObject;
        switch (swaptype)
        {
            case "blight":
                instance = Instantiate(boardManager.customTileEffectPrefabs[0], transform.position, Quaternion.identity);
                // boardManager.InitializeTileExternal(instance , boardManager.IsoToXY(transform.position)); //perhaps a more complex function to add to the tilearray?
                break;
    
            case "normal":
                instance = Instantiate(boardManager.floorTiles[0], transform.position, Quaternion.identity);
                
                break;
    
        }
    
        boardManager.InitializeTileExternal(instance, boardManager.IsoToXY(transform.position));
        Destroy(gameObject);//purge itself from the scene
    }
    

Bugs

  • AP cap not registering - Can still play and get -ve AP
  • Player spawn timing? Can spawn on rock.

Cheats

  • F12 will be wildcard rebind - Comment out prev. and replace w/ other short rebinds.
  • Currently sets position 3,3 to blighttile