cute demon crashers! documentation

33
Cute Demon Crashers! Documentation Release 1.0.7 SugarScript June 28, 2016

Upload: others

Post on 03-Jun-2022

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Cute Demon Crashers! Documentation

Cute Demon Crashers! DocumentationRelease 1.0.7

SugarScript

June 28, 2016

Page 2: Cute Demon Crashers! Documentation
Page 3: Cute Demon Crashers! Documentation

Contents

1 Common Ren’Py/Python utilities 31.1 Ren’Py definitions / utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2 Python utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 Custom actions 92.1 Class: SetPersistent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3 Saving, Loading, and Rollback 113.1 Class: StoreBackedObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113.2 Class: StoreBackedSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123.3 Class: SexChoiceSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4 Custom Displayables & Graphics 174.1 Class: StateMachineDisplayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174.2 Class: ComposedSprite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

5 Indices and tables 27

i

Page 4: Cute Demon Crashers! Documentation

ii

Page 5: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

Contents:

Contents 1

Page 6: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

2 Contents

Page 7: Cute Demon Crashers! Documentation

CHAPTER 1

Common Ren’Py/Python utilities

This file contains common utility functions that are used througout the Cute Demon Crashers! source code.

init -100 python:

1.1 Ren’Py definitions / utilities

The functions in this category deal with providing simpler and less repetitive ways of defining some stuff in Ren’Py,as well as giving access for things for which Ren’Py lacks built-in functions for, or the built-in is awkward to use.

1.1.1 get_screen_var()

get_screen_var()

str -> None | object

Returns the value of a screen variable, or None if called from outside of a screen.

Ren’Py allows one to define screen-scoped variables (which may even be assigned default values in commonscreen syntax), but doesn’t seem to provide any functionality for accessing them programmatically outside ofinline Ren’Py code.

This is probably fine for most usages of Screens, but sometimes you want to mix some Python code in yourScreen language code in order to make some things less repetitive. The following definition is taken from theinternals of SetScreenVariable, which might not be the best of the ideas, but works.

Listing 1.1: Example

1 screen foo:2 default x = "foo"3 $ ui.text(get_screen_var("x") + "bar")

def get_screen_var(name):cs = renpy.current_screen()if cs is not None:

return cs.scope[name]

3

Page 8: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

1.1.2 naked()

naked(path[, suffix=”_cen”])

str[, str] -> str

Returns a displayable that correctly chooses between the censored and regular versions of an image, given apath to said image.

The substring {0} in the path stands for the place where the suffix will be inserted if the game is running inthe censored mode.

For this to work, images must be stored in their censored and uncensored versions, and censored images musthave a common suffix. As an example, if one were to use this for CGs, the CG folder might look like thefollowing:

- akki_cg1.png- akki_cg1_censored.png- akki_cg2.png- akki_cg2_censored.png- ( ... )

In the example above, the suffix chosen was _censored (note that it also includes the underscore). So, onewould be able to define the CGs in Ren’Py in the following way:

Listing 1.2: Example

1 image akki_cg1 = naked("cgs/akki_cg1{0}.png")2 image akki_cg2 = naked("cgs/akki_cg2{0}.png")3 ( ... )

def naked(path, suffix="_cen"):return ConditionSwitch(

"persistent.censor_18", path.format(suffix),True, path.format("")

)

1.1.3 sprite()

sprite(name, prefix, image)

str, str, str -> str

Constructs a path to a particular sprite image.

Sprites in Cute Demon Crashers! are organised into a [character]/[character abbrev]_[assettype]_[asset variation].png hierarchy, and this function just makes building that simpler.

def sprite(name, prefix, image):return "assets/sprites/{0}/{1}{2}.png".format(name, prefix, image)

1.1.4 sprite_orias()

sprite_orias(asset)

4 Chapter 1. Common Ren’Py/Python utilities

Page 9: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

def sprite_orias(asset):return sprite("orias", "ori_", asset)

1.1.5 sprite_kael()

