[ Jocelyn Ireson-Paine's Home Page | Ducks animation as an OGV video | Ducks animation as an animated GIF | Contact ]
I based the program below on the Pyret flight lander game. However, I've chosen variable names that I think easier to understand. I've also extended the world state so that it contains some other types of information, and I've animated one of my cartoons, to show that there's nothing special about the images used in animations. Copy the code below and paste it into the online Pyret editor, and you should be able to run it. You'll need to click the "Start Coding" button on the above page. There's a nice explanation of how Pyret worlds and animations work in http://www.bootstrapworld.org/materials/spring2016/courses/bs2/units/unit4/index.html, "Unit 4: Welcome to the World".
The top of this page links to a video of an animation, shown both as a .ogv file and an animated GIF. To make the .ogv file, I used RecordMyDesktop as described in its user guide, delineating Pyret's "Big Bang" window (which is where it shows animations) as the region to be videoed. I then converted the .ogv file to JPEGs and then an animated GIF as described in the AskUbuntu thread "How to create animated GIF images of a screencast?", using these commands:
mplayer -ao null ducks.ogv -vo jpeg:outdir=output convert output/* -layers Optimize output.gif
Here, finally, is my Pyret program:
import image as I import world as W # # These import the libraries that # provide operations for handling # images and worlds. See # https://www.pyret.org/docs/latest/Tutorial__A_Flight_Lander_Game.html#%28part._.Preliminaries%29 . DUCK1-URL = "http://www.j-paine.org/pyret/duck1.png" DUCK1 = I.image-url( DUCK1-URL ) # # The URL of our picture of duck 1, # and the picture itself. I've taken this # and the other pictures (apart from the # "GULP!" caption) from # http://www.jocelyns-cartoons.co.uk/cartoons2/duck_and_orange.html . DUCK2-URL = "http://www.j-paine.org/pyret/duck2.png" DUCK2 = I.image-url( DUCK2-URL ) # # The URL of our picture of duck 2, # and the picture itself. ORANGE-ON-BRANCH-URL = "http://www.j-paine.org/pyret/orange.png" ORANGE-ON-BRANCH = I.image-url( ORANGE-ON-BRANCH-URL ) # # The orange on a branch. EATEN-ORANGE-ON-BRANCH-URL = "http://www.j-paine.org/pyret/eaten_orange.png" EATEN-ORANGE-ON-BRANCH = I.image-url( EATEN-ORANGE-ON-BRANCH-URL ) # # The branch once the orange has been eaten. GULP-URL = "http://www.j-paine.org/pyret/gulp.png" GULP = I.image-url( GULP-URL ) # # The "GULP!" caption. BACKGROUND-URL = "http://www.j-paine.org/pyret/background.png" BACKGROUND = I.image-url( BACKGROUND-URL ) # # The background, consisting of two islands in a # pond. DUCK1-X-INIT = 220 DUCK1-Y-INIT = 280 # # Duck 1's initial coordinates. These are the coordinates # of the centre of the rectangle enclosing the duck # in http://www.jocelyns-cartoons.co.uk/cartoons2/duck_and_orange.html . DUCK1-X-CHANGE = 0.3 DUCK1-Y-CHANGE = -0.3 # # How much duck 1 moves to the right # and up on every clock tick. DUCK2-X-INIT = 515 DUCK2-Y-INIT = 289 # # Duck 2's initial coordinates. ORANGE-X = 387 ORANGE-Y = 104 # # The coordinates of the orange. EATEN-ORANGE-X = 392 EATEN-ORANGE-Y = 90 # # The coordinates of the eaten orange. GULP-X = 387 GULP-Y = 104 # # The coordinates of the "GULP!" caption. data WorldState: | world-state( duck1-x, duck1-y, duck1-time-since-starts-eating, duck1-is-gulping, duck2-x, duck2-y, orange-is-there ) end # # The world state. This contains: duck 1's coordinates; # the time in ticks since it starts eating the # orange, or the string "N/A" if it hasn't yet; # whether it is saying "GULP!"; duck 2's # coordinates; and whether the orange is there # (i.e. hasn't been eaten). fun numbers-are-near( x1, x2 ): delta = x1 - x2 num-abs( delta ) < 100 end # # An auxiliary function called by 'is-near'. Returns # true if the difference between x1 and x2 is less # than 100. fun is-near( x1, y1, x2, y2 ): numbers-are-near( x1, y1 ) and numbers-are-near( y1, y2 ) end # # Called to determine whether the duck is near the # orange. Returns true if its x and y coordinates # are both less than 100 from the orange's. fun duck1-is-at-orange( ws ): is-near( ws.duck1-x, ws.duck1-y, ORANGE-X, ORANGE-Y ) end # # Given the world state, returns true if duck1 # is roughly at the same position as the orange, # false otherwise. fun new-world-state( ws ): starts-eating = ws.orange-is-there and duck1-is-at-orange( ws ) orange-still-there = ws.orange-is-there and not( starts-eating ) time-since-starts-eating = if starts-eating: 0 else if ws.duck1-time-since-starts-eating == "N/A": "N/A" else: ws.duck1-time-since-starts-eating + 1 end gulping = not( time-since-starts-eating == "N/A" ) and ( time-since-starts-eating < 200 ) world-state( ws.duck1-x + DUCK1-X-CHANGE, ws.duck1-y + DUCK1-Y-CHANGE, time-since-starts-eating, gulping, ws.duck2-x, ws.duck2-y, orange-still-there ) end # # Given the world state on one # tick, returns it updated. Updates are to: # the position of the ducks (in this version, # duck 2 doesn't move); the time since # duck 1 started eating; whether it is # saying "GULP!"; and whether the orange # is still there. fun draw-world-state( ws ): background-with-duck1 = I.place-image( DUCK1, ws.duck1-x, ws.duck1-y, BACKGROUND ) background-with-ducks = I.place-image( DUCK2, ws.duck2-x, ws.duck2-y, background-with-duck1 ) background-with-ducks-and-orange = if ws.orange-is-there: I.place-image( ORANGE-ON-BRANCH, ORANGE-X, ORANGE-Y, background-with-ducks ) else: I.place-image( EATEN-ORANGE-ON-BRANCH, EATEN-ORANGE-X, EATEN-ORANGE-Y, background-with-ducks ) end background-with-ducks-and-orange-and-gulp = if ws.duck1-is-gulping: I.place-image( GULP, GULP-X, GULP-Y, background-with-ducks-and-orange ) else: background-with-ducks-and-orange end I.scale( 0.50, background-with-ducks-and-orange-and-gulp ) end # # Given the world state, returns a # picture of it. This entails: placing the ducks in their # correct positions; placing either the orange or the # eaten orange; and placing the "GULP!" if duck 1 is # saying it. W.big-bang( world-state(DUCK1-X-INIT,DUCK1-Y-INIT,"N/A", false, DUCK2-X-INIT,DUCK2-Y-INIT,true), [list: W.on-tick( new-world-state ), W.to-draw( draw-world-state ) ] ) # # Makes an animation. Initially, this # makes a world state which is 0. Then # on every clock tick, it makes a new # world state by calling new-world-state # on the old one. It also calls # draw-world-state to # convert the state to an image and # make it the next frame in our # animation. See # https://www.pyret.org/docs/latest/Tutorial__A_Flight_Lander_Game.html#%28part._.Observing_.Time__and_.Combining_the_.Pieces_%29 .