├── .gitignore
├── LICENSE
├── README.md
├── Runtime
├── CONSOLA.TTF
├── CheatAttribute.cs
├── CheatConsole.cs
├── CheatManager.cs
└── Pyton.CheatConsole.asmdef
├── Sample
├── Scenes
│ └── SampleScene.unity
└── Scripts
│ └── Test.cs
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | *.meta
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 BenPyton
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cheat Console for Unity
2 | ## Overview
3 | This is an in-game cheat console that allow you to execute cheat command during runtime.\
4 | Press the quote key in-game to display the console and enter a command.
5 |
6 | The cheat console is available only in Unity editor and in development build, but no code are generated in release build.\
7 | Thus, players will not be able to enable cheat console in the game by using a tool like dnSpy to open and modify your game assemblies.
8 |
9 | ## Installation
10 | Download the [package](https://github.com/BenPyton/cheatconsole/archive/master.zip).\
11 | Extract the content into `YourProject\Packages\` folder (not in a subfolder).\
12 | Unity will load automatically the package.
13 |
14 | ## Usage
15 | To add a cheat console in your game, simply go into `GameObject` menu or right click in your scene hierarchy and add a `Cheat Console` in the scene.
16 |
17 | To define cheat commands, add the `[Cheat]` attribute above static methods.
18 |
19 | You can listen for `OnOpen` and `OnClose` events to execute specific code when the console is displayed or hidden (like pausing your game).
20 |
21 | Example:
22 | ```
23 | public class YourClass : MonoBehaviour
24 | {
25 | private void Start()
26 | {
27 | CheatConsole.OnOpen.AddListener(() => Debug.Log("Opened"));
28 | CheatConsole.OnClose.AddListener(() => Debug.Log("Closed"));
29 | }
30 |
31 | [Cheat]
32 | private static void CheatMethod()
33 | {
34 | CheatConsole.Log("Hello");
35 | }
36 |
37 | [Cheat]
38 | private static void CheatMethodWithString(string str)
39 | {
40 | CheatConsole.Log("Hello " + str);
41 | }
42 | }
43 | ```
44 |
45 | Then in-game, you only have to open the console, and type the method name (case sensitive) followed by all parameters separated by a space.\
46 | If you want to enter a string parameter with spaces inside, wrap your string with double quotes `"`.
47 |
48 | Examples (output in the console):
49 | ```
50 | > CheatMethod
51 | Hello
52 | > CheatMethodWithString world!
53 | Hello world!
54 | > CheatMethodWithString "everybody, and the world too!"
55 | Hello everybody, and the world too!
56 | ```
57 |
58 | ## Limitations
59 | The attribute work with public, protected and private methods, but only with static methods (until I find a way to reference a class instance).
60 |
61 | Currently, only string, int and float parameters work in these methods.
62 | You will not be able to call a method if there is any other type of parameter.
--------------------------------------------------------------------------------
/Runtime/CONSOLA.TTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenPyton/cheatconsole/3457cfa929d57c5fe8d8d2bd1081c72087269f26/Runtime/CONSOLA.TTF
--------------------------------------------------------------------------------
/Runtime/CheatAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | [AttributeUsage(AttributeTargets.Method)]
4 | public class CheatAttribute : Attribute
5 | {
6 | }
7 |
--------------------------------------------------------------------------------
/Runtime/CheatConsole.cs:
--------------------------------------------------------------------------------
1 | #if DEVELOPMENT_BUILD || UNITY_EDITOR
2 | #define CONSOLE_DEBUG
3 | #endif
4 |
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using UnityEngine;
8 | using UnityEngine.Events;
9 | using UnityEngine.UI;
10 |
11 | #if UNITY_EDITOR
12 | using UnityEditor;
13 | #endif
14 |
15 | public sealed class CheatConsole : MonoBehaviour
16 | {
17 | public readonly static UnityEvent OnOpen = new UnityEvent();
18 | public readonly static UnityEvent OnClose = new UnityEvent();
19 |
20 | public static void Log(string message)
21 | {
22 | #if CONSOLE_DEBUG
23 | if (s_instance != null)
24 | {
25 | s_instance.m_outputLog += "\n" + message;
26 | }
27 | #endif
28 | }
29 |
30 | public static void LogError(string message)
31 | {
32 | #if CONSOLE_DEBUG
33 | if (s_instance != null)
34 | {
35 | s_instance.m_outputLog += "\n[Error] " + message + "";
36 | }
37 | #endif
38 | }
39 |
40 | #region Private
41 |
42 | // Start is called before the first frame update
43 | void Awake()
44 | {
45 | #if CONSOLE_DEBUG
46 | if (s_instance == null)
47 | {
48 | s_instance = this;
49 | DontDestroyOnLoad(gameObject);
50 | m_container?.SetActive(m_consoleEnabled);
51 | m_cursor = Instantiate(m_inputText, m_container?.transform);
52 | m_outputText.text = string.Empty;
53 | m_inputText.text = string.Empty;
54 | CheatManager.GetAllMethodNames(); // used only to init cheat manager, but used later to autocomplete
55 | }
56 | else
57 | {
58 | Destroy(gameObject);
59 | }
60 | #else
61 | Destroy(gameObject);
62 | #endif
63 | }
64 |
65 |
66 | #if CONSOLE_DEBUG
67 | void Update()
68 | {
69 | if (Input.GetKeyDown(KeyCode.Quote))
70 | {
71 | m_consoleEnabled = !m_consoleEnabled;
72 | m_container?.SetActive(m_consoleEnabled);
73 | if(m_consoleEnabled)
74 | {
75 | OnOpen.Invoke();
76 | }
77 | else
78 | {
79 | OnClose.Invoke();
80 | }
81 | }
82 | else if (m_consoleEnabled)
83 | {
84 | UpdateInputField();
85 | UpdateOutputLog();
86 | }
87 | }
88 |
89 | private void UpdateInputField()
90 | {
91 | if (Input.GetKeyDown(KeyCode.UpArrow))
92 | PreviousCommand();
93 | else if (Input.GetKeyDown(KeyCode.DownArrow))
94 | NextCommand();
95 | else if (Input.GetKeyDown(KeyCode.RightArrow))
96 | {
97 | m_cursorIndex++;
98 | m_cursorIndex = Mathf.Min(m_cursorIndex, m_inputCommand.Length);
99 | m_dirty = true;
100 | }
101 | else if (Input.GetKeyDown(KeyCode.LeftArrow))
102 | {
103 | m_cursorIndex--;
104 | m_cursorIndex = Mathf.Max(m_cursorIndex, 0);
105 | m_dirty = true;
106 | }
107 | else if (Input.GetKeyDown(KeyCode.Delete)
108 | && m_cursorIndex < m_inputCommand.Length)
109 | {
110 | m_inputCommand = m_inputCommand.Remove(m_cursorIndex, 1);
111 | m_dirty = true;
112 | }
113 | else if (Input.GetKeyDown(KeyCode.Tab))
114 | {
115 | string[] allMethodNames = CheatManager.GetAllMethodNames();
116 | List matchingNames = new(allMethodNames.Where(s => s.Contains(m_inputCommand, System.StringComparison.CurrentCultureIgnoreCase)));
117 | if (matchingNames.Count == 0)
118 | {
119 | LogError($"No methods found that contain {m_inputCommand}");
120 | }
121 | else if (matchingNames.Count == 1)
122 | {
123 | m_inputCommand = matchingNames[0];
124 | m_cursorIndex = m_inputCommand.Count();
125 | m_dirty = true;
126 | }
127 | else
128 | {
129 | foreach (string methodName in matchingNames)
130 | {
131 | Log($"[Suggestion] {methodName}");
132 | }
133 | }
134 | }
135 | else
136 | {
137 | foreach (char c in Input.inputString)
138 | {
139 | if (c == '\n' || c == '\r')
140 | {
141 | ExecuteCommand();
142 | m_cursorIndex = 0;
143 | m_dirty = true;
144 | break;
145 | }
146 | else if (c == '\b')
147 | {
148 | if (m_cursorIndex > 0)
149 | {
150 | m_inputCommand = m_inputCommand.Remove(m_cursorIndex - 1, 1);
151 | m_cursorIndex--;
152 | m_dirty = true;
153 | }
154 | }
155 | else
156 | {
157 | m_inputCommand = m_inputCommand.Insert(m_cursorIndex, c.ToString());
158 | m_cursorIndex++;
159 | m_dirty = true;
160 | }
161 | }
162 | }
163 |
164 | if (m_dirty)
165 | {
166 | m_inputText.text = m_prefix + m_inputCommand;
167 | m_cursor.text = new string(' ', m_prefix.Length + m_cursorIndex) + '_';
168 | m_dirty = false;
169 | }
170 |
171 | m_cursor.gameObject.SetActive((int)(Time.realtimeSinceStartup * 1000) % 1000 < 500);
172 | }
173 |
174 | private void UpdateOutputLog()
175 | {
176 | m_outputText.text = m_outputLog;
177 | }
178 |
179 | private void ExecuteCommand()
180 | {
181 | if (string.IsNullOrWhiteSpace(m_inputCommand))
182 | return;
183 |
184 | Log(m_prefix + m_inputCommand);
185 |
186 | m_currentHistoryIndex = -1;
187 | m_commandHistory.Add(m_inputCommand);
188 | if(!CheatManager.Execute(m_inputCommand))
189 | {
190 | LogError(CheatManager.ErrorMessage);
191 | }
192 | m_inputCommand = string.Empty;
193 |
194 | while(m_commandHistory.Count > MAX_COMMAND_HISTORY)
195 | {
196 | m_commandHistory.RemoveAt(0);
197 | }
198 | }
199 |
200 | private void PreviousCommand()
201 | {
202 | if(m_currentHistoryIndex < 0)
203 | m_currentHistoryIndex = m_commandHistory.Count - 1;
204 | else if(m_currentHistoryIndex > 0)
205 | m_currentHistoryIndex--;
206 |
207 | m_inputCommand = m_commandHistory[m_currentHistoryIndex];
208 | m_cursorIndex = m_inputCommand.Length;
209 | m_dirty = true;
210 | }
211 |
212 | private void NextCommand()
213 | {
214 | if (m_currentHistoryIndex < 0)
215 | return;
216 |
217 | if (m_currentHistoryIndex >= m_commandHistory.Count - 1)
218 | m_currentHistoryIndex = -1;
219 | else
220 | m_currentHistoryIndex++;
221 |
222 | m_inputCommand = m_currentHistoryIndex < 0 ? string.Empty : m_commandHistory[m_currentHistoryIndex];
223 | m_cursorIndex = m_inputCommand.Length;
224 | m_dirty = true;
225 | }
226 |
227 | private const int MAX_COMMAND_HISTORY = 20;
228 | private static CheatConsole s_instance = null;
229 | private List m_commandHistory = new List();
230 | private Text m_cursor = null;
231 | private string m_prefix = "> ";
232 | private string m_inputCommand = string.Empty;
233 | private string m_outputLog = string.Empty;
234 | private int m_currentHistoryIndex = 0;
235 | private int m_cursorIndex = 0;
236 | private bool m_dirty = true;
237 | private bool m_consoleEnabled = false;
238 | #endif
239 |
240 | [SerializeField] private GameObject m_container = null;
241 | [SerializeField] private Text m_inputText = null;
242 | [SerializeField] private Text m_outputText = null;
243 |
244 | #endregion // Private
245 |
246 | #region Editor
247 |
248 | #if UNITY_EDITOR
249 | [MenuItem("GameObject/Cheat Console", false, 30)]
250 | public static void CreateConsole(MenuCommand menuCommand)
251 | {
252 | CheatConsole canvas = CreateCanvas();
253 | RectTransform container = CreateContainer(canvas.gameObject);
254 | ScrollRect scrollView = CreateScrollview(container.gameObject);
255 | RectTransform viewport = CreateViewport(scrollView.gameObject);
256 | Scrollbar scrollbar = CreateScrollbar(scrollView.gameObject);
257 | RectTransform slidingArea = CreateSlidingArea(scrollbar.gameObject);
258 | RectTransform handle = CreateHandle(slidingArea.gameObject);
259 | RectTransform outputText = CreateOutputText(viewport.gameObject);
260 | RectTransform inputText = CreateInputText(container.gameObject);
261 |
262 | scrollbar.handleRect = handle;
263 | scrollbar.targetGraphic = handle.GetComponent();
264 | scrollView.viewport = viewport.GetComponent();
265 | scrollView.verticalScrollbar = scrollbar;
266 | scrollView.content = outputText;
267 | canvas.m_container = container.gameObject;
268 | canvas.m_outputText = outputText.GetComponent();
269 | canvas.m_inputText = inputText.GetComponent();
270 | container.gameObject.SetActive(false);
271 |
272 | Undo.RegisterCreatedObjectUndo(canvas.gameObject, "Create " + canvas.gameObject.name);
273 | Selection.activeObject = canvas.gameObject;
274 | }
275 |
276 | private static CheatConsole CreateCanvas()
277 | {
278 | GameObject canvas = new GameObject("CheatConsole"
279 | , typeof(RectTransform)
280 | , typeof(Canvas)
281 | , typeof(CanvasScaler)
282 | , typeof(GraphicRaycaster)
283 | , typeof(CheatConsole));
284 |
285 | canvas.GetComponent