sprite_kael(asset)

def sprite_kael(asset):return sprite("kael", "ka_", asset)

1.1.6 sprite_akki()

sprite_akki(asset)

def sprite_akki(asset):return sprite("akki", "ak_", asset)

1.1.7 sprite_mirari()

sprite_mirari(asset)

def sprite_mirari(asset):return sprite("mirari", "mi_", asset)

1.1.8 sprite_claire()

sprite_claire(asset)

def sprite_claire(asset):return sprite("claire", "cl_", asset)

1.2 Python utilities

Python’s built-in libraries lack some fairly useful functions. Functions here vary from “this part would be terser ifI could write it like this,” to “if we had this, we could reduce the number of possible bugs/catch error earlier,” andeverything in between.

We define them here so they may be used everywhere else.

1.2.1 flatten()

flatten(xss)

1.2. Python utilities 5

Page 10: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

list(list(a)) -> list(a)

Takes a list of lists and flattens it one level. So, if you have a list xss that looks like [[1, 2], [3, 4]],flatten(xss) would give you [1, 2, 3, 4].

def flatten(xss):return reduce(lambda: r, x: r + x, xss, [])

1.2.2 merge()

merge(a, b)

a, b -> { a | b }

Returns a new dictionary that contains items from both given ones.

Python’s standard library only contains imperative methods for merging dictionaries, which means you can’thave a single expression updating a dictionary and returning its new value. So this fixes that, and gets rid of thatpesky mutation by explicitly (shallow) copying the dictionary.

def merge(a, b):result = a.copy()result.update(b)return result

1.2.3 enum()

enum(*items)

str... -> Enum

Python 2.x lacks an enumeration type (a weaker form of sum types) so this, together with the provided customEnum class make up for that.

As far as Cute Demon Crashers!’s code goes, this is used for the SexChoiceSet object, so choices in the set(which are exposed as attributes in the object) are constrained by the possible choices and get to have a uniquevalue.

def enum(*items):return Enum(**dict(zip(items, range(len(items)))))

1.2.4 Class: Enum

class Enum

A class in which instances expose the given initialisation dictionary as attributes. Not to be used directly, but ratherthrough the enum function, which accepts a list of names.

class Enum(object):

6 Chapter 1. Common Ren’Py/Python utilities

Page 11: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

1.2.5 #__init__()

__init__(**kwargs)

dict(a, b)... -> Enum

Initialises an Enum instance.

def __init__(self, **kwargs):for (key, value) in kwargs.items():

self.__setattr__(key, value)

1.2. Python utilities 7

Page 12: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

8 Chapter 1. Common Ren’Py/Python utilities

Page 13: Cute Demon Crashers! Documentation

CHAPTER 2

Custom actions

We define here classes that can be used as actions in buttons and other interactable elements in a screen, similar to theactions that you may find in Ren’Py’s core distribution.

init -100 python:

2.1 Class: SetPersistent

class SetPersistent

str, a -> SetPersistent

Parents Action, FieldEquality

Ren’Py lacks an action that changes values from the persistent storage in the core distribution, so this class providesthat.

Usage follows from the other Set* actions in Ren’Py’s core distribution:

Listing 2.1: Example

1 screen foo:2 textbutton _("Click Me!") action SetPersistent("clicked", True)

@renpy.pureclass SetPersistent(Action, FieldEquality):

identity_fields = ['value']equality_fields = ['name']

2.1.1 #__init__()

__init__(name, value)

str, a -> unit

Initialises a SetPersistent instance.

9

Page 14: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

def __init__(self, name, value):self.name = nameself.value = value

2.1.2 #__call__()

__call__()

unit -> unit

Invokes this action, causing the persistent value to be changed and restarts the interaction so any element thatmight depend on that value can update its representation on the screen.

def __call__(self):setattr(persistent, self.name, self.value)renpy.save_persistent()renpy.restart_interaction()

2.1.3 #get_selected()

get_selected()

unit -> unit

