Tutorial & Code: Analog/Digital Clock as User Defined Displa

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Message
Author
User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Tutorial & Code: Analog/Digital Clock as User Defined Displa

#1 Post by trooper6 »

Preamble--Updated: 18 June 2016
Hello All! I'm newish to Ren'py and Python and decided to try and learn more about the language through some ambitious examples--but also because I needed an Analogue clock for my game. With some research and a lot of help from the wonderful people on these boards (big shout out to Alex, apricot orange, PyTom, and MVP xela!), I came up with an analog clock I'm really happy with and I thought I'd share it with you all. All the code is here, but I'm also going to add a bunch of explanations for those people who are new to coding. I'd also like to give a really big thanks to DragoonHP who has given me some grate bug fixes. If anyone sees any more more bugs, let me know!
Note: The version of ren'py as this was originally written was 6.15, the current version was updated when ren'py was at 6.9910.

Also note...this is the second time I'm updating this post. Last time I did, everything got eaten when I went to post. So this time I'm going to save regularly while updating this post. If you see this, I'm not finished updating the post!

Contents
1. Analogue Clock as ATL
2. Uncle Mugen's Cookbook Clock
3. Another's Screen Code Clock
4. Code for my Clock Class with detailed programming explanations
5. Using the Clock in your game (if you don't care about learning to program, just skip to here to learn how to use the clock)

So I wanted an analog clock for the game I'm making. The first thing I did was search through the forums and the main ren'py site itself. I found three different models of analog clock out there. I'm going to summarize the three different approaches first. Here we go!

1. Analogue Clock as ATL
The easiest way to make the clock would be to just add the images to the game like you would any other image and then use ATL (Animation and Transformation Language) to rotate the minute and hour hand.

The ATL page is here: http://www.renpy.org/doc/html/atl.html
But most useful for newbies and relevant to the clock challenge is this excellent tutorial on ATL by saguaro: http://lemmasoft.renai.us/forums/viewto ... 51&t=16604

This thread by saguaro has a code example to put the clock together all laid out nice and neat: http://lemmasoft.renai.us/forums/viewto ... =8&t=15636
Note saguaro's code has the hour hand finishing the full 360 degrees in 60 seconds (linear 60.0 rotate 360) and the minute hand completes the rotation in 5 seconds. To make the clock work properly the linear for the hour hand would have to be "linear 86400.0 rotate 360" (there are 86400.0 seconds in a day and it takes on day for the hour hand to travel the whole 360 degrees) and the linear for the minute hand would have to be "linear 3600.0 rotate 360" (there are 3600 seconds in an hour and it takes one hour for the minute hand to travel the whole 360 degrees).

You'd set the initial coordinates with an initial rotate command outside of the repeat block when you put the images up the first time.
Remember! 6 degrees = 1 minute.

So...I suppose I would implement saguaro's ATL code like so:

Code: Select all

image clockBase = "clockbase.png"
image clockHour = "clockhour.png"
image clockMin = "clockmin.png"

show clockbase:
    xalign 0.5 yalign 0.5
show clockmin:
    xalign 0.5 yalign 0.5
    rotate 0 #This puts the minute hand at the starting position of 12
    block: #Everything in this block repeats over and over
        linear 3600.0 rotate 360
        repeat
show clockhour:
    xalign 0.5 yalign 0.5
    rotate 90 #This puts the hour hand at the 3...making it 3 o'clock
    block:
        linear 86400.0 rotate 360
        repeat
I think that is the easiest way to do the clock. Everything is done in ren'py scripting language and there is no need for python. You can place the clock wherever on the screen, you set the clock's starting time and set if off running. But I don't get the sense that changing time and having fine control over the clock is all that easy with this version. And maybe you don't want the clock to run in real time, but to move forward a predetermined amount at specific moments in your script...

2. Uncle Mugen's Cookbook Clock
So the next stop on the summary journey is the analogue clock in the cookbook:http://www.renpy.org/wiki/renpy/doc/coo ... alog_Clock

This was originally by Uncle Mugen, and later updated by sundownkid, so even though the page says it is out of date, it should be up to date as of 6.18. It also includes three clock image files...which are awesome--and way better than mine...use them! Mugen's code also works well. In the old example, the clock was a function--a block of code that does an action--in this case, changes the clock manually, advancing the hands based on a "minute" variable. This piece of code was the initial inspiration for what I ended up doing...even though my code may not look like it. But what was really important to my thinking was, a) the idea of manually setting the clock using a minutes variable, and the formula where the minute hand moves minutes*6 degrees a click and the hour hand moves minutes*0.5 degrees a click. The sundownkid version doesn't use functions anymore, but screen language ATL transforms and places it in a screen. But you still can specify what time you want with the minute variable and it still doesn't run in realtime. Don't worry, I'll talk about functions later.

This thread: http://lemmasoft.renai.us/forums/viewto ... f=8&t=7257 discusses how to make Mugen's clock run automatically. Note however, the answer that is settled on uses a timer function...but it is important to note that the timer stops repeating once you move forward to the next interaction...so the automatic forward motion of the clock wouldn't last throughout the entire game.

3. Another's Screen Code Clock
There is one more interesting piece of code dealing with an analogue clock I found in the forums I want to bring up. That is here: http://lemmasoft.renai.us/forums/viewto ... 95#p159812

This is by Another and what s/he has done is create the clock as a screen. This clock runs automatically, and it is pretty straight forward if you get the saguaro and mugen examples...but it does another really cool thing that is different than the others: It makes the clock time the actual time in real life using the datetime variable! Which is pretty cool, if you ask me.

So...to summarize the three different versions of clocks discussed so far:
1. As ATL put wherever you can put images normally. This version runs automatically, but I think setting the time is a bit cumbersome (you have to work out all the degrees yourself) and I think changing the time to whatever you want after the initial setting might be a bit tricky.
2. Mugen's clock was a function and now is screen language. This runs manually and makes it really easy to set the time, change the time, and advance it when you want in a story to time with events, but it doesn't have a persistent automatic running function.
3. Another's code also uses screen language for the clock. The clock runs automatically and is synced to real life time.

Okay...so what did I decide to do and why?

4. Code for my Clock Class with detailed programming explanations
Well, first off I wanted to improve my programming. I wanted to do classes and methods in Python (I know Java). I also wanted to figure out how to do Creator Defined Displayables. This was very practical because:

1) I wanted a CDD/UDD because I wanted to be able to place my clock in a frame, and images/UDDs will do that for me. For my game specifically, I wanted to place it in a frame in a control screen.
2) For the play I'm adapting, the scene starts at 3pm and there is an important meeting that happens at 3:10pm...so time is important. But I wasn't certain if the manual method or the automatic method would work best with the story (i.e. do I want the time to advance at predetermined times in the story or do I want it to continually advance in real time). I can't really know until I finish the story and see how much time it takes to play through to the end...which means that I want the clock to have the option to both manual and automatic...and then when I saw Another's code, I wanted to have the real time option for people who aren't me as well.
3) Update: Because the clock is an object I can make it do all sorts of cool things, which I finally got around to coding. So the clock can now switch between an analogue version and a digital version. In the analogue version to can have a clock ticking sound play and have the clock chime on the hour. In both versions you can set an alarm and have it go off when the time comes. In both versions you can make the seconds visible or invisible. You can dictate the size of the clock you want...and you just add it as you would any other displayable.
4) Update: In updating the clock I added a few functions. I made it possible to subtract time from the clock as well as add to it. I added a function that I needed for my game--the ability to find out how much time has passed since a given time. I also added the ability do have the clock run either on a 12hr clock or a 24hr clock. I also created a special method that should be added to the preferences, save, and load screens if you don't want the clock sounds happening when you are in those screens. Which I didn't.

