Python 游戏构建自动化脚本完全指南:从压缩到多渠道打包

引言

在游戏开发过程中,频繁地构建、打包、发布是日常工作的一部分。手动执行这些操作不仅耗时,还容易出错。通过 Python 脚本实现构建自动化,可以大大提高工作效率,确保打包过程的一致性和可靠性。本文将详细介绍如何使用 Python 编写游戏构建自动化脚本,包括文件压缩、多渠道打包等实用功能。

为什么需要构建自动化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─────────────────────────────────────────────────────────────────────┐
│ 手动构建 vs 自动化构建 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 手动构建流程 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 1. 点击 │──►│ 2. 等待 │──►│ 3. 复制 │──►│ 4. 压缩 │ │
│ │ 构建 │ │ 构建 │ │ 文件 │ │ 打包 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │
│ 耗时:15-30分钟/次 ▼ │
│ 错误率:高(容易遗漏步骤) 容易出错 │
│ 可重复性:差 需要重来 │
│ │
│ ═══════════════════════════════════════════════════════════════ │
│ │
│ 自动化构建流程 │
│ ┌─────────┐ │
│ │ 运行脚本 │──────────────────────────────────────────► │
│ └─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 自动化执行: │ │
│ │ • 清理旧文件 │ │
│ │ • 调用 Cocos Creator 构建 │ │
│ │ • 复制渠道特定资源 │ │
│ │ • 压缩打包 │ │
│ │ • 上传服务器(可选) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 耗时:2-5分钟/次(无人值守) │
│ 错误率:极低(脚本确保一致性) │
│ 可重复性:完美(每次结果一致) │
│ │
└─────────────────────────────────────────────────────────────────────┘

基础文件压缩脚本

使用 zipfile 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
基础 ZIP 压缩工具
用于压缩游戏构建输出目录
"""

import os
import zipfile
import sys


def zip_directory(startdir, file_news, basedir=""):
"""
压缩文件夹为 ZIP 文件

Args:
startdir: 要压缩的源文件夹路径
file_news: 输出的 ZIP 文件名
basedir: ZIP 文件中的基础目录名(可选)

Returns:
bool: 压缩是否成功
"""
try:
# 创建 ZIP 文件,使用 DEFLATED 压缩算法
z = zipfile.ZipFile(file_news, 'w', zipfile.ZIP_DEFLATED)

# 遍历源文件夹
for dirpath, dirnames, filenames in os.walk(startdir):
print(f"Processing directory: {dirpath}")

# 计算在 ZIP 中的相对路径
fpath = dirpath.replace(startdir, '')
fpath = fpath and fpath + os.sep or ''

print(f" Relative path in ZIP: {fpath}")

# 添加文件到 ZIP
for filename in filenames:
file_path = os.path.join(dirpath, filename)
arcname = basedir + os.sep + fpath + filename if basedir else fpath + filename

print(f" Adding file: {filename} -> {arcname}")
z.write(file_path, arcname)

z.close()
print(f"\n✓ ZIP created successfully: {file_news}")
return True

except Exception as e:
print(f"\n✗ Error creating ZIP: {e}")
return False


# 使用示例
if __name__ == '__main__':
# 配置
startdir = './build/web-mobile/' # 要压缩的文件夹
file_news = 'game-release.zip' # 输出文件名
basedir = 'web-mobile' # ZIP 中的目录名

# 执行压缩
success = zip_directory(startdir, file_news, basedir)
sys.exit(0 if success else 1)

带进度显示的压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
带进度显示的 ZIP 压缩工具
"""

import os
import zipfile
from tqdm import tqdm # 需要安装: pip install tqdm


def zip_with_progress(source_dir, output_file, basedir=""):
"""
带进度条的文件夹压缩
"""
# 首先统计文件总数
total_files = 0
for root, dirs, files in os.walk(source_dir):
total_files += len(files)

print(f"Found {total_files} files to compress")

# 创建 ZIP 文件
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zf:
# 使用 tqdm 显示进度
with tqdm(total=total_files, desc="Compressing") as pbar:
for dirpath, dirnames, filenames in os.walk(source_dir):
# 计算相对路径
arcroot = dirpath.replace(source_dir, '')
arcroot = arcroot.lstrip(os.sep)

