September 2019 - December 2019 | School Project
Trials of the Archmage is a fantasy game in which you solve puzzles with magic.
Maeve the wizard wants one thing in this world: to take the title of greatest wizard of the age, an Archmage. To do so, instead of sitting around at her magic academy and being spoon-fed theories, she decides to trek through the Forlorn Forest and complete the "Trials of the Archmage", a series of mystical tests in which a wizard must demonstrate their mastery of fire and ice magic.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using PDollarGestureRecognizer;
public class GestureController : MonoBehaviour {
public Transform gestureOnScreenPrefab;
private List trainingSet = new List();
private List points = new List();
private int strokeId = -1;
private Vector3 virtualKeyPosition = Vector2.zero;
private RuntimePlatform platform;
private int vertexCount = 0;
public GameObject gestureEffect;
private List gestureLinesRenderer = new List();
private LineRenderer currentGestureLineRenderer;
private SpellCastingController spellCastingController;
private bool isDrawingGesture = false;
private bool isGestureButtonDown = false;
private bool isWaitingForGesture = false;
//GUI
[HideInInspector]
public string message;
[HideInInspector]
public float score;
private bool recognized;
private string newGestureName = "";
private GameObject currentGestureEffect = null;
// A buffer for continuous drawings
public float gestureBuffer = 0.1f;
void Start () {
spellCastingController = GameObject.Find("GameManagement/Controllers/SpellCastingController").GetComponent();
platform = Application.platform;
//Load pre-made gestures
TextAsset[] gesturesXml = Resources.LoadAll("GestureSet/10-stylus-MEDIUM/");
foreach (TextAsset gestureXml in gesturesXml)
trainingSet.Add(GestureIO.ReadGestureFromXML(gestureXml.text));
//Load user custom gestures
//Debug.Log(Application.persistentDataPath);
string[] filePaths = Directory.GetFiles(Application.persistentDataPath, "*.xml");
foreach (string filePath in filePaths)
trainingSet.Add(GestureIO.ReadGestureFromFile(filePath));
}
void Update () {
if (platform == RuntimePlatform.Android || platform == RuntimePlatform.IPhonePlayer) {
if (Input.touchCount > 0) {
virtualKeyPosition = new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y);
}
} else {
if (Input.GetMouseButton(0)) {
virtualKeyPosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y);
}
}
if (currentGestureEffect != null) {
//Debug.Log(currentGestureEffect.transform.position);
currentGestureEffect.transform.position = Camera.main.ScreenToWorldPoint(new Vector3(virtualKeyPosition.x, virtualKeyPosition.y, 10));
}
if (isDrawingGesture && !isWaitingForGesture) {
points.Add(new Point(virtualKeyPosition.x, -virtualKeyPosition.y, strokeId));
if (currentGestureLineRenderer != null) {
currentGestureLineRenderer.SetVertexCount(++vertexCount);
currentGestureLineRenderer.SetPosition(vertexCount - 1, Camera.main.ScreenToWorldPoint(new Vector3(virtualKeyPosition.x, virtualKeyPosition.y, 10)));
}
}
}
public void StartGestureDrawing(){
isGestureButtonDown = true;
// New Gesture!
if (!isDrawingGesture) {
Transform tmpGesture = Instantiate(gestureOnScreenPrefab, transform.position, transform.rotation) as Transform;
currentGestureLineRenderer = tmpGesture.GetComponent();
currentGestureLineRenderer.SetVertexCount(0);
vertexCount = 0;
currentGestureEffect = Instantiate(gestureEffect, transform.position, Quaternion.identity);
}
isDrawingGesture = true;
}
public void EndGestureDrawing(){
isGestureButtonDown = false;
StartCoroutine(LateEndDrawing());
}
IEnumerator LateEndDrawing() {
isWaitingForGesture = true;
yield return new WaitForSecondsRealtime(gestureBuffer);
isWaitingForGesture = false;
if (!isGestureButtonDown) {
if (isDrawingGesture) {
// It means we get a gesture now!
Recognize();
spellCastingController.EndDrawingGesture(message, score);
ClearStrokes();
}
isDrawingGesture = false;
}
}
public void ClearStrokes(){
strokeId = -1;
points.Clear();
StartCoroutine(LateDestroy(currentGestureEffect.gameObject, 2f));
currentGestureEffect = null;
currentGestureLineRenderer.SetVertexCount(0);
Destroy(currentGestureLineRenderer.gameObject);
}
IEnumerator LateDestroy(GameObject go, float waitTime)
{
yield return new WaitForSeconds(waitTime);
Destroy(go);
}
List CleanUpNearPoints(List points) {
List newPoints = new List();
newPoints.Add(points[0]);
for (int i = 1; i < points.Count - 1; i++) {
if (Point.GetDistance(points[i], points[i - 1]) > 5)
newPoints.Add(points[i]);
}
return newPoints;
}
void Recognize(){
recognized = true;
List pointsForRecognize = CleanUpNearPoints(points);
Gesture candidate = new Gesture(CleanUpNearPoints(points).ToArray());
// A random touch on screen
if (pointsForRecognize.Count == 1) {
message = "NULL";
score = 0;
return;
}
Result gestureResult = PointCloudRecognizer.Classify(candidate, trainingSet.ToArray());
message = gestureResult.GestureClass;
score = gestureResult.Score;
//Debug.Log(gestureResult.GestureClass + " " + gestureResult.Score);
}
}
The GestureController is the script for handling user gesture inputs, also comparing it with the database to find the gesture user inputs.
Spell Casting System is the highlight point of this game. With a real gesture input, players can cast magic with their hands instead of clicking "skill buttons".
The Spell Casting System consists of two parts. The first part is selecting object - Player needs to click on the object they wanted to interact with. The second part is drawing the gesture, where players can draw specific gestures to call the magic.
For implementing the Gesture System, I refered to $P Point-Cloud Recognizer for point set recognition, and it works out pretty well. In our final design, we use "V" gestures for fire magic, and "O" gestures for ice magic. The Gesture System works like a charm in our game.
For helping the Level Designers create the levels quicker, I created several tools for visuallizing the elements in our game.
Moving Platforms System is one of the basic systems for 2D platformers. The challenge of creating the moving platforms are - how to make the characters move with the moving platforms, and how to judge if the character is on the platform.
For the first question, I changed the rigidbody of the character. Instead of setting velocity for the character, I manually change the position of the character to follow with the platforms. For the second question, I use raycast to solve this problem, this also resolved the problem where two platforms are sticking together.