So here is the code for the clock class (which I put in a "clock.rpy" file)--Even though there are comments in the code, I'll explain in more detail afterwards.

Code: Select all

init -2:
    style digi_clock:
        is default
        font "00 fonts/DigitalRegular.ttf"
        color "#f00"
        yalign 0.5

init -1 python:
    from datetime import datetime #This is to get the real world datetime
    
    img = ["00 images/VintageClockBase450x450.png", "00 images/VintageClockHour450x450.png", 
        "00 images/VintageClockMinute450x450.png", "00 images/VintageClockSecond400x400.png", 
        "00 images/DigitalClockBase460x200.png"]
    """
    Use your own clock images here, or use the ones I provided.
    Note: your analogue clock images need to be square, your digital base can be 
    any size but keeping it close to the ratio of 460x200 gives the best results.
    Important: You should have four images listed in this order--The Analogue 
    Clock Base, The Hour Hand, The Minute Hand, The Second Hand, The Digital Clock Base
    """
    renpy.music.register_channel("TockBG", mixer= "sfx", loop=True)
    renpy.music.register_channel("ChimeBG", mixer= "sfx", loop=False, tight=True) 
    renpy.music.register_channel("Alarm", mixer= "sfx", loop=False, tight=True)   
    """These are here so that the various clock sounds play on their own sound channels."""
    
    def clock_audio_pause(): #I use this to pause the audio of the clock when I go to various screens, this is also why it isn't part of the clock, but this in the demo?
        try:
            myClock.a_sound_on(False, False)
        except:
            pass
    
    """As a Creater Defined Displayable this needs to extend the Displayable class)"""
    class Clock(renpy.Displayable):
        
        #The clock class constructor
        def __init__(self, ana=True, h=0, m=0, resize=150, sH=True, mil_time=False, **kwargs):
            """
            This constructor takes the following arguments:
            ana = True gives the analogue version of the clock, False gives digital
            h=Hours the clock should start on
            m=Minutes the clock should start on
            resize=How big you want your clock images to be
            sH=If you want the second hand to be visible or not
            If you do not give any arguments when you create the clock, it will default to
             a clock with 0 hours, 0 minutes, 150 pixels square, with a second hand showing
            """
            super(Clock, self).__init__(**kwargs)
            
            #These lines are for setting up the images used and the size of them
            self.width = resize
            self.d_height = (resize*32)/100
            self.base_image = im.Scale(img[0], resize, resize)
            self.hour_hand_image = im.Scale(img[1], resize, resize)
            self.minute_hand_image = im.Scale(img[2], resize, resize)
            self.second_hand_image = im.Scale(img[3], resize, resize)
            self.digital_base_image = im.Scale("00 images/DigitalClockBase460x200.png", 
                self.width, self.d_height)
            self.offset = (resize*2**0.5-resize)/2
            self.second_hand_visible = sH
 
            #Variables for handling time
            self.minutes = (h*60)+m 
            self.seconds = self.minutes*60
            self.seconds_target = 0 
            self.step = 0 
            self.old_second = None #Used in autorun
            self.alarm_hr = 0
            self.alarm_min = 0
            
            #Variables for determining the clock modes
            self.analogue = ana
            self.auto_run = False
            self.realtime_run = False
            self.forward = True
            self.mil_time = mil_time

            #Variables for handling sound
            self.second_sound_on = False
            self.chime_on = False
            self.last_minute = 0 #Used for chimes
            self.play_chime = False #Chimes are currenlty running or not
            self.alarm_on = False
            self.last_aminute = 0 #Used for alarm
            self.play_alarm = False #Used for alarm
                    
        # Function that continuously updates the graphics of the clock
        def render(self, width, height, st, at):
            if self.seconds_target:
                if self.forward:
                    #Advances the seconds variable until it is the same as the seconds_target variable
                    if self.seconds_target > self.seconds:
                        self.seconds += self.step
                    #Resets the seconds_target variable
                    if self.seconds_target <= self.seconds:
                        self.seconds_target = 0
                else:   
                    if self.seconds_target < self.seconds:
                        self.seconds -= self.step
                    if self.seconds_target >= self.seconds:
                        self.seconds_target = 0
                        self.forward = True
                
            #Makes sure the minutes variable is always in sync with the seconds variable
            if self.minutes != self.seconds//60:
                self.minutes = self.seconds//60
                
            # Calculating how many seconds have passed today.
            if self.seconds >= 86400:
                self.seconds -= 86400

                if self.seconds_target:
                    self.seconds_target -= 86400
                    
            elif self.seconds < 0:
                self.seconds += 86400
                
                if self.seconds_target:
                    self.seconds_target += 86400
                    
            
            #This is all the render information for the Analogue Clock
            if self.analogue:                
                # Create transform to rotate the second hand
                tM = Transform(child=self.minute_hand_image, rotate=self.seconds*0.1, 
                    subpixel=True)
                tH = Transform(child=self.hour_hand_image, rotate=self.seconds*0.008333, 
                    subpixel=True)

                # Create a render for the children.
                base_render = renpy.render(self.base_image, width, height, st, at)
                minute_render = renpy.render(tM, width, height, st, at)
                hour_render = renpy.render(tH, width, height, st, at)
            
                #If we are in chime_run mode, checks to see the chimes need to be rung
                if self.chime_on == True:
                    if self.last_minute != self.minutes:
                        self.play_chime = False
                        self.last_minute = self.minutes
                    self.start_chime()

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

                # Blit (draw) the child's render to our render.
                render.blit(base_render, (0, 0))
                render.blit(minute_render, (-self.offset, -self.offset))
                render.blit(hour_render, (-self.offset, -self.offset))
                
                #If the second hand is visible: renders, transforms, and adds it to our clock
                if self.second_hand_visible:
                    tS = Transform(child=self.second_hand_image, rotate=self.seconds*6, 
                        subpixel=True)
                    sec_render = renpy.render(tS, width, height, st, at)
                    render.blit(sec_render, (-self.offset, -self.offset))
            #This is all the render information for the Digital Clock
            else:
                # create the text that will go in the Digital Clock and boxes text sits in 
                ftht = self.d_height * 0.8
                col =Text(":", style="digi_clock", size=ftht)  
                time = list(Text("{0:02d}".format(item), style="digi_clock", size=ftht) 
                    for item in self.get_time())
                fxsize = (self.width-10)//4
                
                # Determine what to display based on if the seconds are  visible
                if self.second_hand_visible:
                    digi_text = HBox(Fixed(time[0], xsize=fxsize), col, Fixed(time[1], 
                        xsize=fxsize), col, Fixed(time[2], xsize=fxsize), xalign=0.5)
                else:
                    digi_text = HBox(Fixed(time[0], xsize=fxsize), col, Fixed(time[1], 
                        xsize=fxsize), xalign=0.5)
                
                #Put all of our pieces into one Fixed Box
                digi = Fixed(self.digital_base_image, digi_text, xysize=(self.width, 
                    self.d_height)) 
                #Create a render for our Fixed Box
                digi_render = renpy.render(digi, width, height, st, at)
                    
                # Create the render we will return.
                render = renpy.Render(self.width, self.d_height)

                # Blit (draw) the child's render to our render.
                render.blit(digi_render, (0,0))
                
            #Runs the realclock and autoclock functions 
            if not self.seconds_target:
                self.realclock()
                self.autoclock(st)

            #If we are in alarm_on mode, checks to see the alarm need to be rung
            if self.alarm_on == True:
                if self.last_aminute != self.minutes:
                    self.play_alarm = False
                    self.last_aminute = self.minutes
                self.start_alarm()
                    
            #This makes sure our object redraws itself after it makes changes
            renpy.redraw(self, 0)

            # Return the render.
            return render
          
        #####These are functions you can call to do things when using the clock            
        #Returns the current hours, minutes, and seconds of the clock
        def get_time(self):
            h, m = divmod(self.minutes, 60)
            h = int(h)
            m = int(m)
            s = self.seconds%60
            s = int(s) #Can this be placed above?
            if self.mil_time:
                if h > 23:
                    h = h%24
            else:
                if h is 0:
                    h = 12
                elif h > 12:
                    h = h%12
            return h, m, s
            
        #Directly set the time of the clock
        def set_time(self, h=0, m=0):
            """
            h = hours
            m = minutes
            """
            fh, fm = self.fix_time(h,m)
            self.seconds = ((fh*60)+fm)*60
            
        #Manually add time to the clock, with or without animation
        def add_time(self, h=0, m=0, animate=0): 
            """
            h = hours
            m = minutes
            animate = the number of seconds it takes for the time to be added
            """
            if self.realtime_run == False:
                num = ((h*60)+m)*60
                if animate:
                    self.step = num // float(animate*60)
                    self.seconds_target = self.seconds + num
                else:
                    self.seconds += num
                    
        #Manually add time to the clock, with or without animation
        def sub_time(self, h=0, m=0, animate=0): 
            """
            h = hours
            m = minutes
            animate = the number of seconds it takes for the time to be subtracted
            """
            if self.realtime_run == False:
                self.forward = False
                num = ((h*60)+m)*60
                if animate:
                    self.step = num // float(animate*60)
                    self.seconds_target = self.seconds - num
                else:
                    self.seconds -= num
                
        #Determines how much time has passed since a given time
        def time_passed(self, start_h=0, start_m=0, days_ago=0, comb_min=True):
            """
            h, m are the hours and minutes that make up the starting time you
            want to test against
            start_day is the day you'd like to start the time passed count from. That first day is 0
            comb_min, if true it returns just the total minutes, if False returns hours and mintues
            """
            oldnum = (start_h*60)+start_m
            nh, nm, ns = self.get_time()
            newnum = (nh*60)+nm
                
            minpassed = newnum-oldnum
            if days_ago > 0:
                minpassed += (days_ago*1440)
            tph, tpm = divmod(minpassed, 60)
            if comb_min:
                return minpassed
            else:
                return tph, tpm
        
        #Sets the runmode of the clock, the second hand, and the sound
        def runmode(self, mode, secOn=True, chOn=True):
            """
            mode= The mode of the clock: auto, real, or none (None turns off the clock) 
            snd= Turns on or off sound
            """
            if self.analogue:
                self.a_sound_on(secOn, chOn)
            if mode == "none":
                self.seconds_target = 0
                self.realtime_run = False
                self.auto_run = False
            else:
                if mode == "auto":
                    self.realtime_run = False
                    self.auto_run = True
                elif mode == "real":
                    self.auto_run = False
                    self.realtime_run = True
        
        #Sets the analogue sounds for the clock
        def a_sound_on(self, secOn, chOn):
            """
            secOn = Turns the second hand sound on or off
            chOn = Turns the chime sound on or off
            """
            if self.analogue:
                self.second_sound_on = secOn
                self.chime_on = chOn
                if self.second_sound_on:
                    if renpy.music.is_playing(channel='TockBG'):
                        renpy.music.stop(channel='TockBG', fadeout=0.1)
                    renpy.music.play("00 sounds/ClockTick1.flac", channel='TockBG')
                else:
                    renpy.music.stop(channel='TockBG')
                if not self.chime_on:
                    renpy.music.stop(channel='ChimeBG')
        
        #Sets the alarm for the digital clock
        def set_alarm (self, h=0, m=0, on=True):
            fh,fm= self.fix_time(h,m)
            self.alarm_hr = fh
            self.alarm_min = fm
            self.alarm_on = on
            
        #####These functions are used internally, no need to call them yourself
        #Returns a list of all the child displayables for this displayable.
        def visit(self):
            return [self.base_image, self.hour_hand_image, self.minute_hand_image, 
                self.second_hand_image, self.digital_base_image]

        #Runs the clock mechanism automatically
        def autoclock(self, st):
            if self.auto_run:
                self.realtime_run = False
                dt = int(st)
                if self.old_second != dt:
                    self.seconds += 1
                    self.old_second = dt

        #Runs the clock based on the real world time        
        def realclock(self):
            if self.realtime_run:
                self.auto_run = False 
                t = datetime.today()
                self.seconds = (3600 * t.hour) + (60 * t.minute) + t.second
                
        #Determines how many chimes should ring at any given hour and creates audio queue
        def chime_looper(self):
            h, m, s = self.get_time()
            if h is 0:
                h = 12
            elif h > 12:
                h = h%12
            ch = ["00 sounds/ChimePart.ogg",]*(h-1)
            ch.append("00 sounds/Chime1.ogg")
            return ch
        
        #Plays clock chimes on the hour
        def start_chime(self): 
            if self.chime_on:    
                if not self.play_chime:
                    if self.minutes%60 == 0:
                        self.play_chime = True
                        cf = self.chime_looper()
                        renpy.music.queue(cf, channel='ChimeBG')
        
        #Plays the alarm when the time has arrived
        def start_alarm(self): 
            if self.alarm_on:    
                if not self.play_alarm:
                    h, m, s = self.get_time()
                    if self.alarm_hr == h and self.alarm_min == m:
                        self.play_alarm = True
                        if self.analogue:
                            renpy.music.play("00 sounds/BellAlarm.wav", channel='Alarm')
                        else:
                            renpy.music.play("00 sounds/digital-alarm.wav", channel='Alarm')   
                            
        #Make sure the time is within the 0-24hr range
        def fix_time(self, h,m):
            if self.mil_time:
                if m > 59:
                    hadd, m = m%60
                    h += hadd
                if h > 23:
                    h = h%24
            else:
                if m > 59:
                    hadd, m = m%60
                    h += hadd
                if h is 0:
                    h = 12
                elif h > 12:
                    h = h%12
            return h,m
