This page is out of date

You've reached a page on the Ren'Py wiki. Due to massive spam, the wiki hasn't been updated in over 5 years, and much of the information here is very out of date. We've kept it because some of it is of historic interest, but all the information relevant to modern versions of Ren'Py has been moved elsewhere.

Some places to look are:

Please do not create new links to this page.


This is a tutorial on how to use the Unit Engine. The Unit Engine is built on top of the Tile Engine, and so you should read the Tile Engine Tutorial before this page.

Creating a Unit Engine

You can create a UnitEngine in a very similar way to how you create a TileEngine:

e = UnitEngine(geometry=TileEngine.ISOMETRIC, map=map, tilewidth=64, tileheight=32, 
  screenwidth=800, screenheight=600, show_cursor=True)

There are a number of other parameters you can set on the UnitEngine, but you can read about them in the full documentation.

StartGame()

A UnitEngine has a function StartGame(). That's what you'll want to call once you're ready for the player to play, after you've initialised all your units. Do note that if you call StartGame() on a UnitEngine with no units, or with no player-controlled units, then the game will instantly end in defeat without anything being displayed! Similarly, if you have player units but no enemy units, it'll instantly end in victory without anything being displayed. Both of these can be prevented if you create the UnitEngine with the parameter end_when_all_enemies_dead = False.

Creating Units

Now that you've got your UnitEngine e, rather than creating Sprites from it as you would with the TileEngine, you can create Unit objects.

knight = e.Unit(name = "Knight", 
                sprite_base_name = "knight",
                direction_facing = e.DIR_W, 
                max_HP = 50, 
                max_MP = 7, 
                pos = (3, 3))

There's a few more things happening here, so let's look at them.

Hit Points (HP)

Units have a max_HP property. At the start of each UnitEngine map, each unit's HP is set to its max_HP. When a unit's HP drops to zero or below, its IsAlive property is set to False and the unit is removed from the map.

Movement Points (MP)

In the MP (Movement Points) model, units have a max_MP property. At the start of each turn, a unit's MP is set to its max_MP. (In the AP (Action Points) model, exactly the same is true for AP and max_AP instead.)

By default, any map square with a TerrainType of UnitEngine.TT_GROUND costs 1 MP or AP to walk over. Any map square with a TerrainType of UnitEngine.TT_WALL or UnitEngine.TT_GAP can't be walked over. You can define your own TerrainTypes if you want different types of terrain to cost different amounts of MP or AP to cross.

direction_facing

Units have a direction_facing property. By default, the UnitEngine will turn the unit to face in the direction that it moves. The Direction will always be one of the nine TileEngine direction constants. If you don't want to use different images as your unit moves around, instead of specifying direction_facing you can specify display_directions = 1.

sprite_base_name

Rather than just having a sprite_name, Units have a sprite_base_name. This is so that the unit can appear different from the four or eight directions. If you define a unit k with sprite_base_name "knight", then the images that could potentially be used for that unit are:

If you have an action for when your knight attacks an enemy, and in that action you give the unit the sprite_status "sword", then whenever that action is called, the knight will be displayed using one of:

Again, this could be an animation.

Actions

Specifying the action function/s

In most games, as well as letting your players move units around the map, you're going to want to let them take other actions. The typical action is an attack of some form: swinging a sword, firing a laser, casting a spell, and so on. There's plenty of other actions as well: using items, perhaps fortifying position, demolishing a square of wall, creating another unit, or even building a city.

Because these vary so widely, the UnitEngine doesn't assume much about what actions will do. You have to define what all the different possible actions will do, by writing a simple function for each one. You specify this in the unit's actions parameter, which is a dictionary of strings and functions:

knight.actions = {"Attack": KnightAttack}
paladin.actions = {"Attack": KnightAttack, "Cast Spell": CastPaladinSpell}

If a unit's actions parameter is empty, then the "Act" button is disabled when that unit's selected. If there's just one entry in the dictionary (as with the knight above), then when the player selects the "Act" button, the corresponding function (KnightAttack) is immediately called. If the actions dictionary contains multiple items (as with the paladin in the example), then clicking "Act" will hide the normal buttons (Move, Act, End Turn etc) and bring up a menu, with one item for each entry in the dictionary, plus a "Cancel" item. When the player selects an action type (e.g. "Cast Spell"), the corresponding function from the dictionary ("CastPaladinSpell") is called.

