Parent class for game objects

Started by Crimson Wizard, Sat 15/05/2021 17:42:18

Previous topic - Next topic

Crimson Wizard

As we expand script api and try to bring all game objects to consistent behavior it is clear that they share functionality but do not have any base class that would help apply it uniformly. For example, if one likes to add a new generic visual property, they would have to add it separately to each of the game object types. This includes both script declaration and api handlers in the engine code.
This also may be quite problematic to the script writers. For instance, eri0o pointed out that Tween module must have a lot of code duplication to implement things like TweenPosition.

(The only example of a parent class in existing script API is GUIControl that shares functionality with rest of control classes.)

The proposal is to add one or more base classes to the script api, move existing shared properties and functions there, and use these in future whenever we might need to add more api shared among all objects.

I think we may begin with the most obvious:
* properties identifying object; (script name, numeric ID)
* properties defining object location;
* properties defining generic looks;

My suggestion is to have at least one or two parent classes for now: I called them "Thing" ("Thing2D" is an alternate proposal) and "GraphicItem". If having two would seem to be an overkill at this point - they may be merged into one "GraphicItem" class, leaving those objects that do not have graphic representation without a parent class.

These names are ofcoure a matter of debate, ones presented here are made up examples. I might mention that giving an object type name like "Sprite" or "Graphic" unfortunately will be misleading, as these terms are already in use and define something else.

------------------------------------

Thing or Thing2D defines an object in 2D space, which basically has a position (X, Y). I believed it should be a separate base class because there might be subclasses that need only that.
Later this class may be amended with settings, to give some example, which define which coordinate space (layer) the object is located in.

At the moment it's not clear if AGS has any objects that would be derived directly from this class. Viewport and/or Camera could be, but they are under question (as explained below).
A simplier hypothetical example could be an "audio emitter" that simulates a sound coming from certain position in space.

------------------------------------

GraphicItem defines an object which has a graphic representation. Note that it does not have to have explicit Graphic (sprite) property, as it is not necessarily drawing one of the game sprites (see GUI). What it should have is a full collection of settings that adjust looks: visibility, transparency, tint, blend mode (one of the recent additions), and so forth.
Scaling and rotation should go there too; as anything we add for visual effect (shader support?).

Following existing objects will become a child of GraphicItem: Character, GUI, GUIControl, (room) Object, Overlay.

------------------------------------

Viewport and Camera are a questionable at the moment.
It's not clear to me whether Viewport should be a child of Thing or GraphicItem. It depends on how do we see the viewport as an object. Technically it's hardcoded as the rectangle where camera is rendered. It may be made visible, and I was thinking about adding transparency. Probably it could have blend mode too, as it renders, maybe be even e.g. rotated.
However, it's scaling only makes sense in connection with camera (it scales whatever camera shows). If we suppose for the moment that camera's image for viewport is like sprite to an object, then yes, there's definitely same kind of scaling.
Maybe its functionality could be reviewed to be represented more consistently with other objects. For instance, one of the ideas is to let Viewport be a full part of the "logical space" it's in in all aspects, including ordering between objects in that space. Then we may have viewports on both "gui" and "room space". An alternative is to let camera be attached to any graphical object, except at the moment there's no other way to do that but to assign camera's image a "sprite number", and there will also be an implementation problem of drawing camera's texture on the object directly, bypassing sprite management to avoid unnecessary copying and conversions....... anyway, that's completely separate and much more technical topic.

Back to the API concepts, OTOH, Camera may be rotated, and probably could be applied a tint or another shader, but transparency and blend mode would not make much sense there, as it provides certain image but does not apply that image over anything. Even Visible flag does not seem to make much sense... or does it? e.g. one could set camera visible = false, then all viewports that connect to it will display nothing.

Optionally it may be possible to have them originate from GraphicItem, but ignore certain properties as non-applicable. If that makes sense.

Crimson Wizard

BTW, another option for class names is Movable (proposed by eri0o somewhere) and Drawable. These sound a little "amateurish" but maybe reflect the meaning of these types better for any newcomer. E.g. "movable" is something that can be moved - which means it has a position, and "drawable" is something that can be drawn - which means it has a set of graphic properties.

eri0o

Hey, when I looked into this I felt it was a little daunting refactoring character due to it having a single x,y position that was used for both graphics and the game world and the amount of code related to the character in the engine.

