星际歼击 Star Strike:一款纯网页的竖屏太空射击游戏

忙碌间隙想来一局爽快的射击?不用下载、不装 App,打开网页就能打。
本文介绍本站自制的 HTML5 游戏《星际歼击 / Star Strike》——竖屏适配、手机手指拖动与电脑键盘都能玩,含多向弹幕、强化道具、每 5 波一场三形态首领战,还有攒满能量一键清屏的终结技。


一、这是什么游戏

《星际歼击(Star Strike)》是一款运行在浏览器里的竖屏太空射击游戏。不依赖任何后端、不装插件,手机和电脑的浏览器直接打开即玩。

核心循环很经典:操控飞船在底部灵活走位,按住持续开火,从弹幕中突围,一波波清掉敌机;每 5 波遭遇一次首领,活得越久、打得越准,分数越高。

二、操作方式

游戏同时支持触屏和键盘,切换无感:

平台 操作 说明
手机 手指拖动屏幕任意位置 飞船跟随手指移动(竖屏推荐)
手机 按住屏幕 持续开火,松开停火
手机 点击终结技按钮 能量满时释放(竖屏在右下角)
电脑 WASD / 方向键 移动飞船
电脑 空格 开火
电脑 ESC / P 暂停

提示:手机上「拖动即移动、按住即开火」手感很顺,单手就能玩;接外接键盘则是标准街机操作。

三、四种强化道具

击落敌人或清波会掉落道具,捡到后短时间生效:

  • M — 多重射击:从单发变为五向扇形弹幕,清场效率直接翻倍。
  • S — 能量护盾:短时间里免疫伤害,硬抗弹幕的保命神器。
  • B — 引擎加速:提升飞船机动性,更容易穿弹幕、抢走位。
  • H — 护甲修复:立即恢复生命值,残血翻盘的资本。

四、首领战:三形态进化

每 5 波 一场首领战,而且首领不是一刀死的木桩——它会三形态进化:

  1. 扇形弹幕:开局铺开大范围扇形,逼你走位。
  2. 螺旋弹幕:弹道变成旋转螺旋,需要贴着缝隙钻。
  3. 狂暴召唤:进入狂暴状态,一边召小兵一边压制。

这套递进让中后段的每一场首领都像一个小 BOSS 关,策略也从「躲」逐步变成「躲 + 抢输出窗口」。

五、终结技:满能量一键清屏

命中敌人会积攒能量条,攒满后按 J(手机点右下角按钮)释放终结技:

  • 时停减速:全场敌人/弹幕大幅减速,给你喘息空间;
  • 贯穿激光:一道贯穿激光持续输出;
  • 清弹:瞬间清空当前屏幕上的弹幕。

它本质上是给玩家一个「被弹幕压死前翻盘」的保险,也是冲高分的爆发窗口。

六、技术亮点

  • 跨端即开即玩:同一份代码自适应手机与电脑,触屏/键盘双输入。
  • 轻量流畅:纯前端实时渲染,随手打开就能跑,关掉也不占空间。
  • 数据本地留存:最高分、到达波次等个人记录存在浏览器本地,关掉网页也不丢。

七、试玩入口

直接打开即可开打:星际歼击 Star Strike 试玩

游戏免费、即点即玩,适合通勤、摸鱼、等咖啡的碎片时间。欢迎试玩,也欢迎在评论区聊聊手感,或你想加的玩法(比如新道具、排行榜、Boss 机制)。


本文介绍的游戏由本站开发,试玩与反馈入口见上方链接。

Godot 4.6 + Android 真机跑通 AdMob 广告(横幅 / 插页 / 奖励)

这份文档里的每一步、每一个坑,都是在真机上一遍遍试出来的,不是照抄文档写的。
实测机型:Redmi 23013RK75C(Android 13 / Adreno 730),Godot 4.6.stable,
插件:Poing Studios godot-admob v4.0.1。

为什么值得专门写一份:这个插件在 Godot 4.6 上有 三个必须手动打补丁 的地方,
不打的话要么 403、要么 JNI 报错、要么横幅完全静默失败(最难查,本文重点)。


一、先说结论:要打三个补丁

# 症状 根因 补丁位置
1 广告请求返回 403,拿不到广告 插件写死 play-services-ads:24.9.0,该版本已被 Google 停服 addons/admob/android/bin/ads/poing_godot_admob_ads.gd
2 报错 Array[String] does not have the same element type Godot 4.6-stable 引擎 bug:java_class_wrapper.cpp:202 把 && 写成 \|\|,任何 typed Array 传 String[] 都失败 src/api/core/AdRequest.gd + 4 处调用点
3 横幅既不成功也不失败,SDK 一个网络包都不发 GDScript int 传给 Java 时被装箱成 Long,插件 aar 里是 checkcast Integer,抛异常后被 JNI 静默吞掉 src/api/AdView.gd

补丁 1、2 相对好查(有明确报错)。补丁 3 才是真正卡人的地方,下面第五节详细讲。


二、环境清单

项目 版本 / 说明
Godot 4.6.stable(用 _console.exe 版本,命令行能看到完整输出)
AdMob 插件 Poing Studios godot-admob v4.0.1
play-services-ads 25.5.0(跟随 Maven latest,不能用插件默认的 24.9.0)
渲染器 Mobile(Vulkan)
Android 构建模板 必须安装(Gradle 构建,约 200MB)
adb Android SDK platform-tools
测试机 开启 USB 调试,屏幕保持解锁