for filename in filenames:
filepath = os.path.join(dirpath, filename)
arcname = os.path.join(basedir, arcroot, filename) if basedir else os.path.join(arcroot, filename)

# 添加文件
zf.write(filepath, arcname)

# 更新进度
file_size = os.path.getsize(filepath)
pbar.update(1)
pbar.set_postfix({"Size": f"{file_size / 1024:.1f} KB"})

# 显示结果
original_size = sum(
os.path.getsize(os.path.join(dirpath, filename))
for dirpath, dirnames, filenames in os.walk(source_dir)
for filename in filenames
)
compressed_size = os.path.getsize(output_file)

print(f"\n✓ Compression complete!")
print(f" Original size: {original_size / 1024 / 1024:.2f} MB")
print(f" Compressed size: {compressed_size / 1024 / 1024:.2f} MB")
print(f" Compression ratio: {compressed_size / original_size * 100:.1f}%")


# 使用示例
if __name__ == '__main__':
zip_with_progress(
source_dir='./build/web-mobile/',
output_file='game-release.zip',
basedir='web-mobile'
)

多渠道打包系统

渠道配置管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
多渠道打包系统
支持不同渠道的资源替换和配置
"""

import os
import json
import shutil
import zipfile
from datetime import datetime


class ChannelConfig:
"""渠道配置类"""

def __init__(self, config_file='channels.json'):
self.config_file = config_file
self.channels = self.load_config()

def load_config(self):
"""加载渠道配置"""
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
return json.load(f)
return self.get_default_config()

def get_default_config(self):
"""默认渠道配置"""
return {
"google": {
"name": "Google Play",
"package_name": "com.yourgame.google",
"app_name": "Your Game",
"icon": "channels/google/icon.png",
"splash": "channels/google/splash.png",
"sdk_config": {
"analytics": "firebase",
"ads": "admob"
},
"extra_files": [
"channels/google/google-services.json"
]
},
"huawei": {
"name": "Huawei AppGallery",
"package_name": "com.yourgame.huawei",
"app_name": "Your Game",
"icon": "channels/huawei/icon.png",
"splash": "channels/huawei/splash.png",
"sdk_config": {
"analytics": "hms",
"ads": "huawei_ads"
},
"extra_files": [
"channels/huawei/agconnect-services.json"
]
},
"xiaomi": {
"name": "Xiaomi Store",
"package_name": "com.yourgame.xiaomi",
"app_name": "Your Game",
"icon": "channels/xiaomi/icon.png",
"splash": "channels/xiaomi/splash.png",
"sdk_config": {
"analytics": "xiaomi",
"ads": "xiaomi_ads"
},
"extra_files": []
}
}

def save_config(self):
"""保存配置到文件"""
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.channels, f, indent=2, ensure_ascii=False)

def get_channel(self, channel_id):
"""获取指定渠道配置"""
return self.channels.get(channel_id)


class ChannelPackager:
"""渠道打包器"""

def __init__(self, build_dir='./build/web-mobile/'):
self.build_dir = build_dir
self.config = ChannelConfig()
self.output_dir = './output/'

# 确保输出目录存在
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)

def package_channel(self, channel_id, version='1.0.0'):
"""
为指定渠道打包

Args:
channel_id: 渠道标识
version: 版本号

