# CryStole (VR Game)
---
## Description
<font color="#f00">入圍2021台灣放視大賞(VR遊戲組):</font>
<font color="#f00">Shortlisted for 2021 Vision Get Wild Award:</font>
https://www.dcaward-vgw.org.tw/tw/onlineExhibition/winningWorks/detail/31992
The story takes place in the ancient Maya period. In order to treat his seriously ill sister, the protagonist of the story decides to go to the temple to steal the magic medicine to treat his sister. At the beginning of the game, the protagonist has successfully stolen the magic medicine and needs to defeat the one who came. The guards escaped the siege.
---

---

---

---

---
## Creative Development
This is a VR music rhythm game. Our core goal is to let players feel the tension of going deep into the environment and the need to escape the predicament as the plot and music progress. We draw lessons from Beat Saber in the gameplay, and go to make different changes in the game, such as different responses to the mobs that need to be targeted; and we are proud that all the models and music materials are created by ourselves(Not Me).
---
## how to play

<font color="#f00">trigger key: </font> Change Weapon color
<font color="#f00">Big Button: </font> Menu Choose Button
---
## Codes
This is also the first time for me to make a VR game. In the process of making a VR game, I learned that there are many issues that need to be overcome in making a VR game. I will explain it in several sections:
### Player experience optimization:
<font color="#f00">game feedback (遊戲反映回饋) & 3D halo (3D暈)</font>
在我們實際遊玩的時候有角度的轉彎會讓VR玩家有暈眩的感覺,所以我們的行進路線要稍微平滑並且還要可以精確地計算距離才可以讓譜面的節奏準確地隊在拍子上。
When we actually play, the angled turns will make VR players feel dizzy, so our travel path needs to be slightly smooth and the distance can be accurately calculated so that the rhythm of the beat sheet can be accurately aligned with the beat.
<font color="#f00">Player movement trajectory settings (玩家移動軌跡設定)</font>
我們這邊使用了在unity Asset Store 免費可以使用的 pathCreator.cs 可以簡單的調整並平滑化,玩家只要朝著上面的點進行移動抵達終點。
Here we use pathCreator.cs which is freely available in the Unity Asset Store, which can be easily adjusted and smoothed. Players only need to move towards the above point to reach the end point.
```cpp=
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PathCreation;
public class CarType2 : MonoBehaviour
{
public static CarType2 Instance;
public PathCreator pathCreator;
// public StartofPathInstruction start;
public EndOfPathInstruction end;
public float speed = 10;
float distanceTravelled;
// Start is called before the first frame update
private void Awake()
{
Instance = this;
}
void Start()
{
// You can now access the vertex path with pathCreator.path
// For example, this sets the position to the middle of the path:
transform.position = pathCreator.path.GetPoint(0);
}
void Update()
{
distanceTravelled += speed * Time.deltaTime;
transform.position = pathCreator.path.GetPointAtDistance(distanceTravelled, end);
transform.rotation = pathCreator.path.GetRotationAtDistance(distanceTravelled, end);
}
}
```
### Convert file of music charts into enemies that entities:
<font color="#f00">Editing music charts for the first time (初次編輯音樂譜面)</font>
我們當時很苦惱要怎麼實作出讓怪物照著節奏朝玩家過來,我們想了需多辦法,最後看到網路資源有beat saber的方塊編輯軟體,我們研究其輸出的檔案格式,並試著將他讀取進我們的遊戲,這樣一來就可以用這個音樂編輯軟體來產生我們要的音樂並可以對接遊戲產生敵人。
At the time, we were very worried about how to make the monsters come to the player at the rhythm. We thought about the need for many methods. Finally, we saw that there is a block editing software for beat saber in the Internet resource. We studied the output file format and tried to convert He reads it into our game, so we can use this music editing software to generate the music we want and connect the game to generate enemies.
<font color="#f00">How to instantiate beatmaps and reuse them (如何將譜面實例化並可再使用)</font>
```cpp=
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using Lean.Pool;
public class MappingCreator : MonoBehaviour
{
float timer = 0; //會歸零
//objectNumbers 為怪物音符總數量
public int objectNumbers = 0;
public GameObject[] cubes;
public Transform[] points;
public Transform[] points2;
Vector3 position = Vector3.zero;
public TextAsset mapping;
public float time1 = 10; // 累計時間
public float time2; // 間隔時間
public int InitEnemyNum = 20;
public int i, j;
public int a = 0;
public float TIME_NUMBER; //方塊時間間隔
Music2 data;
void Start()
{
//資料要餵進去enemy如: 位置、時間、類別
//在mappingCreater裡建立一個list去管理所有enemy,方便別人對enemy進行操作
data = JsonUtility.FromJson<Music2>(mapping.text);
for (i = 0; i < InitEnemyNum; i++)
{
locateEnemy1(i);
}
}
private void Update()
{
timer += Time.deltaTime;
if (timer >= time2 && a <= objectNumbers)
{
for (a = i + 1; a < InitEnemyNum + i + 1; a++)
{
locateEnemy1(a);
}
i = a;
timer = 0;
}
}
void locateEnemy1(int i)
{
if (data._notes[i]._cutDirection == 0)
{
...
}
else if (data._notes[i]._cutDirection == 1)
{
...
}
else if (data._notes[i]._cutDirection == 2)
{
...
}
else
{
...
}
}
}
[System.Serializable]
public class Mapping
{
public int _time;
public int _lineIndex;
public int _lineLayer;
public int _type;
public int _cutDirection;
}
[System.Serializable]
public class Music2
{
public Mapping[] _notes;
}
```
in ...
```cpp=
GameObject cube = LeanPool.Spawn(cubes[3], points[data._notes[i]._lineIndex * 1 + data._notes[i]._lineLayer * 4]);
cube.GetComponent<Enemy>().Init(id: i, time: data._notes[i]._time * TIME_NUMBER);
Vector3 move = cube.transform.position;
move = new Vector3(move.x, move.y, move.z + 10.0f * (data._notes[i]._time * TIME_NUMBER));
cube.transform.position = move;
```
### Problems to overcome in making VR in Unity:
<font color="#f00">Menu making for VR games (VR遊戲的選單製作)</font>
The VR device used at the beginning was htc vive, and its unity interface api was steamvr. Since its version was updated very quickly, many solutions on the Internet have failed. The following are the effective connection methods at that time:

在每個場景都需要加入這三個Prefabs,[SteamVR] 和 ActivateSwitch 為初始化,而 [CameraRig] 是玩家可以操作空間的場域,裡面包括兩隻玩家可操作的手把和Camera。
These three Prefabs need to be added in each scene, [SteamVR] and ActivateSwitch are initialized, and [CameraRig] is the field where the player can manipulate the space, which includes two player-operable handles and Camera.

<font color="#f00">
Operation settings of the handle (手把的操作設定)</font>

You need to set the plugin for each button here, and Edit -> Project Settings -> XR Control edits detail,
SwitchWeapon.cs
```cpp=
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR.InteractionSystem;
using Valve.VR;
namespace Valve.VR.InteractionSystem.Sample
{
public class SwitchWeapon : MonoBehaviour
{
public SteamVR_Action_Boolean SwitchAction;
private void Update()
{
//Change Left Weapon color
if (SteamVR_Actions.ChangeWeapon.SwitchLeft.GetStateDown(SteamVR_Input_Sources.Any))
{
LeftWeapon.Instance.SwitchLeftEvent();
}
if (SteamVR_Actions.ChangeWeapon.SwitchRight.GetStateDown(SteamVR_Input_Sources.Any)) //Change Right Weapon color
{
RightWeapon.Instance.SwitchRightEvent();
}
if (SteamVR_Actions.ChangeWeapon.Menu.GetStateDown(SteamVR_Input_Sources.Any)) //Open the Menu
{
if (CheckUserInput.Instance.cannotPaused == false)
{
CheckUserInput.Instance.menu = true;
RightWeapon.Instance.PauseChange();
}
}
}
}
}
```
Then there is the handle ray that makes the operation menu when paused:
Poniter.cs
```cpp=
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class Poniter : MonoBehaviour
{
...
void Update()
{
UpdateLine();
}
private void UpdateLine()
{
//Use default or distance
PointerEventData data = m_InputModule.GetData();
float targetLength = data.pointerCurrentRaycast.distance ==
0 ? m_DefaultLength : data.pointerCurrentRaycast.distance;
//Raycast
RaycastHit hit = CreateRaycast(targetLength);
//Default
Vector3 endPosition = transform.position + (transform.forward * targetLength);
//Or based on hit
if (hit.collider != null)
endPosition = hit.point;
//Set position of the dot
m_Dot.transform.position = endPosition;
//Set linerenderer
m_LineRenderer.SetPosition(0, transform.position);
m_LineRenderer.SetPosition(1, endPosition);
}
private RaycastHit CreateRaycast(float length)
{
RaycastHit hit;
Ray ray = new Ray(transform.position, transform.forward);
Physics.Raycast(ray, out hit, m_DefaultLength);
return hit;
}
}
```
VRInputModule.cs
```cpp=
public class VRInputModule : BaseInputModule
{
...
protected override void Awake()
{
base.Awake();
m_Data = new PointerEventData(eventSystem);
}
public override void Process()
{
// Reset data, set camera
m_Data.Reset();
m_Data.position = new Vector2(m_Camera.pixelWidth / 2,
m_Camera.pixelHeight / 2);
//Raycast
eventSystem.RaycastAll(m_Data, m_RaycastResultCache);
m_Data.pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
m_CurrentObject = m_Data.pointerCurrentRaycast.gameObject;
//Clear
m_RaycastResultCache.Clear();
//Hover
HandlePointerExitAndEnter(m_Data, m_CurrentObject);
//Press
if (m_ClickAction.GetStateDown(m_TargetSource))
ProcessPress(m_Data);
//Release
if (m_ClickAction.GetStateUp(m_TargetSource))
ProcessRelease(m_Data);
}
public PointerEventData GetData()
{
return m_Data;
}
private void ProcessPress(PointerEventData data)
{
// Set Raycast
data.pointerPressRaycast = data.pointerCurrentRaycast;
// Check for object hit, get the down handler, call
GameObject newPointerPress =
ExecuteEvents.ExecuteHierarchy(m_CurrentObject, data,
ExecuteEvents.pointerDownHandler);
// If no down handler, try and get click handler
if (newPointerPress == null)
newPointerPress = ExecuteEvents.GetEventHandler<IPointerClickHandler>
(m_CurrentObject);
// Set data
data.pressPosition = data.position;
data.pointerPress = newPointerPress;
data.rawPointerPress = m_CurrentObject;
}
private void ProcessRelease(PointerEventData data)
{
// Execute pointer Up
ExecuteEvents.Execute(data.pointerPress, data,
ExecuteEvents.pointerUpHandler);
// Check for Click handler
GameObject pointerUpHandler =
ExecuteEvents.GetEventHandler<IPointerClickHandler>(m_CurrentObject);
// Check if actual
if (data.pointerPress == pointerUpHandler)
{
ExecuteEvents.Execute(data.pointerPress, data,
ExecuteEvents.pointerClickHandler);
}
// Clear selected gameobject
eventSystem.SetSelectedGameObject(null);
// Reset data
data.pressPosition = Vector2.zero;
data.pointerPress = null;
data.rawPointerPress = null;
}
}
```
If the above steps are completed, you can successfully operate the game through the handle.
---
## production experience:
在當初製作遊戲的時候,我們小組認為做VR遊戲比起製作3D遊戲更有難度,可以讓我們的技術具挑戰性,並促使我們進度,而且市面上VR設備相當昂貴,透過學校的資源製作VR遊戲是難得的機會於是就做下去了。
在過程中真的是遇到好多好多的困難,比如人力上的不足,或是時間不夠等等(我在同時間還準備升碩班考試),以結果論上來說的確有點高估自己了哈哈,但確實讓我進步許多,真的是透過製作這款遊戲讓我學會了如何自己解決問題,因為很多技術上的問題網路資源不多,而且老師同學也不一訂有相關經驗,要靠自己一點一點摸索。
但,跟朋友一起克服困難製作出一款令自己驕傲的遊戲真的很開心很充實。
At the time of making the game, our team thought that making a VR game is more difficult than making a 3D game, it can make our technology challenging and push us to progress, and the VR equipment on the market is quite expensive, we use school resources to make VR The game is a rare opportunity to do it.
In the process, I really encountered a lot of difficulties, such as lack of manpower, or lack of time, etc. (I was also preparing for the entrance exam at the same time). In terms of results, I really overestimated myself haha. , but it did make me improve a lot. It is really through making this game that I learned how to solve problems by myself, because there are not many online resources for many technical problems, and teachers and students do not have relevant experience. Explore for yourself little by little.
But it's really fun and fulfilling to overcome difficulties with friends and make a game.
---
## Responsibility
Number of team member: 2 people
Responsible for:
1.Planning (2 people)
2.Coding all by my self
---
## More Information
Video: https://www.youtube.com/watch?v=Q7bpOHSwJdk&ab_channel=WEI-JHESHU
Github: https://github.com/andy091045/CryStole-2-
HackMD: https://hackmd.io/@andy091045/S14zyZ4P9
{"metaMigratedAt":"2023-06-17T01:15:21.278Z","metaMigratedFrom":"YAML","title":"CryStole (VR Game)","breaks":true,"contributors":"[{\"id\":\"b4665373-7764-4f82-858c-d2f9f36df5f6\",\"add\":17312,\"del\":3144}]"}