Menu
CoddyTech

Game Character Development

Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 64 of 65.

challenge icon

Challenge

Medium

Build the characters of a role-playing game: an abstract Character, three classes that extend it, and abilities that characters learn.

Character in character.gd is an @abstract class:

  • Properties: character_name (from the constructor), level 1, max_health and health 100.0, max_mana and mana 50, strength, intelligence and agility 10, defense 5, and abilities, an Array[Ability]. Health values are float, the other stats int.
  • A signal defeated(character_name: String).
  • Abstract methods class_title() -> String and attack_power() -> float.
  • level_up() -> String: +1 level, +10 maximum health, +5 maximum mana, +1 strength, intelligence, agility and defense; health and mana are refilled to their maximum. Returns NAME reached level N!.
  • attack(target): prints NAME attacks TARGET, then returns target.take_damage(attack_power()).
  • take_damage(amount) -> float: the damage is amount - defense * 0.5, but at least 1.0; health goes down by it and never below 0.0. It prints NAME takes D damage (HP H) with one decimal each, emits defeated when this hit took the health to 0, and returns the damage.
  • is_alive(): health above 0. learn_ability(ability) appends to abilities.
  • use_ability(index, target) -> float: for an index outside abilities, prints NAME has no ability INDEX and returns 0.0; otherwise returns what the ability's use() returns.
  • _to_string(): NAME the TITLE (level L): HP H/MAX, mana M/MAX, STR S, INT I, AGI A, DEF D, health with one decimal.

Each subclass calls the parent constructor, then sets its own starting stats (the others keep the base values), returns its class name from class_title() and overrides level_up() with super():

  • Warrior: health 150.0, mana 30, strength 15, intelligence 5, defense 8; attack power strength * 1.2; on level up also +2 strength and +10 maximum health (health refilled).
  • Mage: health 80.0, mana 150, strength 5, intelligence 20, defense 3; attack power intelligence * 0.4; after a normal attack it gets 5 mana back (never above the maximum); on level up also +2 intelligence and +15 maximum mana (mana refilled).
  • Rogue: health 100.0, mana 60, strength 12, intelligence 8, agility 18, defense 4; attack power strength * 0.5 + agility * 0.7; on level up also +2 agility.

Ability in ability.gd: the constructor takes ability_name, mana_cost (int) and damage (float). use(user, target) -> float prints USER does not have enough mana for ABILITY and returns 0.0 when the user's mana is below the cost. Otherwise it takes the mana, prints USER casts ABILITY on TARGET and returns target.take_damage(damage + user.intelligence * 0.5).

The supplied code connects to every character's defeated signal and prints NAME is defeated!. For this input:

hero warrior|Aragorn
hero rogue|Orc
attack Aragorn|Orc
attack Orc|Aragorn
level Aragorn
status Aragorn

the output is:

Aragorn attacks Orc
Orc takes 16.0 damage (HP 84.0)
Orc attacks Aragorn
Aragorn takes 14.6 damage (HP 135.4)
Aragorn reached level 2!
Aragorn the Warrior (level 2): HP 170.0/170.0, mana 35/35, STR 18, INT 6, AGI 11, DEF 9

Your code goes in character.gd, warrior.gd, mage.gd, rogue.gd and ability.gd. main.gd holds the supplied input/output code and cannot be edited.

Try it yourself

extends Node

func _ready():
	# Supplied input/output code: keep it as it is

	var heroes := {}

	var line := OS.read_string_from_stdin()
	while line != "":
		var parts := line.split(" ", true, 1)
		var f: PackedStringArray = parts[1].split("|") if parts.size() > 1 else PackedStringArray()
		match parts[0]:
			"hero":
				var hero: Character
				match f[0]:
					"warrior":
						hero = Warrior.new(f[1])
					"mage":
						hero = Mage.new(f[1])
					"rogue":
						hero = Rogue.new(f[1])
				hero.defeated.connect(func(who): print(who, " is defeated!"))
				heroes[f[1]] = hero
			"learn":
				heroes[f[0]].learn_ability(Ability.new(f[1], int(f[2]), float(f[3])))
			"attack":
				heroes[f[0]].attack(heroes[f[1]])
			"cast":
				heroes[f[0]].use_ability(int(f[1]), heroes[f[2]])
			"level":
				print(heroes[f[0]].level_up())
			"status":
				print(heroes[f[0]])

		line = OS.read_string_from_stdin()

All lessons in Object Oriented Programming

Practice on your own: Online GDScript compiler