Returns:
str: 输出文件路径
"""
channel = self.config.get_channel(channel_id)
if not channel:
raise ValueError(f"Unknown channel: {channel_id}")

print(f"\n{'='*60}")
print(f"Packaging for channel: {channel['name']}")
print(f"{'='*60}\n")

# 1. 创建临时目录
temp_dir = f"./temp_{channel_id}/"
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
shutil.copytree(self.build_dir, temp_dir)

# 2. 替换渠道特定资源
self.replace_channel_resources(temp_dir, channel)

# 3. 修改配置文件
self.update_config_files(temp_dir, channel, version)

# 4. 复制额外文件
self.copy_extra_files(temp_dir, channel)

# 5. 生成版本信息
self.generate_version_info(temp_dir, channel, version)

# 6. 压缩打包
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_name = f"game_{channel_id}_v{version}_{timestamp}.zip"
output_path = os.path.join(self.output_dir, output_name)

self.create_zip(temp_dir, output_path, channel_id)

# 7. 清理临时目录
shutil.rmtree(temp_dir)

print(f"\n✓ Package created: {output_path}")
return output_path

def replace_channel_resources(self, target_dir, channel):
"""替换渠道特定资源"""
print("Replacing channel resources...")

# 替换图标
if 'icon' in channel and os.path.exists(channel['icon']):
icon_target = os.path.join(target_dir, 'icon.png')
shutil.copy2(channel['icon'], icon_target)
print(f" ✓ Icon replaced: {channel['icon']}")

# 替换启动图
if 'splash' in channel and os.path.exists(channel['splash']):
splash_target = os.path.join(target_dir, 'splash.png')
shutil.copy2(channel['splash'], splash_target)
print(f" ✓ Splash replaced: {channel['splash']}")

def update_config_files(self, target_dir, channel, version):
"""更新配置文件"""
print("Updating configuration files...")

# 更新 config.json(如果存在)
config_file = os.path.join(target_dir, 'config.json')
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)

# 更新配置
config['packageName'] = channel['package_name']
config['version'] = version
config['channel'] = channel['name']
config['sdk'] = channel.get('sdk_config', {})

with open(config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)

print(f" ✓ Config updated: {config_file}")

def copy_extra_files(self, target_dir, channel):
"""复制额外文件"""
if 'extra_files' not in channel:
return

print("Copying extra files...")
for file_path in channel['extra_files']:
if os.path.exists(file_path):
target_path = os.path.join(target_dir, os.path.basename(file_path))
shutil.copy2(file_path, target_path)
print(f" ✓ Copied: {file_path}")
else:
print(f" ⚠ Not found: {file_path}")

def generate_version_info(self, target_dir, channel, version):
"""生成版本信息文件"""
version_info = {
'channel': channel['name'],
'channel_id': channel.get('id', 'unknown'),
'version': version,
'build_time': datetime.now().isoformat(),
'package_name': channel['package_name']
}

version_file = os.path.join(target_dir, 'version.json')
with open(version_file, 'w', encoding='utf-8') as f:
json.dump(version_info, f, indent=2, ensure_ascii=False)

print(f" ✓ Version info generated")

def create_zip(self, source_dir, output_file, basedir):
"""创建 ZIP 文件"""
print(f"\nCreating ZIP archive...")

with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zf:
for dirpath, dirnames, filenames in os.walk(source_dir):
arcroot = dirpath.replace(source_dir, '').lstrip(os.sep)

for filename in filenames:
filepath = os.path.join(dirpath, filename)
arcname = os.path.join(basedir, arcroot, filename)
zf.write(filepath, arcname)

# 显示压缩结果
size = os.path.getsize(output_file)
print(f" ✓ Archive created: {size / 1024 / 1024:.2f} MB")

def package_all_channels(self, version='1.0.0'):
"""为所有渠道打包"""
results = {}

for channel_id in self.config.channels:
try:
output_path = self.package_channel(channel_id, version)
results[channel_id] = {
'success': True,
'path': output_path
}
except Exception as e:
results[channel_id] = {
'success': False,
'error': str(e)
}

# 打印汇总
print(f"\n{'='*60}")
print("Packaging Summary")
print(f"{'='*60}")

for channel_id, result in results.items():
status = "✓ SUCCESS" if result['success'] else "✗ FAILED"
print(f"{channel_id:15} {status}")
if result['success']:
print(f" Output: {result['path']}")
else:
print(f" Error: {result['error']}")

return results


# 使用示例
if __name__ == '__main__':
packager = ChannelPackager()

# 为单个渠道打包
# packager.package_channel('google', version='1.2.3')

# 为所有渠道打包
packager.package_all_channels(version='1.2.3')

完整的构建流水线

集成 Cocos Creator 构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
完整的游戏构建流水线
包含:构建 -> 压缩 -> 多渠道打包 -> 上传
"""

