Dan Cardin
|
|
« on: April 14, 2010, 05:18:43 PM » |
|
I was just wondering if anyone had any suggestions for ways to make my code better. Right now the only real thing i have planned to improve upon the engine is putting the player in a class so i can do npcs and whatnot. So in general, does the way I've done things look like its efficient and/or clean and whatnot. This is basically my first game game (and first game in python) and I don't want to get very far into it then realize i need to change a fundamental part of it and screw everything up. Also, an advice on allowing for slopes while still retaining my tile basedness would be great; they would be triangle slopes not slopy slopes by the way. exe - http://uppit.com/v/QFPY8VT9#!/user/bin/env python
import sys, pygame, os from pygame.locals import *
#--- Our Variables black = (0,0,0) res = (32, 32) size = (20 * res[0], 17 * res[1]) pSize = (res[0] - 1, res[1]) speed = [0,0]
#--- Init pygame os.environ["SDL_VIDEO_CENTERED"] = "1" pygame.init
#--- Display pygame.display.set_caption("Rectangle Man: Story of a Square") screen = pygame.display.set_mode(size)
class Player(object): def __init__(self): self.rect = pygame.Rect(0, 0, pSize[0], pSize[1])
def move(self, dx, dy): if dx != 0: self.move_single_axis(dx, 0) if dy != 0: self.move_single_axis(0, dy) def move_single_axis(self, dx, dy): self.rect.x += dx self.rect.y += dy tx, ty = int(player.rect.x / res[0]), int(player.rect.y / res[1]) rects = [[tx, ty],[tx + 1, ty],[tx, ty + 1],[tx + 1, ty + 1]]
for h in rects: if walls[int(h[0])][int(h[1])].tNum == 1: wam = walls[int(h[0])][int(h[1])].rect if self.rect.colliderect(wam): if dx > 0: self.rect.right = wam.left if dx < 0: self.rect.left = wam.right if dy > 0: self.rect.bottom = wam.top global jump jump = 0 speed[1] = 0 if dy < 0: self.rect.top = wam.bottom speed[1] = 0 return
class Wall(object): def __init__(self, pos): self.rect = pygame.Rect(pos[0], pos[1], pos[2], pos[3]) self.tNum = pos[4] #--- Variables clock = pygame.time.Clock() #ground = pygame.transform.scale(pygame.image.load("ground.bmp"), (res[0],res[0])).convert() jumping, uCr = False, False jump, jumpNum, inc = 0, 2, 1 walls = tot = [] player = Player() camera = pygame.Rect(0, 0, screen.get_width(), screen.get_height())
def translate(rect): global camera return pygame.Rect(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h)
def getEvents(): global camera for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_UP: global jumping, jump if jumping == False: jumping = True jump += 1 speed[1] = -sp[1] global inc inc = 1 if event.key == K_DOWN: player.rect = pygame.Rect(player.rect.x, player.rect.y + pSize[1]/2, pSize[0], pSize[1]/2) pygame.draw.rect(screen, (0,0,0), ballbuff) if event.key == K_LEFT: speed[0] = -sp[0] if event.key == K_RIGHT: speed[0] = sp[0] if event.type == KEYUP: if event.key == K_UP: inc = 2 if event.key == K_DOWN and speed[1] > 0: q = player.rect.x player.move(0, -res[1]/2) if player.rect.x == q: player.move(0, res[1]/2) player.rect = pygame.Rect(player.rect.x, player.rect.y - pSize[1]/2, pSize[0], pSize[1]) if event.key == K_LEFT and speed[0] < 0: speed[0] = 0 if event.key == K_RIGHT and speed[0] > 0: speed[0] = 0 if event.type == pygame.QUIT: running = False sys.exit() def parseLevel(filename): fileObj = open("level.txt") st = fileObj.read() fileObj.close() rez = st.find(".", 0) global tot tot = [float(res[0]) / float(st[0:rez]), float(res[1]) / float(st[0:rez])] x = st.find(".", rez + 1) y = st.find("(", x) h = 0 m = [] for i in range(0, int(st[rez + 1:x])): walls.append([]) for e in range(0, int(st[x + 1:y])): h = st.find("(", h) + 1 h2 = st.find(")", h) for la in range(0, 5): go = st.find(",", h, h2) m.append(int(st[h:go])) h = go + 1 if m[4] == 2: m[4] = 0 player.rect = pygame.Rect(m[0] * tot[0], m[1] * tot[1], m[2] * tot[0], m[3] * tot[1]) walls[i].append(Wall((m[0] * tot[0], m[1] * tot[1], m[2] * tot[0], m[3] * tot[1], m[4]))) m = [] parseLevel("level.txt") sp = [4 * tot[0], 10 * tot[1]] running = True while running: ballbuff = pygame.Rect(player.rect.x, player.rect.y, res[0], player.rect.bottom - player.rect.top) getEvents() player.move(speed[0], speed[1]) camera.x = player.rect.x - (camera.width / 2) if camera.x < 0: camera.x = 0 if camera.x + camera.width > len(walls) * res[0]: camera.x = (len(walls) * res[0]) - camera.width
camera.y = player.rect.y - (camera.height / 2) if camera.y < 0: camera.y = 0 if camera.y + camera.height > len(walls[0]) * res[1]: camera.y = (len(walls[0]) * res[1]) - camera.height if speed[1] < sp[1]: if speed[1] < 0: speed[1] += inc * tot[1] else: speed[1] += 1 * tot[1] if jumping: if (speed[1] >= 0) and (jump <= jumpNum - 1): jumping = False for i in range(0, len(walls)): for e in range(0, len(walls[i])): rect = translate(walls[i][e].rect) col = {0 : (0,0,0), 1: (0,0,255), 2 : (0,0,0)}[walls[i][e].tNum] #pygame.draw.rect(screen, col, rect) if walls[i][e].tNum == -1: screen.blit(ground, rect) else: pygame.draw.rect(screen, col, rect) rect = translate(ballbuff) pygame.draw.rect(screen, (0,0,0), rect) #used to be screen.blit...so change back for images rect = translate(player.rect) pygame.draw.rect(screen, (255, 200, 0), rect) clock.tick(30) pygame.display.flip() + level.txt if you're not downloading the exe. http://mixster.pastebin.com/2GeYePDQ
|
|
« Last Edit: April 14, 2010, 05:23:02 PM by Dan Cardin »
|
Logged
|
|
|
|
Skofo
|
|
« Reply #1 on: April 14, 2010, 06:38:06 PM » |
|
It isn't very Pythonic. Start by reading this: http://www.python.org/dev/peps/pep-0008/Have fun.
|
|
|
Logged
|
If you wish to make a video game from scratch, you must first invent the universe.
|
|
|
muku
|
|
« Reply #2 on: April 15, 2010, 12:08:27 AM » |
|
You're a bit over-reliant on global variables, I think. Maybe make your level a class too, or at the very least make parseLevel() return a tuple (tot,walls) of the parsed data instead of it accessing the globals directly.
If the code works for you right now, you don't necessarily have to change anything. However, as soon as you realize that something you want to implement doesn't play well with the structure of your code, don't just hack around it, but really put in the effort to redesign your code so that the new functionality fits. This is the basis of what is called refactoring, and it's what keeps codebases from deteriorating into unmaintainable balls of goo.
EDIT: One other thing: it's not so nice to have game logic in your getEvents() function. Instead, pass the keypresses on to methods of your Player class and let the class do its own updating.
|
|
« Last Edit: April 15, 2010, 12:38:17 AM by muku »
|
Logged
|
|
|
|
Dan Cardin
|
|
« Reply #3 on: April 15, 2010, 05:44:20 PM » |
|
I changed some things...but i dont really know how to go about getting the movements out of the getevents. I dont know why that would help since its going to be hardcoded somewhere as those being the movements. What does being in the class do? Thanks for the input both of you, but i dont know what you mean by non pythonic even after i read the article you sent. I dont know if you are just reading this or running it, so i shall do both. http://uppit.com/v/EHBQWBTW#!/user/bin/env python
import sys import os import pygame from pygame.locals import *
#--- Our Variables res = (32, 32) size = (20 * res[0], 17 * res[1]) speed = [0, 0]
#--- Init pygame os.environ["SDL_VIDEO_CENTERED"] = "1" pygame.init
#--- Display pygame.display.set_caption("Rectangle Man: Story of a Square") screen = pygame.display.set_mode(size)
class Character(object): def __init__(self, Size): self.rect = pygame.Rect(0, 0, Size[0], Size[1]) self.Wize = Size
class Player(Character): def __init__(self, Size, sp): Character.__init__(self, Size) self.speed = sp self.uc = 16
def move(self, dx, dy): if dx != 0: self.move_single_axis(dx, 0) if dy != 0: self.move_single_axis(0, dy) def move_single_axis(self, dx, dy): self.rect.x += dx self.rect.y += dy tx, ty = int(player.rect.x / res[0]), int(player.rect.y / res[1]) rects = [[tx, ty], [tx + 1, ty], [tx, ty + 1], [tx + 1, ty + 1]]
for h in rects: if levels.map[int(h[0])][int(h[1])].tNum == 1: wam = levels.map[int(h[0])][int(h[1])].rect if self.rect.colliderect(wam): if dx > 0: self.rect.right = wam.left if dx < 0: self.rect.left = wam.right if dy > 0: self.rect.bottom = wam.top global jump jump = 0 speed[1] = 0 if dy < 0: self.rect.top = wam.bottom speed[1] = 0 return
class Wall(object): def __init__(self, pos): self.rect = pygame.Rect(pos[0], pos[1], pos[2], pos[3]) self.tNum = pos[4] self.tile = pos[5]
class Level(object): def __init__(self, returned): self.res = returned[0] self.map = returned[1] self.start = returned[2]
#--- Variables clock = pygame.time.Clock() jumping, uCr = False, False jump, jumpNum, inc = 0, 2, 1
def translate(rect): global camera return pygame.Rect(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h)
def getEvents(): global camera for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_UP: global jumping, jump if jumping == False: jumping = True jump += 1 speed[1] = -player.speed[1] global inc inc = 1 if event.key == K_DOWN: player.rect = pygame.Rect(player.rect.x, player.rect.y + player.Wize[1] / 2, player.Wize[0], player.Wize[1] / 2) pygame.draw.rect(screen, (0, 0, 0), ballbuff) if event.key == K_LEFT: speed[0] = -player.speed[0] if event.key == K_RIGHT: speed[0] = player.speed[0] if event.type == KEYUP: if event.key == K_UP: inc = 2 if event.key == K_DOWN and speed[1] > 0: q = player.rect.x player.move(0, -res[1]/2) if player.rect.x == q: player.move(0, res[1]/2) player.rect = pygame.Rect(player.rect.x, player.rect.y - player.Wize[1] / 2, player.Wize[0], player.Wize[1]) if event.key == K_LEFT and speed[0] < 0: speed[0] = 0 if event.key == K_RIGHT and speed[0] > 0: speed[0] = 0 if event.type == pygame.QUIT: running = False sys.exit() def parseLevel(filename): m, walls, tot = [], [], [] fileObj = open(filename) st = fileObj.read() fileObj.close() rez = st.find(".", 0) tot = [float(res[0]) / float(st[0: rez]), float(res[1]) / float(st[0:rez])] x = st.find(".", rez + 1) y = st.find("(", x) h = 0 for i in range(0, int(st[rez + 1:x])): walls.append([]) for e in range(0, int(st[x + 1:y])): h = st.find("(", h) + 1 h2 = st.find(")", h) for la in range(0, 6): go = st.find(",", h, h2) m.append(int(st[h:go])) h = go + 1 if m[4] == 2: m[4] = 0 star = (m[0] * tot[0], m[1] * tot[1]) walls[i].append(Wall((m[0] * tot[0], m[1] * tot[1], m[2] * tot[0], m[3] * tot[1], m[4], m[5]))) m = [] return (tot, walls, star)
levels = Level(parseLevel("level.txt")) player = Player((res[0] - 1, res[1]), (4 * levels.res[0], 10 * levels.res[1])) player.rect.x, player.rect.y = levels.start[0], levels.start[1] camera = pygame.Rect(0, 0, screen.get_width(), screen.get_height()) tileset = pygame.transform.scale(pygame.image.load("tileset.bmp"), (res[0], res[1] * 9)).convert() running = True while running: ballbuff = pygame.Rect(player.rect.x, player.rect.y, res[0], player.rect.bottom - player.rect.top) getEvents() player.move(speed[0], speed[1]) camera.x = player.rect.x - (camera.width / 2) if camera.x < 0: camera.x = 0 if camera.x + camera.width > len(levels.map) * res[0]: camera.x = (len(levels.map) * res[0]) - camera.width
camera.y = player.rect.y - (camera.height / 2) if camera.y < 0: camera.y = 0 if camera.y + camera.height > len(levels.map[0]) * res[1]: camera.y = (len(levels.map[0]) * res[1]) - camera.height if speed[1] < player.speed[1]: if speed[1] < 0: speed[1] += inc * levels.res[1] else: speed[1] += 1 * levels.res[1] if jumping: if (speed[1] >= 0) and (jump <= jumpNum - 1): jumping = False screen.fill((0, 0, 0)) for i in range(0, len(levels.map)): for e in range(0, len(levels.map[i])): rect = translate(levels.map[i][e].rect) til = pygame.surface.Surface((res[0],res[1])) til.set_colorkey((73,154,191)) til.blit(tileset, (0, -levels.map[i][e].tile * res[1], res[0], res[1])) screen.blit(til, rect) #rect = translate(ballbuff) #pygame.draw.rect(screen, (0,0,0), rect) #used to be screen.blit...so change back for images rect = translate(player.rect) pygame.draw.rect(screen, (255, 200, 0), rect) clock.tick(30) pygame.display.flip()
|
|
|
Logged
|
|
|
|
David Pittman
|
|
« Reply #4 on: April 15, 2010, 06:45:05 PM » |
|
i dont really know how to go about getting the movements out of the getevents. I dont know why that would help since its going to be hardcoded somewhere as those being the movements. What does being in the class do?
It keeps logic specific to a class contained within that class, and it draws a clear delineation between player input and the resulting actions. Jumping and speed should members of player, not global variables. Again, encapsulate things related to a class within the class. I'd recommend that your main loop contain as little object-specific code as possible. Instead of calling player.move(), call something like "player.tick()", and handle the player's movement in there. Again, this should be done with the intention of keeping that behavior local; your eventual goal would be to iterate through an array of objects which all implement the tick() function to do whatever they need to do each frame. (Along these lines, the camera's position should be updated in its own tick, and the jumping and speed logic should be moved into the player's tick along with the variables.) Variables names like "m" are not helpful. When I read a line like: walls[i].append(Wall((m[0] * tot[0], m[1] * tot[1], m[2] * tot[0], m[3] * tot[1], m[4], m[5]))) I blank out and just move on to the next function without ever understanding that. Document your code. Comments are good, but self-commenting code is better: use clear, descriptive variable and function names. "jumpNum" could be "jumpPhase" if I understand its usage correctly, and that has a much more explicit meaning. (Edit: Actually, I didn't understand its meaning, which is exactly the problem.)You use a lot of "magic numbers" throughout, things like the 6 in: I don't know why that's a 6. I simply don't know what it's supposed to represent. It would be more readable to declare a "constant" variable (Python doesn't really have constants) and iterate to that. LA_RANGE_MAX = 6 for la in range(0, LA_RANGE_MAX): Again, this is more descriptive, and it also means that if that number is used in multiple places and you want to change the value, you only have to change the place it is declared instead of hunting down each place you used the number 6 for that purpose (or worse, maybe you used 5 somewhere to imply LA_RANGE_MAX-1, and then you're really in trouble). In general, keep things clearly named and encapsulated.
|
|
« Last Edit: April 15, 2010, 06:48:30 PM by David Pittman »
|
Logged
|
|
|
|
|
Dan Cardin
|
|
« Reply #6 on: April 16, 2010, 06:19:16 PM » |
|
Thanks for the advice. I've tried to incorporate your suggestions (though that 6 is the only "magic" number that I can find. Though I didn't know how to go about stopping pygame from crying when event.key wasn't defined, so what i did i don't think is very correct with the try except clause. #!/user/bin/env python
import sys import os import pygame from pygame.locals import *
#--- Init pygame os.environ["SDL_VIDEO_CENTERED"] = "1" pygame.init
#--- Variables res = (32, 32) clock = pygame.time.Clock()
#--- Display pygame.display.set_caption("Rectangle Man: Story of a Square") screen = pygame.display.set_mode((20 * res[0], 17 * res[1]))
class Entity(object): def __init__(self, Size): self.rect = pygame.Rect(0, 0, Size[0], Size[1]) self.Wize = Size
class Player(Entity): def __init__(self, Size, Speed): Entity.__init__(self, Size) self.topSpeed = Speed self.speed = [0, 0] self.uc = 16 self.currentJump = 0 self.totJumpNum = 2 self.jIncrement = 1 self.jumping = False
def tick(self): self.move(self.speed[0], self.speed[1]) if self.speed[1] < self.topSpeed[1]: if self.speed[1] < 0: self.speed[1] += self.increment * levels.res[1] else: self.speed[1] += 1 * levels.res[1] if self.jumping: if (self.speed[1] >= 0) and (self.currentJump <= self.totJumpNum - 1): self.jumping = False
def move(self, dx, dy): if dx != 0: self.move_single_axis(dx, 0) if dy != 0: self.move_single_axis(0, dy) def move_single_axis(self, dx, dy): self.rect.x += dx self.rect.y += dy tx, ty = int(player.rect.x / res[0]), int(player.rect.y / res[1]) rects = [[tx, ty], [tx + 1, ty], [tx, ty + 1], [tx + 1, ty + 1]]
for h in rects: if levels.map[int(h[0])][int(h[1])].tNum == 1: wam = levels.map[int(h[0])][int(h[1])].rect if self.rect.colliderect(wam): if dx > 0: self.rect.right = wam.left if dx < 0: self.rect.left = wam.right if dy > 0: self.rect.bottom = wam.top self.currentJump = 0 self.speed[1] = 0 if dy < 0: self.rect.top = wam.bottom self.speed[1] = 0 return def input(self, type, keys): if type == KEYDOWN: if keys == K_UP: if self.jumping == False: self.jumping = True self.currentJump += 1 self.speed[1] = -self.topSpeed[1] self.increment = 1 if keys == K_DOWN: self.rect = pygame.Rect(self.rect.x, self.rect.y + self.Wize[1] / 2, self.Wize[0], self.Wize[1] / 2) pygame.draw.rect(screen, (0, 0, 0), ballbuff) if keys == K_LEFT: self.speed[0] = -self.topSpeed[0] if keys == K_RIGHT: self.speed[0] = self.topSpeed[0] if type == KEYUP: if keys == K_UP: self.increment = 2 if keys == K_DOWN and self.speed[1] > 0: q = self.rect.x self.move(0, -res[1]/2) if self.rect.x == q: self.move(0, res[1]/2) self.rect = pygame.Rect(self.rect.x, self.rect.y - self.Wize[1] / 2, self.Wize[0], self.Wize[1]) if keys == K_LEFT and self.speed[0] < 0: self.speed[0] = 0 if keys == K_RIGHT and self.speed[0] > 0: self.speed[0] = 0
class Wall(object): def __init__(self, pos): self.rect = pygame.Rect(pos[0], pos[1], pos[2], pos[3]) self.tNum = pos[4] self.tile = pos[5]
class Level(object): def __init__(self, (size, resolution, mapArray, playerStart)): self.size = size self.res = resolution self.map = mapArray self.start = playerStart
class Camera(Entity):
def __init__(self, Size): Entity.__init__(self, Size) def tick(self): self.x = player.rect.x - (self.rect.width / 2) if self.x < 0: self.x = 0 if self.x + self.rect.width > len(levels.map) * res[0]: self.x = (len(levels.map) * res[0]) - self.rect.width
self.y = player.rect.y - (self.rect.height / 2) if self.y < 0: self.y = 0 if self.y + self.rect.height > len(levels.map[0]) * res[1]: self.y = (len(levels.map[0]) * res[1]) - self.rect.height
def translate(rect, Cam): return pygame.Rect(rect.x - Cam.x, rect.y - Cam.y, rect.w, rect.h)
def getEvents(): for event in pygame.event.get(): if event.type == KEYDOWN or KEYUP: try: player.input(event.type, event.key) except: False if event.type == pygame.QUIT: gameRunning = False sys.exit() def parseLevel(filename): COMMA_ITER = 6 pos, walls, tot = [], [], [] fileObj = open(filename) s = fileObj.read() fileObj.close() mRes = s.find(".", 0) tot = [float(res[0]) / float(s[0: mRes]), float(res[1]) / float(s[0: mRes])] mx = s.find(".", mRes + 1) my = s.find("(", mx) strPart = 0 for i in range(0, int(s[mRes + 1: mx])): walls.append([]) for e in range(0, int(s[mx + 1: my])): strPart = s.find("(", strPart) + 1 strPart2 = s.find(")", strPart) for la in range(0, COMMA_ITER): final = s.find(",", strPart, strPart2) pos.append(int(s[strPart: final])) strPart = final + 1 if pos[4] == 2: pos[4] = 0 star = (pos[0] * tot[0], pos[1] * tot[1]) walls[i].append(Wall((pos[0] * tot[0], pos[1] * tot[1], pos[2] * tot[0], pos[3] * tot[1], pos[4], pos[5]))) pos = [] return ((mx, my), tot, walls, star)
levels = Level(parseLevel("level.txt")) player = Player((res[0] - 1, res[1]), (4 * levels.res[0], 10 * levels.res[1])) player.rect.x, player.rect.y = levels.start[0], levels.start[1] camera = Camera((screen.get_width(), screen.get_height())) tileset = pygame.transform.scale(pygame.image.load("tileset.bmp"), (res[0], res[1] * 9)).convert()
gameRunning = True while gameRunning: ballbuff = pygame.Rect(player.rect.x, player.rect.y, res[0], player.rect.bottom - player.rect.top) getEvents() player.tick() camera.tick() screen.fill((0, 0, 0)) for i in range(0, len(levels.map)): for e in range(0, len(levels.map[i])): rect = translate(levels.map[i][e].rect, camera) til = pygame.surface.Surface((res[0],res[1])) til.set_colorkey((73,154,191)) til.blit(tileset, (0, -levels.map[i][e].tile * res[1], res[0], res[1])) screen.blit(til, rect) rect = translate(player.rect, camera) pygame.draw.rect(screen, (255, 200, 0), rect) clock.tick(30) pygame.display.flip() And I haven't enough money to buy such a book. Anyways, does anyone know of a good way to do slopes in a system similar to this, as i mentioned above? I suppose i don need them if they will require a lot of changing, I just thought if it could be done simply, then ti could be nice.
|
|
« Last Edit: April 16, 2010, 06:24:21 PM by Dan Cardin »
|
Logged
|
|
|
|
Skofo
|
|
« Reply #7 on: April 17, 2010, 12:19:25 AM » |
|
And I haven't enough money to buy such a book. If only there were some place where one could rent books for free.
|
|
|
Logged
|
If you wish to make a video game from scratch, you must first invent the universe.
|
|
|
muku
|
|
« Reply #8 on: April 17, 2010, 12:40:53 AM » |
|
def getEvents(): for event in pygame.event.get(): if event.type == KEYDOWN or KEYUP: You have a bug here. What you actually check for here is (event.type == KEYDOWN) or (KEYUP) and since KEYUP is some nonzero number, this expression is always true! Instead you should say event.type == KEYDOWN or event.type == KEYUP This is also the reason why you got exceptions: you were accessing event.key for all events, not only the key presses. So once you fix this you can remove the try/except block.
|
|
|
Logged
|
|
|
|
Dan Cardin
|
|
« Reply #9 on: April 19, 2010, 06:52:20 PM » |
|
Thank you for that. Quite the helpful fellows you are. those suggestions were very applicable because i tested running multiple characters and inter player collision and etc, and i was surprised that they worked, while only taking 2 minutes of my time.
I have heard wind of people doing "Vector" based systems for their movement. I wonder, would this actually give me an advantage were i to try to figure out how so to something like this? I dont know how difficult a thing it would be but i don't think it would make sense to try it unless it was definitively better for a game such as this.
|
|
|
Logged
|
|
|
|
|