Difficulty: 5/5

Countdown

A creator defined displayable that counts down

CDD stands for Creator Defined Displayable (Previously known as UDD, User Defined Displayable), and is one of the most powerful things you can code in Ren'Py, as it allows you to define a potentially incredibly complex object - that can interact with many internal values - and control what it comes up as on the screen.

A great example of a CDD is the one on their documentation page, called Appearing. It is a displayable that changes it's transparency based on how far it is from the mouse cursor.

TODO: Add childSize to it like there is in Unveil.

I talk about displayables in my Screen Language Part #3 tutorial, but that's mostly the basics. You'll learn much more about displayables here, but we'll be using a lot of Python, defining a child class (subclass) of a basic Ren'Py displayable class and talk about rendering.

That being said, this page talks about how the code works.
If you just want to use it in your project, grab it from the final code block. You should be able to understand how to use it from the example included.

init python:

    class Countdown(renpy.Displayable):

        def __init__(self, **kwargs):

            # Initialize stuff from the parent class.
            super(Countdown, self).__init__(**kwargs)

            # Child of the displayable. This is what we'll actually see.
            self.child = None

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

            # Create the render we will return.
            render = renpy.Render(width, height)

            # Place the child into the render.
            render.place(self.child)

            # Return the render.
            return render

        def event(self, ev, x, y, st):

            # Pass the event to our child.
            return self.child.event(ev, x, y, st)

        def visit(self):
            return [ self.child ]   

label start:

    call screen ourScreen

    return

screen ourScreen():

    null
(This does nothing.

Explain the parent and the methods.
Sounds easy enough but I'm guessing it will actually be pretty long.)

(Next, our CDD with some basic layout)

init python:

    class Countdown(renpy.Displayable):

        def __init__(self, time, **kwargs):

            # Initialize stuff from the parent class.
            super(Countdown, self).__init__(**kwargs)

            # Time to count down from.
            self.countdown = float( time )

            # This will be updated to show remaining time.
            self.time = self.countdown

            # Whether the countdown has finished.
            self.finished = False

            # Child of the displayable. This is what we'll actually see.
            self.child = None

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

            # Create the render we will return.
            render = renpy.Render(width, height)

            # Place the child into the render.
            render.place(self.child)

            # Return the render.
            return render

        def event(self, ev, x, y, st):

            # Pass the event to our child.
            return self.child.event(ev, x, y, st)

        def visit(self):
            return [ self.child ] 

        # Will update the visible text.
        def updateChild(self):
            return None   

        # Will be ran when the time runs out.
        def timeRanOut(self):
            return None

(What's the point of all the added things, what they'll be doing)

init python:

    class Countdown(renpy.Displayable):

        def __init__(self, time, **kwargs):

            # Initialize stuff from the parent class.
            super(Countdown, self).__init__(**kwargs)

            # Time to count down from.
            self.countdown = float( time )

            # This will be updated to show remaining time.
            self.time = self.countdown

            # Whether the countdown has finished.
            self.finished = False

            # Child of the displayable. This is what we'll actually see.
            self.child = None

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

            # As long as the countdown is going on,
            if not self.finished:

                # Check if we're not over the countdown yet,
                if not at > self.countdown:

                    # and update the time accordingly.
                    self.time = float( self.countdown - at )

                    # Update the child with new time.
                    self.updateChild()

                # Once the time goes past the countdown,
                else:

                    # call the function to handle time running out.
                    self.timeRanOut()

                # Trigger render method again, repeating the process.
                renpy.redraw(self, 0)

            # Create the render we will return.
            render = renpy.Render(width, height)

            # Place the child into the render.
            render.place(self.child)

            # Return the render.
            return render

        def event(self, ev, x, y, st):

            # Pass the event to our child.
            return self.child.event(ev, x, y, st)

        def visit(self):
            return [ self.child ] 

        # Will update the visible text.
        def updateChild(self):
            return None   

        def timeRanOut(self):

            # Stops the updating, making countdown stay at 0.0.
            self.finished = True
            self.time = 0.0

            # Update the child one last time.
            self.updateChild()

            # Here is the place for things happening when the countdown runs out.
            renpy.notify( "The countdown of {} seconds is over!".format(self.countdown) )

(render method explained, timeRanOut method)

init python:

    class Countdown(renpy.Displayable):

        def __init__(self, time, **kwargs):

            # Initialize stuff from the parent class.
            super(Countdown, self).__init__(**kwargs)

            # Time to count down from.
            self.countdown = float( time )

            # This will be updated to show remaining time.
            self.time = self.countdown

            # Whether the countdown has finished.
            self.finished = False

            # Child of the displayable. This is what we'll actually see.
            self.child = None

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

            # As long as the countdown is going on,
            if not self.finished:

                # Check if we're not over the countdown yet,
                if not at > self.countdown:

                    # and update the time accordingly.
                    self.time = float( self.countdown - at )

                    # Update the child with new time.
                    self.updateChild()

                # Once the time goes past the countdown,
                else:

                    # call the function to handle time running out.
                    self.timeRanOut()

                # Trigger render method again, repeating the process.
                renpy.redraw(self, 0)

            # Create the render we will return.
            render = renpy.Render(width, height)

            # Place the child into the render.
            render.place(self.child)

            # Return the render.
            return render

        def event(self, ev, x, y, st):

            # Pass the event to our child.
            return self.child.event(ev, x, y, st)

        def visit(self):
            return [ self.child ] 

        def updateChild(self):

            # Child that represents what we see.
            # It is Text created from the remaining time, rounded to 2 decimal places.
            # The Text also has two style properties.
            self.child = Text( str( round( self.time , 2) ) , color = "f00" , size = 36 )

        def timeRanOut(self):

            # Stops the updating, making countdown stay at 0.0.
            self.finished = True
            self.time = 0.0

            # Update the child one last time.
            self.updateChild()

            # Here is the place for things happening when the countdown runs out.
            renpy.notify( "The countdown of {} seconds is over!".format(self.countdown) )

# Finally we get to add our Countdown to the screen and see it in action!!
screen ourScreen():

    # Default the countdown, since it's a variable that changes.
    default countdown = Countdown(30.0)

    # Add it into the screen.
    add countdown:
        pos (0.5, 0.5)

(finally, updating the child and showing the CDD in a screen)


(How this could be improved?
That it can be shown in labels, too, like any other displayable?)


(Set of skills learned, more than things specific to countdown)

  • What labels are
  • How we can create dialogue lines, with or without someone saying them
  • That we can have multiple labels
  • That we can move between them with the call and return statements

Good job!