坦克和子弹:
1、坦克基类
不管是敌人的坦克还是玩家的坦克都有大部分相同的属性,不同的地方在于:
a)敌人有红坦克,打了可以生成宝物
b)敌人有装甲坦克一枪打不烂,会变颜色
c)敌人的坦克样式不同,行进速度也不一样
d)敌人可以被道具炸死
e)敌人可以被道具定住
f)敌人的子弹可以穿过敌人坦克,形成子弹叠加效果
g)玩家出生时有无敌效果
h)玩家可以加一众宝物
i)玩家不开启友军伤害
2、敌坦克
敌坦克只能是电脑控制,所以要有AI程序,太聪明了不行,游戏难度太大,可以简单实现,除了前进、转向就是开火
当然可以加上些随机转向功能,随机值向下倾向要大一些,不然老是在最上边转悠也不好。
开火也不能开个不停,也随机开火
3、玩家坦克
要考虑到加五星、加钢盔改变外形的问题,行进与不行进油门声音应该不同
玩家坦克的等级决定火力和装甲,加一个五星可以提升一个等级,火力和装甲会有相应变化,0级开始,最多到3级
4、子弹
火力等级从0开始,1级子弹加速,2级双弹,3级最强(双弹且可以打烂钢板)
方向、火力、速度由坦克开火时的状态来决定,开火后坦克就消耗一颗子弹,子弹爆炸或被抵消后才能装填下一发
子弹在前进的过程中不停检测是否碰到障碍物,直到碰到边界为止
子弹击中敌方的子弹会抵消,没有爆炸效果
玩家的子弹击中另一玩家子弹直接消失,无友军伤害
5、坦克工厂
负责生成双方的坦克,第一关敌人方不生产装甲坦克,每关最多出3个红坦克
import random
from block import *
from const import *
class Bullet(Block):
#子弹
def __init__(self, tank, dir):
Block.__init__(self, tank.engine, tank.who, E_000, (0, 0), 0, LIST_BULLET)
self.tank = tank
self.level = tank.fire_level
self.dir = dir
self.mode = 1 #正常
self.can_through = True
self.speed = 8 if self.level > 0 else 4
self.image_change(E_000, 0)
self.effect_time = 0 #特效开始时间
self.width = self.image.get_width()
self.height = self.image.get_height()
pos = [tank.pos[0] + 16, tank.pos[1] + 16] #坦克的中心点
offset = DIR_CFG[self.dir]["move"]
pos[0] += offset[0] * 12
pos[1] += offset[1] * 12 #坐标调整到炮口位置
self.pos = [pos[0] - self.width // 2, pos[1] - self.height // 2]
def move(self):
if self.mode == 1:#正常状态才移动
targets = self.test_hit()
if len(targets) > 0:
for target in targets:
self.hit_target(target)
else:
offset = DIR_CFG[self.dir]["move"]
self.pos[0] += offset[0] * self.speed
self.pos[1] += offset[1] * self.speed
pt = [self.pos[0] + self.width // 2, self.pos[1] + self.height // 2]
if not self.engine.in_bound(pt):#超出边界就爆炸
if self.who != WHO_ENEMY:
self.engine.play_sound(SND_BORDER)
self.speed = 0
self.mode = 2 #爆炸
self.image_change(E_EXP, 3)
#纠正子弹爆炸位置,不可超出边界
if pt[0] < 32:pt[0] = 32
if pt[0] > 32 + 13 * 32:pt[0] = 32 + 13 * 32
if pt[1] < 32:pt[1] = 32
if pt[1] > 32 + 13 * 32:pt[1] = 32 + 13 * 32
self.pos = [pt[0] - self.image.get_width() // 2, pt[1] - self.image.get_height() // 2]
self.effect_time = self.engine.timer #特效开始时间
def test_hit(self):
#TODO 直接比较对象区域太慢,还是在地图上生成一个表格,每次只判断当前坐标上下左右8个16*16元素
#产生一个前进后的坐标,并得到一个Rect
#收缩Rect(-1, -1)后,与所有地面障碍和活动目标比较,判断是允许前进
blocks = []
src_rc = pygame.Rect(self.pos, (self.width, self.height))
hit_enemy = False
for block in self.engine.get_blocks(True):
if hit_enemy:#一颗子弹只能击中一辆敌方坦克
break
if block == self or block == self.tank or block.who == self.who or block.mode != 1:
continue
if block.can_destory and src_rc.colliderect(block.get_rect()) :
blocks += [block]
if block.who != WHO_BALK and block.who != self.who:
hit_enemy = True
return blocks
def hit_target(self, target):
#击中敌人的子弹,抵销
target.hit(self)
#击中已方单位(坦克或子弹)或 对方子弹
#玩家子弹击中玩家 或 击中玩家,但玩家是无敌模式
if target.who == self.who or target.type == self.type \
or (self.who in PLAYER_CFG and target.who in PLAYER_CFG) \
or (target.who in PLAYER_CFG and target.is_god):
self.die()
else:
pt = [self.pos[0] + self.width // 2, self.pos[1] + self.height // 2]
self.speed = 0
self.mode = 2 #爆炸
self.can_through = True
self.image_change(E_EXP, 5)
self.pos = [pt[0] - self.image.get_width() // 2, pt[1] - self.image.get_height() // 2]
self.effect_time = self.engine.timer #特效开始时间
def hit(self, bullet):
if bullet.who == self.who or bullet.type == self.type:
self.die()
def inc_counter(self, timer):
#累计一定时间后进行帧切换
#-1销毁 0出生 1正常 2爆炸
if self.mode == 1:
self.move()
else:
super().inc_counter(timer)
#只有击中边界才自爆
if self.type == E_EXP and timer - 15 > self.effect_time:#闪烁5帧后状态销毁
self.die()
def die(self):
if self.tank:
if self.tank.send_bullet > 0:
self.tank.send_bullet -= 1
else:
self.tank.send_bullet = 0
super().die()
class Tank(Block):
#坦克
def __init__(self, engine, who, type, pos, dealy):
Block.__init__(self, engine, who, type, pos, dealy, LIST_TANKS)
self.born_type = type
self.dir = DIR_UP #方向
self.mode = 0 #出生
self.killer = -1 #被谁干掉的
self.speed_level = 1 #速度等级(只在init中设置)
self.max_bullets = 1 #弹药数
self.send_bullet = 0 #已发射弹药
self.fire_level = 0 #火力等级(弹药速度和弹药数)
self.is_god = True #是否无敌
self.effect_time = 0 #特效开始时间
self.turn_time = 0 #转向时间,帧率太高时连发太快,所以要转向不能直接前进
def bomb(self):
self.mode = 2 #爆炸
self.die_type = self.type
self.speed_level = 0
self.can_through = True
self.scale = True
self.animation = True
self.die_pos = self.pos
pt = [self.pos[0] + self.width // 2, self.pos[1] + self.height // 2]
self.image_change(E_EXP, 5)
self.pos = [pt[0] - self.image.get_width() // 2, pt[1] - self.image.get_height() // 2] #动画切换后再计算位置
self.effect_time = self.engine.timer #特效开始时间
def hit(self, bullet):
if bullet.who == self.who or self.is_god:
return
if bullet.who in PLAYER_CFG and self.who in PLAYER_CFG:
#TODO 玩家的子弹打玩家,被击中者定住
return
#被炸死的红坦克不产生宝物(必须是被玩家干掉的)
if self.who == WHO_ENEMY and self.type in (T_E1S, T_E2S, T_E3S, T_E4S) and bullet.who in PLAYER_CFG:
Food(self.engine)
#装甲0级的一枪就爆
if self.type in (T_E1N, T_E1S, T_E2N, T_E2S, T_E3N, T_E3S, T_E4N, T_Y01, T_G01):
self.killer = bullet.who
self.bomb()
else:
self.engine.play_sound(SND_STEEL)
if self.who == WHO_ENEMY:
self.image_change({T_E4S:T_E4G, T_E4G:T_E4Y, T_E4Y:T_E4N}[self.type])
else:
self.add_fire(-1)
def test_move(self):
#产生一个前进后的坐标,并得到一个Rect
#收缩Rect(-1, -1)后,与所有地面障碍和活动目标比较,判断是允许前进
kv = DIR_CFG[self.dir]["move"]
prepos = [self.pos[0], self.pos[1]]
if kv[0] != 0:
prepos[0] += kv[0] * self.speed_level * MOVE_PIXES
if kv[1] != 0:
prepos[1] += kv[1] * self.speed_level * MOVE_PIXES
if prepos[0] < 32 or prepos[0] > 13 * 32 or prepos[1] < 32 or prepos[1] > 13 * 32:
return False
#TODO 前进正面没有障碍允许前进,所以收缩Rect
if self.dir in (DIR_UP, DIR_DOWN):
pt = [prepos[0]+4, prepos[1]]
src_rc = pygame.Rect(pt, (self.width-8, self.height))
else:
pt = [prepos[0], prepos[1]+4]
src_rc = pygame.Rect(pt, (self.width, self.height-8))
if self.dir == DIR_UP:
src_rc = src_rc.clip(src_rc.move(0, -16))
elif self.dir == DIR_DOWN:
src_rc = src_rc.clip(src_rc.move(0, 16))
elif self.dir == DIR_LEFT:
src_rc = src_rc.clip(src_rc.move(-16, 0))
else:
src_rc = src_rc.clip(src_rc.move(16, 0))
for block in self.engine.get_blocks():
if block == self or block.can_through:
continue
if src_rc.colliderect(block.get_rect()):
return False
return True
def move(self, dir):
if self.mode != 1:
return
if self.dir != dir:
self.dir = dir
self.turn_time = self.engine.timer
#调整方向时,自动纠正坐标位置
self.pos[0] = 32 + (self.pos[0] - 32 + 8) // 16 * 16
self.pos[1] = 32 + (self.pos[1] - 32 + 8) // 16 * 16
self.image_change()
elif self.engine.timer - 2 > self.turn_time and self.test_move():
self.frame_delay = 5
kv = DIR_CFG[self.dir]["move"]
if kv[0] != 0:
self.pos[0] += kv[0] * self.speed_level * MOVE_PIXES
if kv[1] != 0:
self.pos[1] += kv[1] * self.speed_level * MOVE_PIXES
if self.engine.food != None and self.engine.food.mode == 1 and self.who in PLAYER_CFG:
src_rc = pygame.Rect(self.pos, (32, 32))
if src_rc.colliderect(self.engine.food.get_rect()):
self.engine.food.eat(self)
def attack(self):
#开火(生成子弹发射, 2级火力可以发射2枚)
if self.send_bullet < self.max_bullets:
self.send_bullet += 1
Bullet(self, self.dir)
if self.who != WHO_ENEMY:
self.engine.play_sound(SND_SHOOT)
def die(self):
self.engine.kill_tank(self.who, self.die_type, self.killer)
super().die()
class Enemy(Tank):
def __init__(self, engine, who, type, pos, speed):
Tank.__init__(self, engine, who, E_NEW, pos, 5)
self.type = type
self.dir = DIR_DOWN
self.dir_fail = 0
self.can_through = True #刚出生时允许通过
self.speed_level = speed
self.travel_time = 0 #行进时间
self.policy_interval = random.randrange(300, 500, 40) #转向决策时间间隔
self.effect_time = self.engine.timer
def ai(self):
if self.test_move():
self.can_through = False
self.dir_fail = 0
if self.travel_time == 0:
self.travel_time = self.engine.timer
#长时间向时一方向行驶时,隔几秒产生一个决策,是否变向(非前进方向)
if self.engine.timer - self.travel_time > self.policy_interval:
self.travel_time = 0
dirs = list(DIR_CFG.keys());
dirs.remove(self.dir)
dirs += [DIR_LEFT, DIR_DOWN, DIR_DOWN, DIR_RIGHT]
dir = random.choice(dirs)
self.move(dir)
else:
self.move(self.dir)
else:
self.dir_fail += 1
#5次转向失败,等半秒再试
if self.dir_fail < 5 or self.dir_fail % 30 == 29:
dirs = list(DIR_CFG.keys());
dirs.remove(self.dir)
dir = random.choice(dirs)
self.move(dir)
if self.send_bullet < self.max_bullets and random.randint(0, 20) == 15:
self.attack()
def inc_counter(self, timer):
#-1销毁 0出生 1正常 2爆炸
if self.mode == 1 and not self.engine.enemy_stopped:
self.ai() #如果两个坦克夹在一起,先出生的先离开
super().inc_counter(timer)
if self.mode == 0:
if timer - 50 > self.effect_time:#闪烁10帧后状态变成正常
self.mode = 1
self.is_god = False
self.animation = not self.engine.enemy_stopped
self.image_change()
else:
if self.type == E_EXP and timer - 25 > self.effect_time:
if self.killer == -1:
self.die()
else:
self.frame_delay = 0
self.scale = False
self.animation = False
self.dir = DIR_UP #分数的图片不旋转
self.pos = [self.die_pos[0], self.die_pos[1] + 8]
next_type = {T_E1N:S_100, T_E1S:S_100, T_E2N:S_200, T_E2S:S_200, T_E3N:S_300, T_E3S:S_300, T_E4N:S_400}[self.die_type]
self.image_change(next_type)
self.effect_time = timer
if self.type in (S_100, S_200, S_300, S_400) and timer - 20 > self.effect_time:#闪烁5帧后状态销毁
self.die()
class Player(Tank):
def __init__(self, engine, who, type, level):
Tank.__init__(self, engine, who, E_NEW, [32+4*32, 32+12*32] if who == WHO_PLAYER1 else [32+8*32, 32+12*32], 5)
self.type = type
self.speed_level = 2 #速度等级
self.fire_level = 0 #初始火力等级
self.add_fire(level)
self.god_duration = MYGOD_DURATION
self.god_images = self.engine.get_frame(E_CAP)
def add_fire(self, add_level = 1):
old_level = self.fire_level
self.fire_level += add_level #火力等级(弹药速度和弹药数)
if self.fire_level > 3:self.fire_level = 3
self.max_bullets = 1 if self.fire_level < 2 else 2 #弹药数
if old_level != self.fire_level:
self.engine.players[self.who]["Level"] = self.fire_level
self.image_change(TANK_CFG[self.who][self.fire_level]) #改变形象
def set_god(self):
self.god_duration = TOGOD_DURATION #10秒
self.is_god = True
self.effect_time = self.engine.timer
def inc_counter(self, timer):
#-1销毁 0出生 1正常 2爆炸
super().inc_counter(timer)
if self.mode == 0:
if timer - 25 > self.effect_time:#闪烁10帧后状态变成正常
self.mode = 1
self.is_god = True
self.can_through = False
self.image_change()
self.effect_time = timer
self.animation = False #无方向操作时坦克履带不动(帧序号要变,但不改变帧图像)
elif self.mode == 1:
if timer - self.god_duration > self.effect_time:#闪烁5秒后状态变成正常
self.mode = 1
self.is_god = False
else:
if self.type == E_EXP and timer - 25 > self.effect_time:
self.die()
def draw(self, screen):
super().draw(screen)
if self.is_god and self.mode == 1 and not self.engine.pause:
cap_img = self.god_images[self.frame_index % 2]
screen.blit(cap_img, self.pos)
class TankFactory:
#坦克工厂
def __init__(self, engine, stage):
self.engine = engine
self.stage = stage
self.next_pos = 0
self.foods = 3 #带宝的坦克数,出一个少一个
self.player1 = None #玩家1对象(用于操作方法和开火)
self.player2 = None #玩家2对象(用于操作方法和开火)
self.new_player(WHO_PLAYER1)
self.new_player(WHO_PLAYER2)
self.new_enemy()
self.new_enemy()
self.new_enemy()
def new_player(self, who):
dict = self.engine.players[who]
if dict["Life"] > 0:
dict["Life"] -= 1
level = dict["Level"]
print(level)
type = {WHO_PLAYER1:T_Y01, WHO_PLAYER2:T_G01}[who]
self.engine.players[who]["Object"] = Player(self.engine, who, type + level, level)
def new_enemy(self):
if self.engine.enemy_count > 0:
#随机生成敌坦火力等级
if self.stage == 1:
if self.foods > 0:
type = random.choice([T_E1N, T_E1S, T_E2N, T_E3N, T_E1N, T_E3S, T_E2N, T_E2S, T_E3N])
else:
type = random.choice([T_E1N, T_E2N, T_E3N])
else:
#1/3机率生成重型坦克
if self.foods > 0:
type = random.choice([T_E1N, T_E2N, T_E3N, T_E1S, T_E2S, T_E3S, T_E4N, T_E4G, T_E4Y, T_E4S])
else:
type = random.choice([T_E1N, T_E2N, T_E3N, T_E4N, T_E4G, T_E4Y])
if type in (T_E1S, T_E2S, T_E3S, T_E4S):
self.foods -= 1
if type == T_E2N or type == T_E2S:
speed = 1
else:
speed = 0.6
self.engine.enemy_count -= 1
Enemy(self.engine, WHO_ENEMY, type, [32+((self.next_pos % 3)*6)*32, 32], speed)
self.next_pos += 1