三、第 1 步:建项目

3.1 路径必须全 ASCII

✅ C:\Users\xxx\Documents\AdMobPortrait
❌ C:\Users\xxx\Documents\实验广告

Godot 导出到中文路径会失败,这是硬限制。

3.2 project.godot 的关键项

config/name="AdMobPortrait"
run/main_scene="res://Main.tscn"
config/features=PackedStringArray("4.6", "Mobile")
config/icon="res://icon.svg"          ; 不写会报 "No project icon specified"

[autoload]
AdManager="*res://AdManager.gd"

[display]
window/size/viewport_width=720
window/size/viewport_height=1280
window/handheld/orientation=1          ; 1 = 竖屏
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"

[rendering]
renderer/rendering_method="mobile"
renderer/rendering_method.mobile="mobile"
textures/vram_compression/import_etc2_astc=true   ; ★ 缺了导出会失败

⚠ 坑:导出报 due to configuration errors: 但消息是空的

ERROR: Cannot export project with preset "portrait" due to configuration errors:
(后面什么都没有)

根因在引擎 platform/android/export/export_plugin.cpp:3047,
has_valid_project_configuration() 里有一段不写任何错误信息就直接置 false 的判定:

if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
    valid = false;      // ← 没有 r_error,所以错误消息为空
}

解决:补上 textures/vram_compression/import_etc2_astc=true。


四、第 2 步:装插件

4.1 放插件并启用

把 addons/admob 整个目录放进项目,然后 project.godot 加:

[editor_plugins]
enabled=PackedStringArray("res://addons/admob/plugin.cfg")

4.2 ★ 必须生成一次全局类名缓存

新建项目第一次打开时,脚本里的 AdView / MobileAds / AdRequest 等全局类名
全部报 “not declared”,因为 .godot/global_script_class_cache.cfg 还没生成。

godot --headless --editor --quit

跑一次(会自动生成缓存然后退出),再打开编辑器/导出就正常了。

4.3 安装 Android 构建模板

编辑器菜单:项目 → 安装 Android 构建模板,会生成 android/build/(约 200MB)。

如果从别的项目拷现成的 android/ 目录,要剔除 build/build、.gradle,
但 assetPackInstallTime 是模板自带的,别删。


五、第 3 步:三个必打的补丁 ★核心章节

补丁 1 —— play-services-ads 升到 25.5.0(解决 403)

文件:addons/admob/android/bin/ads/poing_godot_admob_ads.gd

# 原值 24.9.0 已被 Google 停服,服务端直接返回 403
var _dependency_library := ["com.google.android.gms:play-services-ads:25.5.0"]

版本号别写死,去
https://dl.google.com/dl/android/maven2/com/google/android/gms/play-services-ads/maven-metadata.xml
看 <latest> 是多少,跟着走。

补丁 2 —— typed Array 换 PackedStringArray(解决 JNI 报错)

根因:Godot 4.6-stable 的 platform/android/java_class_wrapper.cpp:202:

if (arr.is_typed() && (builtin != STRING || builtin != STRING_NAME))
//                                      ^^ 应该是 &&

|| 导致这个条件恒为真,于是任何 typed Array 传 String[] 都被拒。
(master / 4.7-dev 已修正为 &&。)

改法 —— 在 src/api/core/AdRequest.gd 加一个转换函数:

func jni_keywords() -> PackedStringArray:
    return PackedStringArray(keywords)

然后把 4 处调用点 的第 3 个参数改掉:

文件 行 改法
src/api/AdView.gd 89 ... ad_request.jni_keywords())
src/api/InterstitialAdLoader.gd 44 ad_request.jni_keywords()
src/api/RewardedAdLoader.gd 44 ad_request.jni_keywords()
src/api/RewardedInterstitialAdLoader.gd 44 ad_request.jni_keywords()

另外 src/api/MobileAds.gd:42 也要改:

_plugin.set_request_configuration(
    request_configuration.convert_to_dictionary(),
    PackedStringArray(request_configuration.test_device_ids))   # ← 加 PackedStringArray

补丁 3 —— Integer 装箱(解决横幅完全静默失败)★最难查

症状

插页、奖励都正常,只有横幅:发出加载请求之后,
既没有 on_ad_loaded,也没有 on_ad_failed_to_load,SDK 一个包都不发。
日志里干干净净,什么都不报错。

排查三步法(这套手法可以复用到别的插件问题)

第 1 步:对照插件日志找断点

adb logcat -d -v brief | grep -i "poing-godot-admob\|godot   "

正常应该看到 safeInsetRect 之后紧跟着:

D/poing-godot-admob: Safe Area of screen: Rect(...).
D/poing-godot-admob: marginTop: 0

如果 Safe Area of screen: 从没出现,说明 Banner 构造中途就断了。

第 2 步:看视图层级,确认 AdView 到底建出来没有

adb shell dumpsys activity top

往下找 godot_fragment_container,正常应该能看到
com.google.android.gms.ads.AdView。看不到就说明 addView 那段没执行。

第 3 步:反编译 aar 看字节码