Called internally by Ren’Py when used with buttons and similar elements to automatically update the state ofthose elements according to the value of the persistent field.

def get_selected(self):return getattr(persistent, self.name) == self.value

10 Chapter 2. Custom actions

Page 15: Cute Demon Crashers! Documentation

CHAPTER 3

Saving, Loading, and Rollback

Ren’Py has a store (well, multiple stores now) where it logs how values change over the course of a visual novel,such that it can properly rollback to previous lines of dialogue while also showing the correct images, as well assaving/loading the current user state, which is quite neat.

However, the store only “saves” the new value of a particular object in the store when one replaces the ol value witha new one. In other words, modifying the fields of an object will not cause that object to be saved in the store, whichmight cause quite some trouble when you have many moving parts.

Compound data structures help you alleviate some of the problem of keeping track of all the parts by giving you datacoherence, but Python’s built-in ones aren’t compatible with the Ren’Py store model, so we can’t use them directly.

The objects in this file try to solve that by reconcilling the Ren’Py store and compound data structures. It works verynaïvely by having an object claim an unique name (which must be provided by the user since this affects future runsof the game as well) in the store, and properly updating the store reference in response to changes in the object.

init -100 python:

3.1 Class: StoreBackedObject

class StoreBackedObject

str -> StoreBackedObject

The StoreBackedObject is the core class that encompasses the premise above. It does so by exposing twomethods: store and load, both manipulating the contents of the Ren’Py’s store in your behalf.

One should call store when they want to update the value in the store to whatever new object. Whereas load is tobe called when one desires to read the value from the store.

Since StoreBackedObject is a low-level class, it’s not meant to be used directly, but rather mixed into higher-level classes to give them the ability of storing and manipulating pieces of a compound data as a shole in the Ren’Py’sstore.

class StoreBackedObject(object):

3.1.1 #__init__()

__init__(slot_name)

11

Page 16: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

str -> unit

Initialises an instance of StoreBackedObject.

slot_name must be an unique name in the Ren’Py store.

def __init__(self, slot_name):self.slot_name = slot_name

3.1.2 #store()

store(value)

a -> unit

Updates the contents of the Ren’Py store at the slot this object has claimed.

This would be the equivalent of doing [slot_name] = value in Ren’Py, where slot_name is the nameof the slot claimed by this object.

def store(self, value):renpy.store.__setattr__(self.slot_name, value)

3.1.3 #load()

load([default=None])

a? -> a | None

Tries to retrieve the value stored in the Ren’Py store at the slot claimed by this object. If there is no value storedat that slot yet, returns the provided default value, if any.

def load(self, default=None):try:

return renpy.store.__getattribute__(self.slot_name)except AttributeError:

return default

3.2 Class: StoreBackedSet

class StoreBackedSet

str -> StoreBackedSet(a)

Parents StoreBackedObject

The StoreBackedSet is similar to Python’s built-in set data structure, but provides only a subset of its function-ality. It is, however, completely compatible with Ren’Py’s store model, by automatically updating the underlying storewhenever parts of it are modified.

12 Chapter 3. Saving, Loading, and Rollback

Page 17: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

As explained in StoreBackedSet, you must provide a unique name in the store (i.e.: not being used by any othervariable), which will be the slot claimed by this set structure, and will be used to save changes to the Ren’Py storebehind the scenes.

Listing 3.1: Example

1 init python:2 cards_seen = StoreBackedSet("seen_cards")3

4 label cards:5 menu:6 "Select a card to see."7

8 "Ace" if "ace" not in cards_seen:9 cards_seen.add("ace")

10

11 "Queen" if "queen" not in cards_seen:12 cards_seen.add("queen")13

14 "Joker" if "joker" not in cards_seen:15 cards_seen.add("joker")

class StoreBackedSet(StoreBackedObject):

3.2.1 #__init__()

__init__(slot_name)

self:StoreBackedSet(a), str -> unit

Initialises an instance of StoreBackedSet.

slot_name must be an unique name in the Ren’Py store, at least amongst other StoreBackedSet objects.

