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!