It felt breaking into a graphic x,y that used a single point of reference and world x,y (the character feet) would make it easier to think about. But I wasn't sure. Character code is huge.

Crimson Wizard

#3
Quote from: eri0o on Fri 28/05/2021 14:34:19
Hey, when I looked into this I felt it was a little daunting refactoring character due to it having a single x,y position that was used for both graphics and the game world and the amount of code related to the character in the engine.

It felt breaking into a graphic x,y that used a single point of reference and world x,y (the character feet) would make it easier to think about. But I wasn't sure. Character code is huge.

Hmm, I don't think that's correct, character's x.y is definitely its feet position. But maybe you are refering to some calculations which they are involved in?

In any case, the objects definitely need to have x,y as their "origin" point, not graphic position. Graphic position should be dealt with separately. Object position should be accompanied with a "origin factor" which tells how sprite is aligned to origin. For example: characters have sprite centered and above origin pt, object - to the right and above origin pt, GUI - to right-below, and so on.
By the way, I changed some things there when was implementing Rotation. Now almost each object there has a struct called GraphicSpace that determines its bounding box and that is used to get a top-left image corner in parent space. Working on rotation definitely made me rethink the program structure...


I'm currently considering another ticket, but in short, I'd like to suggest having an internal "drawable object" struct that is shared among all types and stored separately from them in some array (to avoid messing up with the game logic and keep related things together in code and memory), like an external component. These "drawable" structs would contain a reference to image (original sprite and/or texture), and all the possible transformation/effect settings.
Each game type would reference one "drawable object" and configure it according to its own properties, but then all "drawable objects" are processed similarily by the render function.

[character][room obj][gui control]
    |                |                  |
[drawable][drawable][drawable][drawable][drawable][drawable][...]

eri0o

#4
Uhm... Do you think these drawable would be close to the overlays in concept? Like the overlay would contain only a drawable as it's property (and well, an additional property for text for text overlays, but I am ignoring them right now)

Crimson Wizard

#5
Quote from: eri0o on Fri 28/05/2021 14:54:20
Uhm... Do you think these drawable would be close to the overlays in concept? Like the overlay would contain only a drawable as it's property (and well, an additional property for text for text overlays, but I am ignoring them right now)

To elaborate, these structs will not be exposed as properties to script, they are not supposed to be part of game logic or API. It's just an internal engine's object meant for convenience, so that render function worked only with one kind of struct regardless of what it means.

Okay, I guess mentioning "Drawable" as a parent class and "drawable struct" as an internal engine's struct may cause confusion here. These are completely different things. Maybe I should invent another name for these internal things.

eri0o

Thanks, makes sense now! Take a look at issue 968, don't know if it's related but maybe it is. Also 967.

Crimson Wizard

Quote from: eri0o on Fri 28/05/2021 15:03:40
Thanks, makes sense now! Take a look at issue 968, don't know if it's related but maybe it is. Also 967.

More like #969, except my understanding changed in many ways since, so maybe I will close 969 and replace with a new one.

Oh btw #968 is probably mostly covered by the work done for Rotation property.

967 is a different thing though.

Crimson Wizard

I'd like to add, many years ago I was trying to stick to strict OOP in my mind, and thought that the goal is to have just one class for each type with everything related in it.

After years my views on this changed alot, not without seeing how other engines deal with that, but also after getting troubles with this approach myself.

Today I think more preferred approach would be to divide into components. Not necessarily meaning a "proper ECS", but at least in dividing responsibilities between unrelated classes (not connected in hierarchy or anything).

To make a basic example, we may have a class that only describes the object in its game properties. This class is useful for loading, saving and binding with scripts (as these properties are what make API).

Then we may have a class that deals with drawing an arbitrary object. Such class does not need to be in same class hierarchy with the "game logic class", it may be completely separate thing. This is useful, because if we have a part of the engine that does the rendering, this part does not have to know anything about game logic, all it needs to know is what and how to draw. So what we may have is a list of "draw objects" that are configured by the "game objects", and then sent into the render function. This approach is also beneficial if we have a multithreaded program, because then there will be less data to synchronize (we won't lock whole game object, but only this "drawable" piece).

And so on...

SMF spam blocked by CleanTalk