nodes — Your objects on the scene

Node reference

Nodes are the core concept of the kaa engine. They’re “objects” which you can add to the Scene. Each Node has its spatial properties such as position, rotation or scale. A Node may also have a sprite (graphics loaded from a file), which can be animated. Nodes have other properties such as z_index, shape, color, origin etc. All those properties are described in the documentation below.

A Node can have child Nodes, which you can add with the Node.add_child() method, thus creating a tree-like structure of nodes on the scene. As the parent node gets transformed (changes its position, rotation or scale) all its children nodes will transform accordingly.

Each engine.Scene instance has a root node - this is the first node on the Scene to which you can start adding your own Nodes.

A node without a sprite image (or without a shape and color properties set explicitly) will be just a logical entity on the scene, in other words: you won’t see it. Such logical Nodes are often very useful, for example, as a containers for grouping other nodes.

Although the bare Node will do its job well and allow you to create simple games, the Kaa engine comes with a collection of other specialized Nodes (they all inherit from the Node class):

  • physics.SpaceNode - a container node to simulate the physical environment.

  • physics.BodyNode - a physical node which can have hitbox nodes. Can interact with other BodyNodes. Must be a direct child of SpaceNode. Can have zero or more Hitbox Nodes.

  • physics.HitboxNode - defines an area that can collide with other hitboxes, and allows wiring up your own collision handler function. Hitbox Node must be a child node of a BodyNode.

  • fonts.TextNode - a node used to render text on the screen.

For your game’s actual objects such as Player, Enemy, Bullet, etc. we recommend writing classes that inherit from the Node class (or BodyNode if you want the object to utilize kaaengine’s physics features).

class nodes.Node(position=Vector(0, 0), rotation=0, scale=Vector(1, 1), z_index=None, color=Color(0, 0, 0, 0), sprite=None, shape=None, origin_alignment=Alignment.center, lifetime=None, transition=None, visible=True, views=None)

A basic example how to create a new Node (with a sprite) and add it to the Scene:

from kaa.nodes import Node
from kaa.sprites import Sprite
from kaa.geometry import Vector
import os

# inside a Scene's __init__ :
my_sprite = Sprite(os.path.join('assets', 'gfx', 'arrow.png')  # create a sprite from image file
self.node = Node(position=Vector(100, 100), sprite=my_sprite))  # create a Node at (100, 100) with the sprite
self.root.add_child(self.node)  # until you add the Node to the Scene it won't not show up on the screen!

Instance Properties:

Node.children

Returns a list of child nodes of this Node.

Node.scene

Returns a Scene instance to which this Node belongs. Will be None if the node has not been added to any Scene yet. Use Node.add_child() method to add nodes. Each Scene has a root node to which you can add nodes.

Node.position

Gets or sets node position, as a geometry.Vector.

IMPORTANT: Node position is always get or set relative to its parent node. To get the absolute position, use the absolute_position property.

If the Node is few levels deep in the nodes hierarchy and you want to know the position of the node in relation to one of its ancestors, use get_relative_position() method.

from kaa.nodes import Node
from kaa.geometry import Vector

# inside a Scene's __init__ :
self.node1 = Node(position = Vector(100, 100))
self.root.add_child(self.node1)  # adding to scene's root node, so node1 absolute position is (100, 100)
# create a child node
self.node2 = Node(position = Vector(-20, 30))
self.node1.add_child(self.node2)
print(self.node2.position) # prints out V[-20, 30]
print(self.node2.absolute_position) # prints out V[80, 130]

Also see: Node origin points.

Node.absolute_position

Read only. Gets an absolute position of the node, i.e. the position on the scene. Returns geometry.Vector.

Check out the example in the position property section.

Node.parent

Retruns this node’s parent Node, or None in case of the root node.

Node.root_distance

Retruns this node’s distance to the root node (counted as number of parents on the way to the root), or None in case of the root node.

Node.z_index

Gets or sets node z_index (integer). Nodes with higher z_index will overlap those with lower z_index when drawn on the screen. Default z_index is None meaning the node will inherit z_index value from its parent. To find the actual z_index value in this case, use effective_z_index property.

Scene’s root node (engine.Scene.root) has z_index = 0.

Note

If parent and child nodes have the same z_index, then the child node will be rendered on top of the parent.

