Degree project

A Diablo-like RPG

Let me proudly present my own take on the top-down RPG genre, combined with elements from my favourite game-series Dark Souls


What started as a small free-time project evolved into four months of dedication, refining gameplay mechanics and mastering the implementation of Diablo-like features. However, this isn't just another mob-grinding RPG. My goal was to create a challenging and punishing experience inspired by Dark Souls, where strategy and precision are key. By incorporating a rolling mechanic, players must carefully plan their moves and dodge enemies to survive, making every encounter a test of skill and reflexes.


Features overview

Skill tree

Abilities

Inventory

Player controls

Point to click

Hold or click the mouse button to move the character to that position


  • Gets the mouse input position on the NavMesh everytime you click
    • Sets the bool "isMoving" to true when pressing, and sets it to false when releasing
  • Moves player to that position
    • In case mouse raycast hits an interactable object, it runs a HandleInteraction() function instead
    • Gets the center point of that object and moves the player there

Move() runs while isMoving is true

In case mouse hit is Interactable

Buffered input

To make the game more responsive, I have implemented a "buffered input"-system. While doing an attack or performing a roll, the player can hit a key which performs another action during the current one, and when the current action is finished it queues the action the player has pressed.


  • Every action stores 2 floats:
    • A float which represents the time required for the moment he starts the action, to the point when it is ready for another input
    • The other float represents the time for when the buffered input is ready to perform
  • The PlayerInput script includes all functions for handling the different inputs
    • It is structures in a way that it first checks if player can do action at all
    • Then it checks if player is ready for another input
    • If that is false, is then checks if player is already doing an action, and if that is false, it then performs the action that the function is meant for
  • The PlayerMovement script stores three bools that checks if:
    • hasBufferedRoll
    • hasBufferedAttack
    • hasBufferedMovement

Roll mechanic

When pressing space, the player performs a roll.


  • The rolls mechanic is divided into four main functions:
    • RollStart()
      • First it clears any previous attacks (if performing)
      • Then it gets the mouse position which will be the direction of the roll
      • Then it disables the NavMesh agent
      • Then triggers the animation
    • RollUpdate()
      • This is used to handle any buffered input during different time marks of the roll
    • RollEnd()
      • Resets the NavMesh and warps the NavMeshAgent Y position to the current Y
    • OnAnimatorMove()
      • The player position of the roll is handled by the animation itself
      • It sets the player position to the Animator delta position
      • It does a raycast to the ground and lerps the Y-value to that, so that the player can roll up and down stairs
  • It also has a function that checks if the player is colliding with wall
    • In that case, it ends the roll and goes back to idle

RollStart:

RollEnd and OnAnimatorMove:

RollUpdate:

IsCollidingWithWall

Result

Enemy AI

Event handler

For the Enemy AI, I used something called Event Handler. Every event has an OnBegin, OnUpdate and OnEnd function, and a bool that returns if event is done. The enemy has a List (that acts as a stack) where it keeps track off which current event is active, when when the current event is done it removes it from the list and proceeds with the previous event. Some examples of events are:

  • Idle
  • Follow Player
  • Attack
  • Dead


Enemy base script

This is the script that all enemies use as a base. The scripts consists of:

  • OnObjectSpawn function
    • Since I am using Object pooling for this project, everytime an object spawns from the pool it runs this function
    • For the enemy, it resets some values like:
      • Sets the health to max again
      • Enables the NavMeshAgent
      • Clears the Event Stack
      • Disables ragdoll
      • Adds new "Idle event"
  • TakeDamage function
  • EnableRagdoll function
  • Die function
  • Attack function
    • Which simply adds the wanted attack-event
  • Has a list of the enemies drop rate


Enemy Event 

This is the script I have used for the base of all the other event scripts. Every enemy-event like Idle or Attack inherits from this.

  • Includes some important functions that all the other events can use:
    • SetNewDestination
      • Gets a random destination up to 3 times
      • As soon as the destination is valid, it sets the agent's target destination to that position
    • IsValidDestination
      • Returns true if the enemy can create a path to desired destination
    • CheckAnimationInterval
      • Checks every few milliseconds if any attack animations are playing
    • IsAnyAttackAnimationPlaying
      • Is called by CheckAnimationInterval
      • Loops through all the attack animations the enemy has and checks if any is playing
    • IsAnimationPlaying
      • Check if specific animation is playing
    • IsCloseToPlayer(distance)
      • Returns true if distance between enemy and player are less than the desired distance
    • IsTargetedAtPlayer
      • Does a raycast and returns true if it hits player

Some other functions that are inherited from Character script:

  • SetsStats
    • At the start of the game, all the stats are effected by which level the enemy is in
    • For every level, it multiplies the stats by 1.5 
    • Stats include:
      • Health
      • Damage
      • Defense
  • CanSeeTarget
    • Has a vision cone in front of enemy
    • Returns true if player is hit by raycast
  • SetFloatRunSpeed
    • Lerps the current speed and target speed
    • Sets the animator float "RunSpeed" to that value
    • This is to avoid a stuttery effect while transitioning to different animations
  • HandleRotation
    • Updates the rotation to face the target
    • Slerps between the current and target rotation to make it smooth


Boss fight