Transcript for:
Understanding Python Classes

Would you be surprised if I told you  that you've already used classes before,   at least for printing Hello World? You don't believe me? Well, what is Hello World? It's a string, right? Easy question. Okay, but what is a string exactly? Those who new to object-oriented  programming might get confused,   but you'll understand everything  by the end of this video. String, along with the other built-in  data types like integer, boolean,   etc. are predefined classes in Python. Whenever you create a variable, it will  instantiate an object of the corresponding class. So when I say, message = "Hello World",   we silently initialize an object of the  string class with the value: "Hello World". I know it might look confusing at first, I've  been there too, but let's simplify the concept. We can think of classes as blueprints for specific   objects. And objects are stuff  we implement using these classes. In this video we'll be covering three kinds of  classes. Characters, weapons and health bars. And by the end of this video we'll  have a functional battle scene. It would make the most sense if we  started with characters, so let's go! Let's think of a game character. What is it like? What can it do? These are the two questions a  character class can give answers to. To make literally the  simplest functional character,   it should have a name, some health and damage. These traits are what we  call attributes of an object. To make a simple battle scene, we  should also let this character attack. Every action we code into  a class is called a method. A method looks just like a function, with  the difference that it's linked to a class. Now let's create the Character blueprint and  two character objects to simulate a battle! Let's start with an empty  file, I call it character.py. We define a Character class this way. For classes the naming convention is CamelCase,  so all starting letters are capitalized. Then we define the special  initializing method of this class. This is a double underscore method. They also call it dunder method, or magic method. You will only come across this kind of methods   if you create your own classes or  look up Python's built-in classes. These are the ones that you  shouldn't really overwrite. You could, but you overwrite  default functionality with this. Fun fact, built-in methods  like len or other stuff you   probably used so far are defined this way as well. So this init method is the one that automatically   gets called every time when you  create a new object of a class. This is the part where we pass the attributes  that we want to give to our new object. So we can write the ones I  mentioned: name, health and damage. We can even use type hints, to remind  ourselves what kind of data types we expect. The name is a string, while  health and damage are integers. To store these input parameters  in the object itself,   we will save it right here in the init method. To tell the code that we want to assign the  values to the object, we can use self keyword. You can name it anything,  self is just a convention. So whatever name, health, damage  you pass into this blueprint,   the constructed object will hold these values. If you set variables before the init method, they  will be shared across all instances of a class,   so we can call these ones class-level variables. The ones in the init are unique to the created  object, so these are object-level variables. Also, don't forget to set a max hp  property for the character. Alright,   so the attributes are done. Let's define the attack method. It will accept only one parameter, that will  be the target that we want to deal damage to. Since we will pass another character  here that has the same fields,   we can write target.hp -= self.damage. So the health attribute of the target will  be reduced by the damage of the attacker. And to avoid going below zero,  we can use the max function. Easy as that! Good news, the character blueprint is completed! We can even create a battle scene now. To keep the project modularized  and the module files clean,   I'll create a main.py file that will  be the entry point of the project. Now to create the characters, we have to  import the blueprint from the character.py. Let's make a hero object first by calling  the Character class with parentheses. We can pass the required values either  as positional or keyword arguments. I usually go with keyword arguments, this  way I'm always sure what I assign to. Similarly, the enemy. In our game loop, we can start calling the  attack methods of both the hero and the enemy. The hero will attack the enemy and  the enemy will attack the hero.  To see something let's print  their health to the console. To access the attributes of the created instances,  we can type hero dot any attribute you assigned. If you use a code editor, it should give  you the recommendations automatically. Cool! And to control the flow, let's include an input. Alright, it works. Now comes something pretty useful. Objects can hold other objects as well. Now that we have characters,  we can give them weapons! Think about it for a second! What properties should these weapons have? I'll go with name, type, damage, and value. And that's it. Let's open an empty file, I name it weapon.py. We can define a Weapon class,  and in to the init method,   we can pass the attributes I just mentioned. Now let's make three weapons! An Iron Sword... A Short Bow... And Fists! We now have three objects  that the characters can use. So now let's head back to the character.py,  and type from weapon import fists. Then, in the init method, we  can say self.weapon = fists. All the character objects will  have fists as weapon this way.  Let's change the attack method  now and istead of our own damage,   we can withdraw the damage of our weapon. We can even remove the damage  attribute for the character. It's all coming together! Although, I want the Hero and  the Enemies to work differently. The Hero should be able to change it's  weapon by equiping or dropping one. One enemy should have the same weapons all the  time and I want to control this when creating one. So I want the Hero and the  Enemies to function differently. And with this, we reached the  topic of class inheritance. Now that I have a complete class, I can  create as many subclasses out of it as I want. To do that, let's type class Hero  and put Character in parentheses. This way we're referring to the parent class. This means that the Hero subclass will inherit all  the stuff that we created in the Character class. But just like before, we need to initialize the  hero object that we create with this blueprint. Again, our hero will have a name and some health. But unlike before, I won't  save these variables here. Instead, I pass these  arguments to the parent init. To do that, we can call super() that refers  to the Character, then .init name and health. This way we initialize the core of our subclass. Let's duplicate this for  the Enemy subclass as well. And now if we head back to our main.py, we can  import Hero and Enemy instead of Character,   and call them with the same arguments. It works fine, just like before! You can notice that we didn't define attack   methods in these subclasses  but we can still call them. That's because they inherit it  from the Character parent class.  And while we are here, let's print some  stuff to the screen with an f string. Now if we try it, it looks even better! Now we can implement the weapon  logic inside Hero and Enemy. Let's start with Enemy, that's the easier one. Right now all characters have fists. For the enemies I want to overwrite this  right in the init method, when I create one. So if we import another weapon, like  bow into the main.py and pass it as the   weapon argument of the enemy, it will  use this bow instead of it's fists. Great progress! Now let's see the Hero. Instead of passing a weapon argument when creating  the hero object, I want to define an equip method. We can pass the object here and this object  will overwrite the current weapon of the hero. Yeah, it's the same as if we called  hero.weapon = something outside the class,   but we could extend this method if we wanted to. Like print out "you equipped a weapon", etc. Let's make the other method now. With this one we simply set  our weapon to the default one. To do that, we need to set a default  weapon up in the init method first. Remember, we have a self.weapon  in the Character init, so we can   just type self.default_weapon = self.weapon. Now our default weapon is the fists,   so whenever we drop our current  weapon, it will be reset to fists. Cool! Let's import the iron_sword to the  main.py and equip it for the hero. We can see it in action as well. If we drop the weapon, the hero uses it's fists. Just as we wanted. Only one thing left for this video! This looks pretty ugly, so  let's make proper health bars! In my last video I already covered  the topic, although not with classes. Let's think about health bars with oop in mind. A health bar should have the  following properties: length,   maximum value, dynamic value, symbol  for both remaining and lost amount,   it's decorating barriers, if it's  colored, it's color, and that's it. It will have two actions. An update method to update it's current value,  and a draw method to print it to the screen. Let's get coding! I'll make a health_bar.py and right  here and before I make the class,   I'll import the built-in os library. After that I call it's system  method with an empty command. This is a hotfix if your console  doesn't print colors properly.  Now we make a HealthBar class   and to the init method we can pass length,  max_value, current_value, is_colored, and color. Since we're big brains, instead of  max_value and current_value, we can   pass an entity object that will be a character  and we can reach their health values directly. So let's save all these. We can even give default values to  the parameters, so if we don't give   these arguments when creating the health  bar object, it will refer to these ones. Okay, but what about the  other properties I mentioned? The symbols and the decorating barrier. I assume, that these will be same for all  health bars, so I define them on class-level. I could put length here as well, but what if I  want longer health bars for bosses, for example. Okay, now what about colors? I can't just use a string for that. So I made a couple of color values here  that I store in the colors dictionary. And in the init method, the  color property of the object   will be accessed from it's colors  dictionary by the color parameter. We can write it all in one line! First we try to find the color key in our  dictionary and it's corresponding value. So if we have pass "blue" as color and the  colors have "blue" in it, the bar will be blue. If the word is not in the keys,  this statement will be False. And if it False, we should set a fallback value. That should be the default, no-color value. Now this self.color is bulletproof. Cool! The init part is complete. Only the two methods left to make. The first one is the update method.   We can set the current value of the  health bar to the health of it's entity. The next one is the draw method. Here I want to print the health of the  character as a health bar, and with numbers. As before, the remaining bar amount will be the   length multiplied by the ratio  of the current and max values. To get the lost bars, we simply withdraw  the previous value from the length. Now let's print the health with numbers. Then we can make a pretty long f-string. We put one barrier to the  front, and one to the back. Between them comes the remaining amount with it's  corresponding symbol, same for the lost amount. And don't forget to add the coloring  part before and after these symbols. We start with the color attribute,  and end it with the default color. But only if the health bar is colored. Let's use one-line conditionals here. Now the draw method is completed. Before we display these bars,  let's add them to the characters. If we head to our character.py, we  can import this HealthBar class. The Hero will have a health_bar object with green  color, and the Enemy gets one with red color. We can also update the attack method with  the update method of the target's health_bar,   so it will be refreshed right after the action. Now if we go to our main.py and update  our game loop with the draw methods... We can see all our hard work pay  off with a very cool battle scene! If you'd like to clear the screen in  your terminal, you can import os in the   main.py and call os.system and put "cls"  in parentheses at the start of your loop. Now it looks even better! Alright, this way my comprensive  Python Classes tutorial. I hope you enjoyed it and either learned some  cool new stuff or had a refreshing experience! If you did, please leave a like and  give me some feedback in the comments. And of course, subscribe for more! See you guys! Stay safe, and take care!