I’ll try to add my take on this - also since you said you are not a programmer.
As @yesko explained an Abstract class is nothing more than a class that can’t be instantiated. It is heavily used in inheritance-focued OOP. However - at least in my experience - Godot doesn’t really push for an inheritance-focused approach, rather a composition-based one.
If you don’t know about composition please refer to this nice video by Bitlytic.
So how does abstraction come in handy in a composition-based approach? Since you don’t really describe an entity as a set of variable in a class, but rather a collection of nodes in the hierarchy, it may seem useless, right?
I already use a similar approach - which is just a naming criteria that I apply in my daily programming - I simply name all my abstract classes along the lines of AbstractMyClass, and then inherit from there.
But instead of creating an abstract class for a generic Entity, I use this approach for a single component that - for a reason or for another - needs to have different behaviours.
As for the why abstract classes are useful and enforce the fact that they can’t be instantiated, well it’s simply to put some logical restrictions/rules into place.
To give you an easy example - focussing on inheritance and fogetting for a moment the concept of composition - If you have a Player and an Enemy class, they are likely to be both inheriting from a base Entity class which holds fields such as health, damage, movement_speed, etc. Something like this:
class_name Player
extends AbstractEntity
# All the following is Player specific (Enemy does not have this)
func some_player_specific_action() -> void:
pass
class_name Enemy
extends AbstractEntity
# All the following is Enemy specific (Player does not have this)
func some_enemy_specific_action() -> void:
pass
abstract class_name AbstractEntity
extends Node
# All the following will be present in both Player and Enemy
@export var health: float = 100.0
@export var damage: float = 10.0
@export var movement_speed: float = 5.0
func take_damage(amout: float) -> void:
# just an example here
self.health -= amount
func some_generic_entity_action() -> void:
pass
Now, of course you want to have an instance of Player and one of Enemy, at it would make sense. But would it make sense to - even by accident - have an instance of an Entity that is a non-defined something? Of course not! And that’s where abstraction comes into play. That way you can simply avoid any unwanted instantiation.
Not to mention the fact that by leveraging inheritance you can also leverage polymorphism also to refer at both entity indistinctively. So for instance, if you want to have an object (a trap) damage an entity, you don’t need to know if it’s a Player or an Enemy. You simply know it’s an AbstractEntity, and that AbstractEntity has a function take_damage, so you leverage it to apply damage regardless of the actual instance you collide with.
I am not aware if abstraction will also be applicable to functions (I hope so), but using other languages other than GDScript, you can also apply abstraction to methods to enforce overriding them and make sure that must-haves are implemented for every child class.