import os
import sys
import subprocess
import shutil
import json
import zipfile
from datetime import datetime
from pathlib import Path


class GameBuildPipeline:
"""游戏构建流水线"""

def __init__(self, config_file='build_config.json'):
self.config = self.load_build_config(config_file)
self.project_path = self.config.get('project_path', './')
self.build_path = self.config.get('build_path', './build/')
self.output_path = self.config.get('output_path', './output/')

# 确保目录存在
os.makedirs(self.output_path, exist_ok=True)

def load_build_config(self, config_file):
"""加载构建配置"""
default_config = {
'project_path': './',
'build_path': './build/',
'output_path': './output/',
'cocos_creator_path': 'C:/CocosDashboard/resources/.editors/Creator/2.4.6/CocosCreator.exe',
'platforms': ['web-mobile', 'android', 'ios'],
'channels': ['google', 'huawei', 'xiaomi'],
'upload_enabled': False,
'upload_config': {
'ftp_host': 'your-ftp-server.com',
'ftp_user': 'username',
'ftp_pass': 'password',
'remote_path': '/uploads/'
}
}

if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
return {**default_config, **json.load(f)}

return default_config

def build_project(self, platform='web-mobile', version='1.0.0'):
"""
调用 Cocos Creator 构建项目
"""
print(f"\n{'='*60}")
print(f"Building for platform: {platform}")
print(f"{'='*60}\n")

creator_path = self.config['cocos_creator_path']

# 构建参数
build_params = {
'platform': platform,
'buildPath': self.build_path,
'debug': False,
'md5Cache': True,
'sourceMaps': False
}

# 保存构建参数到临时文件
param_file = './temp_build_params.json'
with open(param_file, 'w') as f:
json.dump(build_params, f)

# 调用 Cocos Creator 命令行构建
cmd = [
creator_path,
'--path', self.project_path,
'--build',
'configPath=' + os.path.abspath(param_file)
]

print(f"Executing: {' '.join(cmd)}\n")

try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
print(result.stdout)
print(f"\n✓ Build completed for {platform}")
return True
except subprocess.CalledProcessError as e:
print(f"\n✗ Build failed:")
print(e.stdout)
print(e.stderr)
return False
finally:
# 清理临时文件
if os.path.exists(param_file):
os.remove(param_file)

def compress_build(self, platform, version):
"""
压缩构建输出
"""
print(f"\nCompressing build output...")

source_dir = os.path.join(self.build_path, platform)
if not os.path.exists(source_dir):
print(f"✗ Build directory not found: {source_dir}")
return None

timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
zip_name = f"game_{platform}_v{version}_{timestamp}.zip"
zip_path = os.path.join(self.output_path, zip_name)

# 创建 ZIP
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for dirpath, dirnames, filenames in os.walk(source_dir):
arcroot = dirpath.replace(source_dir, '').lstrip(os.sep)

for filename in filenames:
filepath = os.path.join(dirpath, filename)
arcname = os.path.join(platform, arcroot, filename)
zf.write(filepath, arcname)

size_mb = os.path.getsize(zip_path) / 1024 / 1024
print(f"✓ Compressed: {zip_name} ({size_mb:.2f} MB)")

return zip_path

def run_full_pipeline(self, version='1.0.0'):
"""
运行完整的构建流水线
"""
print(f"\n{'#'*60}")
print(f"# Starting Build Pipeline - Version {version}")
print(f"{'#'*60}")

results = {
'version': version,
'timestamp': datetime.now().isoformat(),
'builds': []
}

for platform in self.config['platforms']:
# 1. 构建
if not self.build_project(platform, version):
results['builds'].append({
'platform': platform,
'status': 'failed',
'stage': 'build'
})
continue

# 2. 压缩
zip_file = self.compress_build(platform, version)
if not zip_file:
results['builds'].append({
'platform': platform,
'status': 'failed',
'stage': 'compress'
})
continue

results['builds'].append({
'platform': platform,
'status': 'success',
'output': zip_file
})

# 保存构建报告
self.save_build_report(results)

# 打印汇总
self.print_summary(results)

return results

