Skip to content

Commit

Permalink
Merge pull request #10 from OUCC/#4-ChatGPT
Browse files Browse the repository at this point in the history
#4 chat gpt
  • Loading branch information
Mizuhane authored Jun 18, 2023
2 parents 6070ad4 + 47e7fe2 commit 784ed71
Show file tree
Hide file tree
Showing 17 changed files with 426 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ crashlytics-build.properties
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*

# Open AI API Key
/[Aa]ssets/[Rr]esources/ApiKey.txt
/[Aa]ssets/[Rr]esources/ApiKey.txt.meta
8 changes: 8 additions & 0 deletions Assets/Resources.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions Assets/Scenes/SampleScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,54 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &764219436
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 764219438}
- component: {fileID: 764219437}
m_Layer: 0
m_Name: GameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &764219437
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 764219436}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 75c1c8d2f998caf46b7f794475bd2e1d, type: 3}
m_Name:
m_EditorClassIdentifier:
PlayerHP: 100
EnemyHP: 100
--- !u!4 &764219438
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 764219436}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
37 changes: 37 additions & 0 deletions Assets/Scripts/ChatGPTConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using UnityEngine.Networking;
using Newtonsoft.Json;

namespace HackathonA
{
public class ChatGPTConnection
{
private readonly JsonSerializerSettings settings = new();
private readonly string apiKey;
private readonly string apiUrl;


public ChatGPTConnection(string apiKey, string apiUrl)
{
this.apiKey = apiKey;
this.apiUrl = apiUrl;
settings.NullValueHandling = NullValueHandling.Ignore;
}

public RequestHandler CreateCompletionRequest(ChatGPTDatas.RequestData requestData)
{
var json = JsonConvert.SerializeObject(requestData, settings);

byte[] data = System.Text.Encoding.UTF8.GetBytes(json);

var request = new UnityWebRequest(apiUrl, "POST")
{
uploadHandler = new UploadHandlerRaw(data),
downloadHandler = new DownloadHandlerBuffer()
};
request.SetRequestHeader("Authorization", $"Bearer {this.apiKey}");
request.SetRequestHeader("Content-Type", "application/json");

return new RequestHandler(request);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/ChatGPTConnection.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions Assets/Scripts/ChatGPTDatas.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;

namespace HackathonA.ChatGPTDatas
{
[Serializable]
public class RequestData
{
public string model = "gpt-3.5-turbo";
public List<Message> messages;
public float? temperature = null; // [0.0 - 2.0]
public float? top_p = null;
public int? n = null;
public bool? stream = null;
public List<string> stop = null;
public int? max_tokens = null;
public float? presence_penalty = null;
public float? frequency_penalty = null;
public Dictionary<int, int> logit_bias = null;
public string user = null;
}

[Serializable]
public class Message
{
public string role;
public string content;
}

[Serializable]
public class Usage
{
public int prompt_tokens;
public int completion_tokens;
public int total_tokens;
}

[Serializable]
public class Choice
{
public Message message;
public string finish_reason;
public int index;
}

[Serializable]
public class ResponseData
{
public string id;
public string @object;
public int created;
public string model;
public Usage usage;
public List<Choice> choices;
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/ChatGPTDatas.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions Assets/Scripts/DebugLoger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Diagnostics;
using Unity.VisualScripting;
using UnityEngine;

namespace HackathonA
{
public class DebugLoger
{
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void LogError(string error)
{
UnityEngine.Debug.LogError(error);
}

[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void Log(string error)
{
UnityEngine.Debug.Log(error);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/DebugLoger.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 108 additions & 0 deletions Assets/Scripts/EnemyAI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using HackathonA.ChatGPTDatas;

namespace HackathonA
{
public class EnemyAI
{
public int DefaultPhysicalAttack { get; set; } = 20;
public int DefaultMagicAttack { get; set; } = 20;
public int DefaultRecoveryPoint { get; set; } = 15;
public int DefaultActionOnError { get; set; } = 0;

private const string API_URL = "https://api.openai.com/v1/chat/completions";
private readonly string _apiKey;
private readonly ChatGPTConnection _chatGPTConnection;

// ChatGPTに入力するメッセージのリスト(過去の内容も一緒に渡すためリスト)
private List<Message> _messages;

public EnemyAI(string apiKey)
{
_apiKey = apiKey;
_messages = new List<Message>()
{
new Message(){role = "system", content = GenerateSystemMessage()},
};
_chatGPTConnection = new ChatGPTConnection(_apiKey, API_URL);
}

private string GenerateSystemMessage()
{
return GenerateSystemMessage(DefaultPhysicalAttack, DefaultMagicAttack, DefaultRecoveryPoint);
}

private string GenerateSystemMessage(int physicalAttack, int mamgicAttack, int recoveryPoint)
{
return @$"Please answer with an integer from 0-4 for your action
You can take the following actions
0: Physical attack. {physicalAttack} damage
1: Magic attack. {mamgicAttack} damage
2: Recovery. {recoveryPoint} recovery
3: Physical Counter. bounce back if opponen's action is Physical attack
4: Magic counter. bounce back if opponent's action is Magic attack
Input example: Your HP is currently 1000 and your opponent's HP is 1000
Output example: 0";
}

// 敵の設定
public void Set(int physicalAttack, int mamgicAttack, int recoveryPoint)
{
_messages = new List<Message>()
{
new Message(){role = "system", content = GenerateSystemMessage(physicalAttack, mamgicAttack, recoveryPoint)},
};
}

/// <summary>
/// ChatGPTとの通信を開始
/// </summary>
/// <param name="playerHP">プレイヤーのHP</param>
/// <param name="enemyHP">敵のHP</param>
/// <returns>アクションのタイプ(0-4)。エラー時にはDefaultActionOnErrorで設定した値が返ってくる</returns>
public async UniTask<int> GetEnemyActionAsync(int playerHP, int enemyHP)
{
string currentMessage = $"Currently, your HP is {enemyHP} and your opponent's HP is {playerHP}.\nChoose from the options above and respond with the corresponding number.";
DebugLoger.Log(currentMessage);
_messages.Add(new Message() { role = "user", content = currentMessage });

using var request = _chatGPTConnection.CreateCompletionRequest(new RequestData() { messages = _messages });

await request.SendAsync();

// エラーがあった場合は、それをコンソールに出力
if (request.IsError)
{
DebugLoger.LogError(request.Error);
return DefaultActionOnError;
}
else
{
var responseMessage = request.Response.choices[0].message.content;
DebugLoger.Log($"ChatGPT replied '{responseMessage}'");

// 文字列を数値に変換
if (int.TryParse(responseMessage[..1], out var action))
{
if (0 <= action && action <= 4)
{
return action;
}
else
{
return DefaultActionOnError;
}
}
else
{

DebugLoger.LogError($"Unable to parse '{responseMessage}'");
return DefaultActionOnError;
}
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/EnemyAI.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions Assets/Scripts/RequestHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using UnityEngine.Networking;
using Newtonsoft.Json;
using Cysharp.Threading.Tasks;
using System;

namespace HackathonA
{
public class RequestHandler: IDisposable
{
public bool IsCompleted { get; private set; }
public bool IsError => Error != null;
public string Error { get; private set; }
public ChatGPTDatas.ResponseData Response { get; private set; }

private UnityWebRequest request;

public RequestHandler(UnityWebRequest request)
{
this.request = request;
}

public async UniTask SendAsync()
{
using (request)
{
await request.SendWebRequest();

if (request.result != UnityWebRequest.Result.Success)
{
Error = "[ChatGPTConnection] " + request.error + "\n\n" + request.downloadHandler.text;
}
else
{
Response = JsonConvert.DeserializeObject<ChatGPTDatas.ResponseData>(request.downloadHandler.text);
}
}
}

public void Dispose()
{
request?.Dispose();
}
}
}
Loading

0 comments on commit 784ed71

Please sign in to comment.