diff --git a/MP4 Project Proposal.pdf b/MP4 Project Proposal.pdf new file mode 100644 index 00000000..b7859284 Binary files /dev/null and b/MP4 Project Proposal.pdf differ diff --git a/README.md b/README.md index 5f822327..e0788d8f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ -# InteractiveProgramming -This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. +This is the repo for the Live Wallpaper Program. Created for the fourth Mini-Project for Software Design, Spring 2018 at Olin College. + +All art is under copyright by Hwei-Shin Harriman and is not for distribution except with express permission from the artist. + +Link to Project Reflection: https://github.com/hsharriman/InteractiveProgramming/tree/master/Writeup + +Before running, install PyGame from terminal: +$ apt-get build-dep python-pygame +$ apt-get install mercurial python-dev python-numpy ffmpeg libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsmpeg-dev libsdl1.2-dev libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev +$ pip install pygame + +To run the program: +navigate to the InteractiveProgramming folder on your computer and type: +$ python main +into the command line + +To spawn hot-air balloons, click anywhere in the sky. +To spawn a cactus flower, click on the big cactus. +To exit the program, click the 'x' in the upper left-hand corner. diff --git a/Scrolling.ipynb b/Scrolling.ipynb new file mode 100644 index 00000000..125533e9 --- /dev/null +++ b/Scrolling.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "import pygame\n", + "import PIL\n", + "\n", + "class Main:\n", + "\n", + "\n", + " def __init__(self, width=640,height=480):\n", + " pygame.init()\n", + " self.width = width\n", + " self.height = height\n", + " self.screen = pygame.display.set_mode((self.width, self.height))\n", + "\n", + " def MainLoop(self):\n", + " while 1:\n", + " for event in pygame.event.get():\n", + " if event.type == pygame.QUIT:\n", + " sys.exit()\n", + "\n", + "\n", + "\n", + "class Background():\n", + " def __init__(self):\n", + " self.bgimage = pygame.image.load('images/backgroundtest.png')\n", + " self.bgX1 = 0\n", + "\n", + " self.bgY2 = self.bgimage.get_height()\n", + " self.bgX2 = 0\n", + "\n", + " self.movingUpSpeed = 2\n", + "\n", + " def update(self):\n", + " self.bgY1 -= self.movingUpSpeed\n", + " self.bgY2 -= self.movingUpSpeed\n", + " if self.bgY1 <= -self.rectBGimg.height:\n", + " self.bgY1 = self.rectBGimg.height\n", + " if self.bgY2 <= -self.rectBGimg.height:\n", + " self.bgY2 = self.rectBGimg.height\n", + "\n", + " def render(self):\n", + " surface.blit(self.bgimage, (self.bgX1, self.bgY1))\n", + " surface.blit(self.bgimage, (self.bgX2, self.bgY2))\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " MainWindow = Main()\n", + " MainWindow.MainLoop()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Writeup/UML.pdf b/Writeup/UML.pdf new file mode 100644 index 00000000..b419cd13 Binary files /dev/null and b/Writeup/UML.pdf differ diff --git a/Writeup/Writeup.md b/Writeup/Writeup.md new file mode 100644 index 00000000..7862e585 --- /dev/null +++ b/Writeup/Writeup.md @@ -0,0 +1,17 @@ +# Interactive Live Wallpaper +#### Hwei-Shin Harriman and Jessie Potter +### Project Overview + When the project was introduced, both of us were attracted to the idea of coding interactive art. We decided to pursue a project aimed to create an interactive scrolling background, much like the live wall-screens of Android products. We did this using art by Hwei-Shin, which we transformed into a background, platforms, and sprites to form our live background. + +### Results + Our results were a scrolling background that continuously moved through our background image, seamlessly connecting the end and beginning of the image while looping through. Platform clouds floated at varying speeds and directions above the background. There were also a variety of other sprites, such as hot air balloons, flowers, and cacti that added depth to our scenery. + +### Implementation + We implemented our program using the Pygame software. We started with a SpriteSheet class, which would take sprite data we stored in a separate "constants" script, and get the image associated with each sprite. Then, we created a Sprite class, which used SpriteSheet to extract any sprite whose data we passed into the argument and also defined its own version of draw. The next classes, Balloon, BlackCloud, PurpleCloud, SmallCloud, Cactus, and Flower, were all derived classes of the Sprite Class. Each one contains its own update method, specific to the behavior of the sprite it is representing. This allowed us to avoid defining pygame group objects for every type of sprite we used, and allowed us to write a more general update function for all of the sprites later on. + + We then used a superclass Scene to define the background, the main update, draw, and shift_world methods, as well as the the spawn balloon and flower methods. We chose to avoid using the pygame group classes all together in favor of writing our own update functions and derived classes to avoid needless repetition of very similar functions for each group class. This means that we chose to avoid using any of the pygame group methods like .add(), .update(), .draw(), or .kill(), but it made the code much more readable and accessible in the long run. We used Summer as a subclass to generate all of our sprites and sort them by type by calling the appropriate derived class that we had defined above. + + To run the code, we created a third script that contained our main() function. This function references all of the class objects from livebackground.py. It sets up the environment so that the program can run, keeps track of time steps, tracks user interaction, and tells the program when to update the screen. + +### Reflection + Reflecting back on our project experience, there were definitely things that worked better than others. Our actual ideation process went really well, as we were both passionate about similar topics. We also did pivot our project at a reasonable time, because initially we wanted to work with hardware to create a lighting display. The project was fairly well scoped for the time period, but it would've been a lot easier to manage if we did not lose that first weekend. Both teammates did put quite a bit of time into the project, however Hwei-Shin was far more successful. While Jessie put a lot of hours into the project and worked with ninjas, she struggled and was not able to contribute as much. Jessie was also gone for the main weekend of the project, which made it hard to pursue our initial plan of Paired Programming throughout this project. Additionally, there was an issue with poor communication and proactivity throughout important times in the week, which resulted in the entire workload getting pushed back to the last couple of days. Addressing this issue proved to be complicated, because both of us had to set aside our learning goals for the sake of finishing the project in time. Thus, going forward, we would definitely work in the Paired Programming style more often and arrange more regular meeting times/check-ins. We would also try to implement more unit testing throughout our work, and also work towards making more interactive sprites. diff --git a/Writeup/wallpaper.png b/Writeup/wallpaper.png new file mode 100755 index 00000000..9af9e636 Binary files /dev/null and b/Writeup/wallpaper.png differ diff --git a/bigcactus.png b/bigcactus.png new file mode 100755 index 00000000..3b6f9924 Binary files /dev/null and b/bigcactus.png differ diff --git a/blackclouds.png b/blackclouds.png new file mode 100755 index 00000000..c7d19cf0 Binary files /dev/null and b/blackclouds.png differ diff --git a/comets.png b/comets.png new file mode 100755 index 00000000..c3070e4a Binary files /dev/null and b/comets.png differ diff --git a/constants.py b/constants.py new file mode 100644 index 00000000..c7e01477 --- /dev/null +++ b/constants.py @@ -0,0 +1,75 @@ +""" +Global constants +""" +#Colors +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) + +#Screen dimensions +SCREEN_WIDTH = 1920 +SCREEN_HEIGHT = 1080 + +#------------PIXEL POSITION, WIDTH, HEIGHT OF ALL SPRITES----------------- + +#small clouds +SMALL1 = (0, 0, 399, 113) +SMALL2 = (0, 113, 334, 178) +SMALL3 = (9, 291, 561, 128) +SMALL4 = (0, 419, 582, 168) + +#purple clouds +PURP1 = (0, 0, 1145, 577) +PURP2 = (1145, 0, 1008, 277) +PURP3 = (0, 577, 877, 280) +PURP4 = (877, 540, 977, 413) + +#black clouds +BLK1 = (0, 0, 1197, 345) +BLK2 = (1197, 0, 1992, 248) +BLK3 = (0, 345, 908, 359) +BLK4 = (908, 245, 971, 363) + +#Hot-air balloons +BIG1 = (0, 0, 127, 180) +BIG2 = (0, 180, 164, 201) +BIG3 = (0, 381, 199, 239) +BIG4 = (56, 620, 138, 194) +BIG5 = (164, 0, 215, 276) + +SMOL1 = (0, 620, 56, 57) +SMOL2 = (0, 677, 43, 52) +SMOL3 = (0, 729, 56, 73) +SMOL4 = (164, 276, 69, 103) +SMOL5 = (233, 276, 70, 109) +SMOL6 = (199, 385, 70, 81) + +#comets +SHORT1 = (91, 0, 38, 78) +SHORT2 = (91, 94, 78, 50) +SHORT3 = (175, 0, 91, 32) +LONG1 = (0, 0, 68, 187) +LONG2 = (0, 187, 182, 175) +LONG3 = (0, 362, 350, 148) + +#big cactus +CACTUS = (0, 0, 363, 495) +#[CACTUS, 3012, 489] +FLOWER = (0, 0, 82, 105) + +#--------------------SPRITE LISTS---------------------- +summer = [[BLK1, 1739, 18], + [BLK2, 45, 46], + [BLK3, 2538, 32], + [BLK4, 1046, -30], + [PURP1, 862, -15], + [PURP2, 196, 260], + [PURP3, 1188, 300], + [PURP4, 2773, 139], + [SMALL1, 429, 390], + [SMALL2, 3488, 375], + [SMALL3, 1911, 400], + [SMALL4, 1301, 420], + [CACTUS, 2958, 497], + [FLOWER, 2971, 728]] + +balloons = [BIG1, BIG2, BIG3, BIG4, BIG5, SMOL1, SMOL2, SMOL3, SMOL4, SMOL5, SMOL6] diff --git a/flower.png b/flower.png new file mode 100755 index 00000000..8040912e Binary files /dev/null and b/flower.png differ diff --git a/hotairballoons.png b/hotairballoons.png new file mode 100755 index 00000000..37f23a85 Binary files /dev/null and b/hotairballoons.png differ diff --git a/livebackground.py b/livebackground.py new file mode 100644 index 00000000..bb8262b8 --- /dev/null +++ b/livebackground.py @@ -0,0 +1,245 @@ +"""Interactive Programming Mini-Project 4: Live Wallpaper + +Authors: Hwei-Shin Harriman and Jessie Potter +References: http://programarcadegames.com/python_examples/en/sprite_sheets/""" +import pygame +import constants +import random +import math + +class SpriteSheet(object): + """Class used to grab images out of a sprite sheet""" + + def __init__(self, file_name): + self.sprite_sheet = pygame.image.load(file_name).convert_alpha() + + def get_image(self, x, y, width, height): + """Grab image out of a sprite sheet. + x,y: x,y location of sprite + width, height: width/height of sprite""" + + #create new blank image + image = pygame.Surface([width, height], pygame.SRCALPHA) + + #copy the sprite from large sheet onto small image + image.blit(self.sprite_sheet, (0,0), (x,y, width, height)) + + #Return the image + return image + +class Sprite(pygame.sprite.Sprite): + """Takes two arguments and generates a sprite + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__() + + sprite_sheet = SpriteSheet(sheet_name) + self.image = sprite_sheet.get_image(sprite_sheet_data[0], sprite_sheet_data[1], sprite_sheet_data[2], sprite_sheet_data[3]) + + self.rect = self.image.get_rect() + def draw(self, surface): + surface.blit(self.image, self.rect) + + def update(self, shift_x): + return True + +class Balloon(Sprite): + """Takes two arguments and generates a balloon + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__(sprite_sheet_data, sheet_name) + self.change_x =0 + self.change_y = 1 + + self.boundary_top = self.rect.y + 2 + self.boundary_bottom = self.rect.y - 2 + self.age = 0 + self.rate = random.randint(10,20) + + #makes the balloon bob up and down and travel to the left until it despawns off-screen + def update(self, shift_x): + self.age += 1 + + #move up/down + self.rect.y = 2*math.sin(self.age/self.rate) + self.rect.y + + #move left/right + self.rect.x += shift_x + + #survive until off-screen to the left + return self.rect.x >= -200 + +class BlackCloud(Sprite): + """Takes two arguments and generates a cloud from the blackcloud sprite sheet + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__(sprite_sheet_data, sheet_name) + + #moves the cloud to the left at a constant rate. Once off-screen, repositions itself to the right of the screen. + def update(self, shift_x): + #move left/right + self.rect.x += shift_x - 1 + if self.rect.x < -2100: + self.rect.x = random.randint(20,38)*100 + return True + +class PurpleCloud(Sprite): + """Takes two arguments and generates a cloud from the purple cloud sprite sheet + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__(sprite_sheet_data, sheet_name) + + #moves the cloud to the left at a constant rate. Once off-screen, repositions itself to the right of the screen. + def update(self, shift_x): + #move left/right + self.rect.x += shift_x - 2 + if self.rect.x < -1500: + self.rect.x = random.randint(20,38)*100 + return True + +class SmallCloud(Sprite): + """Takes two arguments and generates a cloud from the small cloud sprite sheet + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__(sprite_sheet_data, sheet_name) + + #moves the cloud to the left at a constant rate. Once off-screen, repositions itself to the right of the screen. + def update(self, shift_x): + #move left/right + self.rect.x += shift_x - 3 + if self.rect.x < -1000: + self.rect.x = random.randint(20,38)*100 + return True + +class Cactus(Sprite): + """Takes two arguments and generates a cactus. + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__(sprite_sheet_data, sheet_name) + + #moves the cactus to the left at the same rate as the background. Once off-screen, repositions itself back to its original place on the right side of the screen. + def update(self, shift_x): + #move left/right + self.rect.x += shift_x + if self.rect.x < -1000: + self.rect.x = 2958 + return True + +class Flower(Sprite): + """Takes two arguments and generates a flower. + sprite_sheet_data: an array of 4 numbers (xpos, ypos, width, height) of a sprite from the sprite sheet + sheet_name: a string of the filename that the sprite is being pulled from""" + def __init__(self, sprite_sheet_data, sheet_name): + super().__init__(sprite_sheet_data, sheet_name) + + #moves the flower to the left. Once off-screen, despawns. + def update(self, shift_x): + #move left/right + self.rect.x += shift_x + return self.rect.x >= -200 + +class Scene(): + """Generic super class used to define the objects, can create subclasses to actually create specific landscapes""" + def __init__(self): + + #background image + self.background = pygame.image.load("summerbackground.png").convert() + self.background.set_colorkey(constants.WHITE) + self.background_size = self.background.get_size() + self.background_rect = self.background.get_rect() + self.w, self.h = self.background_size + self.x = 0 + self.x1 = self.w + + #How far this world has been scrolled left/right + self.world_shift = 0 + + #list containing all sprites that need to be drawn + self.active_sprites = [] + + #Update all of the sprites based on their individual update functions + def update(self, shift_x): + sprites = self.active_sprites + self.active_sprites = [] + for s in sprites: + survive = s.update(shift_x) + if survive: + self.active_sprites.append(s) + + #Update everything in the landscapes + def draw(self, screen): + for s in self.active_sprites: + s.draw(screen) + + def shift_world(self, shift_x, screen): + #make everything scroll at a nice, constant rate + + #keep track of the shift amount + self.world_shift += shift_x + + #Keep track of background loop shift + self.x += shift_x + self.x1 += shift_x + + screen.blit(self.background, (self.x,0)) + screen.blit(self.background, (self.x1,0)) + if self.x < -self.w: + self.x = self.w + if self.x1 < -self.w: + self.x1 = self.w + + + #adds a random hot air balloon from the pool of possible balloons to the active sprites list + def spawnballoon(self, xpos, ypos): + currentballoon = constants.balloons[random.randint(0,10)] + + block = Balloon(currentballoon, "hotairballoons.png") + block.rect.x = xpos + block.rect.y = ypos + self.active_sprites.append(block) + + #adds a flower sprite to the active sprite list + def spawnflower(self, xpos, ypos): + block = Flower(constants.FLOWER, "flower.png") + block.rect.x = xpos + block.rect.y = ypos + self.active_sprites.append(block) + +class Summer(Scene): + """Defintion for Summer live background.""" + + def __init__(self): + #Call parent constructor + Scene.__init__(self) + + #Array with type of cloud, and x, y location of the cloud + enviro = constants.summer + + #Go through the array above and add cloud_list + for i in range(len(enviro)): + if 0 <= i <= 3: + block = BlackCloud(enviro[i][0], "blackclouds.png") + block.rect.x = enviro[i][1] + block.rect.y = enviro[i][2] + self.active_sprites.append(block) + elif 4 <= i <= 7: + block = PurpleCloud(enviro[i][0], "purpleclouds.png") + block.rect.x = enviro[i][1] + block.rect.y = enviro[i][2] + self.active_sprites.append(block) + elif 8 <= i <= 11: + block = SmallCloud(enviro[i][0], "smallclouds.png") + block.rect.x = enviro[i][1] + block.rect.y = enviro[i][2] + self.active_sprites.append(block) + elif i == 12: + block = Cactus(enviro[i][0], "bigcactus.png") + block.rect.x = enviro[i][1] + block.rect.y = enviro[i][2] + self.active_sprites.append(block) diff --git a/main b/main new file mode 100644 index 00000000..287a3114 --- /dev/null +++ b/main @@ -0,0 +1,77 @@ +"""Interactive Programming Mini-Project 4: Live Wallpaper + +Authors: Hwei-Shin Harriman and Jessie Potter +References: http://programarcadegames.com/python_examples/en/sprite_sheets/""" +import pygame +import constants +import livebackground +import random + +def main(): + """Main Program""" + pygame.init() + + #set height and widght of Screen + size = [constants.SCREEN_WIDTH, constants.SCREEN_HEIGHT] + screen = pygame.display.set_mode(size) + + pygame.display.set_caption("Southwest") + + #Create the scenery + level_list = [] + level_list.append(livebackground.Summer()) + + #Set level + current_level_no = 0 + current_level = level_list[current_level_no] + + active_sprite_list = pygame.sprite.Group() + shift = 0 + count = 0 + + #active_sprite_list.add(player) + + #Loop until the user clicks the close button + done = False + + #Used to manage how fast the screen updates + clock =pygame.time.Clock() + + #----------Main Program Loop--------- + while not done: + for event in pygame.event.get(): #user did something + if event.type == pygame.QUIT: #If user clicked close + done = True #Flag that we are done so we exit the Loop + elif event.type == pygame.MOUSEBUTTONDOWN: + xpos, ypos = event.pos + if 0 <= ypos <= 500: + current_level.spawnballoon(xpos, ypos) + if (3020+shift) <= xpos <= (3200+shift) and 497 <= ypos <= 985: + if count<=2: + current_level.spawnflower(xpos, ypos) + count+=1 + + #update items in the level + current_level.update(-5) + + #shift the world left (-x) + current_level.shift_world(-5, screen) + shift += -5 + if shift == -3900: + shift = 0 + count = 0 + + #ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT + current_level.draw(screen) + + #ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT + + #Limit to 60 fps + clock.tick(30) + + #Update screen with what we've drawn + pygame.display.flip() + pygame.quit() + +if __name__=="__main__": + main() diff --git a/purpleclouds.png b/purpleclouds.png new file mode 100755 index 00000000..27186569 Binary files /dev/null and b/purpleclouds.png differ diff --git a/smallclouds.png b/smallclouds.png new file mode 100755 index 00000000..6e50d02a Binary files /dev/null and b/smallclouds.png differ diff --git a/summerbackground.png b/summerbackground.png new file mode 100755 index 00000000..e095157d Binary files /dev/null and b/summerbackground.png differ diff --git a/summershade.png b/summershade.png new file mode 100755 index 00000000..077889e9 Binary files /dev/null and b/summershade.png differ