def __init__(self, slot_name):super(StoreBackedSet, self).__init__("store_set__" + slot_name)

3.2.2 #load()

load()

self:StoreBackedSet(a) -> set

Tries to retrieve the set from the Ren’Py store. If it fails, returns an empty set.

def load(self):return super(StoreBackedSet, self).load(default=set())

3.2.3 #__contains__()

__contains__(value)

3.2. Class: StoreBackedSet 13

Page 18: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

self:StoreBackedSet(a), a -> bool

Allows one to check for set membership using Python’s regular in operator:

Listing 3.2: Example

1 s1 = StoreBackedSet("set1")2 "foo" in s1 # => false3 s1.add("foo")4 "foo" in s1 # => true

def __contains__(self, value):return value in self.load()

3.2.4 #add()

add(value)

self:StoreBackedSet(a), a -> StoreBackedSet(a)

Adds a new value to the set.

def add(self, value):old = self.load()old.add(value)self.store(old)return self

3.2.5 #reset()

reset()

self:StoreBackedSet(a) -> StoreBackedSet(a)

Removes all items from the set.

def reset(self):self.store(set())return self

3.2.6 #remove()

remove(value)

self:StoreBackedSet(a) -> StoreBackedSet(a)

Removes a particular item from the set.

def remove(self, value):old = self.load()if value in old:

old.remove(value)

14 Chapter 3. Saving, Loading, and Rollback

Page 19: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

self.store(old)return self

3.3 Class: SexChoiceSet

class SexChoiceSet

str, list(str) -> SexChoiceSet

The SexChoiceSet keeps track of all the choices possible during some character’s sex scene, and helps catchingtypos/avoiding the same variable name being used in two different places.

Cute Demon Crashers! presents the user with a plethora of choices in each route, and keeping track of each variable(when they may be introduced/accessed in several different places) is not an easy thing to do. So, SexChoiceSetallows one to declare all of the possible choices upfront, and not have to worry about possible collisions or typos:

Listing 3.3: Example

1 akki_sex_choices = SexChoiceSet('akki', [2 'kissing',3 'top_off',4 ( ... )5 ])6 # Makes sure we reset all choices at this point (usually the7 # start of the game), otherwise choices from a previous8 # playthrough would still be active.9 akki_sex_choices.reset()

10

11 akki_sex_choices.kissing = True # activate kissing12 if akki_sex_choices.kissing:13 # Executed if `kissing` was activated.

Basically, this class will intercept all attribute messages (reading and modifying), and ensure that all of these attributenames are in the choices declared upfront. A StoreBackedSet is used behind the scenes to keep track of the stateof each attribute.

When an attribute is accessed, as in akki_sex_choices.kissing, the object will see if the unique value inden-tifying kissing is in the internal StoreBackedSet. If it is, it’ll return True, otherwise it’ll return False.

When an attribute is modified, as in akki_sex_choices.kissing = x, the object will add the unique valueidentifying kissing to the internal StoreBackedSet if x is a truthy value, and remove it if it’s False.

All internal field access in the class is done through the internal class dictionary, since it would otherwise trigger the__getattr__ and __setattr__ methods.

class SexChoiceSet(object):

3.3.1 #__init__()

__init__(slot_name, options)

str, list(str) -> unit

3.3. Class: SexChoiceSet 15

Page 20: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

Initialises a SexChoiceSet instance.

def __init__(self, slot_name, options):self.__dict__["storage"] = StoreBackedSet("sexchoice_slot__" + slot_name)self.__dict__["attributes"] = set(options)self.__dict__["options"] = enum(*options)

3.3.2 #reset()

reset()

unit -> SexChoiceSet

Removes all choices from the internal set.

def reset(self):self.__dict__["storage"].reset()return self

3.3.3 #__getattr__()

__getattr__(name)

str -> bool

Checks if the unique value for name is stored in the internal set, and also if name was declared when creatingthis instance.

