-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Objects
Objects represent every entity in the game world. Everything is described as an object, from units and buildings over ambient entities such as cliffs up to technologies and boni.
ObjectName():
# This is a comment
member_name : type = value
other_member : type = value
member_without_value : type # Comments can also be here
...
The example above shows the initial declaration of a nyan object. A nyan object is a named group of key-value pairs. The name of the group is the nyan object name (in this example: ObjectName
). Key-value pairs assigned in this object are called members. The number of members per object is unlimited. Each initial definition of a member must be annotated with a name, which is used as an identifier, and a type. Members can have no value (see Abstract objects).
There must not be two members with the same name in an object. The order of the members does not matter. A member's type determines which vales can be assigned to it. The chapter Data Types explains the types offered by nyan.
Comments are denoted by a #
and can be placed after the declaration of a member in the same line. The nyan interpreter will skip everything in the line after reading the #
character.
Instead of redefinig an object from scratch, one can use inheritance to specialize an already defined object. When an object inherits from another one it automatically aquires all members of the object it inherits from, including their defined types and assigned values. The inheriting object can also add its own members or choose to reassign values to members aquired. One can look at inheritance as a form of specialization.
The inheriting object is called child object or child for short. Objects that are inherited from are called parent objects. Thus the phrases "Object B inherits from Object A" and "B is a child of A" or "A is a parent of B" are equal.
In nyan, inheritance is transitive, so a child object is of type of all its parent objects, and the parents of those, and so on. Inheritance is declared by writing the name of the parent object in the paranthesis next the the name of the child object.
# Empty paranthesis means the object does not inherit
BaseObject():
parent_member : int = 2
other_member : text = "Hello"
# Let ChildObject inherit from BaseObject
ChildObject(BaseObject):
child_member : float = 12.65
By inheriting ChildObject
automatically aquires the members parent_member
and other_member
. It also defines its own member child_member
which is independent of BaseObject
. By definition, ChildObject
is also of type BaseObject
. Without inheritance, the objects' declaration would look like this:
BaseObject():
parent_member : int = 2
other_member : text = "Hello"
ChildObject():
parent_member : int = 2
other_member : text = "Hello"
child_member : float = 12.65
Clearly this is inefficient because we define the same members twice. We would also have to make every change to parent_member
in both BaseObject
and ChildObject
.
Children are not stuck with the values assigned to a parents member. They have the option to reassign or change a new value in their object declaration. On the other hand, the type of the member must not be changed.
BaseObject():
name : text = "Gustav"
parent_member : int = 2
ChildObject(BaseObject):
# Reassigns a new value to the member "name"
name = "Lukas"
child_member : float = 12.65
In the above example the value of the member name
is changed to Lukas
by the child object. Because type changes are not allowed for inheritance the reassignment of name
does not need to specify it. Doing so will result in a error. parent_member
is also a member of ChildObject
and keeps the value 2
from the declaration in the parent. child_member
is first declared in ChildObject
and is therefore required to specify the type.
BaseObject():
name : text = "Gustav"
parent_member : int = 2
ChildObject(BaseObject):
# Adds the value to the member "parent_member"
parent_member += 5
child_member : float = 12.65
In this example parent_member
is not reassigned, but changed by using the +=
operator. The value of parent_member
in ChildObject
is now 7
, while it is still 2
in BaseObject
. The available operations for each type can be found in the chapter Data Types.
nyan supports a mechanism called multiple inheritance which means that an object can inherit from an unlimited number of other objects.
BaseObject():
parent_member : int = 2
other_member : text = "Hello"
DifferentObject():
different_member : bool = true
# Let ChildObject inherit from BaseObject and DifferentObject
ChildObject(BaseObject, DifferentObject):
child_member : float = 12.65
The names of the parents are separated by a comma (,
) in the declaration of ChildObject
. ChildObject
has four members. The members parent_member
and other_member
are gained from the parent BaseObject
. different_member
is aquired from DifferentObject
. There is also child_member
which is first declared in ChildObject
and not inherited. Because of the rules of transitivity ChildObject
is also of type BaseObject
and DifferentObject
.
Multiple inheritance can create the problem of naming ambiguities. They can occur when multiple parents define a member with the same name. An example is shown below.
Base():
setting : int = 0
Intermediate0(Base):
setting += 1
property : int = 10
Intermediate1(Base):
setting *= 2
property : int = 20
Independent():
setting : int = 100
property : int = 1234
Mixed(Intermediate0, Intermediate1, Independent):
setting += 3 # which origin is meant?
property += 5
Here the intended origin of both setting
and property
in MixedObject
is unclear. For example, the result for setting
could be 4
(origin: Intermediate0
), 3
(origin: Intermediate1
) or 103
(origin: Independent
). To resolve this conflict, one has to specify the intended parent in front of the member name, for example Independent.setting
.
Base():
setting : int = 0
Intermediate0(Base):
setting += 1
property : int = 10
Intermediate1(Base):
setting *= 2
property : int = 20
Independent():
setting : int = 100
property : int = 1234
Mixed(Intermediate0, Intermediate1, Independent):
# origin is clear now
Independent.setting += 3
Intermediate0.property += 5
The above example is only one of the possible solutions. Intermediate0.setting
or Intermediate1.setting
would have also been viable for clarifying the origin of setting
.
A special variation of a normal nyan object is the patch. Patches are used to change member values of an object or add additional members. They are discussed in detail in the chapter Patches.
An object is called abstract when one of its members was not assigned a value. Abstract objects cannot be assigned as values to a member (but can be set as types). The values have to be specified by a child object.
From a non-technical viewpoint, abstract objects are templates for the game entities players eventually interact with. An example is the abstract object Unit
used in the openage engine. For example, Unit
contains the members hp
and name
, but does not define values for them. That way one can guarantee that an ingame object derived from Unit
always provides the members hp
and name
and has also assigned values for them.
Objects can also be defined within another object. The inner one is a nested object, and can contain nested objects itself. The purpose of nested objects is to allow further grouping. In openage they are useful for specifying abiltiies and boni of units. Nested objects are allowed to have their own members and inherit from other objects. The name of a nested object is a combination of the name of its parent and the nested object name.
Rainbow(): # top-level object
Goldpot(): # nested object
amount : int = 9001
In this example Goldpot
is a nested object of Rainbow
. The member amount
belongs to the nested object.