Unity接入ChatGPT基于Python.Runtime的实现

目录

前言

编译Python.Runtime.dll

Unity接入ChatGPT

1.第一步 准备环境

2.第二步 python代码的书写

3.第三步 C#调用python代码


前言:

相信各位游戏人都用过ChatGPT吧,那么怎么在unity里接入ChatGPT呢?本文章会通过一种极其简单的方式来实现,最终效果如下:

  1. 可以进行中文对话,可以记住一点的上下文(实际上几乎记不住任何上下文,可能是free版本的限制吧,等拿到万事达卡再试试plus版本的)
  2. 可调节的准确率(诚恳度越低准确度越高,但是更冷漠),Ai说话时的“温度”
  3. 简单的python和c#交互

好的,下面开始教程:

编译Python.Runtime.dll

看标题可知,这是基于Python.Runtime的,所以首先要拿到这个.dll文件

github地址
GitHub - pythonnet/pythonnet: Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers.Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers. - GitHub - pythonnet/pythonnet: Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers.https://github.com/pythonnet/pythonnet

在GitHub上clone 下来的文件需要编译成dll文件,建议使用vs进行编译,下面是教程

用vs打开解决方案(根目录下的pythonnet.sln)

 

 在python.runtime项目上右键打开属性

  1. 将目标平台设置为x64
  2. 选择一个基路径,这个路径是你在编译好的dll文件的存放路径,是相对根目录而言的,我的就在这里面了,大家编译完成后直接在根目录里面找就好了
  3. 哦,忘记了,输出类型选择类库

 然后再次右键选择生成

 等待编译完成后即可在设置的基目录下看到这个dll文件

 然后把这个dll文件拖到unity里

 好了,准备工作已经完成,现在开始正式在Unity里接入ChatGPT!!!

Unity接入ChatGPT

首先要获取openai的api的使用权限,所以我们要有key才行

在openai的官网可以获取:key在这里弄

弄好key一定要复制好,保存好,因为一旦获取了之后,key就永久隐藏了。如果丢失了就只能再获取一次(虽然不麻烦,但是耗money呀)

ok废话介绍完毕,开始撸码,注意哦,教程是循循渐进的,代码都不是完整的,如果只想copy可以直接跳到底部复制完整代码自己看。

1.第一步 准备环境

首先引入using Python.Runtime的命名空间,设置好时间间隔,建议为20s,为啥,我也不知道,官网说的...

再就是要告诉这个插件python解释器的dll文件路径,这个很好弄,在官网上下载好python就直接能看到这个dll,还有环境变量别忘记配置了,有的大佬可能懒得登录,但是又想复制,所以我直接贴出来代码,hh

using System.IO;
using Python.Runtime;
using UnityEngine;

public class PythonManager : MonoBehaviour
{
    // Start is called before the first frame update
    [SerializeField] public float _timer;
    private float _interval = 20;

    void Start()
    {
        _timer = _interval;
        // 获取python.all的路径(调用python解释器来执行python代码)
        string pythonPath = @"C:\Python311"; 
        string dllPath = Path.Combine(pythonPath, "python311.dll");
        Runtime.PythonDLL = dllPath;
    }
}
using System.IO;
using Python.Runtime;
using UnityEngine;

public class PythonManager : MonoBehaviour
{
    // Start is called before the first frame update
    [SerializeField] public float _timer;
    private float _interval = 20;

    void Start()
    {
        _timer = _interval;
        // 获取python.all的路径(调用python解释器来执行python代码)
        string pythonPath = @"C:\Python311"; 
        string dllPath = Path.Combine(pythonPath, "python311.dll");
        Runtime.PythonDLL = dllPath;
    }
}

2.第二步 python代码的书写

这里我先说一下我的实现思路,免得大家看不懂。

我没有使用文件读取的方式来提交prompt和获取gpt回答的内容,因为我很菜,hhh,用python调用resources里的文件实在是麻烦透顶,研究了一会就放弃了,当然也可以使用绝对路径的方式来用,但是这样一打包就彻底废了,因为路径全乱了,所以干脆放弃使用文件读取的方式。然后怎么办呢???突然我想到了一个极其极其的野路子,哈哈哈哈,就是用游戏物体的名字来当临时储存“文件”,这样就方便多了,而且还不用开线程去读文件,性能也高了不少,哈哈哈哈哈快夸我。