Will return None if the given name isn’t one of the ones we’re expecting, which will cause Python to throw anAttributeError.

def __getattr__(self, name):attrs = self.__dict__.get("attributes", set())if name in attrs:

value = self.__dict__["options"].__dict__[name]return value in self.__dict__["storage"].load()

3.3.4 #__setattr__()

__setattr__(name, data)

str, bool -> unit

Modifies the state of name. If given a True value, will add name to the internal set, if given a False value,will remove it from the internal set.

def __setattr__(self, name, data):if name in self.__dict__.get("attributes", set()):

value = self.__dict__["options"].__dict__[name]if data:

self.__dict__["storage"].add(value)else:

self.__dict__["storage"].remove(value)

16 Chapter 3. Saving, Loading, and Rollback

Page 21: Cute Demon Crashers! Documentation

CHAPTER 4

Custom Displayables & Graphics

Note: You’ll need the 00functions.rpy and 02store.rpy files in order to use the classes defined here.

Ren’Py ships with a handful of displayable objects, which fits quite a lot of use cases. As for Cute Demon Crashers!,we wanted to have the equivalent of the built-in ConditionSwitch, but which could use transitions between eachstate.

The side image is actually the only place where using something like ConditionSwitch would be absolutelyrequired, but in all other places it would cut down the amount of work (and image tags!) by quite a lot, since we’vegot a plethora of variants for eyes, mouths, and all sorts of parts that compose a character’s sprite.

In light of this, the ideal solution was to write a new displayable that works in a way very similar toConditionSwitch, but that allows transitions to be defined when moving from one state to another. The cus-tom StateMachineDisplayable does this by encoding a kind of Finite State Machine, where one may providean additional transition when moving between states. Unlike ConditionSwitch, however, moving between statesin the StateMachineDisplayable is explicit, and this comes with the nice side-effect of being able to providedifferent transitions at different points in time.

Furthermore, while the StateMachineDisplayable can only handle one state at a given time (so, for example,it could only be used for a single layer in a image), the ComposedSprite object makes up for that by providing theanalogous of LiveComposite for ‘‘StateMachineDisplayable‘‘s.

init -100 python:

4.1 Class: StateMachineDisplayable

class StateMachineDisplayable

Parents renpy.Displayable, StoreBackedObject

The StateMachineDisplayable allows one to have a displayable that changes throughout the time. It operatesas a Finite State Machine, in which there are several possible states that it might be, but only one of those can be activeat any given time.

Each state has an associated Ren’Py displayable, which will be shown when that this object reaches that state, andtransitions may be provided for moving from one state to another.

17

Page 22: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

4.1.1 Creating StateMachineDisplayables

To create a StateMachineDisplayable, one needs to pass in an unique name for the displayable, the initial stateof the displayable, and a mapping of states to other Ren’Py displayables, which will constitute the possible states thatthe displayable might ever be in its lifetime.

The unique name is used to store the current state of the displayable in a way that’s compatible with Ren’Py’ssave/load/rollback mechanism. See 02store.rpy for more information.

Listing 4.1: Creating a StateMachineDisplayable

1 claire_base = StateMachineDisplayable(2 "claire_base", # An unique name for this displayable3 "default", # The initial state for this displayable4 # A mapping of "state": displayable5 {6 "default": "assets/sprites/claire/cl_base_clothed.png",7 "lazy": "assets/sprites/claire/cl_base/lazy.png",8 "chemise": "assets/sprites/claire/cl_base_chemise.png"9 }

10 )

4.1.2 Showing and moving between states

You can change the current state of a StateMachineDisplayable by calling the set_state method andpassing in the new state (and optionally a transition to be used when showing the new displayable).

Listing 4.2: Showing and switching states

1 $ claire_base.set_state("lazy")2 show claire_base with dissolve3 "I feel so lazy today..."4 $ claire_base.set_state("default", transition=Dissolve(1.0, alpha=True))5 "Why must I change? I just want to stay in bed all day..."

