godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 79 forks source link

Allow ability to override parent variables #338

Open Feniks-Gaming opened 4 years ago

Feniks-Gaming commented 4 years ago

Describe the project you are working on:

Arcanoid Clone for learning

Describe the problem or limitation you are having in your project:

I have a scene BrickParent that has behaviours for all my bricks in a game. I then want multiple inherited scenes of that scene to be different colour bricks. Each brick is worth different amount of points in the game

In my BrickParent scene I have var points = 0 I then want this var points to be different in each inherited scene. So In my inherited scene I created a inherited script that inherits from BrickParent.gd if I type var points = 50 U will get error Member 'points' already exists in parent class. I would like to be able to override this just how I can override some of the functions in parent class that already exist.

Describe how this feature / enhancement will help you overcome this problem or limitation:

This will will make code easier to follow across different inherited scenes. And allow for faste rprototyping

Show a mock up screenshots/video or a flow diagram explaining how your proposal will work:

N/A

Describe implementation detail for your proposal (in code), if possible:

I can't help here I have no idea how to do it.

If this enhancement will not be used often, can it be worked around with a few lines of script?:

I would expect it to be used every time people create inherited scripts. Work around for now is to add all the variables we want to change in _ready() function it works but for example you can't override constants in that way.

Is there a reason why this should be core and not an add-on in the asset library?:

It's pretty much part of the way core

Calinou commented 4 years ago

I believe you could implement something similar by overriding a method that returns a constant value. I think it's done this way in some other programming languages.

KoBeWi commented 4 years ago

You can just export the variable if your bricks are scenes. Or overwrite it in _ready().

dalexeev commented 4 years ago

This may actually be helpful!

For example, if a script inherits from the Node class, then it can be added to the scene. But at the same time, the inherited properties will have default values from the base class. It would be nice to be able to override default values something like this:

extends RichTextLabel
class_name TypeWriter

default rect_size = Vector2(128, 64) # only constant values
default bbcode_enabled = true
default visible_characters = 0
default scroll_active = false
default scroll_following = true

# ...

(I know that you can use the tool keyword.)

ADD. I mean that in the editor (if you do not use the tool keyword), the inherited properties by default have some values, and in _ready() they will be assigned different values.

Feniks-Gaming commented 4 years ago

@KoBeWi I know I can but this doesn't solve my problem as exactly as I would like it to be.

For example say I have a scene Bullet it has const SPEED = 400 I want it to be always 400 on run time in parent. But I would like the SPEED to be always 800 in it's child FastBullet I can't change it in _ready() function because you can't change constants

However I don't want to change constant to var. Because it will no longer act as constant during runtime. Even tho this is exactly what I want it to do.

Ability to override variables rather than just change them will solve situations like this.

KoBeWi commented 4 years ago

Ability to override variables rather than just change them will solve situations like this.

But still, constants aren't variables.

Also, what's the problem with changing const to var? Keeping them UPPERCASE should be enough, there aren't any performance differences.

DleanJeans commented 4 years ago

You can have a multiplier variable, defaulted to 1. Then just use BASE_SPEED * multiplier in _physics_process and change multiplier in inherited scenes. Say multiplier = 2 in your FastBullet.

Feniks-Gaming commented 4 years ago

Difference is that is I accidentally change constantly SPEED somewhere in a game I will get an error knowing that it was a mistake I intended to avoid if I call my var SPEED I can change it by accident and not know about it.

Calinou commented 4 years ago

@Feniks-Gaming Would https://github.com/godotengine/godot/issues/23695 solve this particular use case?

Feniks-Gaming commented 4 years ago

Yes that would solve my issue. I would be able to create a parent with empty variable and let SPEED and then assign it on other children like to whatever I want and it would act like a constant otherwise.

vnen commented 4 years ago

Problem with this is that it allows for potential mistakes on the other end (when you override the parent variable without realizing). Maybe with ahem annotations we could have a tag to allow an explicit override:

@override
var name = "New Node Name"

Can be a keyword but I don't want to introduce yet another one.

Still, doing that for constants may not be possible. After all, they are supposed to be constants, and they are (or should be) optimized based on this assumption.

ModProg commented 3 years ago

I would like to bring another point that this would enable to provide different defaults for custom SubClasses from Standard Nodes. Similar to the ToolButton when you open the Documentation it actually says [override: true] for the flat property:

grafik

This is something currently impossible to do via GDScript, because when you change a default via a tool script (in _init or _ready etc.) you wont be able to change the default in the Inspector afterwards, because it will always reset to the value set in script on reloading the scene.

dalexeev commented 2 years ago

Please correct the title: "override" instead of "overwrite".

CharityMoor commented 2 years ago

I also run into this issue a lot when working with inherited classes. Using _init() works well enough for changing the default values of non-exported variables, but if there is a way to make it work with exported variables it seems like it would need to involve @tool scripts, which themselves can interact unintuitively with the inheritance structure, and require a lot more care throughout the script to make sure only the right code is being executed in the editor. Assuming that one of the main advantages of inheritance is to avoid duplicating work, and the main advantage of changing a default value is the same, implementing a workaround that involves duplicating work and opening code to difficult-to-foresee errors seems undesirable.

