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社」和朋友们讨论有趣的话题,你⼀起来吧?

立刻查看