Another method in the StateMachineDisplayable class is snapshot, which gives you the displayable asso-ciated with a particular state. This was introduced here primarily so we could show the CGs that are constructed withthis class in the gallery in a simpler way.

Listing 4.3: StateMachineDisplayable snapshots

1 image claire lazy = claire_base.snapshot("lazy")2 $ claire_base.set_state("default")3 show claire lazy at left # Still the correct `lazy` displayable4 show claire_base at right

class StateMachineDisplayable(renpy.Displayable, StoreBackedObject):

4.1.3 #__init__()

__init__(slot, initial_state, states, **kwargs)

18 Chapter 4. Custom Displayables & Graphics

Page 23: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

str, 𝛼, { 𝛼: Displayable } -> unit

Initialises a StateMachineDisplayable instance.

def __init__(self, slot, initial_state, states, **properties):

Ren’Py’s core displayable classes must be called with the additional displayable properties (like style things andwhat not).

super(StateMachineDisplayable, self).__init__(**properties)

Since this class also uses StoreBackedObject to properly handle Ren’Py’s save/loading/rollback system,we need to initialise that with an unique slot. We prepend smd_state__ to the provided slot so it won’tcollide with other variables/StoreBackedObject classes.

StoreBackedObject.__init__(self, "smd_state__" + slot)

A Transition object needs to be provided with two displayables, old and new, it then transitions from theold displayable to the new one.

We keep track of the old state of this object in the old_state field, then dynamically compute which dis-playable was that from the state mapping. This assumes that states never changes.

self.old_state = None

Since Ren’Py can rollback, and we only keep track of the current state in the store, we need to make sure wedon’t show incorrect transitions when rolling back/forward. Just keeping track of the current_state issufficient for that, but the core of this is done in the per_interact method.

self.current_state = None

The mappings of state to displayable are stored in the states field. We assume this field never changes.

self.states = states

At any point in time we’ll be showing a displayable to the user. This may be a transition, if we’ve just changedthe state in this interaction, or a regular displayable.

The transition field stores the transition we’re showing the user in this interaction, in response to aset_state call.

self.transition = None

The displayable field stores the displayable associated with the current state of the displayable, and wefallback to showing just this when no transition is being shown.

self.displayable = None

Furthermore we need to keep track of the transition’s shown/animation times, so we pass the correct valueswhen rendering it.

self.shown_time = 0self.anim_time = 0

We use the reset field to keep track of when we’ve changed states, so we can update the shown_time andanim_time values accordingly and get the transition animation to play correctly.

self.reset = False

Finally, we move this displayable to the provided initial state, so it’s ready to be shown on the screen.

4.1. Class: StateMachineDisplayable 19

Page 24: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

self.set_state(initial_state)

4.1.4 #snapshot()

snapshot([state=None])

a -> Displayable

Returns the displayable associated with the provided state in this object. Fallsback to the current state if no stateis provided, and finally to the Null displayable if we can’t find any displayable in the state mapping.

def snapshot(self, state=None):return self.states.get(state or self.current_state) or Null()

4.1.5 #redraw()

redraw()

unit -> unit

Forces this displayable to redraw itself with the new state information. Usually called after moving states, ornew interactions.

def redraw(self):self.reset = Truerenpy.redraw(self, 0)

4.1.6 #set_state()

set_state(new_state[, transition=None])

a, Transition -> unit

Transitions to the provided new state, optionally with a nice transition animation.

We’ll construct a transition by changing from the current state (which is loaded from the Ren’Py store) to thenew one. If we can’t get a displayable for either, we use the Null displayable, which means we don’t get anytransition for things like Dissolve.

This also updates the store with the new state, so state changes works properly with rollbacks and loading.

def set_state(self, new_state, transition=None):self.current_state = new_stateself.old_state = self.load()self.store(new_state)

old_d = self.states.get(self.old_state) or Null()cur_d = renpy.easy.displayable(self.states.get(new_state) or Null())self.displayable = cur_dself.transition = anim.TransitionAnimation(old_d, 0.0, transition, cur_d)self.redraw()