unzip -o addons/admob/android/bin/ads/libs/poing-godot-admob-ads-release.aar
cd <解压目录> && unzip -o classes.jar
javap -p -c com/poingstudios/godot/admob/ads/adformats/Banner.class

在 Banner.<init> 的字节码里会看到:

63: checkcast  java/lang/Integer     // ad_position

convertToAdSize 里也有两处(width / height)。

根因

GDScript int  ──写进 Dictionary──▶  Godot 装箱成 java.lang.Long
                                            │
                                   Java 侧 checkcast java/lang/Integer
                                            │
                                   ClassCastException
                                            │
                                   被 JNI 层静默吞掉 → Banner 构造中断
                                            │
                              runOnUiThread{ 创建 AdView + addView } 永不执行
                                            │
                                   没有 AdView → 零网络请求 → 零回调

Godot 的 int 是 int64,装箱成 Long;而插件 aar 里的 Kotlin 是 this["ad_position"] as Int。
这个异常发生在 safeArea 属性初始化之后、runOnUiThread 之前,
所以 Banner 没被加进 banners 列表,后续 load_ad(uid) 取 banners[uid] 越界 —— 整条链静默。

注意:GitHub master 上的 DictionaryExtensions.kt 已经改成安全的
(this[key] as? Number)?.toInt(),但发布的 aar 4.0.1 仍是旧代码。
这就是为什么”看源码明明修过了,跑起来还是崩”。

修复代码

文件:addons/admob/src/api/AdView.gd

# Godot 的 JavaClass 构造器方法名 = 类的简单名
# (java_class_wrapper.cpp:1488: java_constructor_name = 类名最后一段)
# 所以是 Integer.Integer(v),走 env->NewObjectA(..., "<init>", ...),
# 返回 JavaObject;JavaObject 进 Dictionary 时引擎直接透传 jobject。
static var _integer_class = null
static var _integer_class_ready := false

static func _java_int(value: int):
    if not _integer_class_ready:
        _integer_class_ready = true
        if OS.get_name() == "Android":
            _integer_class = JavaClassWrapper.wrap("java.lang.Integer")
    if _integer_class == null:
        return value
    return _integer_class.Integer(value)

然后 _init() 里三个 int 全部走它:

var ad_view_dictionary := {
    "ad_unit_id": ad_unit_id,
    "ad_position": _java_int(ad_position),
    "ad_size": {
        "width": _java_int(ad_size.width),
        "height": _java_int(ad_size.height)
    }
}

为什么插页/奖励不受影响

它们的 AdRequest.convert_to_dictionary() 里只有 String 和空 Dictionary,
没有 int,碰不到这个 checkcast。这也解释了”为什么偏偏只有横幅坏”。


六、第 4 步:业务代码

6.1 官方测试广告单元 ID(任何包名下都能出测试广告)

const BANNER_ID       := "ca-app-pub-3940256099942544/6300978111"
const INTERSTITIAL_ID := "ca-app-pub-3940256099942544/1033173712"
const REWARDED_ID     := "ca-app-pub-3940256099942544/5224354917"

6.2 注册测试设备

真机上跑测试广告必须把设备注册成测试设备,否则拿不到测试广告。
设备哈希从 logcat 里 SDK 自己打印的那句取:

Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("599DD0E66111F3FAC24560DEABA32062"))
adb logcat -d | grep -i "setTestDeviceIds"

6.3 AdManager.gd(AutoLoad)

func _ready() -> void:
    if OS.get_name() != "Android" and OS.get_name() != "iOS":
        return

    # 1) 注册测试设备
    var cfg := RequestConfiguration.new()
    cfg.test_device_ids = [TEST_DEVICE_ID]
    MobileAds.set_request_configuration(cfg)

    # 2) 初始化(异步)
    var listener := OnInitializationCompleteListener.new()
    listener.on_initialization_complete = func(_status: InitializationStatus) -> void:
        _initialized = true
    MobileAds.initialize(listener)

横幅(贴在竖屏底部):

func load_banner() -> void:
    if _banner_view != null:
        _banner_view.destroy()
        _banner_view = null

    var ad_size := AdSize.get_current_orientation_anchored_adaptive_banner_ad_size(AdSize.FULL_WIDTH)
    var view := AdView.new(BANNER_ID, ad_size, AdPosition.Values.BOTTOM)

    view.ad_listener.on_ad_loaded = func() -> void:
        view.show()                      # 加载成功才 show
    view.ad_listener.on_ad_failed_to_load = func(err: LoadAdError) -> void:
        print("横幅失败 code=%d %s" % [err.code, err.message])

    _banner_view = view
    view.load_ad(AdRequest.new())

插页:

func load_interstitial() -> void:
    var cb := InterstitialAdLoadCallback.new()
    cb.on_ad_loaded = func(ad: InterstitialAd) -> void:
        _interstitial = ad
        ad.full_screen_content_callback.on_ad_dismissed_full_screen_content = func() -> void:
            _interstitial.destroy()
            _interstitial = null
            load_interstitial()          # 关闭后自动预加载下一则
    cb.on_ad_failed_to_load = func(err: LoadAdError) -> void:
        print("插页失败 code=%d %s" % [err.code, err.message])
    InterstitialAdLoader.new().load(INTERSTITIAL_ID, AdRequest.new(), cb)

奖励:

func show_rewarded() -> void:
    if _rewarded == null:
        load_rewarded()
        return
    var listener := OnUserEarnedRewardListener.new()
    listener.on_user_earned_reward = func(item: RewardedItem) -> void:
        print("发放奖励 type=%s amount=%d" % [item.type, item.amount])
    _rewarded.show(listener)

6.4 界面布局要点

横幅是 Android 原生 View,叠在 Godot 画面上方,会盖住底部内容。
所以 UI 底部要留白:

root.add_theme_constant_override("margin_bottom", 140)   # 给横幅留位

6.5 时序

MobileAds.initialize 是异步的,别在 _ready() 里立刻发请求:

await get_tree().create_timer(2.5).timeout
AdManager.load_interstitial()
AdManager.load_rewarded()
await get_tree().create_timer(3.0).timeout
AdManager.load_banner()

七、第 5 步:导出 APK

7.1 export_presets.cfg 最小配置

[preset.0]
name="portrait"
platform="Android"
export_path="./AdMobPortrait.apk"

[preset.0.options]
gradle_build/use_gradle_build=true
gradle_build/export_format=0
architectures/arm64-v8a=true
package/unique_name="com.example.admobportrait"
package/signed=true
version/code=1
version/name="1.0"
screen/immersive_mode=true

7.2 ★ 每次导出前先清 *.tmp

中断导出会留下
android/build/res/values-*/godot_project_name_string.xmlNNNN.tmp,
下次构建直接失败:

mergeStandardDebugResources FAILED - The file name must end with .xml
find android/build/res -name "*.tmp" -delete

7.3 导出命令

godot --headless --export-debug "portrait" "C:/path/to/AdMobPortrait.apk"

首次 Gradle 构建约 7~15 分钟(之后有缓存会快很多)。
APK 出现在 build/ 下时就已构建完成,Godot 进程可能还在做收尾。


八、第 6 步:装到手机

⚠ 坑:INSTALL_FAILED_USER_RESTRICTED: Install canceled by user

adb install -r -t app.apk
# → INSTALL_FAILED_USER_RESTRICTED: Install canceled by user

试过 keyevent 224 唤醒、wm dismiss-keyguard、pm install、文件管理器安装,都无效。
真正的原因是 AppOps 权限被关:

adb shell appops set com.android.shell REQUEST_INSTALL_PACKAGES allow
adb install -r -t app.apk
# → Success

在 Git Bash 下记得 export MSYS_NO_PATHCONV=1,否则 /data/local/tmp/...
这类路径会被自动转换。


九、第 7 步:验证广告真的出来了

9.1 抓日志

adb logcat -c                                        # 先清空
adb shell monkey -p com.example.admobportrait -c android.intent.category.LAUNCHER 1
sleep 14
adb logcat -d -v brief | grep -i "poing-godot-admob\|godot   "

9.2 健康日志长这样

[07:23:02] 正在初始化 MobileAds ...
[07:23:03] MobileAds 初始化完成 ✔
[07:23:06] 插页:已发起加载请求 ...
[07:23:06] 奖励:已发起加载请求 ...
[07:23:09] 横幅尺寸:411 x 64 dp
[07:23:09] 横幅:java.lang.Integer 装箱 ✔
D/poing-godot-admob: Safe Area of screen: Rect(0, 104 - 0, 0).
D/Ads: AdRequestServiceImpl: Sending request: .../mads/gma     ← 真的发出请求了
[07:23:11] 横幅:加载成功 ✔ → 自动展示
[07:23:11] 横幅:已展示(impression)
[07:23:11] 插页:加载成功 ✔ 可以展示了
[07:23:11] 奖励:加载成功 ✔ 可以展示了

9.3 自动点按钮

Godot 的按钮是自绘的,uiautomator 定位不到,只能用坐标点:

adb shell wm size            # 确认分辨率,例如 1080x2400
adb shell input tap 540 690  # 点"显示插页广告"

9.4 截图确认

adb exec-out screencap -p > screen.png

底部能看到 Google 测试横幅(「测试广告 / Nice job! This is a 320×50 test ad.」)即为成功。


十、排错速查表

现象 根因 解决
导出报 due to configuration errors: 消息为空 import_etc2_astc 未开 project.godot 加 textures/vram_compression/import_etc2_astc=true
脚本报 AdView / MobileAds not declared 全局类名缓存未生成 跑一次 godot --headless --editor --quit
No project icon specified 缺图标 加 config/icon="res://icon.svg"
mergeStandardDebugResources FAILED ... must end with .xml 上次导出中断留了 *.tmp find android/build/res -name "*.tmp" -delete
INSTALL_FAILED_USER_RESTRICTED AppOps 权限被关 adb shell appops set com.android.shell REQUEST_INSTALL_PACKAGES allow
广告请求 403 play-services-ads 版本停服 补丁 1,升到 Maven latest
Array[String] does not have the same element type Godot 4.6 typed-array bug 补丁 2,改 PackedStringArray
横幅无任何回调、SDK 不发请求 int 装箱成 Long,checkcast Integer 崩溃 补丁 3,_java_int()
插页/奖励一直”未准备好” 没等初始化完成就发请求 初始化回调后再等 2~3 秒
横幅盖住 UI 底部 原生 View 叠在画面上 UI 底部留 140px 空白