Node.effective_z_index

Gets effective z_index value of the node. Use it to find actual the actual z_index value when node inherits it from its parent.

# ... inside a Scene class...

node = Node(z_index=15)
child = Node()  # z_index is None

node.add_child(child)
self.root.add_child(node)

print(child.z_index)  # prints None
print(child.effective_z_index)  # prints 15
Node.rotation

Gets or sets node rotation, in radians. There is no capping value, meaning you can set it to values greater than math.pi*2 or lower than -math.pi*2.

IMPORTANT: Node rotation is always get or set relative to its parent node. To get the absolute rotation, use the absolute_rotation property.

Changing node rotation will make the node rotate around its origin point. Read more about Node origin points.

import math
from kaa.nodes import Node
from kaa.geometry import Vector

# inside a Scene's __init__ :
# add node 1
self.node1 = Node(position = Vector(100, 100), rotation=math.pi / 4)
self.root.add_child(self.node1)
# add node 2 as child of node 1
self.node2 = Node(position = Vector(10, 10), rotation=math.pi / 4)
self.node1.add_child(self.node2)

print(self.node1.rotation) # 0.7853981633974483 (math.pi / 4)
print(self.node2.rotation) # 0.7853981633974483 (math.pi / 4)
print(self.node2.absolute_rotation) # 1.5707963705062866 (math.pi / 2)
Node.absolute_rotation

Read only. Returns an absolute rotation of the node, in radians. Check out the example in the rotation property section.

Node.rotation_degrees

Same as rotation property, but uses degrees (as float). There is no capping value, meaning you can set it to values greater than 360 degrees or smaller than -360 degrees.

Changing node rotation will make the node rotate around its origin point. Read more about Node origin points.

See also: absolute_rotation_degrees.

Node.absolute_rotation_degrees

Read only. Same as absolute_rotation but returns degrees.

Node.scale

Gets or sets the node scale, as geometry.Vector.

IMPORTANT: Node scale is always get or set relative to its parent node. To get the absolute scale, use the absolute_scale property.

The x value of the vector represents scaling in the X axis, while y value is for scaling in the Y axis. Negative values of x or y are possible - it will make the node to be rendered as a mirror reflection in X and/or Y axis respectively.

import math
from kaa.nodes import Node
from kaa.geometry import Vector

# inside a Scene's __init__ :
self.node1 = Node(position = Vector(100, 100))
self.root.add_child(self.node1)
self.node1.scale = Vector(2, 0.5)  # stretch the node by a factor of 2 in the X axis and shrink it by a factor of 0.5 in the Y axis

# add a child node
self.node2 = Node(position=Vector(-5, -15), scale=Vector(4, 0.5))
self.node1.add_child(self.node2)

print(self.node1.scale)  # V[2.0, 0.5]
print(self.node2.scale)  # V[4.0, 0.5]
print(self.node2.absolute_scale)  # V[8.0, 0.25]
Node.absolute_scale

Read only. Returns an absolute scale, as geometry.Vector. Check out the example in the scale property section.

Node.visible

Gets or sets the visibility of the node (shows or hides it), using bool.

Makes most sense for nodes which are rendered on the screen such as nodes having sprites, or text nodes.

Note that this has only a visual effect, so for example setting visible to False on a physics.HitboxNode will not make the hitbox inactive - it will still detect collisions normally.

Setting visible to False will hide all of its child nodes (recursively) as well.

Node.sprite

Gets or sets a sprites.Sprite for the node.

A sprite is an immutable object that wraps a graphical image.

Assigning a Sprite to a Node will make the sprite be displayed at node’s position, with node’s rotation and scale.

Creating a frame by frame animation is a two step process:

First you’ll need to have a list of frames, each frame being an individual sprites.Sprite instance. You can load each frame from a separate file or, if you have a spritesheet (a single graphical file which includes all frames) use the utility function sprites.split_spritesheet() to cut the sprites out of the file.

Second, you’ll need to create a transitions.NodeSpriteTransition transition using the list of sprites, which also allows you to specify the animation duration, looping etc. and assign that transition to the node

Note

Transitions are a more general mechanism than just sprite animations. Read more about transitions here..

Since sprite is a dimensional object (has its width and height) and node position is just a 2D (x, y) coords, it is important to understand the concept of node’s origin point. Read more about Node origin points.