20 Chapter 4. Custom Displayables & Graphics

Page 25: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

4.1.7 #per_interact()

per_interact()

unit -> unit

Ren’Py calls per_interact internally every time a new interaction begins. This gives us a chance of showingthe proper state to the user in case of rollbacks.

def per_interact(self):new_state = self.load()

To avoid creating displayables unecessarily, we only call set_state when the new state is really a new state.

if self.current_state != new_state:self.set_state(new_state)

Also, in order to avoid showing the wrong transition to people, we get rid of it in new interactions.

if not self.reset:self.transition = Noneself.redraw()

4.1.8 #current_displayable()

current_displayable()

unit -> Displayable

Returns the displayable that should be shown to the user in this interaction.

def current_displayable(self):return self.transition or self.displayable

4.1.9 #render()

render(width, height, st, at)

int, int, int, int -> renpy.Render

Renders the current displayable so Ren’Py can show it on the screen.

def render(self, width, height, st, at):

We need to reset the times if this is the first time we’re showing this state, so transitions/animations workcorrectly.

if self.reset:self.reset = Falseself.shown_time = stself.anim_time = at

d = self.current_displayable()if d:

4.1. Class: StateMachineDisplayable 21

Page 26: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

return renpy.render(d,width,height,st - self.shown_time,at - self.anim_time)

else:return renpy.Render(0, 0)

4.1.10 #visit()

visit()

unit -> list(Displayable)

Ren’Py uses this list to predict images and stuff.

def visit(self):return [self.transition, self.displayable]

4.2 Class: ComposedSprite

class ComposedSprite

Since StateMachineDisplayable only deals with a single displyable at a time, like ConditionSwitch, weneed a way of composing several of them to form a single displayable. One could just use LiveComposite, butthen they would need to call set_state on each of the parts, one at a time. To solve this, ComposedSpritebundles several displayables into a single thing, one of which may be a StateMachineDisplayable, and allowsone to change the state of several parts with a single command.

4.2.1 Creating ComposedSprites

To construct a ComposedSprite, one needs to pass in the size of the final image, as a (width, height) tuple,and a series of “layers”, as a (layer name, (x offset, y offset), displayable) tuple. This is verysimilar to what LiveComposite expects, except that we accept an additional layer name value for displayablesthat are StateMachineDisplayable.

The layer name may be a string that provides an unique name for that layer in this ComposedSprite dis-playable, or None if the displayable in that layer isn’t a StateMachineDisplayable.

Listing 4.4: Creating a ComposedSprite

1 glasses_claire = ComposedSprite(2 (360, 504),3 ("base", (0, 0), claire_base),4 ("eyes", (0, 0), claire_eyes),5 ("mouth", (0, 0), claire_mouth),6 (None, (0, 0), "assets/sprites/claire/cl_glasses.png")7 )

22 Chapter 4. Custom Displayables & Graphics

Page 27: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

4.2.2 Showing ComposedSprites & moving between states

Since ComposedSprite isn’t a displayable itself, you can’t show it directly on the screen. Instead, it gives you twomethods to create displayables from the initial ComposedSprite description. In both cases the layers are composedin the order they’re given, where the first ones are at the very bottom of the image, and last ones are at the very top ofthe image.

The displayable method gives you a live displayable, which reflects the current state of all ‘‘StateMachineDis-playable‘‘s that constitute the image:

Listing 4.5: Using .displayable() to show ComposedSprites

1 image claire glasses = glasses_claire.displayable()2 show claire glasses with dissolve

Similar to StateMachineDisplayable, you can use a set_state method in the ComposedSprite objectto change the current state of the ComposedSprite, but here you’ll need to pass in a mapping of which layers youwant the state to change. A transition argument may be likewise provided:

Listing 4.6: Moving between states in ComposedSprites

1 show claire glasses with dissolve2 $ glasses_claire.set_state(base="lazy", transition=dissolve)3 "Hello."4 $ glasses_claire.set_state(eyes="happy", mouth="kitty")5 "'Tis nice to see ya."