OverlaidMenu()

Now, a lot of unit actions will need the player to make further choices. For example, "Attack" probably doesn't damage every surrounding unit, but requires the player to specify a target square or unit. Or for a mage unit, selecting the action type "Cast Spell" might need to bring up a menu of possible spells. So the UnitEngine provides a few useful functions you can plug together to create a very diverse range of possible actions.

You can call a UnitEngine's OverlaidMenu() function to replace the normal buttons with menu items of your own. Supply it with a dictionary, mapping strings to callbacks, just like the actions parameter:

def CastPaladinSpell():
  spells = {"Cure": CastCureSpell, "Holy": CastHolySpell, "Smite": CastSmiteSpell}
  e.OverlaidMenu(spells) 

The OverlaidMenu will add a "Cancel" button to your menu by default, which will cancel the whole action. If you don't want this button or you want to do your own, specify CancelButton = False.

HighlightSquares()

What about the fundamental part of tactical combat: choosing which unit to attack?

The function to use for this, like anything where you want to select a square of the map, is UnitEngine's HighlightSquares() function. As the name suggests, this will highlight some subset of the map's squares, according to a criterion you supply. You also specify a function which you want to be called when the player selects one of the highlighted squares.

The criterion can be any kind of function which takes an (x,y) position and returns True or False. However, quite often your criteria will be based on one of a few basic criteria, which the UnitEngine provides for convenience.

You can combine these to create quite versatile conditions, for example:

 # We can call down holy fire on any enemy unit
 def HolyTargetCriterion(pos):
   return e.ContainsNonPlayerUnit(pos)
 # We can swing a sword at any enemy unit right next to the selected unit
 def KnightAttackCriterion(pos):
   return e.WithinNSpacesOf(1, pos) and e.ContainsNonPlayerUnit(pos)
 # We can heal any friendly unit within six spaces
 def HealableUnitCriterion(pos):
   return e.WithinNSpacesOf(6, pos) and  e.ContainsPlayerUnit(pos)

Of course, you can perform your own tests as well. For example, the criterion for whether a square is a viable target for the Cure spell might also want to specify that the friendly unit's been damaged:

 # We can heal any *wounded* friendly unit within six spaces
 def HealableUnitCriterion((gx, gy)):
   thisUnit = e.map[gx][gy].unit
   return e.WithinNSpacesOf(6, (gx, gy)) and e.ContainsPlayerUnit((gx, gy)) and thisUnit.HP < thisUnit.max_HP

So now, we've got enough to let the player choose a target square for the action:

def KnightAttack():
 e.HighlightSquares(KnightAttackCriterion, KnightAttackDamage)

def CastCureSpell():
 e.HighlightSquares(HealableUnitCriterion, CureFriendlyUnit)

def CastHolySpell():
 e.HighlightSquares(HolyTargetCriterion, HolySpellDamage)

As you can see, apart from a criterion, the other thing you need to use HighlightSquares() is a function describing what you want to happen when the player clicks the unit. In the examples above, these are CureFriendlyUnit, HolySpellDamage and KnightAttackDamage.

Healing and Damaging Units

Functions supplied to HighlightSquares take one input, a (gx, gy) tuple, indicating which square was clicked. Let's look at one example:

def CureFriendlyUnit((gx, gy)):
 # Show the unit casting the spell
 thisUnit = e.map[gx][gy].unit
 e.selected_unit.sprite_status = "casting"
 thisUnit.sprite_status = "healing"
 thisUnit.HP = min(thisUnit.HP+30, thisUnit.max_HP)
 PaladinCharacter("May God's mercy restore your vitality. Cure!")
 renpy.say("%s went up to %d HP!" % thisUnit.HP)
 e.FinishAction()