Set up
1) The first this we have is the creation of the style for the digital clock font. This section notes the font used, the color (red), and that is should be center aligned. Since this style is used by the clock, which is initialized at -1, this style should be initialized at -2 so it happens before the clock is made.

2) Next part of the set-up before we get to the clock itself is importing datetime. This is a Python module that allows us to access the real world time.

3) After that I place the images for the clock in a list...I'll provide the images in the file I'm uploading below (the minute hand is a bit off-center...but we'll just call it charming--again, I think you should use Uncle Mugen's analogue clock images). Your analogue clock images should be square, mine are 450x450. The last image in the list is the base image for the digital clock, that image is 460x200...and the program will stretch the digital base image to be the right size, but the closer your own image (if you use your own) is in ratio to mine, the less distortion there will be.

4) Next up I declare the channels that the clock sounds will play on. I declare one for the ticktock sound, one for the Chimes that happen on the hour, and one for the Alarm. Info on declaring audio channels can be found here: http://www.renpy.org/doc/html/audio.html

5) The last thing that happens before we get to the clock itself, is I created a function called clock_audio_pause. What this does is it tries to turn the sound of myClock off. If this isn't possible for some reason, it does nothing. What is this function for? I noticed in my game, that when I went to the save, load, or preferences screen after starting my game, the second hand ticking sound would keep going...and I didn't like that. So I created this method to turn the sound off when the player is in one of those three screens. How do I do that? With this line of code:

Code: Select all

on "show" action Function(clock_audio_pause) 
How it looks in practice? In the screens.rpy file I've added that line to the end of each of those three screens. Here is an example of what it looks like for the save screen:

Code: Select all

screen save:

    # This ensures that any other menu screen is replaced.
    tag menu

    use navigation
    use file_picker
    on "show" action Function(clock_audio_pause) 
If you don't want your sound playing while in those screens, just add that line of code to the save, load, and preferences screens in the screens.rpy file of your project like I wrote above. Note, for this to work, your clock needs to be named myClock. If you named it something else, just make sure that is reflected in the function.

Clock Class
After all of that, I define the Clock class. A class is an object...really just a collection of variables and methods all put in one convenient package. For a regular class, the only method you must have is a constructor...which explains how to make your class...this is usually where you put your variables. For a User Defined Displayable, you must have the constructor method, a render method (which continuously updates the graphics of your displayable), and a visit method (which returns a list of all the displayables in your class. But the clock wouldn't be nearly as fun if that was all we had, so I've added a lot more method.
Note: My understanding is that, a function and a method are basically the same thing, except that technically, if it belongs to a class you should call it a method, and if it doesn't you should call it a function. So clock_audio_pause() is a function because it doesn't belong to any class, and add_time() is a method because it belongs to the clock itself.
Another Note:
Info on User Defined Displayables can be found here:
http://www.renpy.org/doc/html/udd.html
http://www.renpy.org/wiki/renpy/doc/ref ... splayables

[*]Clock Class Constructor
1) First thing you do is announce that you are making a Clock class, because the Clock is a User Defined Displayable, it must extend the Displayable class. That is the first line of code:

Code: Select all

class Clock(renpy.Displayable):
After the class is announced I have to provide the methods that make up the class, the first method that every class must have is the constructor (that is the __init__ method that is the first one there. The constructor is the method that builds a clock when you make a new clock. This particular constructor has the following parameters: (self, ana, h, m, resize, sH, mil_time, **kwargs). Self is always there. ana is a boolean (True/False) that determines if the clock is in Analogue Mode (True) or Digital Mode (False). "h" (for hour) and "m" (for minutes) sets the starting time for the clock. So if you want it to be 3:10, you'd type: 3, 10. After that there is a resize variable that you put in to say how wide you want your clock to be...this will be used to resize the images. Then you have a boolean that determines if the clock should show the second hand or not. The second to last thing in the constructor is a boolean that says whether the clock should be in military time or not. The very last thing in the constructor is **kwargs, which is a sort of a dummy variable that says you might add in some keyword arguments later. You won't. But I'm pretty sure you have to have **kwargs as part of your constructor when you are making a UDD, because the parent class (Displayable) also has **kwargs as part of its constructor.

2) After that in the constructor, you have to call the original (i.e. Displayable class) constructor that the Clock descents from (the "super" line). You just have to do it.

3) Finally, we have all the variables the Clock needs in order to work. The variables are preceded by "self"...because these variables belong to the clock. The first block of variables are all the ones dealing with the images and sizing them. The width of the clock is what you entered for resize (or the default). The d_height will only be used for digital clocks to figure out the perfect height. Through trial and error (and measuring) I determined that for the font I'm using for the digital clock, the height should be 32% of the width. Then I declare all the images taking them from our image list from the set up. And I have to have an offset variable. PyTom explained that when rotating images, Renpy puts that image in a larger box that as a hight and width that is the hypotenuse of the right triangles that make up that square. This means that the minute and hour hands are going to be in the wrong place once they start rotating. So, I worked out how out of place the hands will be and how much the hands would need to be offset to put them back to where they need to be based on how big of a clock you have. That is what the offset is used for. You finish up the section noting if the second hand is visible or not.

4) The next set of variables are to handle the calculation of time. Although when using the clock you enter in hours and minutes, the clock itself runs on seconds when it is in being animated. This allows for smoother animation. So the clock needs to know how many minutes it has and how many seconds it has. Those are the two main variables it uses to keep time. The minutes to deal with the user, the seconds to deal with animation. The other variables in that block are used for record keeping. The is a "second_target" and "step" variables are used to animate adding time to the clock. The "old_second" is used to work out the autorun function. And the "alarm_hr" and "alarm_min" are the variables that hold what the alarm is.

5) The next block of variables just keep track of what mode the clock is in: Analogue or Digital? Auto Run? Realtime Run? Should the clock be moving forward or backward? Should it be in military time or the regular 12hr clock?

6) After that we get the block of variables are for dealing with sound. Should the second hand ticking sound be on? Should the chimes be on? Should the alarm be on?

All of that completes the clock constructor.

[*]Clock Render Method
1) So...when creating a UDD, you must override (i.e make new, specific to your UDD versions) a few methods: The ones to think about are: the constructor method (you need to override so that you have your own thing), the render method (which is what draws the object), the visit method (if your object has children, you much provide this method that returns those children)...and while I suppose you don't have to some some sort of event method, since part of the point of having a UDD is to have it do cool things, you'll want to have some sort of even methods to do those cool things...my events involve turning the clock on and off and switching modes, setting an alarm, doing audio, etc.

2) The render method is the big deal. It is the method that makes your displayable show up.
The basic render method has a few mandatory parts: Any transforms you want to do to the images (this is optional), The render for each sub-object (child) that is a part of the whole displayable (like the second hand is a child of the clock), the main render, then you must add (blit) the children's renders to the main render, tell the method to redraw it self regularly, then return the render are you are done.
But here is the cool thing! The render method is a loop that is constantly refreshing itself. Which means you can put some other things in that method you want continuously checked...like I want the clock to continuously check if it time to ring the alarm, or continuously check if it should go into autorun, etc.

So this render starts with some stuff to make animations work. If the target time is higher than the current time, then it increases the seconds bit by bit until the time goal and the actual goal are the same. Once that happens it resets the target. Then I just want to make sure that the seconds and minute variable never are out of sync.

The next little block is there to make sure that the number of seconds never get out of hand. there are 86400 seconds in a day. If the clock goes on for multiple days, then the number of seconds get really big and there is no need to make the computer work that hard. So, whenever we reach 86400 seconds, I reset back to zero. similarly, if the seconds ever go below 0 (because you are subtracting time) then it adds 86400 so that we are never dealing with negative numbers.