Example 1 - a node with a static sprite.

from kaa.nodes import Node
from kaa.sprites import Sprite
from kaa.geometry import Vector, Alignment
import os

# inside a Scene's __init__ :
my_sprite = Sprite(os.path.join('assets', 'gfx', 'arrow.png')  # create a sprite from image file
self.node = Node(position=Vector(100, 100), sprite=my_sprite))  # create a Node at (100, 100) with the sprite
self.node.origin_alignment = Alignment.center # this makes the (100, 100) position be at the center of the sprite
self.root.add_child(self.node)  # until you add the Node to the Scene it won't show up on the screen!

Example 2 - a node with frame by frame animation running in an infinite loop:

from kaa.nodes import Node
from kaa.sprites import Sprite
from kaa.geometry import Vector
from kaa.transitions import NodeSpriteTransition

# inside a Scene's __init__:
spritesheet = Sprite(os.path.join('assets', 'gfx', 'spritesheet.png')  # a 1000x1000 spritesheet with hundred 100x100 frames
frames = split_spritesheet(spritesheet, Vector(100,100)) # cut the spritesheet into 100 individual <Sprite> instances
animation = NodeSpriteTransition(frames, duration=2., loops=0, back_and_forth=False) # With 100 frames a duration of 2 secs means 20 miliseconds per frame.
self.node = Node(position=Vector(100, 100), transition=animation)  # the transition will take care of setting the appropriate <Sprite> over time, thus creating an animation effect.
self.root.add_child(self.node)  # until you add the Node to the Scene it won't show up on the screen!

To stop playing an animation simply set the node’s transition to None

Node.color

Gets or sets the color of the shape of the node, using colors.Color.

In practice, if a node has a sprite that means that a sprite will be tinted in that color.

If a node does not have a sprite it still can have a shape (see the shape property). In that case setting a color will make the shape be rendered in that color.

For text nodes (fonts.TextNode) it gets or sets the color of the text.

It is often useful to set a color for hitbox nodes (physics.HitboxNode) to see where the hitboxes are in relation to the node’s sprite. Just remember to set a high enough z_index on the hitbox node.

The default color of a Node is a “transparent” color (r=0, g=0, b=0, a=0).

Node.shape

Gets or sets a shape of a Node. A shape can be one of the following types:

The most common scenario for setting a shape manually is for the hitbox nodes (physics.HitboxNode). It defines an area that will generate collisions. More information is available in the physics module documentation).

If you set a Sprite for a Node, its shape will be automatically set to a rectangular polygon corresponding with the size of the sprite.

Overriding sprite node’s shape is usually not necessary, but you can always do that. For example, you can set a 100x200 px sprite for a node and then set a custom shape e.g. a non-rectangular polygon or a circle. The drawn image will be fit inside the defined shape.

Node.origin_alignment

Gets or sets origin alignment of a node, as geometry.Alignment.

It’s best to show what origin point is on an example. Assume you have a Node with a 100x50 px sprite. You tell the engine to draw the node at some specific position e.g. position=Vector(300, 200). But what does this actually mean? Which pixel of the 100x50 image will really be drawn at (300, 200)? The top-left pixel? Or the central pixel? Or maybe some other pixel?

By default it’s the central pixel and that reference point is called the ‘origin’. By setting the origin_alignment you can change the position of the point to one of the 9 default positions: from top left, through center to the bottom right.

Setting the origin alignment is especially useful when working with text nodes (fonts.TextNode) as it allows you to align text to the left or right.

If you need a custom origin point position, not just one of the 9 default values, you can always wrap a node with a parent node. Remember that node positions are always set in relation to their parents, so by creating a parent-child node relations and setting origin_alignment appropriately, you can lay out the nodes on the scene any way you want.

Node.lifetime

Gets or sets a lifetime of the node, in seconds.

By default nodes live forever. After you add them to the scene with Node.add_child() method they will stay there until you delete them by calling Node.delete().

Setting the lifetime of a node will remove the node automatically from the scene after given number of seconds. It’s important to note that the timer starts ticking after you add the node to the scene, not when you instantiate the node.

Node.transition

Gets or sets a default transition object.