Another way of getting a displayable out of a ComposedSprite is to use the snapshot method. This again worksin a similar fashion to StateMachineDisplayable‘s snapshot method, except that it takes mappings fromlayers to states, and gives you back a proper (static) composition of all those layers.

Listing 4.7: Snapshots in ComposedSprites

1 image claire kitty = glasses_claire.snapshot(eyes="happy", mouth="kitty")2 show claire glasses at left3 show claire kitty at right4 # This will only affect the `claire glasses` image.5 $ glasses_claire.set_state(mouth="grin")

class ComposedSprite(object):

4.2.3 #__init__()

__init__(size, *layers)

(int, int), (str | None, (int, int), Displayable)... -> unit

Initialises an instance of ComposedSprite

def __init__(self, size, *layers):

We use LiveComposite for composing the sprites, so we need to know the size of the final image beforehand.

self.size = size

4.2. Class: ComposedSprite 23

Page 28: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

The layers this ComposedSprite has, as a list.

self.layers = layers

The layer_map helps us to easily set the state of StateMachineDisplayable layers, by mapping layernames directly to those objects.

self.layer_map = {}for (name, _, displayable) in layers:

if name is not None:self.layer_map[name] = displayable

4.2.4 #set_state()

set_state([transition=None], **kwargs)

Transition, { str: any } -> unit

Changes the state of all layers provided by the mappings.

def set_state(self, transition=None, **kwargs):for key in kwargs:

self.layer_map[key].set_state(kwargs[key], transition)

4.2.5 #snapshot()

snapshot(**kwargs)

{ str: any } -> Displayable

Returns a static composition of the ComposedSprite, as a displayable. Static here just means that theresulting image won’t be changed by calling set_state in any of the layers.

def snapshot(self, **kwargs):return LiveComposite(

self.size,

*flatten([[pos, displayable.snapshot(kwargs.get(name))]for (name, pos, displayable) in self.layers

]))

4.2.6 #displayable()

displayable()

unit -> Displayable

Returns a live displayable for this ComposedSprite.

24 Chapter 4. Custom Displayables & Graphics

Page 29: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

def displayable(self):return LiveComposite(

self.size,

*flatten([[pos, displayable] for (_, pos, displayable) in self.layers]))

4.2. Class: ComposedSprite 25

Page 30: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

26 Chapter 4. Custom Displayables & Graphics

Page 31: Cute Demon Crashers! Documentation

CHAPTER 5

Indices and tables

• genindex

• modindex

• search

27

Page 32: Cute Demon Crashers! Documentation

Cute Demon Crashers! Documentation, Release 1.0.7

28 Chapter 5. Indices and tables

Page 33: Cute Demon Crashers! Documentation

Index

Symbols__call__(), 10__contains__(), 13__getattr__(), 16__init__(), 7, 9, 11, 13, 15, 18, 23__setattr__(), 16

Aadd(), 14

CComposedSprite (built-in class), 22current_displayable(), 21

Ddisplayable(), 24

EEnum (built-in class), 6enum() (built-in function), 6

Fflatten() (built-in function), 5

Gget_screen_var() (built-in function), 3get_selected(), 10

Lload(), 12, 13

Mmerge() (built-in function), 6

Nnaked() (built-in function), 4

Pper_interact(), 21

Rredraw(), 20remove(), 14render(), 21reset(), 14, 16

Sset_state(), 20, 24SetPersistent (built-in class), 9SexChoiceSet (built-in class), 15snapshot(), 20, 24sprite() (built-in function), 4sprite_akki() (built-in function), 5sprite_claire() (built-in function), 5sprite_kael() (built-in function), 5sprite_mirari() (built-in function), 5sprite_orias() (built-in function), 4StateMachineDisplayable (built-in class), 17store(), 12StoreBackedObject (built-in class), 11StoreBackedSet (built-in class), 12

Vvisit(), 22

29