Problem with this is that it allows for potential mistakes on the other end (when you override the parent variable without realizing). Maybe with ahem annotations we could have a tag to allow an explicit override:

@override
var name = "New Node Name"

Can be a keyword but I don't want to introduce yet another one.

...

I'm self-taught in GDScript (and a couple other high level scripting languages), so my programming fundamentals are pretty lacking and I don't know how hard any of this would be to implement under the hood, but I was wondering if the implementation of annotations in GDScript 2.0 makes the above proposal any more workable/worth revisiting?

lohmaster commented 2 years ago

If I'm not mistaken it's possible to do this in Godot3 overriding getset methods in inherited classes. It seems that this particular issue is where GDscript2.0 is somewhat lacking when compared to old system.

me2beats commented 1 year ago

Does this @override means the property still have same type?

class A:
    var x:int= 1

class B:
    extends A
    x = 2 # ok 

class C:
    extends A
    var x = 2 # should still give the error "the variable exists in parent class"
dalexeev commented 1 year ago

If no, I think this is a bad idea to implement

Of course not, this contradicts the Liskov substitution principle. We are talking about overriding the default value, which is already used in the engine.

suggest simply not to use var keyword to indicate that the member value is overridden

It's confusing. Users may think that it is allowed to write code outside of a function, but this is not true.

dannymate commented 12 months ago

What about allowing child classes to change variable types if the type specified is a child. For example:

class MyResource
    extends Resource

class A:
    var x: Resource

class B:
    extends A
    @override var x: MyResource 

class C:
    extends B
    @override var x: Resource # Give error because it isn't a child of MyResource

Edit: I suppose it's similar to a generic parameter in C#. Without the generics. Edit2: You can override functions without a special attribute. Why would it be different for variables wouldn't that make it more confusing. Why not add an icon in the editor next to overridden variables and functions?

dalexeev commented 12 months ago

What about allowing child classes to change variable types if the type specified is a child.

This contradicts Liskov substitution principle. Covariance and contravariance do not work for properties:

class A:
    var x: Node
    var y: Node

class B extends A:
    @override var x: Node2D
    @override var y: Object

func _ready() -> void:
    var b: B = B.new()
    var a: A = b

    a.x = Node3D.new() # Valid.
    print(b.x) # Invalid.

    b.y = Resource.new() # Valid.
    print(a.y) # Invalid.
dannymate commented 12 months ago

Covariance and contravariance do not work for properties

Technically your 'y' wouldn't be allowed as Object is a parent of Node not a child. But yes I do see your point. I suppose that's why generics exist in the first place.

I just wanted a way to get more specific type hints/limit inspector selection in child classes.

moritztim commented 10 months ago

Related: https://github.com/godotengine/godot/issues/19113

BasilYes commented 7 months ago

Some use case for this proposal. Now I'm working on addon where I use different resources to specify node behavior. So for some extended resources I need to redefine base resource values defaults for user now I'm using some sort of workaround (tool script):

func _init() -> void:
    if not Engine.is_editor_hint() or _lock:
        return
    _lock = preload("res://addons/fast_ui/triggers/ui_trigger_lock.tres")

But it's work not ideal because it's not actually change defaults but override on start (restore to default button shown): image

tektrip-biggles commented 3 months ago

Is this suggestion still actively being considered? Apologies for the duplicate feature request submission, for some reason neither this one nor #10020 showed up when I did a cursory search earlier.

I'm currently working on one Godot project and another UE4-based one & was reminded how nice (and extremely quick) this workflow is in UE4:

  1. Implement a blueprint class with behaviour determined by some default values
  2. Create subclasses of that BP with slightly different default values & maybe a function override
  3. Create subclasses of those BPs with similar tweaks & so on...

Conversely in Godot this is the sort of "seemingly trivial" thing that often trips me up or otherwise slows me down at unexpected times & leads to that (imho) "death by a thousand papercuts" feeling when using the engine for anything non-trivial...

PhillypHenning commented 3 months ago

I'm new to GDScript so take my suggestion with a grain of salt... However, if you're trying to market GDScript as an OOP programming language then it makes no sense to not provide a means of abstraction.. abstraction is a fundamental concept in Object-Oriented Programming (OOP) and by your own documentation: "Still, many best practices using Godot involve applying object-oriented programming principles to the scripts and scenes that compose your game."

Consider the following: abstract_example.gd

class_name AbstractClassExample
extends Node

@export var example_boolean: bool = false

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass

child_example.gd

extends AbstractClassExample

#var example_boolean = true
#@export var example_boolean = true
#@override var example_boolean = true

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass

It's clear what I'd like to do. I understand that this can be accomplished in within the _ready function, however, that's not good enough as:

  1. It doesn't update the UI, which makes things confusing for the downstream user
  2. It's round-about and isn't clean or zen (principal of Python which GDScript is heavily inspired by)

It seems to me that this is an oversight, and experienced developers will not enjoy this as it layers the truth behind function calls when, simply put, the solution could be more elegant, clean and user friendly.