All of our housekeeping out of the way, we get into the big time rendering. What the clock will look like will depend on if the clock is analogue or digital so we move to an if statement. If the clock is analogue, we render the analogue images, if it is digital, we render the digital images.

For the Analogue part, I created a transform that rotates the minute hand .1 degrees for every second in the second variable. The other transform rotates the hour hand .0083 degrees for every second in the second variable. Then you need to make a render for each child that is going to be drawn on the screen. The base screen has no transform, so the first parameter is just the base image, the other two children have transforms on them, so the first parameter is the transform I want them to have. The rest of the parameters are fixed and you have to have them just like they are.

I have a quick block that checks every time there is a new minute if the chimes are on. If there is a new minute, then then render goes and check if it should chime (start_chime() method). After that I make the render, and then blit the children's renders to the clock's renders. I finish up that analogue block with render information for the second hand, which I've put in an if block. So it will only showw up if the second_hand_visible variable is true. Which means you can turn your second hand on or off at will.

That finishes the Analogue Render section and moves into the Digital render section. I get the current time on the clock and create a text version of those numbers (with our without the seconds depending on if they are to be visible). I put each element of the time (hours, minutes, seconds) into its own Fixed box so that the numbers stay centered, and put into an HBox with all the elements evenly spaced out. Then I make a larger Fixed box that holds the digital base and that time text. I put it in a render, make an overall render, and blit it to that render. Done with the Digital specific render section!

Then I do some quick checks that are relevant to the clock regardless of digital/analogue. I check if the clock is in realclock mode or autoclock mode (over and over because the render is a loop...which means you can change the mode and the render will pick up the changes), and check if we hit the alarm.

You need to actually have the clock redraw itself regularly. So this clock redraws itself continuously. and then I return the render...so that the whole drawing process can actually happen.

[*]The methods you will want to mess with
1) get_time returns the time as three numbers: hours, minutes, seconds. It makes sure the numbers it returns are ints. I use the % function to get the remainder to good effect. Note, I also make sure that 00 hours = 12 hours when you aren't using military time, because you can't ring only 0 bells and this isn't a 24 clock.

2) set_time lets you set the time to a specific time by adding hours and minutes. I use a method I made called fix_time, to make sure if that strange numbers entered are translated into normal numbers.

3) add_time lets you add an amount of time to the time you currently have, with our without an animation. If there is an animation, it is the number of seconds you want the clock to take to get to the new time.

4) sub_time lets you subtract an amount of time to the time you currently have, with our without an animation. If there is an animation, it is the number of seconds you want the clock to take to get to the new time, running the clock backward.

5) time_passed is a method where you provide the starting time and it tells you how long has passed since that starting time. It also tracks how many days ago you want to test from. Lastly, you can say if you want the method to return two numbers (hours and minutes) or return one number, just the minutes. If it has been 1hr and 15 minutes since the start time, comb_min False would return 1, 15. comb_min True would return 75.

6) runmode I put in there to make it easier for the user. You just type in the mode you want: auto or real or non, and if you want the second hand sound on and if you want the chimes sounds on. Then method adjusts all the relevant variables for you.

7) a_sound_on lets you decide if you want the second hand sound on or the chimes on, both or neither. This method is here for it you want some fine control of the sounds, so if you want to have one on but not the other, or if you want to adjust one sound specifically.

8 ) set_alarm. Pretty self-explanatory. Sets the alarm and turns the alarm sound on or off.

[*]Methods that are used by other methods, that you probably don't need to worry about and probably won't use.
1) The last mandatory thing I have to have here is the visit method...I never use it...but I have to have it in there. I just returns the four children of the clock class. Don't ask me why. You just have to.

2) autoclock basically adds a second to the second variable every second. How do you know when a second is passed? That is the st variable. Displayable objects have an st variable that measures how long that object has been visible on screen in seconds. So I just use that st variable (that comes from the render), to add a second. Now, there is a trick. The st is all of the seconds the object has been visible and you don't want to add a gazillion seconds, you only want to add one. So I every second I subtract the old second total from the current second total, which gives me 1 (which I put in the dt variable) and I add that do seconds.

3) realclock gets the real world time in hours, minutes, and seconds, turns the whole thing into sends and continuously syncs the clocks seconds with the real world seconds.

4)chime_looper. So this method creates a queue of chime sounds. I have two different chime sounds, the first, "ChimePart" gets cut off and the final one "Chime1" fades out. So, if it is 4 o'clock, I want 3 partial chimes and one final chime. This loop gets the hour variable and makes a the list of sounds and puts in in the cf variable then returns it to whoever called it. Who called it...

5) start_chime. If the clock hits the hour and it isn't already playing chimes, it takes the cf queue of chimes and plays them.

6) start_alarm, which is just like the start_chime, but no looper. Instead, it checks to see which mode we are in. If it is a digital clock it plays a digital alarm sound, if it is analogue it plays a bell alarm.

7) The last method is the fix_time method that translates whatever strange number was given by the user into a more workable hour and minute set of numbers based on if it is a 12hr clock or a 24hr clock.

So...that is the clock class. If you have any questions about how I made it, feel free to ask and I'll answer!

But how do you use it?
5. Using the Clock in your game (if you don't care about learning to program, just skip to here to learn how to use the clock)

So what do you do if you want to use the clock?
If you just want to try out my file, download it, unzip it, and run it. If you want to put it into your own projects:
1) Download the attached Zip file and unzip it.
2) Put the clock.rpy file and the 00 images, 00 fonts, and 00 sounds folders into your projects game file.
3) If you have the chimes and the second hand on for your game, but you don't want them to sound while your are in the save, load, or preferences screen, add this line to the end of each of those screens:

Code: Select all

on "show" action Function(clock_audio_pause) 
4) Make a clock, put it on a screen, show the screen and then give it commands! How do you do that? Check out my script file that shows everything in action.

But first here is a summary of all the commands you'll generally need:

Code: Select all

    $ clock_audio_pause()                       #This pauses the all audio of the clock and should be added on sho to thee load, save, and preferences screens
    $ myClock = Clock(True, 3,0, 150, True,True)#Create a clock. Args: Analogue?(If Flase it is digital), Hours, Minutes, Size of the image, if you want a second hand?, if you want military time?
    $ h, m, s = myClock.get_time()              #Returns the current time's hours, minutes, and seconds
    $ myClock.reset()                           #Sets the seconds and days to 0
    $ myClock.add_time(0, 15)                   #Add 15 minutes to the time. Args: Hours, Minutes
    $ myClock.add_time(1,30, 3)                 #Add 1 hour, 15 minutes, but animate the adding and have it take 3 seconds. Args: Hours, Minues, Seconds it takes for the animation
    $ myClock.sub_time(0, 15)                   #Subtrack 15 minutes to the time. Args: Hours, Minutes
    $ myClock.sub_time(1,30, 3)                 #Subtract 1 hour, 15 minutes, but animate the adding and have it take 3 seconds. Args: Hours, Minues, Seconds it takes for the animation
    $ h,m = myClock.time_passed(2, 15, 1, False)#Returns how many hours and minutes have passed since a given starting time. Args: starting hour, starting minutes, how many days in the past, False for hours and minutes (True--the default--will return minutes only)
    $ m = myClock.time_passed(2, 15)            #Returns how many minutes have passed since a given starting time. Args: starting hour, starting minutes
    $ myClock.runmode('auto', False, False)     #Sets the runtime mode of the clock. Args: Mode ('auto', 'real', or 'none'), Then second hand and chimes sounds (True/False, True is the default, sounds won't play for digital clocks)
    $ myClock.a_sound_on(False, False)          #Turn the clock's second hand and/or chimes sound on/off. Args: (Seconds True/False, Chimes True/False)
    $ myClock.set_alarm(5,30)                   #Set the alarm of your clock for 5:30 (Args: hours, minutes)
    $ myClock.set_alarm(0,0,False)              #Turn your alarm off
