diff --git a/Basic_Movements.py b/Basic_Movements.py new file mode 100644 index 00000000..62f3ce3d --- /dev/null +++ b/Basic_Movements.py @@ -0,0 +1,69 @@ +import pygame +from pygame.locals import * + +#Initialize the screen +screen = pygame.display.set_mode((1024, 768)) +pygame.display.set_caption("Smash Clone") + + +clock = pygame.time.Clock() +FPS = 120 + +#Variable to keep the game running +running = True + +#the sample rectangle, danny +danny = pygame.rect.Rect((20, 20, 250, 100)) +print(danny) + +#booleans for control toggles. +left = False +right = False +up = False +down = False + +while running: + #make the background pretty close to black + screen.fill((0, 15, 15)) + + #For each event at the moment: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + #KEYDOWN events toggle movement on + if event.type == KEYDOWN: + if event.key == pygame.K_LEFT: + left = True + if event.key == pygame.K_RIGHT: + right = True + if event.key == pygame.K_UP: + up = True + if event.key == pygame.K_DOWN: + down = True + #KEYUP events toggles movement off + if event.type == KEYUP: + if event.key == pygame.K_LEFT: + left = False + if event.key == pygame.K_RIGHT: + right = False + if event.key == pygame.K_UP: + up = False + if event.key == pygame.K_DOWN: + down = False + #actually moving the rectangle. move() is a fruitful function + if left: + danny = danny.move(-10, 0) + if right: + danny = danny.move(10, 0) + if up: + danny = danny.move(0, -10) + if down: + danny = danny.move(0, 10) + + #drawing the rectangle to the screen, and updating the screen + pygame.draw.rect(screen, [255, 255 ,255], danny) + pygame.display.update() + #clock for consistent for loop timing. + clock.tick(FPS) + +pygame.quit() diff --git a/GameView.py b/GameView.py new file mode 100644 index 00000000..a071df4f --- /dev/null +++ b/GameView.py @@ -0,0 +1,68 @@ +import pygame +import terrain +from character import Character +from model import Model +from pygame.locals import * +class GameView: + def __init__(self, model, size): + self.model = model + self.size = size + self.screen = pygame.display.set_mode(size) + pygame.font.init() + self.font = pygame.font.SysFont('Arial', 120) + self.textsurface = self.font.render('GAME OVER', False, (0, 0, 0)) + def draw(self): + """ + Updates graphics to game screen + """ + self.screen.fill(pygame.Color(200,210,255)) + for t in self.model.terrains: + pygame.draw.rect(self.screen, pygame.Color(150,120,10), t.rect) + for char in self.model.characters: + if char.attacking: + + self.screen.blit(char.attack_img, char.attack_rect) + if char.shielding: + self.screen.blit(char.shield_img, char.rect) + elif(char.left): + self.screen.blit(char.left_img, char.rect) + elif(char.right): + self.screen.blit(char.right_img, char.rect) + elif (char.vel_y < 0): + self.screen.blit(char.down_img, char.rect) + else: + self.screen.blit(char.up_img, char.rect) + for i in range (char.lives): + self.screen.blit(char.up_img, (570 + 120 * i, 950 - char.player * 150)) + if model.game_over: + self.screen.blit(self.textsurface,(400,100)) + pygame.display.update() + +if __name__ == "__main__": + clock = pygame.time.Clock() + FPS = 30 + + char1 = Character(label = 'char1') + char2 = Character(pos_x = 1200, label = 'char2', keys = {"left": pygame.K_j, "right": pygame.K_l, "up" : pygame.K_i, "down": + pygame.K_k, "attack" : pygame.K_o}, + left_img = "left2.png", + right_img = "right2.png", + up_img = "up2.png", + down_img = "down2.png", + shield_img = "shield2.png", + player = 2) + model = Model(char1, char2) + view = GameView(model, (1500, 1000)) + while model.game_running: + for event in pygame.event.get(): + model.quit(event) + if not model.game_over: + for char in model.characters: + model.x_movement(event, char) + model.y_movement(event, char) + model.attack_command(event, char) + model.shield(event, char) + model.check_lives() + model.update_motion() + view.draw() + clock.tick(FPS) diff --git a/Planning for MP4 .pdf b/Planning for MP4 .pdf new file mode 100644 index 00000000..a966e74f Binary files /dev/null and b/Planning for MP4 .pdf differ diff --git a/Project writeup and reflection.pdf b/Project writeup and reflection.pdf new file mode 100644 index 00000000..f75b02d0 Binary files /dev/null and b/Project writeup and reflection.pdf differ diff --git a/README.md b/README.md index 5f822327..20ab3b67 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. + +# Dependancies: +This project utilizes pygame. To install, execute the following command: +``` +pip install pygame +``` + +# How to Run: +The game is run by executing the following command: +``` +python GameView.py +``` +Character 1, the one on the left, is controlled with the W, A, S, D, and E keys. The W, A, and D keys are for movement, S if for shield, and E is to attack. +Charcter 2, the one on the right, is controlled with the I, J, K, L, and O keys. The I, J, and L keys are for movement, K is for shield, and O is to attack. + +# Project Reflection +A project reflection can be found [here](https://github.com/mhreid/InteractiveProgramming/blob/master/Project%20writeup%20and%20reflection.pdf). diff --git a/UML.jpg b/UML.jpg new file mode 100644 index 00000000..bb1952d8 Binary files /dev/null and b/UML.jpg differ diff --git a/character.py b/character.py new file mode 100644 index 00000000..e29a897d --- /dev/null +++ b/character.py @@ -0,0 +1,183 @@ +import pygame +class Character: + def __init__(self, pos_x = 300, pos_y = 0, label = "blank", + attack = 10, + defense = 10, + weight = 1, + jump_vel = -80, + acceleration = 3, + speed = 15, + width = 75, + height = 150, + max_health = 100, + max_jumps = 3, keys = {"left": pygame.K_a, "right": pygame.K_d, "up" : pygame.K_w, "down": pygame.K_s, "attack": pygame.K_e}, + left_img = "left.png", + right_img = "right.png", + up_img = "up.png", + down_img = "down.png", + shield_img = "shield.png", + attack_img = "fire.png", + lives = 3, + player = 1): + self.keys = keys + self.label = label + self.attack = attack + self.defense = defense + self.weight = weight + self.jump_vel = jump_vel + self.pos_x = pos_x + self.pos_y = pos_y + self.width = width + self.height = height + self.health = max_health + self.left = False + self.right = False + self.attacking = False + self.vel_x = 0 + self.vel_y = 0 + self.acc_x = acceleration + self.speed = speed + self.acc_direction = 1 + self.lives = 3 + self.max_jumps = max_jumps + self.jumps = max_jumps + self.rect = pygame.Rect(self.pos_x, self.pos_y, self.width, self.height) + self.attack_rect = pygame.Rect(self.pos_x, self.pos_y, 0, 0) + self.attack_time = 0 + self.damage_time = 0 + self.keys = keys + self.left_img = pygame.transform.scale(pygame.image.load(left_img), (self.width, self.height)) + self.right_img = pygame.transform.scale(pygame.image.load(right_img), (self.width, self.height)) + self.up_img = pygame.transform.scale(pygame.image.load(up_img), (self.width, self.height)) + self.down_img = pygame.transform.scale(pygame.image.load(down_img), (self.width, self.height)) + self.shield_img = pygame.transform.scale(pygame.image.load(shield_img), (self.width, self.height)) + self.attack_img = pygame.transform.scale(pygame.image.load(attack_img), (int(self.width * 0.7), int(self.height * 0.5))) + self.player = player + self.shielding = False + + + def __str__(self): + output = self.label + ':\n' + output += "attack: " + str(self.attack) + output += "\ndefense: " + str(self.defense) + output += "\nweight: " + str(self.weight) + output += "\njump vel: " + str(self.jump_vel) + output += "\nacceleration: " + str(self.acc_x) + output += "\nspeed: " + str(self.speed) + output += "\nwidth: " + str(self.width) + output += "\nheight: " + str(self.height) + output += "\nhealth: " + str(self.health) + return output + + def in_air(self, terrain): + """ + checks to see if character is in the air, or supported by terrain + *args are (probably) terrain objects and their coordinates + """ + return not self.rect.colliderect(terrain) + + def alive(self): + """ + checks to see if the character is still alive + """ + return self.health > 0 + + def accelerate(self): + """ + when there is no movement, acceleration = 0 + direction is -1 or 1 + """ + #self.vel_x += self.acc_x * self.acc_direction + #if self.vel_x > self.speed: + # self.vel_x = self.speed + #if self.vel_x < -self.speed: + # self.vel_x = -self.speed + + #Decelerates the object when there are no inputs. + # if self.vel_x > 0: + # self.vel_x -= self.acc_x + # elif self.vel_x < 0: + # self.vel_x += self.acc_x + + def move(self): + """ + updates the position of the character laterally. + direction is either -1 or 1 + """ + #For the second to last frame of the damaged animation, the velocity + #should be set to zero + if self.damage_time == 1: + self.vel_x = 0 + self.vel_y = 0 + if self.damage_time > 0: + self.damage_time -= 1 + if self.shielding: + self.vel_x = 0 + + + self.pos_x += self.vel_x + self.pos_y += self.vel_y + self.rect = pygame.Rect(self.pos_x, self.pos_y, self.width, self.height) + if(self.pos_y > 1000): + self.pos_y = 0 + self.pos_x = 700 + self.lives -= 1 + self.weight = 1 + + def attack_action(self): + """ + updates the hitbox of the character to reflect the action of an attack. + """ + if self.damage_time > 0: + self.attack_time = 0 + #If the attacking time is up, then toggle off attacking. + if self.attack_time < 1: + self.attack_time = 0 + self.attacking = False + + if self.attacking: + #offsets that represent the size of the attack hitbox + x_offset = self.width * 0.7 + y_offset = self.height * 0.5 + self.vel_x = 0 + #If the character is facing left, draw the attack hitbox on the left + if self.left: + self.attack_rect = pygame.Rect(self.pos_x - x_offset, + self.pos_y + y_offset, + x_offset, + y_offset) + #If char is facing right, draw the attack hitbox on the right + else: + self.right = True + self.attack_rect = pygame.Rect(self.pos_x + self.width, + self.pos_y + y_offset, + x_offset, + y_offset) + #update the time spent attacking + self.attack_time -= 1 + #If the character isn't attacking, remove the attack hitbox + else: + #put the hitbox underground where it can't interfere wither others + self.attack_rect = pygame.Rect(self.pos_x, 1000, 0, 0) + + def detect_damage(self, other_char, direction): + """ + detects whether a character object is subjected to an attack or not, and + then appropriately designates a knockback force. + """ + if self.rect.colliderect(other_char.attack_rect): + if not self.shielding: + #add 10 to the damage timer + self.damage_time = 5 + #set push directions + self.vel_x = direction * self.speed * 0.75 * self.weight + self.vel_y = -abs(self.vel_x * 1.5) * self.weight + self.weight += 0.05 + self.damage_time = int((2 * self.vel_y / -15)) + 3 + else: + + #add 10 to the damage timer + other_char.damage_time = 5 + #set push directions + other_char.vel_x = -direction * other_char.speed * 0.75 * other_char.weight + other_char.vel_y = -abs(other_char.vel_x * 1.5) * other_char.weight diff --git a/down.png b/down.png new file mode 100644 index 00000000..ba5aae3a Binary files /dev/null and b/down.png differ diff --git a/down2.png b/down2.png new file mode 100644 index 00000000..7de7479c Binary files /dev/null and b/down2.png differ diff --git a/fire.png b/fire.png new file mode 100644 index 00000000..ff08964c Binary files /dev/null and b/fire.png differ diff --git a/left.png b/left.png new file mode 100644 index 00000000..4657db2c Binary files /dev/null and b/left.png differ diff --git a/left2.png b/left2.png new file mode 100644 index 00000000..56a71b61 Binary files /dev/null and b/left2.png differ diff --git a/main.py b/main.py new file mode 100644 index 00000000..d96bd12e --- /dev/null +++ b/main.py @@ -0,0 +1,51 @@ +import GameView +import model +import pygame +from pygame.locals import * +import time +if __name__ == "__main__": + model = model.Model() + view = GameView.GameView(model, (1000,1000)) + running = True + left, up, down, right = False, False, False, False + while running: + for event in pygame.event.get(): + if event.type == QUIT: + running = False + time.sleep(.001) + view.draw() + if event.type == KEYDOWN: + if event.key == pygame.K_LEFT: + left = True + if event.key == pygame.K_RIGHT: + right = True + if event.key == pygame.K_UP: + up = True + if event.key == pygame.K_DOWN: + down = True + if event.type == KEYUP: + if event.key == pygame.K_LEFT: + left = False + if event.key == pygame.K_RIGHT: + right = False + if event.key == pygame.K_UP: + up = False + if event.key == pygame.K_DOWN: + down = False + if(left): + model.char.accelerate(-1) + if(right): + model.char.accelerate(1) + if(right == False and left == False): + if(model.char.vel_x > 0): + model.char.accelerate(-1) + if(model.char.vel_x < 0): + model.char.vel_x = 0 + elif(model.char.vel_x < 0): + model.char.accelerate(1) + if(model.char.vel_x < 0): + model.char.vel_x = 0 + print(model.char.vel_x) + model.char.move() + + pygame.quit diff --git a/model.py b/model.py new file mode 100644 index 00000000..b794cef0 --- /dev/null +++ b/model.py @@ -0,0 +1,129 @@ +import character +import terrain +import random +import pygame +from pygame.locals import * + +class Model: + def __init__(self, *args): + self.game_running = True + self.game_over = False + self.g = 15 + self.terrains = [] + self.characters = [] + + for char in args: + self.characters.append(char) + + self.terrains.append(terrain.Terrain((200,600), (1100,50))) + + def x_movement(self, event, game_object): + """ + Toggles the different modes of acceleration depending on key up + events and key down events. + """ + if (not (event.type == KEYDOWN or event.type == KEYUP) or + game_object.attacking or + game_object.damage_time > 0 or + game_object.shielding): + return + + #KEYDOWN events toggle movement on + if event.type == KEYDOWN: + if event.key == game_object.keys["left"]: + game_object.left = True + game_object.right = False + game_object.vel_x = -game_object.speed + elif event.key == game_object.keys["right"]: + game_object.right = True + game_object.left = False + game_object.vel_x = game_object.speed + #KEYUP events toggles movement off + if event.type == KEYUP: + if (event.key == game_object.keys["left"] and + not game_object.right): + game_object.vel_x = 0 + elif (event.key == game_object.keys["right"] and + not game_object.left): + game_object.vel_x = 0 + + """ + #If left XOR right + if game_object.left != game_object.right: + if game_object.left: + game_object.vel_x = -game_object.speed + if game_object.right: + game_object.vel_x = game_object.speed + #Slow character down when there's conflicting input or no input + else: + game_object.vel_x = 0 + """ + def y_movement(self, event, game_object): + """ + used to update the v velocity of the object + """ + if event.type == KEYDOWN: + if event.key == game_object.keys["up"] and game_object.jumps > 0: + game_object.vel_y = game_object.jump_vel + game_object.jumps -= 1 + + def shield(self,event, game_object): + """ + Used to update the shield state of a character + """ + if (game_object.attack_time > 0 or + game_object.damage_time > 0): + return + if event.type == KEYDOWN: + if event.key == game_object.keys["down"]: + game_object.shielding = True + if event.type == KEYUP: + if event.key == game_object.keys["down"]: + game_object.shielding = False + + def attack_command(self, event, game_object): + """ + Used to toggle the attacking mode for a character object for a given + amount of time. + """ + if game_object.shielding: + return + if event.type == KEYDOWN: + #NOTE: Change to custom/dynamic character attack button. + if event.key == game_object.keys["attack"]: + game_object.attacking = True + #number of frames spent attacking + game_object.attack_time = 30 + + def update_motion(self): + """ + Updates the position and velocity and attack hitbox of each character. + """ + for char in self.characters: + if char.in_air(self.terrains[0].rect): + char.vel_y += self.g + elif char.vel_y > 0: + char.vel_y = 0 + char.pos_y = self.terrains[0].rect.top - char.height + 1 + char.jumps = char.max_jumps + for other_chars in self.characters: + if other_chars.left: + char.detect_damage(other_chars, -1) + else: + char.detect_damage(other_chars, 1) + print(char.damage_time) + + char.move() + char.attack_action() + + def check_lives(self): + """ + Checks to see a player has lost + """ + for char in self.characters: + if char.lives < 1: + self.game_over = True + + def quit(self, event): + if event.type == QUIT: + self.game_running = False diff --git a/right.png b/right.png new file mode 100644 index 00000000..011c28b8 Binary files /dev/null and b/right.png differ diff --git a/right2.png b/right2.png new file mode 100644 index 00000000..5310d53e Binary files /dev/null and b/right2.png differ diff --git a/shield.png b/shield.png new file mode 100644 index 00000000..76819731 Binary files /dev/null and b/shield.png differ diff --git a/shield2.png b/shield2.png new file mode 100644 index 00000000..098bdc7a Binary files /dev/null and b/shield2.png differ diff --git a/terrain.py b/terrain.py new file mode 100644 index 00000000..1fe174df --- /dev/null +++ b/terrain.py @@ -0,0 +1,11 @@ +import pygame +class Terrain: + def __init__(self, pos, size = (100,100), kind = "Ground"): + """ + Kind signifies the type of terrain (used for color and skins later) + Each object is one terrain "block", with a 100x100 default size + """ + self.pos = pos + self.size = size + self.kind = kind + self.rect = pygame.Rect(pos[0], pos[1], size[0], size[1]) diff --git a/up.png b/up.png new file mode 100644 index 00000000..6c89ddaa Binary files /dev/null and b/up.png differ diff --git a/up2.png b/up2.png new file mode 100644 index 00000000..70aea74f Binary files /dev/null and b/up2.png differ