├── .gitignore ├── Assets ├── Codecks_io.meta └── Codecks_io │ ├── Codecks Bug & Feedback Reporter.meta │ └── Codecks Bug & Feedback Reporter │ ├── Documentation │ ├── Codecks Unity Plugin Manual.pdf │ ├── Codecks Unity Plugin Manual.pdf.meta │ ├── docs.md │ └── images │ │ └── codecks-hand-screen.png │ ├── Editor.meta │ ├── Editor │ ├── CodecksEditor.asmdef │ ├── CodecksEditor.asmdef.meta │ ├── CodecksTokenCreator.cs │ └── CodecksTokenCreator.cs.meta │ ├── Resources.meta │ ├── Resources │ ├── powered-by-color-dark.png │ ├── powered-by-color-dark.svg │ ├── powered-by-color-light.png │ ├── powered-by-color-light.svg │ ├── powered-by-mono-dark.png │ ├── powered-by-mono-dark.svg │ ├── powered-by-mono-light.png │ └── powered-by-mono-light.svg │ ├── Runtime.meta │ ├── Runtime │ ├── CodecksCardCreator.cs │ ├── CodecksCardCreator.cs.meta │ ├── CodecksCardCreatorForm.cs │ ├── CodecksCardCreatorForm.cs.meta │ ├── CodecksRuntime.asmdef │ └── CodecksRuntime.asmdef.meta │ ├── Scenes.meta │ └── Scenes │ ├── CodecksSampleScene.unity │ └── CodecksSampleScene.unity.meta ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Recordings can get excessive in size 18 | /[Rr]ecordings/ 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | *.app 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | # Packed Addressables 68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 69 | 70 | # Temporary auto-generated Android Assets 71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 72 | /[Aa]ssets/[Ss]treamingAssets/aa/* 73 | -------------------------------------------------------------------------------- /Assets/Codecks_io.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c921fa31f5c434499a7a51864b622ae 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ac8f62948b9438469ba125cbbbea25f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Documentation/Codecks Unity Plugin Manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecks-io/codecks-unity/4c6c3b4e39be709f58215e47b7fc3c5a01ef9d4e/Assets/Codecks_io/Codecks Bug & Feedback Reporter/Documentation/Codecks Unity Plugin Manual.pdf -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Documentation/Codecks Unity Plugin Manual.pdf.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 43b54add644dceb47a4e12b28f6e0386 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Documentation/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | stylesheet: https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.10.0/github-markdown.min.css 3 | body_class: markdown-body 4 | css: |- 5 | .page-break { page-break-after: always; } 6 | .markdown-body { font-size: 11px; } 7 | .markdown-body pre > code { white-space: pre-wrap; } 8 | --- 9 | 10 | Codecks Unity Bugs & Feedback Reporter 11 | ====================================== 12 | 13 | Thanks for downloading our plugin. The plugin allows you to collect bug and feedback reports right from a easy-to-use form within your game. You can track all the received information using our Codecks platform. 14 | 15 | Users also have the option to leave their email which you can use to send them follow-up questions. They will also automatically be notified via e-mail when their card is set to done in Codecks, showing them that they’re feedback is actually being processed and potentially motivating them to return to your game and leave even more feedback. It’s a win-win situation! 16 | 17 | ![](./images/codecks-hand-screen.png) 18 | 19 | Unity Plugin 20 | ============ 21 | 22 | After importing the plugin you will find the plugin in the folder `Assets/Codecks_io/Codecks Bugs & Feedback Reporter`. You can find a scene named `CodecksSampleScene` that contains a default layout and sample setup for you to check out. 23 | 24 | The sample scene already provides all the UI elements and component setup for you to get started right away. You can test the initial setup of the report tool right from the sample scene by entering your created report token (the one that you created in your Codecks User Report settings screen) into the Default Token property of the `CodecksCardCreator` component on the `CardCreator` scene object (found under the Canvas object). Once you’ve done that you can hit Play. Click the “Give Feedback!” button, fill out the form and press “Send report”. If everything works you should see a card pop-up in your Codecks just moments later. In case of issues, an error message should be printed to your screen and console. 25 | 26 | After testing the initial setup, we recommend copying or integrating the sample scene into your own UI game scene, where you can hook it to show up when pressing a hotkey or by selecting a menu entry according to your own needs. You may also modify the layout to fit your game thematically or use the Codecks default layout as provided. In any case please make sure to not hide the Powered by Codecks sprite and display it next to the report form. 27 | 28 | Here’s an explanation what the two provided `MonoBehavior` classes do: 29 | 30 | * `CodecksCardCreator` handles the basic API communication with Codecks for the purpose of creating cards inside your Codecks project. This class does not handle any UI related tasks and contains only the basic functionality. 31 | * `CodecksCardCreatorForm` is a helper class that manages the UI and forwards the input to the `CodecksCardCreator` class. You may write your own UI handling in case you’re not using the default Unity Canvas system or in case you have special requirements for your UI. The class provides a method `GetMetaText` which you can edit to add your own game related meta data. By default the component also creates a screenshot and attaches it to the request sent to the `CodecksCardCreator` class. You may choose to add additional files to the request (e.g. attaching a savegame or world state dump). 32 | 33 | If you want to generate a new token for each build you distribute, you can integrate the `CodecksTokenCreator` class into your build scripts. Simply call `CodecksTokenCreator.CreateAndSetNewToken` and wait for the callback before your call to `BuildPipeline.BuildPlayer`. This will request a token and store it in your build (in the Resources folder). The `CodecksCardCreator` will check for this created token on startup and use it instead of the default token if present. 34 | 35 | Alternatively, call `CodecksTokenCreator.CreateNewToken` to just get a new token and do what you want with it! 36 | 37 | Note that because of how unity hands http requests async, your batchmode scripts will need to run without `-quit` in the commandline, and instead need to call `EditorApplication.Exit` manually. 38 | 39 | User Reports 40 | ============ 41 | 42 | User Reports allow your users to send feedback directly from within your application. Each feedback will result in a new Codecks card in the deck(s) of your choice. You can use either directly talk to our API endpoint or use our ready-made Unity integration to talk to our Codecks server. 43 | 44 | Set up your first application 45 | ----------------------------- 46 | 47 | As a first step you need to open up your Organization Settings and look for the User Report section. You’ll get to chose between the Unity integration and creating reports via the API. Changes in either section will be reflected in the other (i.e. keys created in the via API section can be used in the Unity integration and vice versa). 48 | 49 | Whichever option you’ve picked, you will be asked to add your first application. You need to enter the name and the target deck in which cards shall be added by default. Both options can be changed later on. Once submitted you’ll receive two keys: a Report Tokenand an Access Key. Let’s have a look at those two. 50 | 51 | ### Report Token 52 | 53 | Report Tokens are meant to be embedded within your application. They allow you to create a card according to the options passed to the endpoint and according to the rules you’ve set up in the settings. Report Tokens can be disabled to prevent more reports from coming in. This can be helpful if one token has gone “rogue”. You can re-enable a disabled token anytime. 54 | 55 | A application can have multiple Report Tokens, each with a unique label. This is meant to differentiate feedback coming from different builds. 56 | 57 | By default Codecks creates a Report Token with a Defaultlabel for you. To create new Report Tokens you need to use another API endpoint along with an Access Key. 58 | 59 | ### Access Key 60 | 61 | Access Keys are more security sensitive and should not end up in your builds. They are meant to be used within your build process though. Whenever you are about to release a new version, you can use the Access Key to create a new Report Token with a version-specific label. The label will be used as a tag in generated cards and thus allows you to quickly filter and sort your reports. 62 | 63 | An Access Key is automatically generated when adding a new application in the settings. You won’t be able to see that key again within Codecks. In case you have lost it, you are able to regenerate a new one. This will invalidate the old Access Key however. 64 | 65 | Codecks Settings 66 | ---------------- 67 | 68 | Here's a tour of all options available in the settings screen: 69 | 70 | - **Report Tokens** 71 | 72 | This tab presents you an overview of all created Report Tokens for your application. It allows you to disable or re-enable specific tokens. Disabling a Report Token will prevent any feedback using this token from creating a card. This list also shows the token itself, label, creation date and the number of created cards. 73 | 74 | - **Update Settings** 75 | 76 | This screens allows you to change these settings: 77 | 78 | - **Name of Application:** The name is used in the select box on the top right as well as in emails sent to feedback reporters (see below) 79 | - **Maximum Upload Size:** As Report Tokens are part of your builds they can theoretically be extracted and used for malicious activites. To prevent user reports from quickly filling up your storage quota, you can configure a limit here. 80 | - **Map Severity to Deck:** feedback reports can set a **severity** value. Each severity can be mapped to a different deck allowing e.g more severe issues to be put into a dedicated deck. 81 | - **Map Severity to Priority:** You can also use priority as a signal for severity. 82 | 83 | - **New Access Key** 84 | 85 | In case you either lost your current Access Key or you want to invalidate it, this section allows you to create a new Access Key. Make sure to copy it somewhere safe as it won't be shown again after it has been generated. 86 | 87 | - **Delete Application** 88 | 89 | This tab allows to remove the application from the User Report integration. Any Access Key or Report Token will be deleted and no more emails will be sent out to feedback reporters. Applications calling the endpoint with a deleted token will receive a `401 unauthorized` response. 90 | 91 | ### Multi-Application support 92 | 93 | In case you're working on multiple games or apps, you can add another application to this integration. Look for the `+` icon next to the application selector in the top right of the settings screen. 94 | 95 | Automatic email updates to feedback reporters 96 | --------------------------------------------- 97 | 98 | When submitting feedback to Codecks, you can provide an optional `userEmail` field. This field will be used to send an email to the provided address once the associated card is set to done. Theses types of email will be sent once a day and batch all done cards for any given email address. The email will contain the name of the application as well as the current card title and a link to it. The link of the card is a smart link and has three modes: 99 | 100 | - if the current user is a member of the organization, the smart link will open the card within the organization 101 | - if the card is within a public open deck, the card will be opened in this context 102 | - if neither is true, the link will render a card which only displays the title of the card. 103 | 104 | API 105 | --- 106 | 107 | In case you want to setup your own engine integration or want to understand how the Unity plugin works exactly, we will describe how the API endpoints work exactly: 108 | 109 | ### Create a report 110 | 111 | To create a card issue a `POST` message in the following form: 112 | 113 | `POST https://api.codecks.io/user-report/v1/create-report?token=[REPORT_TOKEN]` 114 | 115 | #### Body 116 | 117 | The body of the message has to be a JSON object of this form: 118 | 119 | { 120 | "content": "text content of your report\n\nthe first line will be treated as the title.", 121 | "severity": "high", 122 | "fileNames": [ 123 | "logs.txt", 124 | "screenshot-1.png" 125 | ], 126 | "userEmail": "user@example.com" 127 | } 128 | 129 | Note that `severity`, `fileNames`, `userEmail` are optional. `severity` can be either `"critical"`, `"high"`, `"low"` or `null`. If a `userEmail` is provided, that user will receive an email once their report is marked as done. 130 | 131 | #### Curl example 132 | 133 | Here is an example of how to issue the message via `curl`: 134 | 135 | curl 'https://api.codecks.io/user-report/v1/create-report?token=[REPORT_TOKEN]' \ 136 | -H 'Content-Type: application/json' \ 137 | --data-binary '{"content":"report content", "fileNames": ["logs.txt"]}' 138 | 139 | #### Response 140 | 141 | The response object for this endpoint has this form: 142 | 143 | { 144 | "ok": true, 145 | "cardId": "[CARD_ID]", 146 | "uploadUrls": [ 147 | { 148 | "fileName": "logs.txt", 149 | "url": "https://[UPLOAD_URL]", 150 | "fields": { 151 | "key1": "[VALUE1]", 152 | "key2": "[VALUE2]" 153 | } 154 | }, 155 | { 156 | "fileName": "screenshot-1.png", 157 | "url": "https://[UPLOAD_URL]", 158 | "fields": { 159 | "key1": "[VALUE1]", 160 | "key2": "[VALUE2]" 161 | } 162 | } 163 | ] 164 | } 165 | 166 | #### Uploading files 167 | 168 | You'll receive an entry in the `uploadUrls` list for each file name you specified in `fileNames`. This contains a signed URL that allows you to directly upload the files to the respective Codecks S3 bucket. 169 | 170 | This means we're relying on [AWS Api](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html) here which unfortunately is a bit more involved. 171 | 172 | For each file you need to create a `POST` request to the specified `url`. The payload is _not_ JSON-based but is `multipart/form-data` encoded. We strongly encourage you to use a http library of your choice to construct the request. The `FormData` contains all the listed `fields` as well as an additional field called `file` containing the binary contents of your file and another field `Content-Type` containing the mime-type of the file (e.g. `text/plain` or `image/png`). 173 | 174 | ### Create a new Report Token 175 | 176 | To create a new report token issue a `POST` of this form: 177 | 178 | `POST https://api.codecks.io/user-report/v1/create-report-token?accessKey=[ACCESS_KEY]` 179 | 180 | #### Body 181 | 182 | The body of the message has to be a JSON object of this shape: 183 | 184 | { 185 | "label": "string describing your version" 186 | } 187 | 188 | #### Curl example 189 | 190 | Here is a `curl` example using this endpoint: 191 | 192 | curl 'https://api.codecks.io/user-report/v1/create-report-token?accessKey=[ACCESS_KEY]' \ 193 | -H 'Content-Type: application/json' \ 194 | --data-binary '{"label":"v0.2.1"}' 195 | 196 | #### Response 197 | 198 | The response is expected to be of this form: 199 | 200 | { 201 | "ok": true, 202 | "token": "[TOKEN]" 203 | } 204 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Documentation/images/codecks-hand-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecks-io/codecks-unity/4c6c3b4e39be709f58215e47b7fc3c5a01ef9d4e/Assets/Codecks_io/Codecks Bug & Feedback Reporter/Documentation/images/codecks-hand-screen.png -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3ccb82ee3b2c1864aba0e48fb1cc9329 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Editor/CodecksEditor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CodecksEditor", 3 | "references": [], 4 | "includePlatforms": [ 5 | "Editor" 6 | ], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Editor/CodecksEditor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b882f17d315a9a4fb71a320f105f75b 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Editor/CodecksTokenCreator.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using UnityEditor; 7 | using UnityEngine; 8 | using UnityEngine.Networking; 9 | 10 | namespace Codecks.Editor 11 | { 12 | [System.Serializable] 13 | public struct TokenRequestData 14 | { 15 | [SerializeField] public string label; 16 | } 17 | 18 | [System.Serializable] 19 | internal struct TokenResponseData 20 | { 21 | [SerializeField] public bool ok; 22 | [SerializeField] public string token; 23 | } 24 | 25 | public class CodecksTokenCreator 26 | { 27 | static UnityWebRequest HttpPost(string url, string bodyJsonString) 28 | { 29 | var request = new UnityWebRequest(url, "POST"); 30 | byte[] bodyRaw = Encoding.UTF8.GetBytes(bodyJsonString); 31 | request.uploadHandler = new UploadHandlerRaw(bodyRaw); 32 | request.downloadHandler = new DownloadHandlerBuffer(); 33 | request.SetRequestHeader("Content-Type", "application/json"); 34 | 35 | return request; 36 | } 37 | 38 | public static void CreateAndSetNewToken(string accessKey, string tokenLabel, Action callback) 39 | { 40 | void HandleTokenResult(string token) 41 | { 42 | if (string.IsNullOrEmpty(token)) 43 | { 44 | callback?.Invoke(false); 45 | return; 46 | } 47 | 48 | bool result = false; 49 | try 50 | { 51 | var resourcePath = Path.Combine(Application.dataPath, "Resources", "Codecks"); 52 | if (!Directory.Exists(resourcePath)) 53 | Directory.CreateDirectory(resourcePath); 54 | 55 | var tokenPath = Path.Combine(resourcePath, "codecksToken.txt"); 56 | File.WriteAllText(tokenPath, token); 57 | AssetDatabase.Refresh(); 58 | result = true; 59 | } 60 | catch (Exception ex) 61 | { 62 | UnityEngine.Debug.LogException(new System.Exception("Error saving codecks token", ex)); 63 | } 64 | 65 | callback?.Invoke(result); 66 | } 67 | 68 | try 69 | { 70 | CreateNewToken(accessKey, tokenLabel, HandleTokenResult); 71 | } 72 | catch (Exception ex) 73 | { 74 | UnityEngine.Debug.LogException(new System.Exception("Error creating codecks token", ex)); 75 | callback?.Invoke(false); 76 | } 77 | } 78 | 79 | public static void CreateNewToken(string accessKey, string tokenLabel, Action callback) 80 | { 81 | TokenRequestData reqData = new TokenRequestData { label = tokenLabel }; 82 | string reqJson = JsonConvert.SerializeObject(reqData); 83 | 84 | var request = HttpPost($"https://api.codecks.io/user-report/v1/create-report-token?accessKey={accessKey}", reqJson); 85 | var reqTask = request.SendWebRequest(); 86 | 87 | var promise = new TaskCompletionSource(); 88 | 89 | reqTask.completed += (aop) => 90 | { 91 | if (request.result != UnityWebRequest.Result.Success) 92 | { 93 | UnityEngine.Debug.Log($"Codecks token response error: {request.error}: {request.downloadHandler.text}"); 94 | callback?.Invoke(null); 95 | return; 96 | } 97 | 98 | var resultString = request.downloadHandler.text; 99 | var response = JsonConvert.DeserializeObject(resultString); 100 | if (!response.ok) 101 | callback?.Invoke(null); 102 | else 103 | callback?.Invoke(response.token); 104 | }; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Editor/CodecksTokenCreator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d4312f13e00da2e4998643ae7308de88 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cbe99f0a902760b498695ae9abbd643e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-color-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecks-io/codecks-unity/4c6c3b4e39be709f58215e47b7fc3c5a01ef9d4e/Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-color-dark.png -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-color-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-color-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecks-io/codecks-unity/4c6c3b4e39be709f58215e47b7fc3c5a01ef9d4e/Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-color-light.png -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-color-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-mono-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecks-io/codecks-unity/4c6c3b4e39be709f58215e47b7fc3c5a01ef9d4e/Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-mono-dark.png -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-mono-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-mono-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecks-io/codecks-unity/4c6c3b4e39be709f58215e47b7fc3c5a01ef9d4e/Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-mono-light.png -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Resources/powered-by-mono-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7d491a6fbc14af4cabb12685d25446b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime/CodecksCardCreator.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using UnityEngine; 8 | using UnityEngine.Networking; 9 | 10 | namespace Codecks.Runtime 11 | { 12 | [Serializable] 13 | public struct CardCreateRequestData 14 | { 15 | [SerializeField] public string content; 16 | [SerializeField] public List fileNames; 17 | [SerializeField] public string severity; 18 | [SerializeField] public string userEmail; 19 | } 20 | 21 | [Serializable] 22 | public struct CardCreateFileResponseData 23 | { 24 | [SerializeField] public string fileName; 25 | [SerializeField] public string url; 26 | [SerializeField] public Dictionary fields; 27 | } 28 | 29 | [Serializable] 30 | struct CardCreateResponseData 31 | { 32 | [SerializeField] public bool ok; 33 | [SerializeField] public string cardId; 34 | [SerializeField] public CardCreateFileResponseData[] uploadUrls; 35 | } 36 | 37 | public class CodecksCardCreator : MonoBehaviour 38 | { 39 | void IL2CPPCompatibility() 40 | { 41 | // to generate proper il2cpp code, generics must be called somewhere 42 | // see https://docs.unity3d.com/Manual/ScriptingRestrictions.html 43 | var dummy = new List(); 44 | 45 | throw new Exception("Never call this!"); 46 | } 47 | 48 | public string codecksURL = "https://api.codecks.io/user-report/v1/create-report"; 49 | public string defaultToken; 50 | 51 | private string loadedToken; 52 | 53 | public delegate void CardCreationResultDelegate(bool success, string result); 54 | 55 | private void Start() 56 | { 57 | var loadedTokenFile = Resources.Load("Codecks/codecksToken"); 58 | if (loadedTokenFile != null) 59 | loadedToken = loadedTokenFile.text; 60 | } 61 | 62 | static UnityWebRequest HttpPost(string url, string bodyJsonString) 63 | { 64 | var request = new UnityWebRequest(url, "POST"); 65 | byte[] bodyRaw = Encoding.UTF8.GetBytes(bodyJsonString); 66 | request.uploadHandler = new UploadHandlerRaw(bodyRaw); 67 | request.downloadHandler = new DownloadHandlerBuffer(); 68 | request.SetRequestHeader("Content-Type", "application/json"); 69 | 70 | return request; 71 | } 72 | 73 | public enum CodecksFileType 74 | { 75 | Binary, 76 | PlainText, 77 | JSON, 78 | PNG, 79 | JPG 80 | } 81 | 82 | public enum CodecksSeverity 83 | { 84 | None, 85 | Low, 86 | High, 87 | Critical 88 | } 89 | 90 | /// 91 | /// Call this method to send a data request to Codecks. 92 | /// 93 | /// The text that will appear on the card. 94 | /// The files to be sent with the report (e.g. savegame). 95 | /// The severity of the card (optional). 96 | /// The email of the user to receive updates (optional) 97 | /// 98 | /// 99 | public void CreateNewCard(string text, Dictionary files = null, 100 | CodecksSeverity severity = CodecksSeverity.None, string userEmail = null, 101 | CardCreationResultDelegate resultDelegate = null) 102 | { 103 | if (files != null && files.Any(f => f.Value.Item1 == null)) 104 | throw new Exception("Null file in files list"); 105 | 106 | StartCoroutine(CreateNewCardCoroutine(text, files, severity, userEmail, resultDelegate)); 107 | } 108 | 109 | public void CreateNewCard(string text, CodecksSeverity severity = CodecksSeverity.None, 110 | string userEmail = null, CardCreationResultDelegate resultDelegate = null) 111 | { 112 | StartCoroutine(CreateNewCardCoroutine(text, null, severity, userEmail, resultDelegate)); 113 | } 114 | 115 | IEnumerator CreateNewCardCoroutine(string text, Dictionary files = null, 116 | CodecksSeverity severity = CodecksSeverity.None, string userEmail = null, 117 | CardCreationResultDelegate resultDelegate = null) 118 | { 119 | string tokenToUse = string.IsNullOrEmpty(loadedToken) ? defaultToken : loadedToken; 120 | if (string.IsNullOrEmpty(tokenToUse)) 121 | { 122 | resultDelegate?.Invoke(false, "empty codecks token"); 123 | yield break; 124 | } 125 | 126 | files ??= new Dictionary(); 127 | 128 | UnityWebRequest request; 129 | try 130 | { 131 | string url = codecksURL + "?token=" + tokenToUse; 132 | 133 | string severityStr = severity switch 134 | { 135 | CodecksSeverity.Low => "low", 136 | CodecksSeverity.High => "high", 137 | CodecksSeverity.Critical => "critical", 138 | _ => null 139 | }; 140 | 141 | CardCreateRequestData cardData = new CardCreateRequestData 142 | { 143 | content = text, 144 | fileNames = files.Keys.ToList(), 145 | severity = severityStr, 146 | userEmail = userEmail 147 | }; 148 | 149 | string json = JsonConvert.SerializeObject(cardData).Replace( 150 | ",\"severity\":null", ""); 151 | 152 | request = HttpPost(url, json); 153 | } 154 | catch (Exception ex) 155 | { 156 | resultDelegate?.Invoke(false, $"exception sending initial request: {ex}"); 157 | yield break; 158 | } 159 | 160 | yield return request.SendWebRequest(); 161 | 162 | if (request.result != UnityWebRequest.Result.Success) 163 | { 164 | resultDelegate?.Invoke(false, $"request unsuccessful: {request.result} {request.error}"); 165 | yield break; 166 | } 167 | 168 | CardCreateResponseData response; 169 | string resultString = request.downloadHandler.text; 170 | 171 | try 172 | { 173 | response = JsonConvert.DeserializeObject(resultString); 174 | } 175 | catch (Exception ex) 176 | { 177 | resultDelegate?.Invoke(false, $"exception deserializing response: {ex}"); 178 | yield break; 179 | } 180 | 181 | if (!response.ok) 182 | { 183 | resultDelegate?.Invoke(true, $"Codecks OK = false {resultString}"); 184 | yield break; 185 | } 186 | 187 | foreach (var uploadUrl in response.uploadUrls) 188 | { 189 | if (!files.ContainsKey(uploadUrl.fileName)) 190 | throw new Exception($"Unexpected file in uploadUrls {uploadUrl.fileName}"); 191 | 192 | List formData = new List(); 193 | foreach (var field in uploadUrl.fields) 194 | { 195 | formData.Add(new MultipartFormDataSection(field.Key, field.Value)); 196 | } 197 | 198 | var fileData = files[uploadUrl.fileName]; 199 | string contentType; 200 | switch (fileData.Item2) 201 | { 202 | default: 203 | contentType = "application/octet-stream"; 204 | break; 205 | case CodecksFileType.PlainText: 206 | contentType = "text/plain"; 207 | break; 208 | case CodecksFileType.JSON: 209 | contentType = "application/json"; 210 | break; 211 | case CodecksFileType.PNG: 212 | contentType = "image/png"; 213 | break; 214 | case CodecksFileType.JPG: 215 | contentType = "image/jpeg"; 216 | break; 217 | } 218 | 219 | formData.Add(new MultipartFormDataSection("Content-Type", contentType)); 220 | formData.Add(new MultipartFormFileSection("file", fileData.Item1, uploadUrl.fileName, contentType)); 221 | 222 | UnityWebRequest uploadRequest = UnityWebRequest.Post(uploadUrl.url, formData); 223 | yield return uploadRequest.SendWebRequest(); 224 | 225 | if (uploadRequest.result != UnityWebRequest.Result.Success) 226 | { 227 | resultDelegate?.Invoke(false, $"Error uploading file {uploadUrl.fileName} to {uploadUrl.url}" + 228 | $" with {fileData.Item1.Length} bytes: {uploadRequest.error}"); 229 | 230 | yield break; 231 | } 232 | } 233 | 234 | resultDelegate?.Invoke(true, response.cardId); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime/CodecksCardCreator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 514c8a2cb4815af4e95c07010e982894 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime/CodecksCardCreatorForm.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using TMPro; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace Codecks.Runtime 9 | { 10 | public class CodecksCardCreatorForm : MonoBehaviour 11 | { 12 | /// 13 | /// Reference to the CardCreator class inside the hierarchy. 14 | /// 15 | public CodecksCardCreator cardCreator; 16 | 17 | [Header("UI References")] 18 | public TMP_Dropdown categoryDropdown; 19 | public TMP_InputField textArea; 20 | public TMP_InputField emailInput; 21 | public TMP_Text statusText; 22 | public Button sendButton; 23 | 24 | [Header("Texts")] 25 | public string statusShortText; 26 | public string statusSending; 27 | public string statusSent; 28 | public string statusError; 29 | 30 | private byte[] queuedScreenshot; 31 | 32 | /// 33 | /// Shows the Codecks Report Form. 34 | /// 35 | public void ShowCodecksForm() 36 | { 37 | // TODO: Implement this to show the UI 38 | 39 | cardCreator.StartCoroutine(ShowCodecksFormCoroutine()); 40 | } 41 | 42 | /// 43 | /// The coroutine that shows Codecks Report Form. 44 | /// 45 | private IEnumerator ShowCodecksFormCoroutine() 46 | { 47 | yield return new WaitForEndOfFrame(); 48 | 49 | var screenshotTex = ScreenCapture.CaptureScreenshotAsTexture(); 50 | 51 | #if UNITY_STANDALONE 52 | queuedScreenshot = screenshotTex.EncodeToJPG(); 53 | #else 54 | // used on consoles to get screenshots easily 55 | queuedScreenshot = screenshotTex.EncodeToPNG(); 56 | #endif 57 | 58 | Destroy(screenshotTex); 59 | 60 | 61 | textArea.text = ""; 62 | sendButton.interactable = true; 63 | gameObject.SetActive(true); 64 | } 65 | 66 | /// 67 | /// Hides the Codecks Report Form 68 | /// 69 | public void HideCodecksForm() 70 | { 71 | // TODO: Implement this to hide the UI 72 | 73 | queuedScreenshot = null; 74 | 75 | gameObject.SetActive(false); 76 | } 77 | 78 | /// 79 | /// Hides the Codecks Report Form after a short delay so that there is enough time to read the status text that 80 | /// confirms sent reports. 81 | /// 82 | private IEnumerator HideCodecksFormWithDelayCoroutine() 83 | { 84 | yield return new WaitForSeconds(1); 85 | HideCodecksForm(); 86 | } 87 | 88 | /// 89 | /// Called when the -Send Report- button is clicked. 90 | /// 91 | public void OnButtonSend() 92 | { 93 | if (textArea.text.Length < 10) 94 | { 95 | statusText.text = statusShortText; 96 | return; 97 | } 98 | 99 | string reportText = $"{textArea.text}\n\n"; 100 | reportText += GetMetaText(); 101 | 102 | var files = new Dictionary(); 103 | 104 | #if UNITY_STANDALONE 105 | files["screenshot.jpg"] = (queuedScreenshot, CodecksCardCreator.CodecksFileType.JPG); 106 | #else 107 | files["screenshot.png"] = (queuedScreenshot, CodecksCardCreator.CodecksFileType.PNG); 108 | #endif 109 | 110 | statusText.text = statusSending; 111 | sendButton.interactable = false; 112 | 113 | cardCreator.CreateNewCard( 114 | text: reportText, 115 | files: files, 116 | severity: (CodecksCardCreator.CodecksSeverity)categoryDropdown.value, 117 | userEmail: emailInput.text, 118 | resultDelegate: (success, result) => 119 | { 120 | if (success) 121 | { 122 | statusText.text = statusSent; 123 | sendButton.interactable = false; 124 | StartCoroutine(HideCodecksFormWithDelayCoroutine()); 125 | } 126 | else 127 | { 128 | sendButton.interactable = true; 129 | statusText.text = statusError; 130 | } 131 | }); 132 | } 133 | 134 | /// 135 | /// Called when the Cancel button is clicked. 136 | /// 137 | public void OnButtonCancel() 138 | { 139 | HideCodecksForm(); 140 | } 141 | 142 | /// 143 | /// This adds some game-related text information to the card content of the report. Feel free to add your own 144 | /// game data here that you want to be able see it at a glance. 145 | /// 146 | private static string GetMetaText() 147 | { 148 | StringBuilder metaText = new StringBuilder(); 149 | metaText.AppendLine($"```"); 150 | metaText.AppendLine($"Platform: {Application.platform.ToString()}"); 151 | metaText.AppendLine($"App Version: {Application.version}"); 152 | metaText.AppendLine("```"); 153 | return metaText.ToString(); 154 | } 155 | 156 | 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime/CodecksCardCreatorForm.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9b55bfa3ac1440e90360e2a78d9c906 3 | timeCreated: 1657549140 -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime/CodecksRuntime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CodecksRuntime", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:6055be8ebefd69e48b49212b09b47b2f" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Runtime/CodecksRuntime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4f019c1aca2afff4da2aca2b63d56bc3 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3f0f76e2593aec942bdebf1e54842308 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Codecks_io/Codecks Bug & Feedback Reporter/Scenes/CodecksSampleScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9fc0d4010bbf28b4594072e72b8655ab 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Codecks GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codecks Bug & Feedback Reporter for Unity 2 | 3 | Collect bugs and feedback right from your Unity game and keep players in the loop what happens with their feedback. 4 | 5 | ## Documentation 6 | 7 | For the complete picture head to the [Codecks Manual Page](https://manual.codecks.io/user-reports/). 8 | 9 | 10 | ## Set up 11 | 12 | Move the `Assets/Codecks_io/Codecks Bugs & Feedback Reporter` folder into the `Assets/Codecks Bugs & Feedback Reporter` folder of your project. Once imported, you can find a scene named `CodecksSampleScene` that contains a default layout and sample setup for you to check out. Unity will also ask to install the `TextMesh Pro` plugin. This plugin is necessary to run the sample scene. 13 | 14 | ## Getting started 15 | 16 | The sample scene already provides all the UI elements and component setup for you to get started right away. You can test the initial setup of the report tool right from the sample scene by entering your created report token (the one that you created in your Codecks User Report settings screen) into the `Default Token` property of the `CodecksCardCreator` component on the `CardCreator` scene object (found under the `Canvas` object). Once you've done that you can hit _Play_. Click the "Give Feedback!" button, fill out the form and press "Send report". If everything works, you should see a card pop up in your Codecks just moments later. In case of issues, an error message should be printed to your screen and console. 17 | 18 | ## Adapting it to your own needs 19 | 20 | After testing the initial setup, we recommend copying or integrating the sample scene into your own UI game scene where you can configure it to show up when pressing a hotkey or by selecting a menu entry according to your own needs. You may also modify the layout to fit your game thematically or use the Codecks default layout as provided. In any case please make sure to not hide the `Powered by Codecks` sprite and display it next to the report form. 21 | 22 | Here's an explanation what the two provided MonoBehavior classes do: 23 | 24 | - **CodecksCardCreator** handles the basic API communication with Codecks for the purpose of creating cards inside your Codecks project. This class does not handle any UI related tasks and contains only the basic functionality. 25 | - **CodecksCardCreatorForm** is a helper class that manages the UI and forwards the input to the `CodecksCardCreator` class. You may write your own UI handling in case you're not using the default Unity Canvas system or in case you have special requirements for your UI. The class provides a method `GetMetaText` which you can edit to add your own game related meta data. By default the component also creates a screenshot and attaches it to the request sent to the `CodecksCardCreator` class. You may choose to add additional files to the request (e.g. attaching a savegame or world state dump). 26 | 27 | ## License 28 | 29 | The code is licensed under the MIT license. See [`LICENSE.md`](./LICENSE.md). 30 | 31 | ## Contribute 32 | 33 | ### Docs 34 | 35 | The sources for the docs can be found in the [`docs.md`](./Assets/Codecks_io/Codecks%20Bug%20%26%20Feedback%20Reporter/Documentation/docs.md) file. 36 | 37 | To create a PDF you need node v14+ installed on your machine. Run this command from the [`Documentation`](./Assets/Codecks_io/Codecks%20Bug%20%26%20Feedback%20Reporter/Documentation/) folder: 38 | 39 | ```sh 40 | cat ./docs.md | npx md-to-pdf > ./Codecks\ Unity\ Plugin\ Manual.pdf 41 | ``` 42 | --------------------------------------------------------------------------------