- Home /
Dynamic function calling
I need a method to dynamically call different functions from a huge pile of functions.
.
A small example part of my problem;
There are passive defensive skills which automatically activate when the player about to takes damage.
Problem is there are hundreds of skills and only a couple of it active at a time.
I don't want to call every single function and check if they are currently active,
.
For example, let's say I have Functions as;
SuperBlock, Avoid, ThornMail, CounterAttack, X, Y, Z, SuperArmor ... etc
.
And only "SuperBlock" is activated for the player so I only need to call SuperBlock function.
.
So basically I am trying to avoid doing this;
bool SuperBlockIsActive = true;
bool AvoidIsActive = false;
bool ThornMailIsActive = false;
bool CounterAttackIsActive = false;
public void DefensiveSkills(int damage, string attackType)
{
if (SuperBlockIsActive)
{
SuperBlock (damage, attackType);
}
if (AvoidIsActive)
{
Avoid (attackType);
}
if (ThornMailIsActive )
{
ThornMail (damage, attackType);
}
if (CounterAttackIsActive )
{
CounterAttack (damage, attackType);
}
}
....
I tried to solve this problem with "Invoke" function but because I need to pass parameters I couldn't achieve what I needed.
What I did was creating a list of active function names and calling them with Invoke.
private List<string> ActiveDefensiveSkills = new List<string>();
private void StartTurn()
{
foreach (string function in ActiveDefensiveSkills)
{
Invoke(function, 0f);
}
}
I need to call functions with parameters like damage, attackType, etc.
I also tried using global variables but because the compiler does not wait for Invoke and continue the code, it doesn't work either.
.
How can I solve this problem?
Is there even a way?
.
Edit:
I decided to share the whole idea just to be more clear so you don't need to read the rest if you are not interested in my code, I am doing a turn-based game and some attack directly hurt the player (Like getting attacked with a sword) and some attacks give damage each turn (Like burn damage for 3 turns).
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Conditions
{
public string attackName;
public int attackDamage;
public string attackType;
public int attackTurn;
public int attackPenetration;
public void NewCondition(string aName, int aDamage, string aType, int aTurn, int aPenetration)
{
attackName = aName;
attackDamage = aDamage;
attackType = aType;
attackTurn = aTurn;
attackPenetration = aPenetration;
}
}
public class Player : MonoBehaviour
{
[Header("[Current]")]
[SerializeField] private int currentHealth = 0;
[Header("[Basic]")]
[SerializeField] private int maxHealth = 100;
[Header("[Defensive]")]
[SerializeField] private int armor = 0;
[SerializeField] private int magicResistance = 0;
[SerializeField] private float blockChance = 0f;
[Header("[ActiveSkills]")]
[SerializeField] private List<string> ActiveDefensiveSkills = new List<string>(); //Trigger when get attacked
[SerializeField] private List<Conditions> ActiveConditions = new List<Conditions>(); //List of active Conditions
//---MainFunctions----
private void StartTurn()
{
//Activated at the beginning of Player's each turn
foreach (Conditions condition in ActiveConditions)
{
Defense(condition.attackDamage, condition.attackType, condition.attackPenetration);
condition.attackTurn--;
if (condition.attackTurn <= 0) ActiveConditions.Remove(condition);
}
}
public void GetAttacked (string attackName, int attackDamage, string attackType, int attackTurn, int attackPenetration)
{
//Enemy calls this function and everything starts from here, after applying the first damage if the attack is turn-based then it gets added to conditions.
Defense(attackDamage, attackType, attackPenetration);
if (attackTurn > 1)
{
attackTurn--;
Conditions condition = new Conditions();
condition.NewCondition(attackName, attackDamage, attackType, attackTurn, attackPenetration);
ActiveConditions.Add(condition);
}
}
private void Defense(int attackDamage, string attackType, int attackPenetration)
{
//Function called when the player takes damage
//This is where I have a problem, I want to call only active defensive skills (their name is stored in "ActiveDefensiveSkills")
foreach (string function in ActiveDefensiveSkills)
{
// attackDamage = function(int attackDamage, string attackType, int attackPenetration); i want to do something like this
}
TakeDamage(attackDamage);
}
private void TakeDamage(int damage)
{
//Implement the damage
Debug.Log("You took " + damage + " dmg");
if (currentHealth - damage <= 0)
{
currentHealth = 0;
Death();
}
else
{
currentHealth -= damage;
}
}
private void Death()
{
Debug.Log("You Died");
}
//---SkillFunctions---
private int Block(int attackDamage, string attackType, int attackTurn, int attackPenetration)
{
//Player can block the some of the incoming damage
if (blockChance >= Random.Range(1, 101))
{
Debug.Log("Damage blocked");
return attackDamage / 2;
}
else return attackDamage;
}
private int Resistance(int attackDamage, string attackType, int attackTurn, int attackPenetration)
{
//Decrease the incoming damage
int result = armor - attackPenetration;
if (result < 0) result = 0;
return attackDamage - result >= 0 ? attackDamage - result : 0;
}
private int LastStand(int attackDamage, string attackType, int attackTurn, int attackPenetration)
{
//Player health will not drop under 1
return currentHealth - attackDamage >= 1 ? attackDamage : 0;
}
private int Invulnerability(int attackDamage, string attackType, int attackTurn, int attackPenetration)
{
//Player become invulnerable to any kind of attacks
return 0;
}
}
Answer by Hellium · Jun 07, 2019 at 09:27 PM
Fore sure there is a way, many ways!
Given the (few) details you gave about your system, a simple solution would be to make all your functions with the same signature: void FunctionName( int damage, string attackType )
, even if all the parameters are not used inside the function.
Then, you would use a delegate to "hold" references to the functions to call.
private System.Action<int,string> activeDefensiveSkills;
private bool SuperBlockIsActive
{
set
{
activeDefensiveSkills -= SuperBlock;
if( value ) activeDefensiveSkills += SuperBlock
}
}
private bool AvoidIsActive
{
set
{
activeDefensiveSkills -= Avoid ;
if( value ) activeDefensiveSkills += Avoid
}
}
private bool ThornMailIsActive
{
set
{
activeDefensiveSkills -= ThornMail ;
if( value ) activeDefensiveSkills += ThornMail
}
}
private bool CounterAttackIsActive
{
set
{
activeDefensiveSkills -= CounterAttack ;
if( value ) activeDefensiveSkills += CounterAttack
}
};
public void DefensiveSkills(int damage, string attackType)
{
if (activeDefensiveSkills != null)
{
activeDefensiveSkills(damage, attackType);
}
}
But, with more details about your system, a more flexible and less verbose solution may be considered
Okay, "delegate" looks very promising and you are right I shared the some of the code just to be sure, I tried to cut unimportant parts and just left the "Defense" part and I added some of the actual skills. Is it possible if you can show an example of how I can edit my code in the way you showed?
After reviewing the code you have provided, I suggest you to rework it. Your Player
class is responsible for too many different things. I believe your current class is at least 1000 or 2000 lines long. Isn't it?
I spent two hours coding a possible rework for your system. I believe the changes are quite heavy, but for maintainability reasons, ease of use and ease of evolution, you should consider it.
The code is commented enough to let you understand how it works, if it's not clear enough, don't hesitate to post comments at the bottom of the page so that we can discuss about it.
I like how you used the ability to add/remove handlers from the delegate instance. Uses the intrinsic abilities of the delegate more than my approach. Nice!
Answer by JDelekto · Jun 08, 2019 at 12:57 AM
One way that I would do this is by having kind of a "sack" of skills that can be added or removed from the script that is attached to your player. The actual actions are private and there are basically three public methods: a) one to add a skill, b) one to remove a skill; and c) one to evoke all the skills that are currently enabled. I typically use a Dictionary of skills (through an enum) which map to a delegate (all of which have the same signature). Here is an example:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum Skill
{
SuperBlock,
Avoid,
ThornMail,
CounterAttack
}
public class PlayerActions : MonoBehaviour
{
private Dictionary<Skill, Action<int, string>> actions = new Dictionary<Skill, System.Action<int, string>>();
// Awake is called when the script instance is being loaded.
protected void Awake()
{
// Add 2 skills here (should be called from the outside)
this.AddSkill(Skill.SuperBlock);
this.AddSkill(Skill.Avoid);
// Test the use of skills here, but UseDefensiveSkills() should be called from the outside...
this.UseDefensiveSkills(3, "simple");
// Remove a skill here (should be called from the outside)
this.RemoveSkill(Skill.SuperBlock);
// Test the use of skills here, but UseDefensiveSkills() should be called from the outside...
this.UseDefensiveSkills(10, "advanced");
}
public void UseDefensiveSkills(int damage, string attackType)
{
foreach (Action<int, string> action in actions.Values)
{
action(damage, attackType);
}
}
public void AddSkill(Skill skillType)
{
if (!actions.ContainsKey(skillType))
{
switch (skillType)
{
case Skill.SuperBlock:
actions.Add(skillType, this.SuperBlock);
break;
case Skill.Avoid:
actions.Add(skillType, this.Avoid);
break;
case Skill.ThornMail:
actions.Add(skillType, this.ThornMail);
break;
case Skill.CounterAttack:
actions.Add(skillType, this.CounterAttack);
break;
default:
break;
}
}
}
public void RemoveSkill(Skill skillType)
{
if (actions.ContainsKey(skillType))
{
actions.Remove(skillType);
}
}
private void SuperBlock(int value, string text)
{
Debug.Log("SuperBlock!");
}
private void Avoid(int damage, string attackType)
{
Debug.Log("Avoid!");
}
private void ThornMail(int damage, string attackType)
{
Debug.Log("ThornMail!");
}
private void CounterAttack(int damage, string attackType)
{
Debug.Log("CounterAttack!");
}
}