这一次就来学习设计一款ASCII 文本的RPG——Dungeon角色扮演游戏,这一章的内容会结合前面所学的知识。
Dungeon游戏
地图的构建
关于地图构建的相关的操作都封装在一个Dungeon类中,存储在名为Dungeon.py文件中:
import random
import sys
import pygame
from pygame.locals import *
from MyLibrary import *
class Dungeon(object):
def __init__(self, offsetx, offsety):
self.text = MySprite()
self.text.load("ascii8x12.png", 8, 12, 32)
self.tiles = list()
self.rooms = list()
for n in range(0, 80 * 45):
self.tiles.append(-1)
self.offsetx = offsetx
self.offsety = offsety
self.generate()
def getCharAt(self, x, y):
if x < 0 or x > 79 or y < 0 or y > 44:
print("error: x,y = ", x, y)
return
index = y * 80 + x
if index < 0 or index > 80*45:
print("error: index = ", index)
return
return self.tiles[index]
def setCharAt(self, x, y, char):
if x < 0 or x > 79 or y < 0 or y > 44:
print("error: x,y = ", x, y)
return
index = y * 80 + x
if index < 0 or index > 80*45:
print("error: index = ", index)
return
self.tiles[index] = char
def draw(self, surface):
for y in range(0,45):
for x in range(0,80):
char = self.getCharAt(x, y)
if char >= 0 and char <= 255:
self.draw_char(surface, x, y, char)
def putCharInRandomRoom(self, targetChar, itemChar):
x = random.randint(0, 79)
y = random.randint(0, 44)
tile = self.getCharAt(x, y)
while tile != targetChar:
x = random.randint(0, 79)
y = random.randint(0, 44)
tile = self.getCharAt(x, y)
self.setCharAt(x, y, itemChar)
def generate(self, emptyChar=175, roomChar=218, hallChar=177):
self.emptyChar = emptyChar
self.roomChar = roomChar
self.hallChar = hallChar
for index in range(0, 80 * 45):
self.tiles[index] = emptyChar
PL = 4
PH = 8
SL = 5
SH = 14
self.createRoom(0, 0, PL, PH, SL, SH)
self.createRoom(20, 0, PL, PH, SL, SH)
self.createRoom(40, 0, PL, PH, SL, SH)
self.createRoom(60, 0, PL, PH, SL, SH)
self.createRoom(0, 22, PL, PH, SL, SH)
self.createRoom(20, 22, PL, PH, SL, SH)
self.createRoom(40, 22, PL, PH, SL, SH)
self.createRoom(60, 22, PL, PH, SL, SH)
for room in self.rooms:
for y in range(room.y, room.y + room.height):
for x in range(room.x, room.x + room.width):
self.setCharAt(x, y, roomChar)
for n in range(0, 7):
self.createHallRight(self.rooms[n], self.rooms[n + 1], hallChar)
choice = random.randint(0, 3)
print("choice:" + str(choice) + "," + str(choice + 4))
self.createHallDown(self.rooms[choice], self.rooms[choice + 4], hallChar)
choice = random.randint(0, 7)
self.entrance_x = self.rooms[choice].x + self.rooms[choice].width // 2
self.entrance_y = self.rooms[choice].y + self.rooms[choice].height // 2
self.setCharAt(self.entrance_x, self.entrance_y, 29)
print("entrance:", choice, self.entrance_x, self.entrance_y)
choice2 = random.randint(0, 7)
while choice2 == choice:
choice2 = random.randint(0, 7)
x = self.rooms[choice2].x + self.rooms[choice2].width // 2
y = self.rooms[choice2].y + self.rooms[choice2].height // 2
self.setCharAt(x, y, 30)
print("exit:", choice2, x, y)
drops = random.randint(5, 20)
for n in range(1, drops):
self.putCharInRandomRoom(roomChar, 70)
self.putCharInRandomRoom(roomChar, 86)
self.putCharInRandomRoom(roomChar, 64)
self.putCharInRandomRoom(roomChar, 71)
self.putCharInRandomRoom(roomChar, 71)
num = random.randint(5, 10)
for n in range(0, num):
self.putCharInRandomRoom(roomChar, 20)
def createRoom(self, x, y, rposx, rposy, rsizel, rsizeh):
room = Rect(x + random.randint(1, rposx),
y + random.randint(1, rposy),
random.randint(rsizel, rsizeh),
random.randint(rsizel, rsizeh))
self.rooms.append(room)
def createHallRight(self, src, dst, hallChar):
pathx = src.x + src.width
pathy = src.y + random.randint(1, src.height - 2)
self.setCharAt(pathx, pathy, hallChar)
if pathy > dst.y and pathy < dst.y + dst.height:
while pathx < dst.x:
pathx += 1
self.setCharAt(pathx, pathy, hallChar)
else:
while pathx < dst.x + 1:
pathx += 1
self.setCharAt(pathx, pathy, hallChar)
if pathy < dst.y + 1:
self.setCharAt(pathx, pathy, hallChar)
while pathy < dst.y:
pathy += 1
self.setCharAt(pathx, pathy, hallChar)
else:
self.setCharAt(pathx, pathy, hallChar)
while pathy < dst.y + dst.height:
pathy -= 1
self.setCharAt(pathx, pathy, hallChar)
def createHallDown(self, src, dst, hallChar):
pathx = src.x + random.randint(1, src.width - 2)
pathy = src.y + src.height
self.setCharAt(pathx, pathy, hallChar)
if pathx > dst.x and pathx < dst.x + dst.width:
while pathy < dst.y:
pathy += 1
self.setCharAt(pathx, pathy, hallChar)
else:
while pathy < dst.y + 1:
pathy += 1
self.setCharAt(pathx, pathy, hallChar)
if pathx < dst.x + 1:
self.setCharAt(pathx, pathy, hallChar)
while pathx < dst.x:
pathx += 1
self.setCharAt(pathx, pathy, hallChar)
else:
self.setCharAt(pathx, pathy, hallChar)
while pathx > dst.x + dst.width:
pathx -= 1
self.setCharAt(pathx, pathy, hallChar)
def draw_radius(self, surface, rx, ry, radius):
left = rx - radius
if left < 0:
left = 0
top = ry - radius
if top < 0:
top = 0
right = rx + radius
if right > 79:
right = 79
bottom = ry + radius
if bottom > 44:
bottom = 44
for y in range(top, bottom):
for x in range(left, right):
char = self.getCharAt(x, y)
if char >= 0 and char <= 255:
self.draw_char(surface, x, y, char)
def draw_char(self, surface, tilex, tiley, char):
self.text.X = self.offsetx + tilex * 8
self.text.Y = self.offsety + tiley * 12
self.text.frame = char
self.text.last_frame = char
self.text.update(0)
self.text.draw(surface)
角色的构建
角色包括玩家操作的角色和随机生成的怪兽,都存放在Play.py文件中:
from chapter13.Dungeon import *
def Die(faces):
roll = random.randint(1, faces)
return roll
class Player(object):
def __init__(self, dungeon, level, name):
self.dungeon = dungeon
self.alive = True
self.x = 0
self.y = 0
self.name = name
self.gold = 0
self.experience = 0
self.level = level
self.weapon = level
self.weapon_name = "Club"
self.armor = level
self.armor_name = "Rags"
self.roll()
def roll(self):
self.str = 6 + Die(6) + Die(6)
self.dex = 6 + Die(6) + Die(6)
self.con = 6 + Die(6) + Die(6)
self.int = 6 + Die(6) + Die(6)
self.cha = 6 + Die(6) + Die(6)
self.max_health = 10 + Die(self.con)
self.health = self.max_health
def levelUp(self):
self.str += Die(6)
self.dex += Die(6)
self.con += Die(6)
self.int += Die(6)
self.cha += Die(6)
self.max_health += Die(6)
self.health = self.max_health
def draw(self, surface, char):
self.dungeon.draw_char(surface, self.x, self.y, char)
def move(self, movex, movey):
char = self.dungeon.getCharAt(self.x + movex, self.y + movey)
if char not in (self.dungeon.roomChar, self.dungeon.hallChar):
return False
else:
self.x += movex
self.y += movey
return True
def moveUp(self):
return self.move(0, -1)
def moveDown(self):
return self.move(0, 1)
def moveLeft(self):
return self.move(-1, 0)
def moveRight(self):
return self.move(1, 0)
def addHealth(self, amount):
self.health += amount
if self.health < 0:
self.health = 0
elif self.health > self.max_health:
self.health = self.max_health
def addExperience(self, xp):
cap = math.pow(10, self.level)
self.experience += xp
if self.experience > cap:
self.levelUp()
def getAttack(self):
attack = self.str + Die(20)
return attack
def getDefense(self):
defense = self.dex + self.armor
return defense
def getDamage(self, defense):
damage = Die(8) + self.str + self.weapon - defense
return damage
class Monster(Player):
def __init__(self, dungeon, level, name):
Player.__init__(self, dungeon, level, name)
self.gold = random.randint(1, 4) * level
self.str = 1 + Die(6) + Die(6)
self.dex = 1 + Die(6) + Die(6)
游戏启动文件:
有了前面两个文件之后,就可以实现后面的操作了:
from chapter13.Player import *
def game_init():
global screen, backbuffer, font1, font2, timer
pygame.init()
screen = pygame.display.set_mode((700, 650))
backbuffer = pygame.Surface((700, 650))
pygame.display.set_caption("Dungeon Game")
font1 = pygame.font.SysFont("Courier New", size=18, bold=True)
font2 = pygame.font.SysFont("Courier New", size=14, bold=True)
timer = pygame.time.Clock()
def playerCollision(stepx, stepy):
global TILE_EMPTY, TILE_ROOM, TILE_HALL, dungeon, player, level
yellow = (220, 220, 0)
green = (0, 220, 0)
char = dungeon.getCharAt(player.x + stepx, player.y + stepy)
if char == 29:
message("portal up")
elif char == 30:
message("portal down")
elif char == TILE_EMPTY:
message("You ran into the wall--ouch!")
elif char == 70:
gold = random.randint(1, level)
player.gold += gold
dungeon.setCharAt(player.x + stepx, player.y + stepy, TILE_ROOM)
message("You found " + str(gold) + " gold!", yellow)
elif char == 86:
weapon = random.randint(1, level + 2)
if level <= 5:
temp = random.randint(0, 2)
else:
temp = random.randint(3, 6)
if temp == 0:
name = "Dagger"
elif temp == 1:
name = "Short Sword"
elif temp == 2:
name = "Wooden Club"
elif temp == 3:
name = "Long Sword"
elif temp == 4:
name = "War Hammer"
elif temp == 5:
name = "Battle Axe"
elif temp == 6:
name = "Halberd"
if weapon >= player.weapon:
player.weapon = weapon
player.weapon_name = name
message("You found a " + name + " +" + str(weapon) + "!", yellow)
else:
player.gold += 1
message("You discarded a worthless " + name + ".")
dungeon.setCharAt(player.x + stepx, player.y + stepy, TILE_ROOM)
elif char == 64:
armor = random.randint(1, level + 2)
if level <= 5:
temp = random.randint(0, 2)
else:
temp = random.randint(3, 7)
if temp == 0:
name = "Cloth"
elif temp == 1:
name = "Patchwork"
elif temp == 2:
name = "Leather"
elif temp == 3:
name = "Chain"
elif temp == 4:
name = "Scale"
elif temp == 5:
name = "Plate"
elif temp == 6:
name = "Mithril"
elif temp == 7:
name = "Adamantium"
if armor >= player.armor:
player.armor = armor
player.armor_name = name
message("You found a " + name + " +" + str(armor) + "!", yellow)
else:
player.gold += 1
message("You discarded a worthless " + name + ".")
dungeon.setCharAt(player.x + stepx, player.y + stepy, TILE_ROOM)
elif char == 71:
heal = 0
for n in range(0, level):
heal += Die(6)
player.addHealth(heal)
dungeon.setCharAt(player.x + stepx, player.y + stepy, TILE_ROOM)
message("You drank a healing potion worth " + str(heal) + " points!", green)
elif char == 20:
attack_monster(player.x + stepx, player.y + stepy, 20)
def attack_monster(x, y, char):
global dungeon, TILE_ROOM
monster = Monster(dungeon, level, "Grue")
defense = monster.getDefense()
attack = player.getAttack()
damage = player.getDamage(defense)
battle_text = "You hit the " + monster.name + " for "
if attack == 20 + player.str:
damage *= 2
battle_text += str(damage) + " CRIT points!"
dungeon.setCharAt(x, y, 70)
elif attack > defense:
if damage > 0:
battle_text += str(damage) + " points."
dungeon.setCharAt(x, y, 70)
else:
battle_text += "no damage!"
damage = 0
else:
battle_text = "You missed the " + monster.name + "!"
damage = 0
defense = player.getDefense()
attack = monster.getAttack()
damage = monster.getDamage(defense)
if attack > defense:
if damage > 0:
if damage > player.max_health:
damage /= 2
battle_text += " It hit you for " + str(damage) + " points."
player.addHealth(-damage)
else:
battle_text += " It no damage to you."
else:
battle_text += " It missed you."
message(battle_text)
if player.health <= 0:
player.alive = False
def move_monsters():
for y in range(0, 44):
for x in range(0, 79):
tile = dungeon.getCharAt(x, y)
if tile == 20:
move_monster(x, y, 20)
def move_monster(x, y, char):
global TILE_ROOM
movex = 0
movey = 0
dir = random.randint(1, 4)
if dir == 1:
movey = -1
elif dir == 2:
movey = 1
elif dir == 3:
movex = -1
elif dir == 4:
movex = 1
c = dungeon.getCharAt(x + movex, y + movey)
if c == TILE_ROOM:
dungeon.setCharAt(x, y, TILE_ROOM)
dungeon.setCharAt(x + movex, y + movey, char)
def print_stats():
fmt = "{:3.0f}"
print_text(font2, 0, 615, "STR:" + fmt.format(player.str))
print_text(font2, 40, 615, "DEX:" + fmt.format(player.dex))
print_text(font2, 80, 615, "CON:" + fmt.format(player.con))
print_text(font2, 120, 615, "INT:" + fmt.format(player.int))
print_text(font2, 160, 615, "CHA:" + fmt.format(player.cha))
print_text(font2, 200, 615, "DEF:" + fmt.format(player.getDefense()))
global att, attlow, atthigh
att[0] = att[1]
att[1] = att[2]
att[2] = att[3]
att[3] = att[4]
att[4] = (player.getAttack() + att[0] + att[1] + att[2] + att[3]) // 5
if att[4] < attlow:
attlow = att[4]
elif att[4] > atthigh:
atthigh = att[4]
print_text(font2, 240, 615, "ATT:" + str(attlow) + "-" + str(atthigh))
print_text(font2, 300, 615, "LVL:" + fmt.format(player.level))
print_text(font2, 360, 615, "EXP:" + str(player.experience))
print_text(font2, 440, 615, "WPN:" + str(player.weapon) + ":" + player.weapon_name)
print_text(font2, 560, 615, "ARM:" + str(player.armor) + ":" + player.armor_name)
print_text(font2, 580, 570, "GOLD:" + str(player.gold))
print_text(font2, 580, 585, "HLTH:" + str(player.health) + "/" + str(player.max_health))
def message(text, color=(255, 255, 255)):
global message_text, message_color
message_text = text
message_color = color
TILE_EMPTY = 177
TILE_ROOM = 31
TILE_HALL = 31
if __name__ == "__main__":
game_init()
game_over = False
last_time = 0
dungeon = Dungeon(30, 30)
dungeon.generate(TILE_EMPTY, TILE_ROOM, TILE_HALL)
player = Player(dungeon, 1, "Player")
player.x = dungeon.entrance_x + 1
player.y = dungeon.entrance_y + 1
level = 1
message_text = "Welcome, brave adventurer!"
message_color = 0, 200, 50
draw_radius = False
att = list(0 for n in range(0, 5))
attlow = 90
atthigh = 0
while True:
timer.tick(30)
ticks = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
sys.exit()
elif event.key == K_TAB:
draw_radius = not draw_radius
elif event.key == K_SPACE:
dungeon.generate(TILE_EMPTY, TILE_ROOM, TILE_HALL)
player.x = dungeon.entrance_x + 1
player.y = dungeon.entrance_y + 1
elif event.key == K_UP or event.key == K_w:
if player.moveUp() == False:
playerCollision(0, -1)
else:
move_monsters()
elif event.key == K_DOWN or event.key == K_s:
if player.moveDown() == False:
playerCollision(0, 1)
else:
move_monsters()
elif event.key == K_RIGHT or event.key == K_d:
if player.moveRight() == False:
playerCollision(1, 0)
else:
move_monsters()
elif event.key == K_LEFT or event.key == K_a:
if player.moveLeft() == False:
playerCollision(-1, 0)
else:
move_monsters()
backbuffer.fill((20, 20, 20))
if draw_radius:
dungeon.draw_radius(backbuffer, player.x, player.y, 6)
else:
dungeon.draw(backbuffer)
player.draw(backbuffer, 0)
screen.blit(backbuffer, (0, 0))
print_text(font1, 0, 0, "Dungeon Level " + str(level))
print_text(font1, 600, 0, player.name)
print_text(font2, 30, 570, message_text, message_color)
print_stats()
pygame.display.update()
运行结果如下:
|