import bpy
import random
import math
from mathutils import Vector, Matrix
from bpy.types import Operator, Panel
import time
TETROMINOS = {
'I': [
[[0,0,0,0],
[1,1,1,1],
[0,0,0,0],
[0,0,0,0]],
[[0,0,1,0],
[0,0,1,0],
[0,0,1,0],
[0,0,1,0]]
],
'O': [
[[1,1],
[1,1]]
],
'T': [
[[0,1,0],
[1,1,1],
[0,0,0]],
[[0,1,0],
[0,1,1],
[0,1,0]],
[[0,0,0],
[1,1,1],
[0,1,0]],
[[0,1,0],
[1,1,0],
[0,1,0]]
],
'S': [
[[0,1,1],
[1,1,0],
[0,0,0]],
[[0,1,0],
[0,1,1],
[0,0,1]]
],
'Z': [
[[1,1,0],
[0,1,1],
[0,0,0]],
[[0,0,1],
[0,1,1],
[0,1,0]]
],
'J': [
[[1,0,0],
[1,1,1],
[0,0,0]],
[[0,1,1],
[0,1,0],
[0,1,0]],
[[0,0,0],
[1,1,1],
[0,0,1]],
[[0,1,0],
[0,1,0],
[1,1,0]]
],
'L': [
[[0,0,1],
[1,1,1],
[0,0,0]],
[[0,1,0],
[0,1,0],
[0,1,1]],
[[0,0,0],
[1,1,1],
[1,0,0]],
[[1,1,0],
[0,1,0],
[0,1,0]]
]
}
TETROMINO_COLORS = {
'I': (0.0, 1.0, 1.0, 1.0),
'O': (1.0, 1.0, 0.0, 1.0),
'T': (0.8, 0.0, 0.8, 1.0),
'S': (0.0, 1.0, 0.0, 1.0),
'Z': (1.0, 0.0, 0.0, 1.0),
'J': (0.0, 0.0, 1.0, 1.0),
'L': (1.0, 0.5, 0.0, 1.0)
}
class TetrisBlock:
"""单个方块块的类"""
def __init__(self, position, color, is_preview=False):
self.position = Vector(position)
self.color = color
self.is_preview = is_preview
self.mesh = None
self.mesh_name = f"Tetris_Block_{id(self)}_{'preview' if is_preview else 'active'}"
self.create_mesh()
def create_mesh(self):
"""创建方块块的3D网格"""
bpy.ops.mesh.primitive_cube_add(size=0.9, location=self.position)
self.mesh = bpy.context.active_object
self.mesh.name = self.mesh_name
# 创建适用于渲染引擎的材质
mat_name = f"Block_{id(self)}_{'preview' if self.is_preview else 'active'}"
if mat_name in bpy.data.materials:
mat = bpy.data.materials[mat_name]
else:
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
for node in nodes:
nodes.remove(node)
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
output = nodes.new(type='ShaderNodeOutputMaterial')
principled.inputs['Base Color'].default_value = self.color
principled.inputs['Metallic'].default_value = 0.1
principled.inputs['Roughness'].default_value = 0.3
# 如果是预览方块,调整透明度
if self.is_preview:
principled.inputs['Alpha'].default_value = 0.6
mat.blend_method = 'BLEND'
links.new(principled.outputs['BSDF'], output.inputs['Surface'])
principled.location = (-200, 0)
output.location = (0, 0)
if self.mesh.data.materials:
self.mesh.data.materials[0] = mat
else:
self.mesh.data.materials.append(mat)
def move_to(self, new_position):
"""移动方块到新位置"""
self.position = Vector(new_position)
if self.mesh_name in bpy.data.objects:
bpy.data.objects[self.mesh_name].location = self.position
def remove(self):
"""删除方块"""
if self.mesh_name in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[self.mesh_name], do_unlink=True)
self.mesh = None
class TetrisPiece:
"""俄罗斯方块棋子类"""
def __init__(self, piece_type, position=(0, 0, 0), is_preview=False):
self.type = piece_type
self.position = Vector(position)
self.rotation = 0
self.color = TETROMINO_COLORS[piece_type]
self.is_preview = is_preview
self.blocks = []
self.create_blocks()
def create_blocks(self):
"""根据形状创建方块块"""
shape = self.get_current_shape()
for y in range(len(shape)):
for x in range(len(shape[y])):
if shape[y][x]:
block_pos = Vector((
self.position.x + x,
self.position.y + y,
0
))
block = TetrisBlock(block_pos, self.color, self.is_preview)
self.blocks.append(block)
def get_current_shape(self):
"""获取当前旋转状态下的形状"""
shapes = TETROMINOS[self.type]
return shapes[self.rotation % len(shapes)]
def rotate(self, clockwise=True):
"""旋转方块(不移动位置,只改变形状)"""
old_rotation = self.rotation
shapes = TETROMINOS[self.type]
if clockwise:
self.rotation = (self.rotation + 1) % len(shapes)
else:
self.rotation = (self.rotation - 1) % len(shapes)
# 更新所有方块的位置
self.update_blocks_positions()
return True
def update_blocks_positions(self):
"""根据当前形状更新所有方块的位置"""
shape = self.get_current_shape()
block_idx = 0
for y in range(len(shape)):
for x in range(len(shape[y])):
if shape[y][x]:
if block_idx < len(self.blocks):
new_pos = Vector((
self.position.x + x,
self.position.y + y,
0
))
self.blocks[block_idx].move_to(new_pos)
block_idx += 1
def move(self, dx, dy):
"""移动方块"""
self.position += Vector((dx, dy, 0))
# 更新所有方块的位置
for block in self.blocks:
if block.mesh_name in bpy.data.objects:
block.move_to(block.position + Vector((dx, dy, 0)))
def get_block_positions(self):
"""获取所有方块的世界坐标位置"""
positions = []
for block in self.blocks:
if block.mesh_name in bpy.data.objects:
positions.append(block.position.copy())
return positions
def get_block_grid_positions(self):
"""获取所有方块在网格中的坐标"""
positions = self.get_block_positions()
grid_positions = []
for pos in positions:
grid_positions.append((int(pos.x), int(pos.y)))
return grid_positions
def remove(self):
"""删除整个方块"""
for block in self.blocks:
block.remove()
self.blocks = []
class TetrisGame3D:
"""3D俄罗斯方块游戏类"""
def __init__(self, width=10, height=20):
self.width = width
self.height = height
self.grid = [[None for _ in range(width)] for _ in range(height)]
self.current_piece = None
self.next_piece_type = None
self.next_preview = None
self.score = 0
self.level = 1
self.lines_cleared = 0 # 已清除的行数
self.game_over = False
self.paused = False # 暂停状态
self.last_drop_time = time.time()
self.drop_interval = 1.0
self.grid_objects = []
self.create_grid()
self.init_game()
def create_grid(self):
"""创建游戏网格(只创建网格线,不创建背景板)"""
# 删除旧的网格对象
for obj_name in list(bpy.data.objects.keys()):
if obj_name.startswith("Grid_"):
bpy.data.objects.remove(bpy.data.objects[obj_name], do_unlink=True)
# 创建网格线
grid_color = (0.5, 0.5, 0.5, 0.3)
for x in range(self.width + 1):
bpy.ops.mesh.primitive_cube_add(size=0.02)
line = bpy.context.active_object
line.name = f"Grid_Line_X_{x}"
line.location = (x - 0.5, self.height/2 - 0.5, -0.5)
line.dimensions = (0.02, self.height, 0.02)
mat = bpy.data.materials.new(name=f"Grid_Line_Mat_X_{x}")
mat.diffuse_color = grid_color
line.data.materials.append(mat)
self.grid_objects.append(line.name)
for y in range(self.height + 1):
bpy.ops.mesh.primitive_cube_add(size=0.02)
line = bpy.context.active_object
line.name = f"Grid_Line_Y_{y}"
line.location = (self.width/2 - 0.5, y - 0.5, -0.5)
line.dimensions = (self.width, 0.02, 0.02)
mat = bpy.data.materials.new(name=f"Grid_Line_Mat_Y_{y}")
mat.diffuse_color = grid_color
line.data.materials.append(mat)
self.grid_objects.append(line.name)
def init_game(self):
"""初始化游戏"""
self.cleanup_old_blocks()
self.grid = [[None for _ in range(self.width)] for _ in range(self.height)]
self.score = 0
self.level = 1
self.lines_cleared = 0
self.game_over = False
self.paused = False
self.drop_interval = 1.0
# 创建新方块
self.next_piece_type = self.get_random_piece_type()
self.spawn_new_piece()
def cleanup_old_blocks(self):
"""清理旧的游戏方块"""
# 清理固定方块和移动方块
objects_to_remove = []
for obj in bpy.data.objects:
if (obj.name.startswith("Fixed_Block_") or
obj.name.startswith("Tetris_Block_")):
objects_to_remove.append(obj)
for obj in objects_to_remove:
bpy.data.objects.remove(obj, do_unlink=True)
# 清理游戏结束文本和暂停文本
for text_name in ["Game_Over_Text", "Paused_Text"]:
if text_name in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[text_name], do_unlink=True)
def get_random_piece_type(self):
"""随机获取一个方块类型"""
return random.choice(list(TETROMINOS.keys()))
def spawn_new_piece(self):
"""生成新的当前方块"""
# 清理旧的预览方块
if self.next_preview:
self.next_preview.remove()
current_type = self.next_piece_type
self.current_piece = TetrisPiece(current_type, position=(self.width//2 - 1, self.height - 4, 0))
self.next_piece_type = self.get_random_piece_type()
self.next_preview = TetrisPiece(self.next_piece_type, position=(11, 15, 0), is_preview=True)
def check_collision(self, piece, dx=0, dy=0):
"""检查碰撞"""
# 获取方块移动后的网格位置
grid_positions = []
for block in piece.blocks:
if block.mesh_name in bpy.data.objects:
new_x = int(block.position.x + dx)
new_y = int(block.position.y + dy)
grid_positions.append((new_x, new_y))
for x, y in grid_positions:
# 检查边界
if x < 0 or x >= self.width:
return True
if y < 0:
return True
# 检查与其他方块的碰撞
if y < self.height and self.grid[y][x] is not None:
return True
return False
def move_piece(self, dx, dy):
"""移动当前方块"""
if not self.current_piece or self.game_over or self.paused:
return False
if not self.check_collision(self.current_piece, dx, dy):
self.current_piece.move(dx, dy)
return True
return False
def rotate_piece(self, clockwise=True):
"""旋转当前方块"""
if not self.current_piece or self.game_over or self.paused:
return False
# 保存当前旋转状态
old_rotation = self.current_piece.rotation
# 尝试旋转
if clockwise:
self.current_piece.rotation = (self.current_piece.rotation + 1) % len(TETROMINOS[self.current_piece.type])
else:
self.current_piece.rotation = (self.current_piece.rotation - 1) % len(TETROMINOS[self.current_piece.type])
self.current_piece.update_blocks_positions()
# 如果旋转后碰撞,尝试左右移动来避免碰撞(墙壁踢)
if self.check_collision(self.current_piece):
# 尝试向右移动
if not self.check_collision(self.current_piece, 1, 0):
self.current_piece.move(1, 0)
# 尝试向左移动
elif not self.check_collision(self.current_piece, -1, 0):
self.current_piece.move(-1, 0)
# 如果都不行,恢复旋转
else:
self.current_piece.rotation = old_rotation
self.current_piece.update_blocks_positions()
return False
return True
def hard_drop(self):
"""硬降落"""
if not self.current_piece or self.game_over or self.paused:
return
# 一直下落直到碰撞
while not self.check_collision(self.current_piece, 0, -1):
self.current_piece.move(0, -1)
self.place_piece()
def place_piece(self):
"""将当前方块放置到网格中"""
# 记录所有方块的位置
placed_positions = []
for block in self.current_piece.blocks:
if block.mesh_name in bpy.data.objects:
x = int(round(block.position.x))
y = int(round(block.position.y))
if 0 <= x < self.width and 0 <= y < self.height:
self.grid[y][x] = self.current_piece.color
placed_positions.append((x, y))
# 创建固定方块
for x, y in placed_positions:
self.create_fixed_block(x, y, self.current_piece.color)
# 检查并清除完整的行
lines_cleared = self.clear_lines()
# 更新行数和分数
if lines_cleared > 0:
self.lines_cleared += lines_cleared
# 计分规则:一行100分,两行300分,三行500分,四行800分
if lines_cleared == 1:
self.score += 100 * self.level
elif lines_cleared == 2:
self.score += 300 * self.level
elif lines_cleared == 3:
self.score += 500 * self.level
elif lines_cleared >= 4:
self.score += 800 * self.level
# 每清除10行升一级
if self.lines_cleared >= self.level * 10:
self.level += 1
self.drop_interval = max(0.1, self.drop_interval * 0.8) # 速度加快
# 生成新方块
self.current_piece.remove()
self.spawn_new_piece()
# 检查游戏是否结束
if self.check_collision(self.current_piece):
self.game_over = True
self.create_game_over_text()
def create_fixed_block(self, x, y, color):
"""创建单个固定方块"""
block_name = f"Fixed_Block_{x}_{y}_{id(self)}"
# 检查是否已经有方块
if block_name not in bpy.data.objects:
bpy.ops.mesh.primitive_cube_add(size=0.9)
block = bpy.context.active_object
block.name = block_name
block.location = (x, y, 0)
# 创建适用于渲染的材质
mat_name = f"Fixed_Mat_{x}_{y}_{id(self)}"
if mat_name in bpy.data.materials:
mat = bpy.data.materials[mat_name]
else:
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# 清除默认节点
for node in nodes:
nodes.remove(node)
# 创建新的材质节点
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
output = nodes.new(type='ShaderNodeOutputMaterial')
# 设置颜色和属性
principled.inputs['Base Color'].default_value = color
principled.inputs['Metallic'].default_value = 0.15
principled.inputs['Roughness'].default_value = 0.25
# 连接节点
links.new(principled.outputs['BSDF'], output.inputs['Surface'])
# 设置节点位置
principled.location = (-200, 0)
output.location = (0, 0)
block.data.materials.append(mat)
def clear_lines(self):
"""清除完整的行并返回清除的行数"""
lines_to_clear = []
for y in range(self.height):
if all(cell is not None for cell in self.grid[y]):
lines_to_clear.append(y)
# 清除行
for line in sorted(lines_to_clear, reverse=True):
# 删除该行的3D方块
for x in range(self.width):
# 查找并删除所有以Fixed_Block_x_line开头的对象
objects_to_remove = []
for obj in bpy.data.objects:
if obj.name.startswith(f"Fixed_Block_{x}_{line}_"):
objects_to_remove.append(obj)
for obj in objects_to_remove:
bpy.data.objects.remove(obj, do_unlink=True)
# 删除该行数据
del self.grid[line]
# 在顶部添加新的空行
self.grid.append([None for _ in range(self.width)])
# 重新创建所有固定方块
if lines_to_clear:
self.recreate_all_fixed_blocks()
return len(lines_to_clear)
def recreate_all_fixed_blocks(self):
"""重新创建所有固定方块"""
# 删除所有固定方块
objects_to_remove = []
for obj in bpy.data.objects:
if obj.name.startswith("Fixed_Block_"):
objects_to_remove.append(obj)
for obj in objects_to_remove:
bpy.data.objects.remove(obj, do_unlink=True)
# 重新创建
for y in range(self.height):
for x in range(self.width):
if self.grid[y][x] is not None:
self.create_fixed_block(x, y, self.grid[y][x])
def create_game_over_text(self):
"""创建游戏结束文本"""
bpy.ops.object.text_add()
text_obj = bpy.context.active_object
text_obj.name = "Game_Over_Text"
text_obj.data.body = "菜鸡\n按 R 重开"
text_obj.data.align_x = 'CENTER'
text_obj.data.align_y = 'CENTER'
text_obj.location = (self.width/2 - 0.5, self.height/2 - 0.5, 2)
text_obj.scale = (0.5, 0.5, 0.5)
mat = bpy.data.materials.new(name="Game_Over_Text_Mat")
mat.diffuse_color = (1.0, 0.0, 0.0, 1.0)
text_obj.data.materials.append(mat)
def toggle_pause(self):
"""切换暂停状态"""
self.paused = not self.paused
if self.paused:
# 创建暂停文本
bpy.ops.object.text_add()
text_obj = bpy.context.active_object
text_obj.name = "Paused_Text"
text_obj.data.body = "暂停\n按 P 恢复"
text_obj.data.align_x = 'CENTER'
text_obj.data.align_y = 'CENTER'
text_obj.location = (self.width/2 - 0.5, self.height/2 - 0.5, 2)
text_obj.scale = (0.5, 0.5, 0.5)
# 创建材质
mat = bpy.data.materials.new(name="Paused_Text_Mat")
mat.diffuse_color = (1.0, 1.0, 0.0, 1.0)
text_obj.data.materials.append(mat)
else:
# 删除暂停文本
if "Paused_Text" in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects["Paused_Text"], do_unlink=True)
def update(self):
"""更新游戏状态"""
if self.game_over or self.paused:
return
current_time = time.time()
if current_time - self.last_drop_time > self.drop_interval:
if not self.move_piece(0, -1):
self.place_piece()
self.last_drop_time = current_time
def restart(self):
"""重新开始游戏"""
self.cleanup_old_blocks()
if self.next_preview:
self.next_preview.remove()
self.init_game()
# 全局游戏实例
tetris_game_3d = None
class TETRIS_OT_StartGame(Operator):
"""开始游戏"""
bl_idname = "tetris.start_game"
bl_label = "Start 3D Tetris"
bl_description = "Start a 3D Tetris game in the viewport"
timer = None
def modal(self, context, event):
global tetris_game_3d
if not tetris_game_3d:
return {'CANCELLED'}
# 游戏控制
if event.type == 'R' and event.value == 'PRESS':
if tetris_game_3d.game_over or tetris_game_3d.paused:
tetris_game_3d.restart()
return {'RUNNING_MODAL'}
if event.type == 'P' and event.value == 'PRESS': # 暂停/继续
tetris_game_3d.toggle_pause()
return {'RUNNING_MODAL'}
if event.type in {'LEFT_ARROW', 'A'} and event.value == 'PRESS': # 左移
tetris_game_3d.move_piece(-1, 0)
return {'RUNNING_MODAL'}
if event.type in {'RIGHT_ARROW', 'D'} and event.value == 'PRESS': # 右移
tetris_game_3d.move_piece(1, 0)
return {'RUNNING_MODAL'}
if event.type in {'DOWN_ARROW', 'S'} and event.value == 'PRESS': # 加速下落
tetris_game_3d.move_piece(0, -1)
return {'RUNNING_MODAL'}
if event.type == 'SPACE' and event.value == 'PRESS': # 硬降落
tetris_game_3d.hard_drop()
return {'RUNNING_MODAL'}
if event.type in {'UP_ARROW', 'J', 'W'} and event.value == 'PRESS': # 旋转
tetris_game_3d.rotate_piece(clockwise=True)
return {'RUNNING_MODAL'}
if event.type == 'TIMER':
tetris_game_3d.update()
return {'PASS_THROUGH'}
def execute(self, context):
global tetris_game_3d
# 清理可能存在的旧游戏
if tetris_game_3d:
tetris_game_3d = None
self.cleanup_game_objects()
# 创建新游戏
tetris_game_3d = TetrisGame3D(width=10, height=20)
# 启动定时器
wm = context.window_manager
self.timer = wm.event_timer_add(0.1, window=context.window)
wm.modal_handler_add(self)
self.report({'INFO'}, "3D Tetris Game Started! Use arrow keys or WASD to move, Z/X or J/K to rotate, P to pause")
return {'RUNNING_MODAL'}
def cleanup_game_objects(self):
"""清理游戏对象"""
objects_to_remove = []
for obj in bpy.data.objects:
if any(name in obj.name for name in ["Tetris_", "Grid_", "Fixed_", "Game_Over", "Paused_", "Score_", "Level_", "Lines_", "Preview_", "Next_"]):
objects_to_remove.append(obj)
for obj in objects_to_remove:
bpy.data.objects.remove(obj, do_unlink=True)
def cancel(self, context):
"""取消游戏"""
global tetris_game_3d
wm = context.window_manager
if self.timer:
wm.event_timer_remove(self.timer)
# 清理游戏对象
if tetris_game_3d:
try:
tetris_game_3d.cleanup_old_blocks()
if tetris_game_3d.next_preview:
tetris_game_3d.next_preview.remove()
except:
pass # 忽略清理错误
self.report({'INFO'}, "Game stopped")
return {'CANCELLED'}
class TETRIS_OT_ResetGame(Operator):
"""重置游戏"""
bl_idname = "tetris.reset_game"
bl_label = "Reset 3D Tetris"
bl_description = "Reset the 3D Tetris game"
def execute(self, context):
global tetris_game_3d
if tetris_game_3d:
tetris_game_3d.restart()
self.report({'INFO'}, "Game reset! Score cleared to 0.")
else:
self.report({'WARNING'}, "No game to reset!")
return {'FINISHED'}
class TETRIS_OT_TogglePause(Operator):
"""暂停/继续游戏"""
bl_idname = "tetris.toggle_pause"
bl_label = "Pause/Resume Game"
bl_description = "暂停和恢复游戏"
def execute(self, context):
global tetris_game_3d
if tetris_game_3d:
tetris_game_3d.toggle_pause()
status = "paused" if tetris_game_3d.paused else "resumed"
self.report({'INFO'}, f"Game {status}!")
else:
self.report({'WARNING'}, "No game to pause!")
return {'FINISHED'}
class TETRIS_OT_CleanupGame(Operator):
"""清理游戏"""
bl_idname = "tetris.cleanup_game"
bl_label = "Cleanup Game"
bl_description = "移除所有游戏物体"
def execute(self, context):
global tetris_game_3d
objects_to_remove = []
for obj in bpy.data.objects:
if any(name in obj.name for name in [
"Tetris_", "Grid_", "Fixed_", "Game_Over",
"Paused_", "Score_", "Level_", "Lines_",
"Next_", "Preview_"
]):
objects_to_remove.append(obj)
removed_count = 0
for obj in objects_to_remove:
try:
bpy.data.objects.remove(obj, do_unlink=True)
removed_count += 1
except:
pass
if tetris_game_3d:
tetris_game_3d = None
self.report({'INFO'}, f"Cleaned up {removed_count} game objects")
return {'FINISHED'}
class TETRIS_PT_GamePanel(Panel):
bl_label = "3D Tetris Game"
bl_idname = "TETRIS_PT_GamePanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "俄方块"
def draw(self, context):
layout = self.layout
global tetris_game_3d
layout.operator("tetris.start_game", text="开始游戏", icon='PLAY')
if tetris_game_3d:
row = layout.row()
row.operator("tetris.reset_game", text="重置游戏", icon='FILE_REFRESH')
pause_text = "恢复" if tetris_game_3d.paused else "暂停"
row.operator("tetris.toggle_pause", text=pause_text, icon='PAUSE')
layout.separator()
layout.operator("tetris.cleanup_game", text="清除游戏", icon='TRASH')
if tetris_game_3d:
box = layout.box()
box.label(text="游戏状态:", icon='INFO')
box.label(text=f"游戏积分: {tetris_game_3d.score}")
box.label(text=f"游戏等级: {tetris_game_3d.level}")
box.label(text=f"清除行数: {tetris_game_3d.lines_cleared}")
if tetris_game_3d.game_over:
box.label(text="游戏结束", icon='CANCEL')
box.label(text="按 R 重启游戏")
elif tetris_game_3d.paused:
box.label(text="暂停", icon='PAUSE')
box.label(text="按 P 恢复游戏")
box = layout.box()
box.label(text="控制:", icon='HAND')
box.label(text="←/→ 或 A/D - 左右移动")
box.label(text="↓ 或 S - 快速下移")
box.label(text="W 或 ↑ 或 J- 旋转")
box.label(text="SPACE - 坠落")
box.label(text="P - 暂停/恢复")
def register():
bpy.utils.register_class(TETRIS_OT_StartGame)
bpy.utils.register_class(TETRIS_OT_ResetGame)
bpy.utils.register_class(TETRIS_OT_TogglePause)
bpy.utils.register_class(TETRIS_OT_CleanupGame)
bpy.utils.register_class(TETRIS_PT_GamePanel)
def unregister():
bpy.utils.unregister_class(TETRIS_OT_StartGame)
bpy.utils.unregister_class(TETRIS_OT_ResetGame)
bpy.utils.unregister_class(TETRIS_OT_TogglePause)
bpy.utils.unregister_class(TETRIS_OT_CleanupGame)
bpy.utils.unregister_class(TETRIS_PT_GamePanel)
global tetris_game_3d
if tetris_game_3d:
tetris_game_3d = None
objects_to_remove = []
for obj in bpy.data.objects:
if any(x in obj.name for x in ["Tetris_", "Grid_", "Fixed_", "Game_", "Paused_", "Score_", "Level_", "Lines_", "Next_", "Preview_"]):
objects_to_remove.append(obj)
for obj in objects_to_remove:
try:
bpy.data.objects.remove(obj, do_unlink=True)
except:
pass
if __name__ == "__main__":
register()
Blender里玩俄罗斯方块(源码)