def save_build_report(self, results):
"""保存构建报告"""
report_file = os.path.join(self.output_path, 'build_report.json')
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"\n✓ Build report saved: {report_file}")

def print_summary(self, results):
"""打印构建汇总"""
print(f"\n{'='*60}")
print("Build Pipeline Summary")
print(f"{'='*60}")

success_count = sum(1 for b in results['builds'] if b['status'] == 'success')
total_count = len(results['builds'])

print(f"Version: {results['version']}")
print(f"Time: {results['timestamp']}")
print(f"Success: {success_count}/{total_count}")
print()

for build in results['builds']:
status_icon = "✓" if build['status'] == 'success' else "✗"
print(f"{status_icon} {build['platform']:15} {build['status'].upper()}")
if build['status'] == 'success':
print(f" Output: {build['output']}")
elif build['status'] == 'failed':
print(f" Failed at: {build.get('stage', 'unknown')}")


# 主函数
if __name__ == '__main__':
version = sys.argv[1] if len(sys.argv) > 1 else '1.0.0'

pipeline = GameBuildPipeline()
pipeline.run_full_pipeline(version)

实用工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
构建脚本实用工具函数
"""

import os
import hashlib
import json
from pathlib import Path


def calculate_md5(file_path):
"""计算文件 MD5"""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()


def generate_file_manifest(directory, output_file='manifest.json'):
"""
生成文件清单(用于热更新)
"""
manifest = {
'version': '1.0.0',
'packageUrl': 'http://your-server.com/remote-asset/',
'remoteManifestUrl': 'http://your-server.com/project.manifest',
'remoteVersionUrl': 'http://your-server.com/version.manifest',
'assets': {},
'searchPaths': []
}

for dirpath, dirnames, filenames in os.walk(directory):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
rel_path = os.path.relpath(filepath, directory)

manifest['assets'][rel_path] = {
'size': os.path.getsize(filepath),
'md5': calculate_md5(filepath)
}

with open(output_file, 'w', encoding='utf-8') as f:
json.dump(manifest, f, indent=2)

print(f"✓ Manifest generated: {output_file}")
print(f" Total files: {len(manifest['assets'])}")

return manifest


def clean_directory(directory, keep_extensions=None):
"""
清理目录,只保留指定扩展名的文件
"""
if keep_extensions is None:
keep_extensions = ['.js', '.css', '.html', '.png', '.jpg', '.json']

removed_count = 0

for dirpath, dirnames, filenames in os.walk(directory):
for filename in filenames:
ext = os.path.splitext(filename)[1].lower()
if ext not in keep_extensions:
filepath = os.path.join(dirpath, filename)
os.remove(filepath)
removed_count += 1
print(f" Removed: {filepath}")

print(f"✓ Cleanup complete, removed {removed_count} files")


def get_directory_size(directory):
"""获取目录大小(字节)"""
total = 0
for dirpath, dirnames, filenames in os.walk(directory):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
total += os.path.getsize(filepath)
return total


def format_size(size_bytes):
"""格式化文件大小显示"""
for unit in ['B', 'KB', 'MB', 'GB']:
if size_bytes < 1024:
return f"{size_bytes:.2f} {unit}"
size_bytes /= 1024
return f"{size_bytes:.2f} TB"


# 使用示例
if __name__ == '__main__':
# 生成文件清单
generate_file_manifest('./build/web-mobile/', './output/manifest.json')

# 清理临时文件
# clean_directory('./build/web-mobile/', keep_extensions=['.js', '.css', '.png'])

# 显示目录大小
size = get_directory_size('./build/web-mobile/')
print(f"Directory size: {format_size(size)}")

总结

使用 Python 实现游戏构建自动化的关键要点:

  1. 标准化流程:将构建步骤脚本化,确保每次执行一致
  2. 配置化管理:使用 JSON 配置文件管理不同渠道和平台的参数
  3. 错误处理:完善的错误处理和日志记录,方便排查问题
  4. 模块化设计:将功能拆分为独立模块,便于维护和复用
  5. 进度反馈:在长时间操作中提供进度反馈

通过本文提供的脚本模板和工具函数,可以快速搭建适合自己项目的构建自动化系统,显著提升开发效率。