其实呢。。。我这个文章的缺陷也很多,就是应该只能在pc上运行,我用安卓测试的时候会闪退,应该和python解释器的调用有关系,没有去研究....要是有大佬知道怎么解决的话欢迎提供解决方案哈,嘻嘻嘻嘻。

然后这是python代码的部分(纯享版,没有注释)

import UnityEngine as ue
import openai
import random
objects = ue.Object.FindObjectsOfType(ue.GameObject)
duiBaQis = []
for obj in objects:
    if obj.tag == 'duiBaQi':
        duiBaQis.append(obj)
openai.api_key = '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'
model_engine = 'gpt-3.5-turbo' 
prompt = 'hello'
temperature = random.uniform(0.1,2)
for o in duiBaQis:
    if('@temperature:' in o.name):
        temperatureStr = format(temperature,'.2f')
        o.name = '@temperature:' + temperatureStr;
max_tokens = 255
for o in duiBaQis:
    if('@prompt:' in o.name):
        promptText = o.name.replace('@prompt:','');
        prompt = promptText
completion = openai.ChatCompletion.create(
   model='gpt-3.5-turbo',
   messages=[
     {'role': 'user', 'content': prompt}
   ],
   temperature = temperature,
   max_tokens = max_tokens
)
print(completion.choices[0].message.content)
for o in duiBaQis:
    if('@ai:' in o.name):
        o.name = '@ai:'+ completion.choices[0].message.content;
import UnityEngine as ue
import openai
import random
objects = ue.Object.FindObjectsOfType(ue.GameObject)
duiBaQis = []#这个数组是储存所有含有duibaqi标签的游戏物体,为了加快查找速度用的,当然要是大家熟悉python的话,应该可以使用一个全局管理器来管理每一个用到的游戏物体,这样就只需要查找一遍就行了(思路就是定义几个bool值,查找完毕后设置为false,检测到false就跳过for in 循环),但是我不太会用python,hhh,没有学过...所以就放弃了,大家完全可以不比这样做
for obj in objects:
    if obj.tag == 'duiBaQi':
        duiBaQis.append(obj)#将这三个游戏物体推进这里面,哪怕是提高一丢丢的效率也是好的...
openai.api_key = '这里填在官网拿到的key'#这里是在官网得到的key
model_engine = 'gpt-3.5-turbo' #在这里选择gpt的模型,因为不同模型的使用方法有点不同,所以这里最好和我一样,等这个实现了以后大家再去琢磨别的。
prompt = 'hello'#这里是prompt,就是大家在用ChatGPT的时候给问他的那些问题
temperature = random.uniform(0.1,2)#这里就是可调节“温度”了,也就是gpt的语气
for o in duiBaQis:
    #查找temperature物体
    if('@temperature:' in o.name):
        temperatureStr = format(temperature,'.2f')
        o.name = '@temperature:' + temperatureStr;
max_tokens = 255#这个是gpt回答内容的最大字符,因为我们用的是游戏物体的名字来存储这个数据,但是在unity里游戏物体的名字最多也就能存255个字符,所以....就这样咯。
for o in duiBaQis:
    #查找prompt物体
    if('@prompt:' in o.name):
        promptText = o.name.replace('@prompt:','');#需要把"@prompt:"这个字符串替换掉才能作为prompt使用,不要忘记了,不然的话prompt都成啥了,笑死。
        prompt = promptText
#最核心的代码,调用openai提供的api,将我们设置好的model,prompt,temperature,max_tokens参数严格按照官方文档给出的格式填入,然后用completion接受返回结果。返回的是一个对象,结构比较复杂,包含很多细节,大家有兴趣可以打印出来看看,这里我们只需要使用choices[0].message.content这个数据即可。
completion = openai.ChatCompletion.create(
   model='gpt-3.5-turbo',
   messages=[
     {'role': 'user', 'content': prompt}
   ],
   temperature = temperature,
   max_tokens = max_tokens
)
print(completion.choices[0].message.content)
for o in duiBaQis:
    #查找ai物体
    if('@ai:' in o.name):
        o.name = '@ai:'+ completion.choices[0].message.content;#直接更改ai物体的名字为gpt回答的内容,注意前缀不要忘记加上"@ai:",因为我们不是只用一次,不要卸磨杀驴了,hhh

 代码讲解我都放在注释里面了,大家可以对照着代码一起看,我觉得还是蛮清晰的,哈哈。

 好了,python代码的部分就是这么多,还是挺简单的,下面是用c#来调用python的部分