And here is my script.rpy file:

Code: Select all

define n = Character(None, color="#c8ffc8")

default myClock = Clock(True, 3, 0, 150, False, False) #Analogue or Digital, hours, minutes, size, second hand, military time

screen clock_screen:
    add myClock:
        xalign 0.5 
        yalign 0.5
        
# The game starts here.
label start: 
    show black

    "Welcome to Trooper6's UDD Clock Extravaganza!"
    show screen clock_screen
    with Dissolve(1)
    
    ###Analogue Clock####
    "So, here is the analogue clock. We are starting with the second hand not showing, the sound off, and military time is off. 
     It is currently 3pm."
    
    ###Setting Time, Adding Time
    $ myClock.a_sound_on(False, True) #second hand sounds, chimes sounds
    $ myClock.set_time(0,45) #hours, minutes
    "But 3pm is too late, I think. Let's set the time to 12:45! I'm also turning on the chimes sound, however, 
     no chimes should ring because we jumped to the time rather than passed over an hour through animation."
    
    $ myClock.add_time(0,15,1) #hours, minutes, how long to animate in seconds 
    "Fifteen minutes pass...with animation, it should take a second and we should hear chimes because we passed an hour."

    $ myClock.a_sound_on(False, False) #second hand sound, chimes sound
    $ myClock.add_time(1,5,5)  
    "Let's forward an hour and five minutes...but let's also turn the chimes sound off, so there should be no chimes. 
     It should take 5 seconds."
    
    "So that is showing how you can turn on the sound of the chimes on and off, and well as how to add time in
     two different ways: by jumping to a time, and by advancing to that time with animation."
    
    ###Time Passed
    "You can check to see how much time has passed with the time_passed function. You have to give it a starting time though."
    $ h, m = myClock.time_passed(0, 30, 0, False) #Starting hours, starting minutes, how may days ago, combined minutes?
    $ n("So how much time has passed since 12:30? {0} {hour} and {1} minutes.".format(h, m, hour="hours" if h>1 else "hour")) #This is written this way so I can use a conditional on whether to use the word 'hour' or 'hours'
    $ tp = myClock.time_passed(5,0,1)
    "So how many minutes have passed since yesterday 5am? [tp] minutes."
    
    ###Subtracting Time
    $ myClock.a_sound_on(False, True)
    "Let's test the subtracting time function, with the chimes on. Let's turn back time 30 minutes. 
     Maybe you want time travel shenanigans!"
    $myClock.sub_time(0,30) #hours, minutes
    "You should see time jumped backwards"
    
    $myClock.sub_time(1, 5, 3) #hours, minutes, how long to animage in seconds
    "Let's try it with subtracting time animation style. The chimes should ring."
    
    ###Getting Time
    "Now, how do you know what time it is? Just ask!"
    $h,m,s = myClock.get_time() #This will return three numbersof the current time: hours, minutes, seconds
    "It is [h]:[m]:[s]"
    
    $ myClock.mil_time = True #Here I'm setting the military time to true
    $myClock.set_time(13,15)
    "Just showing that this can be done with military time as well."  
    
    ###Fun with Alarms
    $ myClock.set_alarm(13,30) #The alarm time
    "Let's set an alarm on the clock, since we are still in military time, let's...say 13:30."
    $ myClock.add_time(0,40,2)
    "Now let's forward through time to trigger the alarm." 
    
    ###Auto Mode

    $ myClock.mil_time =False
    $ myClock.set_time(1,59)
    $ myClock.second_hand_visible = True
    $ myClock.runmode("auto") # mode, you can also add True/False for second hand audio and True/False for chimes audio
    "Okay, that was fun. Let's look at the two other different run modes, 'auto' and 'real,' 
     starting with auto. First let's turn off the military time and the alarm, then let's set 
     the time to 1:59 and then make the second hand visible. Then let's start the clock on 
     auto run. By default the second hand and chime sounds are turned on if the clock is analogue. 
     If you wait a short bit, you should hear chimes when the clock turns 2:00."
    
    $ myClock.add_time(0,10,2) 
    "Ten minutes pass...showing you can add time even when in the auto mode."    
    
    $ myClock.sub_time(0,10,2)
    "And now I'm subtracting time in auto mode."
    
    ###Real Mode
    "So...are you ready to look at the next mode?"
    
    $ myClock.second_hand_visible = False
    $ myClock.runmode("real")
    "So I'm switching from the autoclock mode to the realtime clock mode. But let's also take away the second 
     hand image while keeping the second hand sound going."
    
    $ myClock.second_hand_visible = True
    "So the real time clock is running now, but now with more second hand action!"
    
    $ myClock.add_time(0,10)
    $ myClock.add_time(0,10,3)
    "Ten minutes should not pass here...I tried to add some time, but that won't work in the real time mode."
    
    $ myClock.sub_time(0,10)
    $ myClock.sub_time(0,10,3)
    "Ten minutes should not be subtracted here...I tried to subtract some time, but that won't work in the real time mode."
    
    
    $ myClock.runmode("none")
        
    "Okay...I just turned off the fancy modes. Notice that sound is still happening. 
     You may not like that so just type '$ myClock.runmode('none', False, False)' instead 
     to turn off all the sound."
    $ myClock.runmode("none", False, False) #mode, second hand sound, chimes sound
    
    
    ###Digital Clock###
    $ myClock.analogue = False
    $ myClock.mil_time = True
    $ myClock.set_time(0,0)
    $ myClock.second_hand_visible = False
    "Now let's switch over to Digital mode--because this clock can be digital or analogue!
     Let's start by setting everything back to zero and making sure we are in military time."
    $ myClock.set_time(0,45)
    $ myClock.set_alarm(1,0)
    "Here is the digital clock set to 00:45, but I've turned off the visibility of the seconds ...since most digital 
     clocks don't actually show seconds. I am also setting the alarm for 1 o'clock."
    
    "A quick note, the digital version doesn't have the chimes or the second hand ticking sound available...
     because digital clocks don't make old school ticking and chiming sounds."
     
    $ myClock.add_time(0,5,1)    
    "Five minutes pass...with animation, it should take a second."

    $ myClock.add_time(0,10)
    "We jump ten minutes with no animation...we should hear the alarm go off."
    
    $ myClock.add_time(1,0,3)    
    "Let's forward the clock an hour...It should take 3 seconds."
    
    $ myClock.sub_time(0, 20, 2)
    "Now let's subtrack some time."
    
    $ tp = myClock.time_passed(0,45)
    "By the way, how much time has passed since our starting time of 00:45? [tp] minutes!"
     
    $ myClock.set_time(12,59)
    $ myClock.set_alarm(13,0)
    $ myClock.second_hand_visible = True
    $ myClock.runmode("auto")
    "Okay, that was fun, but let's start starting at 12:59 and including visible seconds. If you wait a short 
     bit, you should hear the alarm."
    
    $ myClock.set_time(23,59)
    $ myClock.set_alarm(0,0,False)
    "Okay, that was fun, let's do it again with alarm off so no alarm should ring."
    
    $ myClock.add_time(0,10,2) 
    
    "Ten minutes pass...in auto run mode."  
    "I just wanted to make a quick note: Rollback/Saving will work in all modes but real time mode...
     because in that mode the clock is always running off of real world time!"
    
    "And now we are going to realtime mode."
    $ myClock.second_hand_visible = False
    $ myClock.runmode("real")
    "Here we are in the real time clock function. But let's also take away the seconds."
    
    $ myClock.second_hand_visible = True
    "So the real time clock is running now, but now with more second hand action!"
    
    $ myClock.add_time(0,10)
    $ myClock.add_time(0,10,3)
    "Ten minutes should not pass here...because we are connected to real time."
    
    $ myClock.sub_time(0,10)
    $ myClock.sub_time(0,10,3)
    "Ten minutes should not be subtracted here...because we are connected to real time."
    
    $ myClock.runmode("none", False)
    
    "Now I've turned off the clock."
    "Well, that is the entire clock tutorial. I hope it is useful to you!"
    
    "Bye!"
    
    hide screen clock_screen
    with Dissolve(1)

    return