十一、换成自己的广告 ID 时要做什么

  1. AdMob 后台创建应用 + 三个广告单元,拿到真实 ID,替换 3 个常量。
  2. 把测试设备注册那段删掉或改成只在 debug 生效,否则线上用户拿不到真实广告。
  3. 改包名 package/unique_name 为你自己的域名反写。
  4. 发布前换成 release 导出(--export-release)并配置正式签名。
  5. 欧盟/英国用户需要接 UMP consent 流程(插件里 PoingGodotAdMobUserMessagingPlatform 已提供)。

附:本项目文件清单

AdMobPortrait/
├── project.godot              竖屏 + AutoLoad + import_etc2_astc
├── export_presets.cfg         preset 名 "portrait"
├── AdManager.gd               广告管理器(AutoLoad)
├── Main.gd / Main.tscn        竖屏演示 UI(纯代码搭建)
├── icon.svg
├── addons/admob/              插件(含 3 处补丁)
│   ├── src/api/AdView.gd                      ← 补丁 3
│   ├── src/api/core/AdRequest.gd              ← 补丁 2
│   ├── src/api/MobileAds.gd                   ← 补丁 2
│   ├── src/api/{Interstitial,Rewarded,RewardedInterstitial}AdLoader.gd  ← 补丁 2
│   └── android/bin/ads/poing_godot_admob_ads.gd   ← 补丁 1
├── android/                   Android 构建模板
└── build/
    ├── admob-portrait.apk     已验证可安装的 debug 包
    └── screen_*.png           真机验证截图

Conda 环境下pip版本冲突不一致问题

核心原理

Conda 环境激活后,终端会优先使用环境内的python和pip。如果pip仍指向系统版本,说明环境内的pip缺失或路径被覆盖,需要修复环境内的pip关联。

以下用ping-pong-web举例子。

解决步骤

1. 确保已激活目标 Conda 环境

首先确认你在正确的环境中操作(终端提示符有ping-pong-web标识):

conda activate ping-pong-web

2. 检查 Conda 环境内的pip是否存在

Conda 环境中默认会安装pip,但可能被误删或未安装。执行以下命令检查环境内的pip路径:

which pip  # Linux/Mac
# 或 Windows: where pip
  • 正常情况:输出应包含conda/envs/ping-pong-web/bin/pip(Linux/Mac)或conda/envs/ping-pong-web/Scripts/pip.exe(Windows),表示使用环境内的pip。
  • 异常情况:输出如/home/bruce/.local/lib/python3.12/site-packages/pip(你的情况),说明使用的是系统或用户目录的pip,而非环境内的。

3. 修复环境内的pip

如果环境内没有pip,或pip关联错误,用conda重新安装环境内的pip:

# 先卸载环境内可能存在的异常pip(如果有)
conda remove --force pip -n ping-pong-web

# 重新在当前环境安装pip(确保已激活环境)
conda install pip

这一步会在ping-pong-web环境中安装与 Python 3.9.25 匹配的pip。

4. 清理用户目录下的全局pip(关键!)

你的pip指向了/home/bruce/.local/lib/python3.12/,这是用户级别的全局安装路径(可能是之前用pip install --user安装的),优先级可能覆盖了 Conda 环境的pip。需要清理:

# 删除用户目录下的pip(注意路径与你的输出对应)
rm -rf /home/bruce/.local/lib/python3.12/site-packages/pip*
rm -rf /home/bruce/.local/bin/pip*

注意:替换路径为你pip --version输出中的路径(确保不要删除系统关键文件)。

2025年流行的Python部署

传统的真机Python+pip部署方式早已被淘汰了。

而venv虚拟部署的方式还有很多人在用,也确实比传统真机部署好很多,但也再被渐渐淘汰。

目前较为流行的一种模式是:Conda+UV的组合方式。

conda:核心是环境管理工具,负责创建隔离的虚拟环境(可包含不同 Python 版本、系统级依赖甚至非 Python 包,如 C 库)。

uv:核心是Python 包管理器,专注于快速安装 / 管理 Python 包(类似 pip,但速度更快、依赖解析更高效),本身不管理环境,而是依赖于当前激活的 Python 环境。

建议在激活 conda 环境后安装 uv,避免全局安装导致的环境混淆。

pip install uv

uv的使用例子:

uv pip install transformers==4.24.0  # 安装指定版本
uv pip uninstall jieba  # 卸载包
uv sync  # 同步依赖(根据 pyproject.toml 或 requirements.txt)

注意事项(避免潜在问题):

1、不混用 conda 和 uv 安装同一 Python 包

例如,不要用 conda install pandas 后再用 uv pip install pandas,可能导致版本冲突(conda 和 pip/uv 管理的包路径虽然一致,但依赖解析逻辑不同)。

  • 优先用 uv 管理纯 Python 包(如 transformers、jieba);
  • 用 conda 管理含系统依赖的包(如 numpy、opencv,conda 会自动安装对应的 C 库)。

2、uv 不处理非 Python 依赖

uv 仅负责 Python 包,若包依赖系统级库(如 libssl、libffi),仍需通过 conda 或系统包管理器(apt)安装:

# 例如安装需要系统库的包,先用 conda 安装依赖 conda install -c conda-forge libsndfile # 安装音频处理的系统库 uv pip install soundfile # 再用 uv 安装依赖该库的 Python 包

