Skip to content

An Introduction to KivEnt

Kovak edited this page Apr 10, 2014 · 27 revisions

KivEnt is a Game Engine designed for the Kivy Framework

It consists of a set of widgets that work together using an Entity Component architecture to make it easier to build interactive 2d scenes, including optimized renderers that batch all your sprite drawing into a few draw calls, a simple interface with Chipmunk2d for fast physics and collision detection (using cymunk), and a heavily profiled and optimized cython-based particle engine compatible with the .pex format output by several editors both free and commercial (including the kivy based https://play.google.com/store/apps/details?id=org.cbl.particlepanda ), and used by many other 2d engines.

KivEnt is still heavily in development and there are areas where features or api are lacking. The most glaring of issues:

  • Currently only circle and rectangle collision shapes are supported from cymunk. We have meshes implemented in cymunk, and I hope to provide a nice implementation for the current physics system in the near future.
  • There is no load balancing for particle managers. If a system emits at a rate that exhausts its supply of particles there may be interruptions in effects.
    *There are no stock sound or music systems. It's not to hard to build these and I have some simple examples, but I have not put significant time into this area.
  • I only support one page atlas at the moment. This is pretty hacky right now and I need to look into this.

With that being said: KivEnt in its current state is fairly stable, there are many types of games that can get by with simple circle and box collision and physics, and the renderers give you the speed of constructing the entire draw call in c optimized cython loops and directly submitting this float array to the gpu with the flexibility of controlling everything from python.

Compiling: In order to compile kivent you must have both kivy and cymunk built and exported into your environment. We need to cimport some stuff from both of these. Building has only been tested on Ubuntu at the moment.

With KivEnt, your game will be made up of 4 things: 1. Your GameWorld, 2. GameSystems, 3. Entities, 4. Their Components.

Entities: an Entity is a python object that has an integer id assigned to it by the gameworld. This is accessible by the attribute 'entity_id', and you will usually use this id number to refer to entities instead of their object directly. The entity also holds a component for each GameSystem that the entity, these are accessible at the attribute given by the GameSystem's system_id property. The entity also has a load_order property that is the order of gamesystem creation used privately for adding and destroying entities in the correct order.

Components: Components can be any python object. As a consequence components can also be cython cdef class objects, allowing for easy cython optimization of areas of your game that are performance sensitive. There are several Components of this type built into KivEnt to make rendering as speedy as possible. Your GameSystems will read and write from these components in order to govern the logic of your game.

GameWorld: The GameWorld managing the overarching flow of your entire game. It is responsible for calling the update loop for your entire game, and this can be accomplished by calling gameworld.update(dt). Your GameWorld will also have a list 'entities' that will allow you to access every entity by entities[entity_id]. The gameworld automatically generates and stores an entity with no systems for entity_id=0 so that when you testing entity_id never returns False for the user. The GameWorld is responsible for creating and destroying every entity in your game, and you should use init_entity(components_to_use, component_order) and either remove_entity(entity_id) or timed_remove_entity(self, entity_id, dt) in order to destroy entities. init_entity will return the entity_id of the new entity

When initializing an entity you will provide a dictionary 'components_to_use' of 'system_id': args_object key-value pairs. In addition a 'component_order' list of 'system_id' strings which contains the order in which these components should be initialized. It is important for instance to initialize the position and rotate system before the physics system, as the physics system will need these components to write data.

GameWorld also has a systems dictionary that allows you to retrieve your GameSystem objects by their 'system_id' string. GameWorld also expects a currentmap property and a viewport property. currentmap is the GameSystem serving as the GameMap, viewport is a string referencing the system_id of the GameView. These 2 systems work in tandem to accomplish the KivEnt Camera System. GameMap determines the size of your view area, and GameView determines where the camera is at.

Finally, GameWorld has a state method which will determine which screen is active in the GameScreenManager as well as which GameSystems are added or removed from canvas as well as paused or unpaused. add_state takes args (state_name, systems_added, systems_removed, systems_paused, systems_unpaused, screen_name) systems_added, removed, paused, and unpaused are lists of system_id to have the corresponding action performed on them. Addition and removal will occur in order of listing. state_name and screen_name are strings corresponding to the name of the state and the name of the screen in GameScreenManager to match with the state.

GameSystems:

GameSystems perform all the logic in your game, they operate on the data held inside components. A GameSystem has:

  • system_id : a string name used to identify the system throughout your program
  • updateable : boolean determining whether or not your GameWorld should try to update this system when its update tick is called
  • paused : boolean telling GameWorld whether to update this system at this particular time
  • gameworld : Your GameSystem will access all entities and other systems through the gameworld, usually I bind my gameworld to this property inside my kv.
  • update_time : this is the tick rate at which you want your GameSystem to update defaults to (1./60.) (60 times a second). This allows you to set individual systems to update slower or faster (multiple times an update loop) than the GameWorld update rate.
  • entity_ids list, this is a list that contains the id of every entity the GameSystem is currently active on.

important methods

  • generate_component(self, args): this method is where the component for your system is created. The default GameSystem takes in a dict of key-value pairs and returns a Component object with attributes of key=value. Many of the reserved systems have different args and return different types of Components.

  • remove_entity(self, entity_id): this method is where you should perform any cleanup if you have a custom component. Default components do not need any extra work.

  • update(self, dt): this is the method where you should perform any logic you intend to have in your game loop. Typically logic looks something like

def update(self, dt):
    gameworld = self.gameworld
    entities = gameworld.entities
    for entity_id in self.entity_ids: 
        entity = entities[entity_id] 
        #do stuff to entity

Reserved Systems: These systems have system_id's that should not be changed as rendering expects these names to optimize lookups for data in these components. Position, Color, Scale, Rotate These GameSystems have been optimized to allow for the fastest rendering possible.

  • PositionComponent has an x and y property. To access in cython _x, _y. To create pass in tuple (x, y) for args. system_id : 'position'
  • ColorComponent has an r, g, b, a property. To access in cython _r, _g, _b, _a. To create pass in tuple (r, g, b, a) : 'color'
  • ScaleComponent has a s property. To access in cython _s. To create pass in float. system_id: 'scale'
  • RotateComponent has a r property. To access in cython _r. To create pass in float. system_id: 'rotate' (In future rotate and scale will probably diverge)

Other Cython Systems: These systems can be named whatever you would like. If you would like to design system to work with any system_id instead of accessing data by entity.system_id, access by getattr(entity, self.system_id)

CymunkPhysics, PhysicsComponent: Component has body, unit_vector, shapes, shape_type properties. body corresponds to the Cymunk Body class. shapes is a list of cymunk Shapes for the body. shape_type is the type of first shape in body. unit_vector is the current heading of the body, for convenience. CymunkPhysics has complicated generate_component, takes in a dict with many arguments and parts. Look at code.

ParticleManager, ParticleComponent Component has parent, offset, system_on, and particle_emitter properties. parent is the entity_id that this particle effect is attached to. system_on is boolean for whether or not to generate particles. particle_emitter is the underlying emitter object that you can modify values of. if offset is not 0, the effect will be extended in opposite direction of the parent physics body unit_vector * offset. (for things like trails) . ParticleManager takes in a dict of {'parent': entity_id, 'particle_file': str_corresponding_to_address_of_pex_file, 'offset': float_value}

Renderers There are several types of GameSystems for rendering. The basic Renderer will try to draw every entity that has been added to it. The DynamicRenderer has been designed to be used with the CymunkPhysics system and uses the spatial indexing there to only draw the subset of added entities that are on screen. StaticQuadRenderer only updates its drawing when entities are added and removed, or drawing information is changed. RenderComponent's contain properties for render:(bool) whether or not the renderer should draw this entity. on_screen:(bool) whether this entity is on screen, always True for renderers that aren't DynamicRenderer. texture:(str) the name of the texture in the renderer's atlas for this entity. width and height properties are how big to draw the image. RenderSystems take in a dictionary of {'texture': str_name, 'size': (width, height)}.

Renderers need to be given a shader_source and an atlas_dir and atlas string properties. These strings correspond to the name of the atlas, the name of the .glsl file, and the path of the directory for the atlas. If you intend to have rotate, color, or scale properties in your shaders you must activate do_rotate, do_color, or do_scale respectively.

A Basic Application can be found inside the kivent_tutorials/2_basic_app directory. The other tutorials are in the process of being updated, the sample_application application has also been updated to work.