3.第三步 C#调用python代码

string code = @"那一坨python代码"
PythonEngine.Initialize();
using (Py.GIL())
{
    PythonEngine.Exec(code);
}
        string code = @"那一坨python代码"
        PythonEngine.Initialize();
        using (Py.GIL())
        {
            PythonEngine.Exec(code);
        }

code就是刚才写的那一坨python代码 ,注意@不要忘记写了哈,不然就没法缩进。

就这么简单?对!就这么简单,哈哈哈哈。

没了,完了,调用成功了,接入完成了,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈。(博主已疯)

至于怎么gpt和游戏进行交互,怎么弄出好玩的效果,就靠各位大佬就自己发挥想象力吧~。

下面贴上我自己的代码给大家,这部分我就放在code里面了,就不贴出来凑字数了,hhh。

这是PythonManager.cs文件

using System.IO;
using Python.Runtime;
using UnityEngine;

public class PythonManager : MonoBehaviour
{
    [SerializeField] public float _timer;
    private float _interval = 20;

    void Start()
    {
        _timer = _interval;
        // 获取python.all的路径(调用python解释器来执行python代码)
        string pythonPath = @"C:\Python311"; 
        string dllPath = Path.Combine(pythonPath, "python311.dll");
        Runtime.PythonDLL = dllPath;
    }
    
    void Update()
    {
        _timer += Time.deltaTime;
        UIManager.Instance.timer.fillAmount = _timer / _interval;
        if (_timer < _interval) return;
        //控制代码执行顺序
        if (GameManager.Instance.canReadPy && GameManager.Instance.typeDone)
        {
            ExecCode();
        }
    }

