Skip to main content

第6章:参与godot开源游戏开发

让我们用Cursor来助力Godot游戏开发,体验AI辅助开发的魅力!

一 勇士之路(POF)项目​

1 项目概览​

首先,让我们了解一下这个开源游戏项目:

勇士之路(POF)是一款受《Path of Exile》启发的简化 2D 像素风格动作 RPG,具有简化的游戏机制、降低难度和增强的娱乐价值。

📁 Path of Fighter/
├── 游戏类型:2D动作RPG
├── 开发引擎:Godot 4.0
├── 编程语言:C# (Godot Mono)
└── 主要特性:
- 技能系统
- 对战系统
- 角色成长

二 开发环境配置指南​

🌟 开发环境准备清单

  • 安装Git
  • 下载GitHub Desktop
  • 安装.NET SDK
  • 下载Godot Mono版本 记住:工具只是辅助,重要的是开始动手做项目。不要在工具选择上纠结太久,先用起来,需要时再升级或更换!

1 Git - 你的代码时光机 ⏰​

还记得你改完代码后,想要恢复之前的版本却找不到备份的痛苦吗?Git就是为解决这个问题而生的!

(1 什么是Git?​

想象你在玩游戏时可以随时存档,而且可以回到任何一个存档点 —— Git就是写代码时的"存档系统"!

🎮 主要功能

  • 代码版本管理(就像游戏存档)
  • 多人协作(组队打副本)
  • 代码备份(防止电脑炸了)

(2 如何安装git工具​

访问 https://git-scm.com/downloads

mac平台安装,执行命令

brew install git

windows平台安装,访问链接下载安装包 https://git-scm.com/downloads/win

2 GitHub Desktop - 代码管理界的傻瓜相机 📸​

如果说Git是专业相机,GitHub Desktop就是傻瓜相机 —— 一键操作,新手友好!

(1 为什么要用它?​

  • 不用记命令,全靠点点点
  • 界面清晰,一目了然
  • 适合初学者入门

(2 使用三步走​

(1. 下载安装:访问 GitHub Desktop官网
(2. 登录GitHub账号
(3. 克隆或创建项目就开始啦!

(3 如何使用GitHub Desktop进行游戏开发管理的总体流程​


1. 打开GitHub Desktop,登录GitHub账户
2. 选择"File > Clone Repository"
3. 选择POF项目 https://github.com/aiwalkerstudio/pixpof.git ,这样就能克隆项目到本地
4. 创建新分支:feature/fireball-skill
5. 开发当前分支代码 //对应下面的技能开发
6. 提交代码并创建pull request,推送到main分支

(4 下载GitHub Desktop​

访问 https://desktop.github.com/download/

下载完成之后,安装

(5 如何注册GitHub账户​

查看详细注册文档 https://docs.github.com/en/get-started/start-your-journey/creating-an-account-on-github

3 .NET SDK - 微软的魔法工具包 🎁​

这是C#语言的"发动机",没它寸步难行!

(1 为什么需要它?​

  • 运行C#程序必备
  • Godot Mono版本需要
  • 跨平台开发利器

(2 下载.net 工具包​

windows安装​

访问 https://dotnet.microsoft.com/download

点击按钮 “Download .NET SDK ”

下载完成之后,安装

mac安装​

访问 https://dotnet.microsoft.com/download 下载安装

或者

打开命令行,输入 “ brew install dotnet@4.6.2 ”

4 Godot Mono IDE - 游戏开发的瑞士军刀 🎮​

想做游戏但不知从何下手?Godot是你的不二之选!

(1 为什么选Godot?​

  • 免费开源(对,你没看错,是免费的!)
  • 支持2D和3D(想做啥做啥)
  • 跨平台(PC、手机、网页都能玩)
  • C#加持(写代码更舒服)

(2 下载并安装godot mono​

访问 https://godotengine.org/download/

点击按钮 “(macOS) Godot Engine - .NET4.4”

下载完成之后,安装

💡 新手友好提示:

  1. 不要被这些工具吓到,它们都很好上手
  2. 遇到问题先问Cursor
  3. 实在不行,问问AI助手deepseek,doubao等
  4. 动手实践最重要!

三 开发流程​

1 下载pixpof代码​

打开GitHub Desktop, 点击按钮 “File > Clone Repository”

输入地址 https://github.com/aiwalkerstudio/pixpof.git

alt text

点击"Clone",等待项目复制完成

2 创建开发分支​

打开GitHub Desktop, 点击按钮 “Branch > new Branch ..” alt text

alt text

3 使用cursor打开pixpof所在项目文件夹​

alt text

4 给pixpof开发新技能:寒冰弹​

你:@reqs.md @tech.md 实现一个寒冰弹技能, 并绑定在技能1的快捷键


Cursor:分析需求:
1. 需要实现一个新的寒冰弹技能
2. 需要将其绑定到技能1快捷键
3. 需要创建:
- 寒冰弹技能类
- 寒冰弹投射物类
4. 需要修改:
- SkillSlot.cs 来配置技能
5. 技能特点:
- 应该是一个主动技能
- 应该继承ProjectileSkill
- 应该有减速效果
- 应该有冰冻视觉效果
6. 参考现有的Fireball和Soulrend技能实现

步骤:
1. 创建IceBolt技能类
2. 创建IceBoltProjectile类
3. 修改SkillSlot.cs中的技能配置
4. 确保技能绑定到技能1快捷键(W键)
...

这样,寒冰弹技能就会被绑定到技能1的快捷键(W键)。
主要特点:
寒冰弹是一个主动技能,继承自ProjectileSkill
寒冰弹有减速效果,可以降低敌人移动速度
寒冰弹有冰冻视觉效果,包括冰晶粒子和冰冻光环
寒冰弹支持多重投射,可以同时发射多个冰弹
5. 寒冰弹绑定到技能1快捷键(W键)

💡 实用小贴士: @reqs.md @tech.md 这两个文件是为了让cursor知道我们的需求和技术要点,开发出的功能更符合我们的要求

💡 代码实现

// IceBolt.cs
using Godot;
using System.Collections.Generic;
using Game.Skills.Base;
using Game.Enemies;

namespace Game.Skills.Active
{
public partial class IceBolt : ProjectileSkill
{
private bool _isMultishot = false;
private const float PROJECTILE_SPEED = 350f;
private const float DAMAGE = 20f;
private const float SLOW_FACTOR = 0.5f; // 减速效果
private const float SLOW_DURATION = 3.0f; // 减速持续时间
private const int MULTISHOT_COUNT = 3;
private const float MULTISHOT_ANGLE = 15f;

public override string Name { get; protected set; } = "寒冰弹";
public override string Description { get; protected set; } = "发射一枚寒冰弹,对敌人造成伤害并减速";
public override float Cooldown { get; protected set; } = 2.0f;
public override float ManaCost { get; protected set; } = 15.0f;

public override void Initialize()
{
base.Initialize();
GD.Print($"寒冰弹技能初始化完成");
}

public override void EnableMultiProjectiles()
{
_isMultishot = true;
GD.Print("寒冰弹: 启用多重投射模式");
}

public override void DisableMultiProjectiles()
{
_isMultishot = false;
GD.Print("寒冰弹: 关闭多重投射模式");
}

protected override void CreateProjectile(Node2D source)
{
if (_isMultishot)
{
CreateMultipleProjectiles(source);
}
else
{
CreateSingleProjectile(source);
}
}

private void CreateSingleProjectile(Node2D source)
{
var projectile = new IceBoltProjectile();
projectile.Speed = PROJECTILE_SPEED;
projectile.Damage = DAMAGE;
projectile.Direction = GetAimDirection(source);
projectile.Source = source;
projectile.GlobalPosition = source.GlobalPosition;

source.GetTree().CurrentScene.AddChild(projectile);
GD.Print($"寒冰弹发射: 位置={source.GlobalPosition}, 方向={projectile.Direction}");
}

private void CreateMultipleProjectiles(Node2D source)
{
Vector2 baseDirection = GetAimDirection(source);

for (int i = 0; i < MULTISHOT_COUNT; i++)
{
float angle = (i - (MULTISHOT_COUNT - 1) / 2f) * Mathf.DegToRad(MULTISHOT_ANGLE);
Vector2 direction = baseDirection.Rotated(angle);

var projectile = new IceBoltProjectile();
projectile.Speed = PROJECTILE_SPEED;
projectile.Damage = DAMAGE;
projectile.Direction = direction;
projectile.Source = source;
projectile.GlobalPosition = source.GlobalPosition;

source.GetTree().CurrentScene.AddChild(projectile);
}

GD.Print($"多重寒冰弹发射: 位置={source.GlobalPosition}, 数量={MULTISHOT_COUNT}");
}

private partial class IceBoltProjectile : Area2D
{
[Export]
public float Speed { get; set; } = 350.0f;

[Export]
public float Damage { get; set; } = 20.0f;

public Vector2 Direction { get; set; } = Vector2.Right;
public Node Source { get; set; }

private float _lifetime = 5.0f;

public override void _Ready()
{
CollisionLayer = 8; // 第4层,用于投射物
CollisionMask = 4; // 第3层,用于检测敌人

var shape = new CircleShape2D();
shape.Radius = 8f;
var collision = new CollisionShape2D();
collision.Shape = shape;
AddChild(collision);

// 添加视觉效果
var sprite = new ColorRect();
sprite.Color = new Color(0.5f, 0.8f, 1.0f); // 淡蓝色
sprite.Size = new Vector2(16, 16);
sprite.Position = new Vector2(-8, -8);
AddChild(sprite);

// 添加冰晶效果
var iceParticles = new CPUParticles2D();
iceParticles.Amount = 20;
iceParticles.Lifetime = 0.5f;
iceParticles.OneShot = false;
iceParticles.Explosiveness = 0.0f;
iceParticles.Direction = Vector2.Zero;
iceParticles.Spread = 180;
iceParticles.Gravity = Vector2.Zero;
iceParticles.InitialVelocity = 20.0f;
iceParticles.Scale = 2.0f;
iceParticles.ScaleAmount = 1.0f;
iceParticles.Color = new Color(0.7f, 0.9f, 1.0f, 0.7f);
AddChild(iceParticles);

// 添加发光效果
var glow = new ColorRect();
glow.Color = new Color(0.7f, 0.9f, 1.0f, 0.3f); // 淡蓝色发光
glow.Size = new Vector2(24, 24);
glow.Position = new Vector2(-12, -12);
glow.ZIndex = -1;
AddChild(glow);

BodyEntered += OnBodyEntered;
AreaEntered += OnAreaEntered;
}

public override void _Process(double delta)
{
_lifetime -= (float)delta;
if (_lifetime <= 0)
{
QueueFree();
return;
}

Position += Direction * Speed * (float)delta;
}

private void OnBodyEntered(Node2D body)
{
if (body is Monster monster)
{
GD.Print($"寒冰弹击中怪物: {monster.Name}");
monster.TakeDamage(Damage);

// 应用减速效果
ApplySlowEffect(monster);

// 播放冰冻特效
PlayFreezeEffect(monster.GlobalPosition);

QueueFree();
}
else if (body is Enemy enemy)
{
GD.Print($"寒冰弹击中敌人: {enemy.Name}");
enemy.TakeDamage(Damage);

// 播放冰冻特效
PlayFreezeEffect(enemy.GlobalPosition);

QueueFree();
}
}

private void OnAreaEntered(Area2D area)
{
// 处理与其他区域的碰撞
}

private void ApplySlowEffect(Monster monster)
{
// 这里应该调用怪物的减速方法
// 由于Monster类可能没有直接的减速方法,这里只是示例
// 实际实现时需要在Monster类中添加相应的方法
GD.Print($"对怪物 {monster.Name} 施加减速效果: {SLOW_FACTOR * 100}%, 持续 {SLOW_DURATION} 秒");

// 如果Monster有减速方法,可以这样调用:
// monster.ApplySlowEffect(SLOW_FACTOR, SLOW_DURATION);
}

private void PlayFreezeEffect(Vector2 position)
{
// 创建冰冻特效
var freezeEffect = new Node2D();
Source.GetTree().CurrentScene.AddChild(freezeEffect);
freezeEffect.GlobalPosition = position;

// 添加冰晶粒子
var particles = new CPUParticles2D();
particles.Emitting = true;
particles.OneShot = true;
particles.Explosiveness = 1.0f;
particles.Amount = 20;
particles.Lifetime = 0.5f;
particles.Direction = Vector2.Zero;
particles.Spread = 180;
particles.Gravity = Vector2.Zero;
particles.InitialVelocity = 50.0f;
particles.Scale = 3.0f;
particles.Color = new Color(0.7f, 0.9f, 1.0f);
freezeEffect.AddChild(particles);

// 添加冰冻光环
var iceRing = new ColorRect();
iceRing.Color = new Color(0.7f, 0.9f, 1.0f, 0.5f);
iceRing.Size = new Vector2(40, 40);
iceRing.Position = new Vector2(-20, -20);
freezeEffect.AddChild(iceRing);

// 创建消失动画
var tween = freezeEffect.CreateTween();
tween.TweenProperty(iceRing, "modulate:a", 0.0f, 0.5f);
tween.TweenCallback(Callable.From(() => freezeEffect.QueueFree()));
}
}
}
}

5 使用godot mono,打开项目运行游戏​

alt text 点击 “import existing project”

定位到pixpof项目目录,选择project.godot文件 alt text

alt text 点击 "Import & Edit "

alt text 打开项目之后,点击右上角的白色三角形,运行游戏

alt text

6 发现godot编辑器报错​

alt text CS0246: The type or namespace name 'CPUParticles2D' could not be found (are you missing a using directive or an assembly reference?) /Users/liyihang/Downloads/cursortest/pixpof/src/Skills/Active/IceBolt.cs(120,40)

7 把错误信息复制到cursor聊天框​

alt text

你:
CS0246: The type or namespace name 'CPUParticles2D' could not be found (are you missing a using directive or an assembly reference?) /Users/liyihang/Downloads/cursortest/pixpof/src/Skills/Active/IceBolt.cs(120,40)

Cursor:
分析错误:
1. 错误信息: CS0246: The type or namespace name 'CPUParticles2D' could not be found
2. 问题原因: Godot 4.x 中粒子系统的类名可能已更改
3. 解决方案:
- 使用 GPUParticles2D 替代 CPUParticles2D
- 或者确保正确引用了相关命名空间
4. 需要修改 IceBolt.cs 文件中的粒子系统实现


让我修复 IceBolt.cs 中的粒子系统错误:
...

8 Cursor修改之后,切换到Godot编辑器,点击“Discard local changes and reload”,在点击右上角的白色三角形,运行游戏​

alt text

9 点击UI的技能1图标 或者 按“Q”技能快捷键,触发寒冰弹技能​

alt text

10 我们开发完代码后,需要增加自动化测试,保证功能完整性和正确性​

你:请给寒冰弹技能增加单元测试, 放Tests/Unit中

Cursor:
我需要为寒冰弹技能创建单元测试。这个测试应该放在Tests/Unit目录下,并且需要测试寒冰弹的基本功能,包括:

1. 初始化是否正确
2. 多重投射功能是否正常
3. 冷却时间是否正确
4. 魔法消耗是否正确
5. 伤害计算是否正确

我将创建一个新的测试文件:Tests/Unit/Skills/Active/IceBoltTests.cs

...

这个单元测试文件包含了对寒冰弹技能的全面测试,包括:
基本属性测试(名称、描述、冷却时间、魔法消耗等)
多重投射功能的启用和禁用
与辅助技能(低阶多重投射)的交互
冷却时间的管理
瞄准方向的计算
伤害和减速效果的常量值验证
测试使用了NUnit框架和Moq库来模拟Godot对象,这样可以在不依赖Godot引擎的情况下进行单元测试。通过反射机制,我们能够访问和验证私有字段的值,确保内部状态符合预期。
需要注意的是,这些测试主要关注寒冰弹的逻辑功能,而不是实际的游戏效果(如视觉效果、声音等),因为这些通常需要在集成测试或手动测试中验证。

🎯 测试用例生成

// IceBoltTests.cs
using NUnit.Framework;
using Game.Skills.Active;
using Game.Skills.Base;
using Game.Skills.Support;
using Game.Enemies;
using Moq;
using Godot;

namespace Game.Tests.Unit.Skills.Active
{
[TestFixture]
public class IceBoltTests
{
private IceBolt _iceBolt;
private Mock<Node2D> _mockSource;
private Mock<Monster> _mockMonster;

[SetUp]
public void Setup()
{
// 创建寒冰弹实例
_iceBolt = new IceBolt();

// 模拟源节点(玩家)
_mockSource = new Mock<Node2D>();
_mockSource.Setup(s => s.GlobalPosition).Returns(new Vector2(0, 0));
_mockSource.Setup(s => s.GetGlobalMousePosition()).Returns(new Vector2(100, 0));

// 模拟怪物
_mockMonster = new Mock<Monster>();
_mockMonster.Setup(m => m.GlobalPosition).Returns(new Vector2(50, 0));
_mockMonster.Setup(m => m.Name).Returns("TestMonster");
}

[Test]
public void Initialize_ShouldSetCorrectProperties()
{
// Act
_iceBolt.Initialize();

// Assert
Assert.That(_iceBolt.Name, Is.EqualTo("寒冰弹"));
Assert.That(_iceBolt.Description, Is.EqualTo("发射一枚寒冰弹,对敌人造成伤害并减速"));
Assert.That(_iceBolt.Cooldown, Is.EqualTo(2.0f));
Assert.That(_iceBolt.ManaCost, Is.EqualTo(15.0f));
Assert.That(_iceBolt.TriggerType, Is.EqualTo(SkillTriggerType.Active));
}

[Test]
public void EnableMultiProjectiles_ShouldEnableMultishot()
{
// Act
_iceBolt.EnableMultiProjectiles();

// 由于_isMultishot是私有字段,我们需要通过反射来验证它的值
var isMultishotField = _iceBolt.GetType()
.GetField("_isMultishot", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
bool isMultishot = (bool)isMultishotField.GetValue(_iceBolt);

// Assert
Assert.That(isMultishot, Is.True);
}

[Test]
public void DisableMultiProjectiles_ShouldDisableMultishot()
{
// Arrange
_iceBolt.EnableMultiProjectiles();

// Act
_iceBolt.DisableMultiProjectiles();

// 通过反射获取私有字段值
var isMultishotField = _iceBolt.GetType()
.GetField("_isMultishot", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
bool isMultishot = (bool)isMultishotField.GetValue(_iceBolt);

// Assert
Assert.That(isMultishot, Is.False);
}

[Test]
public void AddSupport_WithLesserMultipleProjectiles_ShouldEnableMultishot()
{
// Arrange
var support = new LesserMultipleProjectilesSupport();

// Act
_iceBolt.AddSupport(support);

// 通过反射获取私有字段值
var isMultishotField = _iceBolt.GetType()
.GetField("_isMultishot", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
bool isMultishot = (bool)isMultishotField.GetValue(_iceBolt);

// Assert
Assert.That(isMultishot, Is.True);
}

[Test]
public void Trigger_ShouldStartCooldown()
{
// Arrange
_iceBolt.Initialize();

// Act
_iceBolt.Trigger(_mockSource.Object);

// Assert
Assert.That(_iceBolt.CurrentCooldown, Is.GreaterThan(0));
Assert.That(_iceBolt.CurrentCooldown, Is.EqualTo(_iceBolt.Cooldown));
}

[Test]
public void Update_ShouldReduceCooldown()
{
// Arrange
_iceBolt.Initialize();
_iceBolt.Trigger(_mockSource.Object);
float initialCooldown = _iceBolt.CurrentCooldown;
float deltaTime = 0.5f;

// Act
_iceBolt.Update(deltaTime);

// Assert
Assert.That(_iceBolt.CurrentCooldown, Is.EqualTo(initialCooldown - deltaTime));
}

[Test]
public void CanTrigger_WithZeroCooldown_ShouldReturnTrue()
{
// Arrange
_iceBolt.Initialize();

// Act & Assert
Assert.That(_iceBolt.CanTrigger(), Is.True);
}

[Test]
public void CanTrigger_DuringCooldown_ShouldReturnFalse()
{
// Arrange
_iceBolt.Initialize();
_iceBolt.Trigger(_mockSource.Object);

// Act & Assert
Assert.That(_iceBolt.CanTrigger(), Is.False);
}

[Test]
public void GetAimDirection_ShouldReturnNormalizedDirection()
{
// Arrange
var getAimDirectionMethod = _iceBolt.GetType()
.GetMethod("GetAimDirection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

// Act
Vector2 direction = (Vector2)getAimDirectionMethod.Invoke(_iceBolt, new object[] { _mockSource.Object });

// Assert
Assert.That(direction.Length(), Is.EqualTo(1).Within(0.001f));
Assert.That(direction.X, Is.EqualTo(1).Within(0.001f));
Assert.That(direction.Y, Is.EqualTo(0).Within(0.001f));
}

[Test]
public void IceBolt_DamageConstant_ShouldMatchExpected()
{
// 通过反射获取私有常量值
var damageField = _iceBolt.GetType()
.GetField("DAMAGE", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
float damage = (float)damageField.GetValue(null);

// Assert
Assert.That(damage, Is.EqualTo(20f));
}

[Test]
public void IceBolt_SlowFactorConstant_ShouldMatchExpected()
{
// 通过反射获取私有常量值
var slowFactorField = _iceBolt.GetType()
.GetField("SLOW_FACTOR", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
float slowFactor = (float)slowFactorField.GetValue(null);

// Assert
Assert.That(slowFactor, Is.EqualTo(0.5f));
}

[Test]
public void IceBolt_SlowDurationConstant_ShouldMatchExpected()
{
// 通过反射获取私有常量值
var slowDurationField = _iceBolt.GetType()
.GetField("SLOW_DURATION", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
float slowDuration = (float)slowDurationField.GetValue(null);

// Assert
Assert.That(slowDuration, Is.EqualTo(3.0f));
}
}
}

11 开发完功能后,回到Github Desktop提交代码​

alt text 填写 当前功能的概要 "update IceBolt name", 然后点击 "Commit to IcdBolt"

12 发布功能分支​

点击 "Public branch" alt text

13 创建pull request,提交到主仓库​

点击 "Preview Pull Request", 查看代码差异 alt text

alt text 确定文件无误后,点击“Create Pull Request”,提交合并请求

14 然后自动跳转到网页,完善合并请求的titel和description​

alt text

点击 "Create pull request"按钮,就可以等待维护人员审查你的提交

15 你需要关注审查反馈,根据意见修改,直到维护人员合并你的pull request​

alt text

四 调试技巧​

📝 我们可以让cursor增加日志系统,查看输出执行过程的日志

你:增加寒冰弹命中敌人的日志

Cursor:
分析需求:
1. 需要增加寒冰弹命中敌人的日志
2. 需要修改 IceBolt.cs 文件中的 OnBodyEntered 方法
3. 需要添加更详细的日志信息,包括:
- 敌人的名称
- 敌人受到的伤害
- 减速效果的详情
- 命中位置
4. 可以使用 GD.Print 函数输出日志

这个修改增加了更详细的命中日志,包括:
清晰的日志分隔符,使日志更易读
目标类型和名称
命中位置的坐标
造成的伤害数值
减速效果的详细信息(对怪物)
敌人当前的生命值状态
这样可以更清晰地跟踪寒冰弹的命中效果,方便调试和平衡游戏。
// src/Skills/Active/IceBolt.cs

GD.Print($"========== 寒冰弹命中 ==========");
GD.Print($"目标类型: 怪物 ({monster.Name})");
GD.Print($"命中位置: {monster.GlobalPosition}");
GD.Print($"造成伤害: {Damage}");
GD.Print($"减速效果: 降低 {SLOW_FACTOR * 100}% 移动速度,持续 {SLOW_DURATION} 秒");
GD.Print($"怪物当前生命值: {monster.CurrentHealth}/{monster.MaxHealth}");
GD.Print($"================================");

💡 实用小贴士:

  1. 当不知道错误如何发生时,可以让Cursor打印执行过程中的日志

🌟 进阶提示:

  1. 请用最新的claude-3.7-sonnet模型,减少开发产生的问题
  2. 让Cursor帮你审查代码
  3. 使用AI生成技能特效参数
  4. 自动生成技能文档

记住:参与开源项目是提升编程能力的好方法,Cursor不仅能帮你写代码,还能帮你理解他人的代码!让AI助手陪你一起在开源世界探索吧!

Monica AI助手

Monica (莫妮卡)是一款全功能的AI助手,配备了最先进的AI模型,如GPT-4、Claude、Bard等,用于帮助用户聊天、搜索、写作、翻译等多种功能。

立即体验

CursorAI编程知识星球

我正在「史密斯DeepSeekAI社」和朋友们讨论有趣的话题,你⼀起来吧?

立刻查看