I think the script file is all very self explanatory...but I do want to point out that since it takes a second to update the images, I find it is a bit better to make the commands before the text that describes what you are doing.

I hope this was useful to new folks from a new folk! If any of you veterans have things you want to add, please feel free!

I'm uploading the files below, in the form of a Ren'Py project for your perusal!--it includes my sort of sad image files. Enjoy!

Final Note: In case you didn't know it, Renpy is based off of Python 2.7, so if you want to look up Python tutorials on the web, that is the version to go with. The official Python documentation is here in case you want to explore: http://docs.python.org/2/index.html
Attachments
Trooper6_ClockCDD_ExampleU6Com.zip
(2.72 MiB) Downloaded 925 times
Last edited by trooper6 on Sun Jun 19, 2016 1:03 am, edited 15 times in total.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

User avatar
sapiboonggames
Veteran
Posts: 299
Joined: Thu Jan 05, 2012 8:53 am
Completed: I Love You, Brother [BxB]
Contact:

Re: Tutorial & Code: Analog Clock as User Defined Displayabl

#2 Post by sapiboonggames »

Thank you so much for putting this for us!
I didn't think of implementing analog clock to my game, but now that I've seen this I would certainly find an excuse to! :D

I have a question though, how do we make the auto mode runs faster?
Visit my website: http://www.sapiboong.com

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Tutorial & Code: Analog Clock as User Defined Displayabl

#3 Post by trooper6 »

sapiboonggames wrote:Thank you so much for putting this for us!
I have a question though, how do we make the auto mode runs faster?
Your welcome! I'm trying to get better at Ren'py...and it isn't always easy becauese quite a few examples out there are old pre-ATL code, and I want to try and be up to date. And while I learn, I want to share!

Okay, on to your question. How to make the auto mode run faster?

There are a couble different ways of doing it. Though you could change how many degrees the hands change with each minute passing...I don't think that is a good idea. I'd say the key is going directly to the autorun method:

Code: Select all

            if self.auto_run:
                self.realtime_run = False
                mbase = st
                mcheck = st-1
                add = (mbase-mcheck)/60 
                self.minutes += add
Now I'm not at home right now so I can't do anything really involved or test it, but I can give a bit of an idea about my thinking there.

I see two ways of doing what you want. The quick and dirty method (which I will tell you), and the long method (which I'll just described because I can't get into the coding where I'm at right now).

The quick and dirty way is to change the speed manually in the class. If you know you want the clock to run x-speed, you just change the speed and save the file and you are done. The drawback is that the quick method doesn't allow you to change the speed during the game, to change it back, etc. That would involve a bit more coding.

But let me outline the quick method.

So, because of the redraw (self, 1), lower down in the clock class, the clock redraws itself every second. This is important to note for a bit later.

Every second the class figures out how many seconds it has been since the game started and puts that number in the mbase variable (mbase = st), then makes an mcheck variable equal st-1 (mcheck = st-1). This line here will be your key: add = (mbase-mcheck)/60
What this does briefly is assigns a number to the add variable and then because of the line self.minutes += add, it adds that number to the minutes variable...which is what the clock uses to determine what time to draw.

What the line does specifically is subtract mcheck from mbase...which is going to result in a 1. So every second you get a "1"--you don't want to just at that number to the add variable, because then it will add a minute every second. So that is why I divided the number (1), by 60. So that means it takes 60 seconds (a minute) to actually get a full minute added to the minute variable.

So, if you want the clock run faster, change how much you divide by. For example, if you want the clock to run twice as fast, where you get a new minute every 30 seconds you would change that line to:
add = (mbase-mcheck)/30. If you wanted it to run 5 times as fast, then you'd write the line: add = (mbase-mcheck)/12.

So that is the quick way.

Now, if you wanted to the clock to have maximum flexibility this is what I'd do.
1) I'd add a new variable to clock class that I declare with the other variables.
speed = 60

2) I'd change that important line to read:
add = (mbase-mcheck)/self.speed

3) Then I'd create a method that allows the programmer to change the speed. Something like:

Code: Select all

        def change_speed (self, factor):
            self.speed = 60/factor
This should (although remember, I'm not at home, so I can't test it), allow you to change the speed during the game by entering in a number of how much faster you want the clock to go.

Something like:

Code: Select all

"The clock is running normally."
$ myClock.change_speed(2)
"Now the clock is running twice as fast as normal."
$ myClock.change_speed(5)
"Now the clock is running 5 times as fast as normal."
$ myClock.change_speed(.5)
"Now the clock is running twice as slow as normal."
$ myClock.change_speed(1)
"Now the clock is running at normal speed."
This should work...but, again, I can't test it until I get home later tonight.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

User avatar
kuri_chii
Regular
Posts: 69
Joined: Sat Apr 27, 2013 10:34 am
Completed: RHWIMY beta 1.0
Projects: Right here where I met you, Love Snatch, Monarchy High
Organization: VND - AWA
IRC Nick: Kuri
Deviantart: franzzzz002
Skype: franz.mole
Location: Somewhere in Asia
Contact:

Re: Tutorial & Code: Analog Clock as User Defined Displayabl

#4 Post by kuri_chii »

arigatou!! it really helps :3
The best way showing your feelings is to write Novel Stories :3
Image
DeviantArt | Monarchy High | RHWIMY | Love Snatch

User avatar
Watsonia
Newbie
Posts: 10
Joined: Sat Feb 22, 2014 10:02 am
Skype: watsoniaaenor
Location: EU
Contact:

Re: Tutorial & Code: Analog Clock as User Defined Displayabl

#5 Post by Watsonia »

This is very useful code. Thanks so much indeed for the very kind explanation, and for putting the best of what's out there into a convenient package.

It took me a while to realise that there is a 24 hour clock behind the 12 hour clock face. Therefore, one may call a specific event at a certain time AM or PM e.g: '6,00' vs '18,00'