Transitions are “recipes” how the node’s properties (such as position, rotation, scale, color, sprite, etc.) should evolve over time. Transitions system is a very powerful feature, refer to transitions documentation for details.

Node.transitions_manager

Read only. Returns a transitions.NodeTransitionsManager object which allows you to manage multiple transitions on a Node.

Transitions are “recipes” how the node’s properties (such as position, rotation, scale, color, sprite, etc.) should evolve over time. Transitions system is a very powerful feature, refer to transitions documentation for details.

Node.absolute_transformation

Gets the absolute transformation of the Node, in form of a geometry.Transformation instance.

Node.transformation

Gets or sets the transformation of the Node, in form of a geometry.Transformation instance. Applying a transformation to the node is an equivalent of changing its position (translate Transformation), rotation (rotating Transformation) or scale (scaling Transtofmation). Refer to geometry.Transformation for more details on how to work with transformation objects.

Node.views

Gets or sets indexes of views (as a set object) in which this node shall be rendered. Each scene can have a maximum of 32 views (indexed -16 to 15). Default value is None meaning the node will inherit the view from its parent, and to find the actual views value use effective_views property.

Note that the root node of the scene has a view set to {0} (a set with just one element: zero) by default, so all nodes added to root (and their children) will have a views value set to {0}. Read more about views in engine.View reference.

self.root.add_child(Node(position=Vector(32,45), sprite=some_sprite))  # will be rendered in the default view
self.root.add_child(Node(position=Vector(-432,-445), sprite=some_sprite, views={0, 1, 15}))  # will be rendered in views 0, 1 and 15
node = Node(views={13})  # node will be rendered in view 13
child = Node()
node.add_child(child)  # the child will also be rendered in view 13
Node.effective_views

Gets effective views value of the node. Use it to find the actual views value when node inherits it from its parent.

# ... inside a Scene class...

node = Node(views={1, 3})
child = Node()  # views is None

node.add_child(child)
self.root.add_child(node)

print(child.views)  # prints None
print(child.effective_views)  # prints {1, 3}
Node.indexable

Gets or sets whether the node is indexable (as bool). Default is True. If set to True, this Node will be queryable by the engine.SpatialIndexManager.

Setting this value to False yields a slight performance boost.

Node.bounding_box

Returns node’s bounding box as geometry.BoundingBox. Bounding box is X/Y axis - aligned rectangle that contains Node’s shape.

Instance Methods:

Node.add_child(child_node)

Adds a child node to the current node. The child_node must be a Node type or subtype.

Each Scene always has a root node, which allows to add your first nodes.

When a parent node gets transformed (repositioned, scaled, rotated), all its child nodes are transformed accordingly.

You can build the node tree freely, with some exceptions:

Node.delete()

Deletes a node from the scene. All child nodes get deleted automatically as well.

Important: The node gets deleted immediately so you should not read any of the deleted node’s properties afterwards. It may result in segmentation fault error and the whole process crashing down.

See also: Node lifetime

Node.get_relative_position(ancestor)

Returns node’s position (geometry.Vector) relative to given ancestor.

The ancestor parameter must be a Node and it must be an ancestor of a node on which the method is called.

Node.get_relative_transformation(ancestor)

Returns node’s transformation (geomtry.Transformation) relative to given ancestor.

The ancestor parameter must be a Node and it must be an ancestor of a node on which the method is called.

Node.on_detach()

You don’t call this method directly. Instead you can implement it on a class that inherits from Node. The method gets called when the node is removed from the scene (by calling delete(), its lifetime expiring, scene being destroyed, and so on…).

Once the node gets removed, accessing its properties results in an error, so on_detach() offers an opportunity to execute some cleanup code.

class MyBulletNode(Node):

    def on_detach(self):
        # remove the node from our own custom collection
        self.scene.my_bullets_manager.remove_bullet(self)
Node.on_attach()

You don’t call this method directly. Instead you can implement it on a class that inherits from Node. The method gets called when the node is added to the scene.

class MyBulletNode(Node):

    def on_attach(self):
        # add the node to our own custom collection
        self.scene.my_bullets_manager.add_bullet(self)
Node.__bool__(self)

Allows to inspect the node to verify if it’s in a valid state.

# ... inside a scene ....
node = Node()
self.root.add_child(node)
assert node
node.delete()
assert not node