    public void ExecCode()
    {   
        string code = @"
import UnityEngine as ue
import openai
import random
objects = ue.Object.FindObjectsOfType(ue.GameObject)
duiBaQis = []
for obj in objects:
    if obj.tag == 'duiBaQi':
        duiBaQis.append(obj)
openai.api_key = '这里填在官网拿到的key'
model_engine = 'gpt-3.5-turbo' 
prompt = 'hello'
temperature = random.uniform(0.1,2)
for o in duiBaQis:
    if('@temperature:' in o.name):
        temperatureStr = format(temperature,'.2f')
        o.name = '@temperature:' + temperatureStr;
max_tokens = 255
for o in duiBaQis:
    if('@prompt:' in o.name):
        promptText = o.name.replace('@prompt:','');
        prompt = promptText
completion = openai.ChatCompletion.create(
   model='gpt-3.5-turbo',
   messages=[
     {'role': 'user', 'content': prompt}
   ],
   temperature = temperature,
   max_tokens = max_tokens
)
print(completion.choices[0].message.content)
for o in duiBaQis:
    if('@ai:' in o.name):
        o.name = '@ai:'+ completion.choices[0].message.content;
";
        GameManager.Instance.canReadPy = false;
        GameManager.Instance.typeDone = false;
        _timer = 0;

        

        PythonEngine.Initialize();
        using (Py.GIL())
        {
            PythonEngine.Exec(code);
        }

    }
}

 这是DialogueManager.cs文件

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class DialogueManager : MonoBehaviour
{
    public static DialogueManager Instance;
    public string taDialogue;

    private float _timer;
    //缓冲时间
    private float _interval = 1.6f;

    private bool _writeDone;
    private string prompt;
    public GameObject promptObj;
    public GameObject aiObj;
    public GameObject temperatureObj;


    private void Awake()
    {
        Instance = this;
    }

    private void FixedUpdate()
    {
        //判断是不是点击完成了,如果点击了,就开始计时,然后执行readfile函数,放出ai的内容
        if (_writeDone)
        {
            _timer += Time.deltaTime;
            if (_timer >= _interval)
            {
                _writeDone = false;
                _timer = 0;
                ReadFile();
            }
        }
    }

    public void ReadFile()
    {
        //读取在temperature游戏物体的名字,来显示诚恳度,然后更改进度条。
        float temperature = float.Parse(temperatureObj.name.Replace("@temperature:", ""));
        GameManager.Instance.volume = temperature;

        //读取ai游戏物体的名字,来显示ai的内容
        string aiText = aiObj.name.Replace("@ai:", "");
        //哈哈哈,这里就体现我的菜了,不会正则,正能这样傻瓜式的校验
        if (aiText.Contains("AI") || aiText.Contains("ai") || aiText.Contains("语言模型") || aiText.Contains("开发") || aiText.Contains("人工智能")|| aiText.Contains("程序"))
        {
            aiText = aiText.Replace("AI", UIManager.Instance.taName.text);
            aiText = aiText.Replace("ai", UIManager.Instance.taName.text);
            aiText = aiText.Replace("语言模型", "玩伴");
            aiText = aiText.Replace("开发", "培育出来");
            aiText = aiText.Replace("人工智能", "超级聪明");
            aiText = aiText.Replace("程序", "人");
        }
        taDialogue = aiText;
        Debug.Log("@ReadSuccess");
        StartCoroutine(Type(taDialogue));
        Debug.Log(taDialogue);
        

    }

    //这个函数是在玩家输入完毕以后调用的,用的是On End Edit事件,在input field这个组件里面可以看到
    public void Ask(string ask)
    {
        prompt = ask;
    }
    //这个函数是按下准备按钮以后调用的,调用的是On Click事件,在button这个组件里可以看到
    public void WriteFile()
    {
        //加个判断条件,免得玩家们太多兴奋一直按个不停,导致我们的钱钱不断流失,哈哈哈,因为每次发请求都是要收费的呀!!!
        if (UIManager.Instance.timer.fillAmount < 0.99f)
        {
            UIManager.Instance.warning.SetActive(true);
            return;
        }

        UIManager.Instance.taDialogue.text = UIManager.Instance.taName.text + "正在思考哦~";
        
        if (prompt == "")
        {
            prompt = "hello";
        }
        
        promptObj.name = "@prompt:" + prompt;//这里就是更改游戏物体的名字为我们设置好的prompt了,注意"@prompt:"不要忘记了,因为我们在python里是通过这个来查找的
        
        GameManager.Instance.canReadPy = true;
        GameManager.Instance.typeDone = true;

        _writeDone = true;
        Debug.Log("@WriteSuccess");

    }

    private IEnumerator Type(string str)
    {
        UIManager.Instance.taDialogue.text = "";
        for (int i = 0; i < str.Length; i++)
        {
            UIManager.Instance.taDialogue.text += str[i];
            yield return null;
        }
    }
}

既然都贴出来了,那就讲一下吧(其实都在注释里写上了),大家完全可以不用看的,就是我自己想写一下,hh,上面讲的那些就是核心了,这部分大家可以自己发挥创意的。

我的思路就是玩家可以输入文字(那个输入框),然后输入完以后会调用ask函数,然后把输入的内容传到prompt里,然后接着玩家点击小爪子(那个按钮)就会执行writefile函数(但是呢?还要先校验一下是不是在冷却时间里,我的ui改变都是在UIManager里完成的)

然后这是代码:

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    public static UIManager Instance;
    public Image chenKenDu;
    private float _value;
    public Text youName;
    public Text taName;
    public Text taDialogue;
    public Image timer;
    public GameObject warning;
    public Text error;
    public bool useGpt;
    private void Awake()
    {
        Instance = this;
        _value = useGpt ? 2 : 200;
    }
    

    public IEnumerator ChangeValueToUI()
    {
        chenKenDu.fillAmount = 0;
        var filla = GameManager.Instance.volume / _value;
        
        var f = filla / 60;
        for (float i = 0; i < filla; i += f)
        {
            yield return null;
            chenKenDu.fillAmount += f;
        }
    }
}

接着上面的讲,执行完WriteFile这个函数以后,就会把这三个bool值改成true,一旦为true了,PythonManager里马上就会检测到,然后执行ExceCod函数,运行python代码。这个时候_writeDone也成true了,然后开始计时,计时1.6s以后执行readfile函数,读取游戏物体的名字,然后在UI里显示出来。

至此,ChatGPT的接入就完全结束了,感谢大家能读到  3.第三步 C#调用python代码 或者能坚持读到这里,哈哈哈,那就不浪费大家时间了,就这样,结束!拜拜!

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值