I also created some code myself to spawn the clock hands at a position 30 mins prior to a desired set_time, and then decelerate the clock hands from there. It gives a nice 'fast forward' effect to simulate time passing, without taking too much of the player's time to display.

Code: Select all

#Spin the clock hands until a time is met
        def clockspin(self, h, m):
            rem = ((h*60)+m)-30
            decel = ((h*60)+m)-4
            self.minutes = rem
            while self.minutes <= decel:
                self.minutes += 1
                renpy.pause(0.05)
            while self.minutes <= (h*60)+m:
                self.minutes += 1
                renpy.pause(0.50)
To use it, just paste it at the bottom of clock.rpy, and call the same way as the set_time function, e.g.

Code: Select all

    $ myClock.clockspin(10,00)
Thanks so much indeed!

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#6 Post by trooper6 »

I'm just posting here to say that I've updated the clock!

Thanks so much to Xela for all their help and inspiration. Here is the thread were we hashed out quite a lot about the clock...and it includes Xela's take on this CDD clock:

http://lemmasoft.renai.us/forums/viewto ... =8&t=29605
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

User avatar
octacon100
Regular
Posts: 163
Joined: Thu Sep 12, 2013 11:23 pm
Projects: Regeria Hope
Organization: Golden Game Barn
IRC Nick: Octacon100
Location: Boston, MA
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#7 Post by octacon100 »

Thanks very much for this! I've been able to modify this to make a nice digital countdown timer, which should hopefully speed up my game, as my old timer seemed to cause the renpy image prediction to go haywire and reload the same image over and over again. Going to see if using a udd makes everything work better. Mind if I put that modified code up somewhere as a cookbook after I tidy my changes up?

Also, if other people want to have their save game keep the seconds, minutes and hours on their clock, after you've created your "myClock" variable to make your screen, create/overwrite that "myClock" variable somewhere in your story script. Renpy will then "pickle" those variables.
Image
Current Digital Projects -
Image
Regiera Hope Completed Game Forum Post

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#8 Post by trooper6 »

Certainly! Do what ever you like!

I do plan on updating the code for 6.99 as soon as I get some more free time. For example the pickling issue will be fixed by declaring it with default.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#9 Post by DragoonHP »

Hey trooper. Thank you for this amazing entry. I've been using your code heavily in one of the projects I'm working on.

But there are two things that you might want to fix:
* 0.0083 should be 0.008333. Since 0.0083*24*3600 equals to 717.12 so after every 24 hours, the hour hand will be out of sync.
* You should reset self.seconds to zero on 00:00, so less intensive calculations have to be done. Something like this:

Code: Select all

            # Calculating how many seconds have passed today.
            if self.seconds >= 86400:
                self.seconds -= 86400

                if self.seconds_target:
                    self.seconds_target -= 86400

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#10 Post by trooper6 »

DragoonHP wrote:Hey trooper. Thank you for this amazing entry. I've been using your code heavily in one of the projects I'm working on.

But there are two things that you might want to fix:
* 0.0083 should be 0.008333. Since 0.0083*24*3600 equals to 717.12 so after every 24 hours, the hour hand will be out of sync.
* You should reset self.seconds to zero on 00:00, so less intensive calculations have to be done. Something like this:

Code: Select all

            # Calculating how many seconds have passed today.
            if self.seconds >= 86400:
                self.seconds -= 86400

                if self.seconds_target:
                    self.seconds_target -= 86400
DragoonHP, thanks for the input! And I'm so excited that the clock code has been useful to you! I'll incorporate your feedback into the quick polish/update of the clock which I plan on doing over this Spring Break. But I have two questions.

First, the code to simplify the seconds, where did you put that new code snippet? Did you put it in the render right before "if self.analogue"? It seems like the best place to put it, but I could also see it being put in the autoclock function.

Second, this is specific to the game that I'm using the clock for, but I created a method that checks how much time has passed since a time you specify. I think it is quite useful, will probably add it to my update, and this is what it looks like:

Code: Select all

        def time_passed(self, h=0, m=0, comb_min=True):
            """
            h, m are the hours and minutes that make up the starting time you
            want to test against
            comb_min, if true it returns just the total minutes, if False returns hours and mintues
            """
            oldnum = (h*60)+m
            nh, nm, ns = self.get_time()
            newnum = (nh*60)+nm
            minpassed = newnum-oldnum
            tph, thm = divmod(minpassed, 60)
            if comb_min:
                return minpassed
            else:
                return tph, tpm
Will your seconds simplification code mess with the time_passed function? I feel like it might if the time passed is more than a day, no? Perhaps I should create a day variable? What do you think?
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#11 Post by DragoonHP »

First, the code to simplify the seconds, where did you put that new code snippet? Did you put it in the render right before "if self.analogue"? It seems like the best place to put it, but I could also see it being put in the autoclock function.
I put it just above self.analogue.
Will your seconds simplification code mess with the time_passed function? I feel like it might if the time passed is more than a day, no? Perhaps I should create a day variable? What do you think?
Yes it will.
But correct me if I'm wrong, wouldn't your time_passed method return values under 24 hour. Since get_time method returns time in 24 hour format.

And well I personally use Xela's Calendar to keep track of game days (by calling calendar.next() every time the clock strikes 00:00), you can keep track of it by creating a day variable, self.game_day and incrementing it at 00:00.

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#12 Post by trooper6 »

DragoonHP wrote:
First, the code to simplify the seconds, where did you put that new code snippet? Did you put it in the render right before "if self.analogue"? It seems like the best place to put it, but I could also see it being put in the autoclock function.
I put it just above self.analogue.
Will your seconds simplification code mess with the time_passed function? I feel like it might if the time passed is more than a day, no? Perhaps I should create a day variable? What do you think?
Yes it will.
But correct me if I'm wrong, wouldn't your time_passed method return values under 24 hour. Since get_time method returns time in 24 hour format.

And well I personally use Xela's Calendar to keep track of game days (by calling calendar.next() every time the clock strikes 00:00), you can keep track of it by creating a day variable, self.game_day and incrementing it at 00:00.
I just had a burst of messing with the code. Including adding the ability to subtract time....for those that need that sort of thing. Good times! I'm going to mess a bit more with the time_passed function. For my game it works as it is because the game is only 15 or so minutes long, but I want to make the function robust for other people (this is why I added a digital clock when I don't need one for my game). So I'm going to noodle with that a bit more.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#13 Post by DragoonHP »

Another thing I found

Code: Select all

        if not self.second_targets:
            self.realclock()
            self.autoclock(st)
This ensure that when we are animating the clock via add_time, extra time isn't added. It isn't apparent in small addition but you will start noticing deviation in bigger jumps. (For a 12 hour jumps (taking place in 3 seconds), five (extra) minutes were added to the clock).

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Di

#14 Post by trooper6 »

Just posted an update to the clock code, including DragoonHP fixes and some new features. Hope everyone finds that helpful!
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

glithch
Regular
Posts: 93
Joined: Sat Jul 23, 2016 5:32 am
Contact:

Re: Tutorial & Code: Analog/Digital Clock as User Defined Displa

#15 Post by glithch »

hi i wanted to ask about the horizontal stretching out of the digital clock? why does it happen? can i just make it stop?

Post Reply

Who is online

Users browsing this forum: No registered users