Since the clicked square won't always contain a unit, you have to get the unit out of the map[][] yourself. Then the CureFriendlyUnit() function just modifies the unit's HP, announces what's going on, and then calls the UnitEngine's FinishAction() function. After an action completes, you have to call FinishAction(): this will clean up after the action, mark the unit as having acted this turn, restore all units' sprite_status to "", and so on.

One useful function you can call in your actions is DamageUnit(). It takes two inputs: an amount of damage, and a unit object. It subtracts the damage from the target unit's HP, and if this takes the target's HP to 0 or less, removes them from the board.

You can either call this directly from your menus, such as in the "Smite" spell:

def CastSmiteSpell():
 # Show the unit casting the spell
 e.selected_unit.sprite_status = "casting"
 PaladinCharacter("I call down divine justice upon our enemies! Smite!")
 # The Smite spell deals 20 damage to every enemy unit 
 for u in e.units:
   if u.controller is not e.current_player:
     e.DamageUnit(20, u)
 e.FinishAction()

Or you can call it in a function that gets called from the HighlightSquares callback when the player clicks on a target square:

def KnightAttackDamage((gx, gy)): 
 # A knight's attack deals an amount of damage equal to the knight's power
 targetUnit = e.map[gx][gy].unit
 # Turn the unit to face the enemy, and give it the "sword" animation
 e.selected_unit.TurnToFace(targetUnit)
 e.selected_unit.sprite_status = "sword"
 renpy.Pause(0.5)
 e.DamageUnit(e.selected_unit.power, targetUnit)
 e.FinishAction()

def HolySpellDamage((gx, gy)):
 # The Holy spell deals 30 damage to a target enemy unit 
 targetUnit = e.map[gx][gy].unit
 # Turn the unit to face the enemy, and give it the "casting" animation
 e.selected_unit.TurnToFace(targetUnit)
 e.selected_unit.sprite_status = "casting"
 PaladinCharacter("May heaven punish this transgressor! Holy!")
 e.DamageUnit(20, targetUnit)
 e.FinishAction()

You may have noticed the calls to e.selected_unit.TurnToFace(thisUnit). TurnToFace() is a useful function that can take a target unit or a (gx, gy) position, and will turn the unit in question to face the target unit or position, by setting its direction_facing to the appropriate Direction constant.

And there you go. We've just created the full action system for a game with two different types of unit and three different spells. It may have involved writing quite a few different functions, but they're each very short, and hopefully very easy to follow what's going on.

Customising the unit info and button windows

You don't have to do anything to get the UnitEngine to display info on the selected unit. But you can change the style of the various elements of the window that displays that info, as well as the other windows provided by the UnitEngine. You also might want to display different info than the UnitEngine does by default, if you've added custom properties to your units. The UnitEngine provides capabilities for all these.

Changing what's displayed

The UnitEngine has properties unit_info and show_unit_info. If you don't want any unit info display at all, simply set show_unit_info to False.

If you do want the unit info, but not all of it, then instead specify a different unit_info parameter. unit_info is a list of items to be shown. Each item is either a function, in which case that function's called, or a (conditionfunction, displayfunction) tuple, in which case the displayfunction is only called if the conditionfunction returns True. For a game with MP movement style, the default value of unit_info is:

        e.unit_info = [
              e.ShowUnitName,
              e.ShowUnitHP,
              (e.IsPlayerUnit, e.ShowUnitMP),
              (e.IsPlayerUnit, e.ShowUnitActionsLeft),
              e.ShowUnitPosition
                ]

You can create your own information display functions to include in this list. If your function displays text, it should use e.engine_label(yourText), as that'll use the engine_label and engine_label_text styles automatically.

Your function might look like this:

    def ShowUnitMana():
      e.engine_label("Mana: %d" % e.selected_unit.mana)

Changing the styles

All the buttons and windows of the TileEngine and UnitEngine use specific styles. By specifying details of those styles, you can customise the look of the text. The styles used are:

Changing the text and images

The UnitEngine uses a fairly neutral set of phrases for its buttons: "Move", "Act", "End Turn" and so on. If you want to change these strings, you can simply add a mapping to . The same applies to the strings used in the unit info box: "HP", "MP" and so on.

What if you want to take a more drastic route, and replace the buttons and labels with images? You can do that too, using the method described on and .