3、环境迁移时需同步两者的配置

若要复制 conda 环境,除了用 conda env export,还需同步 uv/pip 管理的包(通过 uv pip freeze > requirements.txt 导出)。

总结

uv 和 conda 是互补工具:conda 负责环境隔离和系统级依赖,uv 负责高效管理 Python 包,在 conda 环境中使用 uv 不仅安全,还能显著提升包安装速度,推荐结合使用。

国内使用建议:

/.config/uv/uv.toml
index-url = “https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple”

另外,uv也是可以直接运行py文件的。如图所示。

关于用Suno生成歌曲的若干疑问解答

我可以将我的歌曲分发到 Spotify 等平台吗?

  • 是的!如果您在订阅 Pro 或 Premier 计划期间创作了歌曲,这些歌曲受商业使用许可的保护,允许您进行分发。

Suno 拥有我创作的音乐吗?

  • 如果您在歌曲创作时订阅了 Pro 或 Premier 计划,您将被视为歌曲的所有者,并保留商业使用权。如果您使用免费(Basic)版本,Suno 保留歌曲的所有权,但您可以将其用于非商业目的。

取消订阅后谁拥有我的歌曲?

  • 如果您在订阅期间创作了歌曲,即使取消订阅,您仍保留该歌曲的权利。这意味着您可以在结束订阅后分发这些歌曲。

什么是商业用途或非商业用途?

  • Basic 计划允许您创作歌曲用于非商业用途,而使用 Pro 和 Premier 计划创作的歌曲则包括商业用途。

如果我订阅了,可以分发免费计划中的歌曲吗?

  • 不幸的是,不可以!订阅 Pro 或 Premier 计划不会追溯性地授予您免费计划中创作歌曲的商业使用许可。

我对我创作的歌曲拥有版权吗?

  • 版权是复杂的,并且因地区而异!为了确保您拥有最新信息,我们建议您在申请前咨询当地的版权局。

我可以在我所在地区的版权局注册我的歌曲吗?

  • 版权法可能因地区而异,并且人工智能创作作品的版权规则仍在完善中。因此,我们建议…(具体建议请参考原文)

如果我有一个免费计划但自己写了歌词怎么办?

  • 如果您自己写了在 Suno 中创作的歌曲的歌词,您拥有这些歌词。无论您使用哪种计划,歌词都属于您。

如果我的歌曲中别人写了歌词怎么办?

Suno 最适合创作带有新歌词的新音乐。如果您与另一位创作者合作,您必须获得任何歌词或其他材料的许可。

探索 Dialogic 2:游戏对话与叙事的得力助手

在游戏开发领域,引人入胜的对话系统和精彩的叙事常常能为游戏增色不少,成为吸引玩家深入体验的关键因素。而 Dialogic 2,正是一款专为游戏开发者打造的强大插件,能助力开发者轻松实现复杂且生动的游戏对话与叙事。

Dialogic 2 基于 Godot 引擎开发,对 Godot 4.2 及以上版本提供良好支持。其核心功能极为丰富,为开发者打开了创意的大门。它的对话系统堪称一绝,允许开发者创建复杂的对话树。无论是分支剧情、条件判断,还是多角色同时参与的对话,都能轻松实现。想象一下,在角色扮演游戏中,玩家的不同选择通过对话分支引发截然不同的剧情走向,这无疑大大增强了游戏的重玩性与趣味性。

Godot下载地址:

https://godotengine.org/download

对于视觉小说类游戏开发,Dialogic 2 更是得心应手。通过简单直观的拖放操作,开发者就能快速搭建起视觉小说场景,灵活管理背景、角色立绘以及对话框等元素,让文字与画面完美融合,构建出引人入胜的故事世界。此外,它还提供了便捷的角色管理功能,开发者可以方便地设定角色的各项属性、状态和行为,使角色在游戏中更加鲜活立体。

值得一提的是,Dialogic 2 内置了自动更新功能。这意味着开发者无需手动操心繁琐的更新事宜,插件会自动保持在最新版本,及时享受新功能与修复的漏洞,极大地提升了开发效率与稳定性。同时,Dialogic 2 采用单元测试确保代码质量,利用 gdUnit4 测试框架,开发者可在本地运行测试,为代码的可靠性保驾护航。

从应用场景来看,Dialogic 2 的适用性十分广泛。在视觉小说游戏开发中,它能快速构建起整个游戏架构,管理对话、角色和场景,使开发者专注于故事创作。在角色扮演游戏里,其丰富的对话系统可以支持多角色互动,创造出丰富多彩的剧情分支,为玩家带来沉浸式体验。冒险游戏通过它管理对话和角色,增强玩家代入感;教育游戏借助其对话与叙事功能,实现互动式学习体验,寓教于乐。

总的来说,Dialogic 2 以其易于使用的特性、强大的功能、自动化更新以及活跃的开源社区支持,成为游戏开发者打造精彩游戏对话与叙事的不二之选。无论你是经验丰富的行业老手,还是初涉游戏开发领域的新手,Dialogic 2 都能为你的游戏开发之旅增添助力,让你的创意通过精彩的对话与叙事完美呈现。

一、安装与激活​

