因为这还是一个学习过程产品,所以也仍然只是一个普通的DEMO,只有一个关卡,但是开放了部分自定义的实验性功能,可以自行设计关卡保存后给身边的人玩耍,暂时未提供关卡上传功能。
目前仅在自己的手机上做了适配测试,运行正常,但是可能游戏优化不够好,容易发热。
没有适配全面屏、水滴屏、挖空屏等等。
刘海屏:其中一个方向UI偏移,旋转后正常。
全面屏:周围黑边。(因为画面选择了最广泛的比例16:9,而我手机的比例不是,如果强行全屏会导致画面变形)
游戏玩法已经内置到游戏UI中。
PS:其实我在担心是不是难度大了点……我自己都没玩到90分以上。(A上面还有A+、S、SS、SSS级评定)
TIPS:多注意视角边界的物体和视角的中心点,这样子前者可以判断距离是否正确,后者可以判断方向是否正确。
V1.1版本修改:
之前匹配率后面忘记增加百分号了。
1.修改设置界面文字提示。
2.修改匹配率显示格式。
V1.2版本修改:
1.修改了一个预置线索的视角
2.调整了评分机制
游戏开始……
(部分手机端如果显示异常,如仅局部,可点击链接:查看视频)
文件大小:95.3MB / 描述:未说明
(https://cxbox.luheqiu.com/Game/QuickSnap/v1.2/com.LuHeQiu.QuickSnap.apk)如果直接打不开,可以复制链接到浏览器打开。
适用平台:Android
食用方式:开袋即食。
参考书籍:《Introduction to GAME DESIGN,PROTOTYPING,and DEVELOPMENT》
模型来源:《Introduction to GAME DESIGN,PROTOTYPING,and DEVELOPMENT》
开发引擎:Unity3D
引擎版本:2018.4.16f1
编程语言:C#
工程文件(包含所有数据):这个不是游戏,是制作游戏的工程数据,别下错了
文件大小:328.4MB / 描述:未说明
提取码:63bf
核心代码:
这里为了在安卓上使用,做了虚拟摇杆控制移动和滑动屏幕控制视角偏转。这里也算是踩了很多坑。
一个是基于四元数控制物体旋转,这个必须搞好,不然的话就会出现我一开始的情况(x轴移动正常,但当同时上下滑动改变y轴时,因为物体本身已经发生了偏转,会导致视角的旋转,即不保持水平)
后来的解决方案是:x轴y轴拆分,x轴移动角色对象,y轴控制摄像头仰俯角)
第二个是协同控制,一开始想当然了,没弄好多指触控识别,结果出现了使用虚拟摇杆时无法使用视角控制,使用视角控制时使用虚拟摇杆可以,但是如果松开视角控制的手会发生屏幕抖动甚至跳跃。
使用的原理和之前那个爆破任务的弹弓原理类似,不过更为完善。主要是随触摸点移动,和范围限制(模长大于1时取其单位向量)。然后把偏转的二维向量输出,用于移动控制。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; public class JoystickMovement : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler { #region Fields private Image joystickAreaImg; //背景层 private Image joystickHandleImg; //浮动层 private Vector2 joystickHandleImgInitialPosition; private Vector2 joystickInput; [SerializeField][Range(0.1f, 2f)] private float handleArea = 0.6f; #endregion #region Unity Methods void Awake() { joystickAreaImg = transform.GetChild(0).GetComponent(); joystickHandleImg = joystickAreaImg.transform.GetChild(0).GetComponent(); joystickHandleImgInitialPosition = joystickHandleImg.rectTransform.anchoredPosition; } void OnDisable() { joystickInput = Vector2.zero; joystickHandleImg.rectTransform.anchoredPosition = joystickHandleImgInitialPosition; } #endregion #region Callback Events public virtual void OnPointerDown(PointerEventData eventData) { OnDrag(eventData); } public virtual void OnDrag(PointerEventData eventData) { Vector2 position; if (RectTransformUtility.ScreenPointToLocalPointInRectangle(joystickAreaImg.rectTransform, eventData.position, eventData.pressEventCamera, out position)) { position.x = position.x / joystickAreaImg.rectTransform.sizeDelta.x; position.y = position.y / joystickAreaImg.rectTransform.sizeDelta.y; joystickInput = new Vector2(position.x * 2, position.y * 2); if (joystickInput.magnitude > 1) { joystickInput = joystickInput.normalized; } joystickHandleImg.rectTransform.anchoredPosition = new Vector2(joystickInput.x * (joystickHandleImg.rectTransform.sizeDelta.x / handleArea), joystickInput.y * (joystickHandleImg.rectTransform.sizeDelta.y / handleArea)); } } public virtual void OnPointerUp(PointerEventData eventData) { joystickInput = Vector2.zero; joystickHandleImg.rectTransform.anchoredPosition = joystickHandleImgInitialPosition; } #endregion #region Private Methods public float HorizontalInput() { return joystickInput.x; } public float VerticalInput() { return joystickInput.y; } #endregion }
有了上面的函数,于是就可以……
FirstPersonController.cs部分
float speed; GetInput(out speed); Vector3 desiredMove = transform.forward * m_Input.y + transform.right * m_Input.x; //这个可以不用管,是用于走路时的起伏动作的一个部分。 Physics.SphereCast(transform.position, m_CharacterController.radius, Vector3.down, out hitInfo, m_CharacterController.height / 2f, Physics.AllLayers, QueryTriggerInteraction.Ignore); desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized; m_MoveDir.x = desiredMove.x * speed; m_MoveDir.z = desiredMove.z * speed;
1.读取触摸点
2.如果触摸点的x轴位置在屏幕的左侧2/5,返回。
3.记录起始位置。
4.读取end到start的偏差值,基于偏差值控制视角偏转。
大概思路就是上面那样子。
FirstPersonController.cs部分
private void RotateView() { //其实是做了双端控制的准备,不过暂时只打包了Android,毕竟主要手机用的多可能。 //当初打包apk几乎快把我整吐了,每次打包到最后就是白,多的时候报一百多个error,我真的是&*%¥#@& if (IsMobile)//是否移动端? { startTouchPos = Swipe.TouchStartPositionInput(); difTouchPos = Swipe.TouchDifPositionInput(); if (startTouchPos.x < Screen.width * 0.6f) return; if (prevStartTouchPos != startTouchPos) { prevStartTouchPos = startTouchPos; prevCharactorRot = transform.localRotation; prevCameraRot = m_Camera.transform.localRotation; difTouchPos = Vector2.zero; } float xRot = difTouchPos.x * touchXSensitivity; float yRot = difTouchPos.y * touchYSensitivity; Quaternion m_CameraTargetRot = ClampRotationAroundXAxis(prevCameraRot * Quaternion.Euler(-yRot, 0f, 0f));//这是一条界限控制函数 transform.localRotation = prevCharactorRot * Quaternion.Euler(0f, xRot, 0f); m_Camera.transform.localRotation = m_CameraTargetRot; } else { m_MouseLook.LookRotation(transform, m_Camera.transform); } } //通过对四元数的运算,求出角度,限制仰俯角在-90°到90°之间。 Quaternion ClampRotationAroundXAxis(Quaternion q) { q.x /= q.w; q.y /= q.w; q.z /= q.w; q.w = 1.0f; float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q.x); angleX = Mathf.Clamp(angleX, MinimumX, MaximumX); q.x = Mathf.Tan(0.5f * Mathf.Deg2Rad * angleX); return q; }
下面就是游戏功能性代码了
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityStandardAssets.Characters.FirstPerson; public class TargetCamera : MonoBehaviour { static public TargetCamera S; static public bool editMode = false; public GameObject firstPersonCamera; public TextAsset shotXML; public float maxPosDeviation = 1f; public float maxTarDeviation = 0.5f; public string deviationEasing = Easing.Out; public float passingAccuracy = 0.7f; public bool checkToDeletePlayePrefs = false; public bool _______________________________; public int shotNum; public Text shotCounter, shotRating; public Image checkMark; public Image fpcShutterClick; public Image tcShutterClick; public Shot lastShot; public int numShots; public Shot[] playerShots; public float[] playerRatings; public bool[] playerShotsPassed; public int playerShotedNum; public int grade; private bool isWin = false; private void Awake() { S = this; } private void Start() { GameObject go = GameObject.Find("TC_ShotCounter"); shotCounter = go.GetComponent(); go = GameObject.Find("TC_ShotRating"); shotRating = go.GetComponent(); go = GameObject.Find("TC_Check"); checkMark = go.GetComponent(); go = GameObject.Find("FPC_Shutter_Click"); fpcShutterClick = go.GetComponent(); go = GameObject.Find("TC_Shutter_Click"); tcShutterClick = go.GetComponent(); //初始化 checkMark.enabled = false; fpcShutterClick.enabled = false; tcShutterClick.enabled = false; Shot.LoadShot(shotXML.text); if (Shot.shots.Count > 0) { shotNum = 0; ResetPlayerShotsAndRatings(); ShowShot(Shot.shots[shotNum]); } } private void ResetPlayerShotsAndRatings() { numShots = Shot.shots.Count; //初始化 playerShots = new Shot[numShots]; playerRatings = new float[numShots]; playerShotsPassed = new bool[numShots]; playerShotedNum = 0; grade = 0; } private void Update() { Shot sh; //编辑模式 if (editMode && !FirstPersonController.IsMobile) { if ((Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1))) { sh = new Shot(); sh.position = firstPersonCamera.transform.position; sh.rotation = firstPersonCamera.transform.rotation; //检测摄像机视角图像 Ray ray = new Ray(sh.position, firstPersonCamera.transform.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { sh.target = hit.point; } if (Input.GetMouseButtonDown(0))//添加 { Shot.shots.Add(sh); shotNum = Shot.shots.Count - 1; ShowShot(Shot.shots[shotNum]); } else if (Input.GetMouseButtonDown(1))//替换 { Shot.ReplaceShot(shotNum, sh); ShowShot(Shot.shots[shotNum]); } //ShowShot(sh); Utils.tr(sh.ToXML()); //编辑shot时重置玩家信息 ResetPlayerShotsAndRatings(); } if (Input.GetKeyDown(KeyCode.Q)) { shotNum--; if (shotNum < 0) shotNum = Shot.shots.Count - 1; ShowShot(Shot.shots[shotNum]); } if (Input.GetKeyDown(KeyCode.E)) { shotNum++; if (shotNum >= Shot.shots.Count) shotNum = 0; ShowShot(Shot.shots[shotNum]); } if (Input.GetKey(KeyCode.LeftControl)) { if (Input.GetKeyDown(KeyCode.S)) { Shot.SaveShots(); } if (Input.GetKeyDown(KeyCode.X)) { Utils.tr(Shot.XML); } } } //更新UI shotCounter.text = (shotNum + 1).ToString() + " of " + Shot.shots.Count; if (Shot.shots.Count == 0) { shotCounter.text = "Null"; shotNum = 0; } //shotRating.text = ""; if (playerRatings.Length > shotNum && playerShots[shotNum] != null) { float rating = Mathf.Round(playerRatings[shotNum] * 100f); if (rating < 0) rating = 0; shotRating.text = rating.ToString() + "%"; checkMark.enabled = (playerRatings[shotNum] > passingAccuracy); playerShotsPassed[shotNum] = (playerRatings[shotNum] > passingAccuracy); } else { shotRating.text = ""; checkMark.enabled = false; } //胜利判定 int passedNum = 0; foreach (bool tb in playerShotsPassed) { if (tb) passedNum += 1; } if (passedNum >= numShots && !isWin) { Invoke("WinOver", 2f); isWin = true; } } public void WinOver() { WelcomeUI.S.YouWin(playerShotedNum, playerRatings); } //按下快门 public void ShutterPressed() { Shot sh; if (editMode) { sh = new Shot(); sh.position = firstPersonCamera.transform.position; sh.rotation = firstPersonCamera.transform.rotation; //检测摄像机视角图像 Ray ray = new Ray(sh.position, firstPersonCamera.transform.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { sh.target = hit.point; } Shot.shots.Add(sh); shotNum = Shot.shots.Count - 1; ShowShot(Shot.shots[shotNum]); //ShowShot(sh); Utils.tr(sh.ToXML()); //编辑shot时重置玩家信息 ResetPlayerShotsAndRatings(); } else { playerShotedNum++; sh = new Shot(); sh.position = firstPersonCamera.transform.position; sh.rotation = firstPersonCamera.transform.rotation; //检测摄像机视角图像 Ray ray = new Ray(sh.position, firstPersonCamera.transform.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { sh.target = hit.point; } float acc = Shot.Compare(Shot.shots[shotNum], sh); lastShot = sh; playerShots[shotNum] = sh; playerRatings[shotNum] = acc; StartCoroutine(ClickWhiteOnFPC()); ShowShot(sh); Invoke("ShowCurrentShot", 1f); this.GetComponent().Play(); } } public void LastPressed() { shotNum--; if (shotNum < 0) shotNum = Shot.shots.Count - 1; ShowShot(Shot.shots[shotNum]); } public void NextPressed() { shotNum++; if (shotNum >= Shot.shots.Count) shotNum = 0; ShowShot(Shot.shots[shotNum]); } public void ResetShotOnMobile() { Shot sh; sh = new Shot(); sh.position = firstPersonCamera.transform.position; sh.rotation = firstPersonCamera.transform.rotation; //检测摄像机视角图像 Ray ray = new Ray(sh.position, firstPersonCamera.transform.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { sh.target = hit.point; } Shot.ReplaceShot(shotNum, sh); ShowShot(Shot.shots[shotNum]); } public void SaveShotOnMobile() { Shot.SaveShots(); } public void ShowShot(Shot sh) { //定位 transform.position = sh.position; transform.rotation = sh.rotation; } public void ShowCurrentShot() { StartCoroutine(ClickWhiteOnTC()); ShowShot(Shot.shots[shotNum]); } //协程 public IEnumerator ClickWhiteOnFPC() { fpcShutterClick.enabled = true; yield return new WaitForSeconds(0.05f); fpcShutterClick.enabled = false; } public IEnumerator ClickWhiteOnTC() { tcShutterClick.enabled = true; yield return new WaitForSeconds(0.05f); tcShutterClick.enabled = false; } public void OnDrawGizmos() { List shots = Shot.shots; for (int i = 0; i < shots.Count; i++) { Gizmos.color = Color.green; Gizmos.DrawWireSphere(shots[i].position, 0.5f); Gizmos.color = Color.yellow; Gizmos.DrawLine(shots[i].position, shots[i].target); Gizmos.color = Color.red; Gizmos.DrawWireSphere(shots[i].target, 0.25f); } if (checkToDeletePlayePrefs) { Shot.DeleteShots(); checkToDeletePlayePrefs = false; shotNum = 0; } if (lastShot != null) { Gizmos.color = Color.green; Gizmos.DrawWireSphere(lastShot.position, 0.25f); Gizmos.color = Color.white; Gizmos.DrawLine(lastShot.position, lastShot.target); Gizmos.color = Color.red; Gizmos.DrawWireSphere(lastShot.target, 0.125f); } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityStandardAssets.Characters.FirstPerson; public class WelcomeUI : MonoBehaviour { #region GameField public static WelcomeUI S; public GameObject fPSController; public GameObject fPSCamera; public GameObject targerCamera; public GameObject fPC; public GameObject tC; public GameObject swipe; public GameObject joystick; public GameObject buttonUI; public GameObject welcomeCamera; public GameObject welcomeInterface; public GameObject quickSnap; public GameObject starGame; public GameObject continueGame; public GameObject gameIntroduction; public GameObject gameSettings; public GameObject intro_Title; public GameObject intro_Content; public GameObject intro_Image; public GameObject intro_Point; public GameObject set_Title; public GameObject set_Content; public Slider set_Sensitivity; public Slider set_WalkSpeed; public Toggle set_EditMode; public GameObject set_ShotReset; public GameObject back; public GameObject win_Title; public GameObject win_Content; public GameObject win_Degree; public GameObject win_Grade; public GameObject edit_ReShutter; public GameObject edit_ReSave; [SerializeField] private Vector3 welcomeLocalPos; [SerializeField] private Vector3 startLocalPos; [SerializeField] private Quaternion welcomeLocalRot; [SerializeField] private Quaternion startLocalRot; private bool isStart = false; private bool isMoving = false; private bool isStarted = false; private bool isStartOver = false; private bool isYouWin = false; private bool isReturning = false; private bool isReturned = false; private bool isReturnOver = false; private float timeStart; private float timeDuration = 3.5f; private Vector3 startCamPos; private Quaternion startCamRot; public string tempS = ""; public string tempDegree = ""; #endregion private void Awake() { S = this; } public void Start() { set_Sensitivity.value = 4.3f; set_WalkSpeed.value = 5; } public void Update() { if (!isStartOver) { if (isStart) { isStart = false; isMoving = true; timeStart = Time.time; } if (isMoving && !isStarted) { float u = (Time.time - timeStart) / timeDuration; if (u >= 1) { u = 1; isMoving = false; isStarted = true; } u = u - 0.2f * Mathf.Sin(2 * Mathf.PI * u); //缓动函数 welcomeCamera.transform.position = (1 - u) * welcomeLocalPos + u * startLocalPos; welcomeCamera.transform.rotation = Quaternion.Slerp(welcomeLocalRot, startLocalRot, u); } if (isStarted) { welcomeCamera.SetActive(false); fPSController.SetActive(true); targerCamera.SetActive(true); fPC.SetActive(true); tC.SetActive(true); swipe.SetActive(true); joystick.SetActive(true); buttonUI.SetActive(true); isStarted = false; isStartOver = true; } } if (!isReturnOver) { if (isYouWin) { fPSController.SetActive(false); targerCamera.SetActive(false); fPC.SetActive(false); tC.SetActive(false); swipe.SetActive(false); joystick.SetActive(false); buttonUI.SetActive(false); welcomeCamera.SetActive(true); isYouWin = false; isReturning = true; timeStart = Time.time; startCamPos = fPSCamera.transform.position; startCamRot = fPSCamera.transform.rotation; welcomeCamera.transform.position = startCamPos; welcomeCamera.transform.rotation = startCamRot; } if (isReturning && !isReturned) { float u = (Time.time - timeStart) / timeDuration; if (u >= 1) { u = 1; isReturning = false; isReturned = true; } u = u - 0.2f * Mathf.Sin(2 * Mathf.PI * u); //缓动函数 welcomeCamera.transform.position = (1 - u) * startCamPos + u * welcomeLocalPos; welcomeCamera.transform.rotation = Quaternion.Slerp(startCamRot, welcomeLocalRot, u); } if (isReturned) { welcomeInterface.SetActive(true); win_Title.SetActive(true); win_Content.SetActive(true); win_Degree.SetActive(true); win_Grade.SetActive(true); win_Grade.GetComponent().text = tempS.Replace('T', '\n'); win_Degree.GetComponent().text = tempDegree; isReturnOver = true; } } } public void StartGame() { quickSnap.SetActive(false); starGame.SetActive(false); continueGame.SetActive(false); gameIntroduction.SetActive(false); gameSettings.SetActive(false); welcomeInterface.SetActive(false); isStart = true; } public void ContinueGame() { quickSnap.SetActive(false); starGame.SetActive(false); continueGame.SetActive(false); gameIntroduction.SetActive(false); gameSettings.SetActive(false); welcomeInterface.SetActive(false); fPC.SetActive(true); swipe.SetActive(true); } public void GameIntroduction() { quickSnap.SetActive(false); starGame.SetActive(false); continueGame.SetActive(false); gameIntroduction.SetActive(false); gameSettings.SetActive(false); intro_Title.SetActive(true); intro_Content.SetActive(true); intro_Image.SetActive(true); intro_Point.SetActive(true); back.SetActive(true); } public void GameSettings() { quickSnap.SetActive(false); starGame.SetActive(false); continueGame.SetActive(false); gameIntroduction.SetActive(false); gameSettings.SetActive(false); set_Title.SetActive(true); set_Content.SetActive(true); set_Sensitivity.gameObject.SetActive(true); set_WalkSpeed.gameObject.SetActive(true); set_EditMode.gameObject.SetActive(true); set_ShotReset.SetActive(true); back.SetActive(true); } public void GameMenuBack() { fPC.SetActive(false); swipe.SetActive(false); intro_Title.SetActive(false); intro_Content.SetActive(false); intro_Image.SetActive(false); intro_Point.SetActive(false); set_Title.SetActive(false); set_Content.SetActive(false); set_Sensitivity.gameObject.SetActive(false); set_WalkSpeed.gameObject.SetActive(false); set_EditMode.gameObject.SetActive(false); set_ShotReset.SetActive(false); back.SetActive(false); welcomeInterface.SetActive(true); quickSnap.SetActive(true); if (isStartOver) continueGame.SetActive(true); else starGame.SetActive(true); gameIntroduction.SetActive(true); gameSettings.SetActive(true); } public void SetSensivity() { double value = 0.0145 * Mathf.Exp(set_Sensitivity.value * 0.45f); FirstPersonController.touchXSensitivity = (float)value; FirstPersonController.touchYSensitivity = (float)value; } public void SetWalkSpeed() { FirstPersonController.m_WalkSpeed = set_WalkSpeed.value; } public void EditModeSetOnMobile() { if (set_EditMode.isOn) { TargetCamera.editMode = true; edit_ReShutter.SetActive(true); edit_ReSave.SetActive(true); } else { TargetCamera.editMode = false; edit_ReShutter.SetActive(false); edit_ReShutter.SetActive(false); } } public void ShotPrefsResrt() { Shot.DeleteShots(); } public void YouWin(int playerShotedNum, float[] playerRatings) { float AveRating = 0f; foreach (float rating in playerRatings) { AveRating += rating; } AveRating = Mathf.Round(AveRating / playerRatings.Length * 100f); int AddPoints = 0; if (playerShotedNum < 16) AddPoints = 16 - playerShotedNum; else if (playerShotedNum > 24) AddPoints = 24 - playerShotedNum; int Grade = 100 - (int)((100 - AveRating) * (1.33f)) + AddPoints; if (Grade >= 100) Grade = 100; else if (Grade <= 0) Grade = 0; tempS = "胶卷消耗数:" + playerShotedNum + "T综合匹配率:" + AveRating + "T结算总得分:" + Grade; tempDegree = "D-"; if (Grade >= 98) tempDegree = "SSS"; else if (Grade >= 96) tempDegree = "SS"; else if (Grade >= 93) tempDegree = "S"; else if (Grade >= 90) tempDegree = "A+"; else if (Grade >= 87) tempDegree = "A"; else if (Grade >= 85) tempDegree = "A-"; else if (Grade >= 82) tempDegree = "B+"; else if (Grade >= 79) tempDegree = "B"; else if (Grade >= 76) tempDegree = "B-"; else if (Grade >= 73) tempDegree = "C+"; else if (Grade >= 70) tempDegree = "C"; else if (Grade >= 67) tempDegree = "C-"; else if (Grade >= 64) tempDegree = "D+"; else if (Grade >= 62) tempDegree = "D"; else if (Grade >= 60) tempDegree = "D-"; else if (Grade < 60) tempDegree = "?"; else if (Grade < 50) tempDegree = "??"; else if (Grade < 40) tempDegree = "???"; isYouWin = true; } }
<xml> <shot x="-17.312" y="1.78" z="6.512172" qx="-0.003993143" qy="-0.9950927" qz="0.04518745" qw="-0.08793474" tx="-15.84644" ty="1.019435" tz="-1.715417" /> <shot x="-21.90255" y="1.78" z="29.87937" qx="0.04885036" qy="0.003486181" qz="-0.0001705058" qw="0.9988" tx="-21.87488" ty="1.391284" tz="33.84363" /> <shot x="-24.88841" y="1.78" z="1.778479" qx="0.01991105" qy="0.1261734" qz="-0.002533002" qw="0.9918052" tx="-24.07588" ty="1.649649" tz="4.920304" /> <shot x="-2.820553" y="1.78" z="23.37497" qx="0.06876711" qy="0.9218622" qz="-0.1925887" qw="0.3291668" tx="-0.2400206" ty="0" tz="20.22217" /> <shot x="-17.90158" y="1.78" z="42.33155" qx="0.1302008" qy="0.2338618" qz="0.03161911" qw="-0.9629936" tx="-19.10048" ty="2.500004" tz="44.65438" /> <shot x="-30.00804" y="1.78" z="31.57938" qx="-0.1287968" qy="0.0008646704" qz="-0.0001123022" qw="-0.9916706" tx="-30.01056" ty="1.398686" tz="33.02258" /> <shot x="-2.918861" y="1.78" z="5.072468" qx="-0.1221666" qy="-0.6711882" qz="0.1135243" qw="-0.7222838" tx="-1.56189" ty="1.306174" tz="5.172117" /> <shot x="-6.68367" y="1.78" z="30.49959" qx="-0.01134583" qy="0.01308868" qz="-0.0001485241" qw="-0.99985" tx="-6.747552" ty="1.724607" tz="32.93919" /> </xml>