Dialogic 2 要求 Godot 4.2 或更高版本。确认已安装合适版本后,按以下步骤安装插件:​

  1. 前往Dialogic 官方 GitHub 仓库的 Releases 页面,下载所需版本的 Dialogic 2 压缩包。​
  1. 解压下载的压缩包,找到其中的addons文件夹,该文件夹包含了 Dialogic 插件。​
  1. 将addons文件夹移动到你的 Godot 项目文件夹中。​
  1. 打开 Godot 项目,进入项目>项目设置,点击插件标签页。​
  1. 在插件列表中找到 Dialogic,勾选其旁边的启用按钮。​
  1. 重启 Godot 编辑器,至此 Dialogic 2 插件安装并激活完成。​

Dialogic 2插件下载地址:

https://github.com/dialogic-godot/dialogic/releases

二、认识编辑器界面​

成功安装并激活插件后,在 Godot 编辑器顶部会出现一个新的Dialogic标签页(位于2D、3D、脚本和资源库旁边)。点击进入,映入眼帘的是 Dialogic 主界面。界面顶部能看到 Dialogic 的不同编辑器选项;左侧边栏会展示最近使用过的角色和时间线;右上角有几个常用功能按钮,分别是添加时间线、添加角色、参考管理器和播放时间线 。​

三、创建时间线​

时间线用于编排游戏中的对话流程等事件,创建步骤如下:​

  1. 点击右上角的添加时间线按钮。​
  1. 在弹出窗口中,选择要存放时间线的文件夹,并输入文件名。时间线文件将以.dtl格式保存。​
  1. 点击保存后,编辑器右侧会出现事件面板,可从这里往时间线上添加各种事件。若想了解某个事件及其设置详情,右键点击该事件并选择文档即可查看说明。​
  1. 点击右上角的文本编辑器按钮,可切换到文本编辑模式编写时间线内容。关于文本格式时间线的编写规则,可在相关文档中进一步学习。​
  1. 完成时间线编辑后,点击右上角的播放时间线按钮,可测试当前时间线效果。​

四、创建角色​

为游戏创建角色的步骤如下:​

  1. 点击右上角的添加角色按钮。​
  1. 如同时间线操作,需选择保存角色的位置并命名。角色文件通常以.dchr格式保存。​
  1. 在角色编辑器中,可设置角色的各项属性,如名称、立绘(可添加多个不同表情的立绘)、初始状态等。这些设置将决定角色在对话和游戏场景中的呈现效果。​

五、为游戏添加对话​

  1. 在场景中添加一个Dialogic节点。你可通过在场景树中右键点击,选择添加节点,然后在搜索框中输入Dialogic来找到并添加该节点。​
  1. 选中添加的Dialogic节点,在其属性面板中,从下拉菜单里选择之前创建好的时间线。​
  1. 若要在游戏特定时机触发对话,可通过编写 Godot 脚本实现。例如,在某个场景脚本中添加如下代码,当场景准备好时启动名为ExampleDialog的对话:​
​extends Node2D

func _ready():

Dialogic.start('ExampleDialog')

通过以上步骤,你就能利用 Dialogic 2 插件在 Godot 游戏中搭建起基础的对话系统。随着对插件的深入使用,你还可探索其更多高级功能,如条件判断、多角色同时对话、对话分支剧情等,从而打造出更丰富精彩的游戏体验。

树莓派也能跑Godot了

非官方的,但因为官方开源。所以有人做了这个。好事情,普通2D游戏用它够了。

Unofficial Godot Engine for the Raspberry Pi

https://github.com/hiulit/Unofficial-Godot-Engine-Raspberry-Pi

有兴趣做Godot或Unity开发的请进群。

运行 Godot 游戏在树莓派上需要考虑的事项:

树莓派型号:

树莓派 3B/3B+: 可以运行一些简单的 2D 游戏,但性能可能有限。
树莓派 4/400: 性能更强,可以运行更复杂的 2D 游戏甚至一些轻量级的 3D 游戏。
树莓派 5: 最新型号,拥有更强大的性能,理论上可以运行更多类型的 Godot 游戏。


Godot 版本:

Godot 3.x: 对硬件要求相对较低,可能更适合在较老的树莓派型号上运行。
Godot 4.x: 提供了更多新特性和更好的性能,但在树莓派上可能需要更多的优化。建议使用最新的稳定版本。


操作系统:

推荐使用官方的 Raspberry Pi OS 或其他轻量级的 Linux 发行版。

导出设置:

平台: 选择 Linux/X11 作为导出平台。
架构: 根据你的树莓派型号选择正确的架构 (例如 ARMv7 或 ARM64)。
渲染器: 对于 2D 游戏,Forward+ 渲染器通常可以工作。对于 3D 游戏,可能需要尝试不同的渲染器设置以获得最佳性能。

优化: 启用导出优化选项,例如移除调试符号和压缩资源。
性能优化: 降低分辨率: 在项目设置中降低游戏的默认分辨率可以显著提高性能。
减少资源使用: 优化你的游戏资源,例如使用更小的纹理和更少的多边形。

使用轻量级的脚本: 避免在 _process 函数中进行过于复杂的计算。
考虑使用 GLES2 渲染器 (Godot 3.x): 如果你的游戏是 2D 的,并且在 GLES3 下性能不佳,可以尝试使用 GLES2 渲染器。

输入设备:

确保你的树莓派连接了合适的输入设备,例如键盘、鼠标或游戏手柄。

用树莓派Pico和镍铬丝制作电烙铁

1. 硬件准备

  • 树莓派Pico: 作为控制核心。
  • 镍铬丝: 作为加热元件。
  • MOSFET(如IRF520): 用于控制镍铬丝的电流。
  • 电阻(如220Ω): 用于限制Pico GPIO引脚的电流。
  • 电源: 提供足够的电流和电压。
  • 导线: 耐高温导线。
  • 烙铁头(可选): 可以将铜丝弯曲成烙铁头形状。
  • 隔热材料: 用于包裹镍铬丝,防止烫伤。

2. 硬件连接

  • 将镍铬丝连接到MOSFET的漏极(Drain)。
  • 将MOSFET的源极(Source)连接到GND。
  • 将树莓派Pico的GPIO引脚(例如GPIO15)通过电阻连接到MOSFET的栅极(Gate)。
  • 连接电源到镍铬丝和MOSFET。
  • 将隔热材料包裹在镍铬丝周围。

3. MicroPython编程

  • Thonny IDE: 推荐使用Thonny IDE,方便编写和上传MicroPython代码。
  • GPIO控制: 使用MicroPython的machine库,控制GPIO引脚输出高低电平,控制MOSFET的导通和截止。

示例代码:

from machine import Pin
import time

fire_pin = Pin(15, Pin.OUT)  # 点火引脚,连接到MOSFET栅极

def fire():
    fire_pin.value(1)  # 开启加热
    print("Heating...")

def stop_fire():
    fire_pin.value(0) # 关闭加热
    print("Stop Heating.")

while True:
    input_value = input("Enter '1' to heat, '0' to stop: ")
    if input_value == '1':
        fire()
    elif input_value == '0':
        stop_fire()
    else:
        print("Invalid input.")

4. 安全注意事项

  • 高温风险: 镍铬丝工作时会产生高温,务必小心操作,防止烫伤。
  • 电路安全: 大电流电路存在风险,务必小心操作。
  • 防火措施: 准备灭火器材,防止火灾发生。
  • 测试验证: 在实际焊接前,进行充分的测试验证。
  • 警告:
    • 电烙铁具有一定的危险性,请在安全的环境下操作。
    • 制作和使用电烙铁时,务必遵守相关安全规范。
    • 如果对于电子电路不是十分了解,请勿自行制作。请寻找专业人员的帮助。

5. 补充说明

  • 根据镍铬丝的电阻和电源电压,选择合适的MOSFET。
  • 可以添加温度传感器和PID控制算法,实现温度控制功能。
  • 关于烙铁头的制作:
    • 可以找一段粗的铜丝,然后用锤子敲打铜丝的一端,敲打成扁平状,然后打磨成自己需要的烙铁头形状。
    • 铜具有非常优秀的导热性,所以铜是制作烙铁头的优秀材料。

国内用Gradle构建Android APK

在中国内地,由于网络环境的特殊性,使用Gradle构建Android APK时,经常会遇到依赖下载缓慢或失败的问题。配置SOCKS代理确实是解决这一问题的有效方法之一。以下是一些关于如何为Java和Gradle添加SOCKS代理,以帮助您顺利构建APK的信息:

1. Gradle代理配置

  • gradle.properties文件:
    • 这是配置Gradle代理最常用的方法。您可以在用户主目录下的.gradle文件夹中找到或创建一个gradle.properties文件。
    • 在这个文件中,您可以添加以下属性来配置SOCKS代理:
      • systemProp.socks.proxyHost=your_socks_host
      • systemProp.socks.proxyPort=your_socks_port
      • systemProp.java.net.socks.username=your_socks_username (如果需要)
      • systemProp.java.net.socks.password=your_socks_password (如果需要)
    • 请将your_socks_host、your_socks_port、your_socks_username和your_socks_password替换为您的SOCKS代理服务器的相应信息。
  • 注意事项:
    • 除了SOCKS代理,您可能还需要配置HTTP/HTTPS代理,因为Gradle在某些情况下可能会使用这些协议。
    • nonProxyHosts属性也非常重要,它可以指定不需要通过代理访问的主机。
    • Gradle是以JVM进程启动的,所有和java进程一样,可以提供系统参数,例如-Dproperty=value的形式。

2. Java代理配置

  • 由于Gradle是基于Java的,因此您也可以通过Java系统属性来配置代理。
  • 您可以在启动Gradle时,通过-D参数来设置这些属性。例如:
    • -DsocksProxyHost=your_socks_host
    • -DsocksProxyPort=your_socks_port
  • 但是,通常情况下,在gradle.properties文件中配置代理更为方便。

3. 解决常见问题

  • SOCKS代理与HTTP/HTTPS代理的配合使用:
    • 在某些情况下,您可能需要同时配置SOCKS和HTTP/HTTPS代理,以确保所有依赖项都能正确下载。
  • 代理软件的设置:
    • 确保您的SOCKS代理软件已正确配置,并且可以正常工作。
  • 网络环境的稳定性:
    • 即使配置了代理,不稳定的网络环境也可能导致构建失败。

总结

通过正确配置SOCKS代理,您可以显著提高Gradle在中国内地构建APK的成功率。建议您仔细检查代理设置,并根据您的网络环境进行调整。