2.1.1 Назначение задачи Программный продукт позволит пользователям симулировать бой между двух замков , враг сможет самостоятельно выбрать путь к замку пользователя, преследовать и атаковать дружественные войска. Данная задача может быть использована для получения навыков работы с графическими объектами, с клавиатурой и мышью и может рассматриваться как учебный тренажер. 2.1.2 Требования к техническому и программному обеспечению Для созданного проекта будут выставлены следующие требования: Требования к программному обеспечению: Windows 8 или версия выше. Net Frameworck 4.6 Требования к техническому обеспечению Для пользователей необходимой минимальной конфигурацией будет - 2.0 HGz, оперативная память 2 Gb, место на жёстком диске около 70Mb, так как программа использует малое количество ресурсов. Для пользователей подойдет разрешение: Если использовать приложение в окне от 320Х200 до 1366Х768 Если использовать приложение в развернутом виде от 320Х200 до любого разрешения экрана. 2.1.3 Организация входных и выходных данных Для реализации проекта необходимо создать входные и выходные данные, так как есть в проекте база данных, для подключения других пользователей, так же нужно организовать водные и выходные данные. Входными данными будут являться: Расположение войск врага. Расположение своих войск. Время ивента бомба. Выходными данными будут являться: Появление персонажа на заданной точки. Запуск ивента бомба. 2.2 Схема алгоритма выполнения программы На рисунке 5 представлен алгоритм функции начало игры. Рисунок 5 – блок-схема функции начало. 2.3 Текст программы using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.EventSystems; using UnityEngine.UI; //настройки войск / подразделений [System.Serializable] public class Troop{ public GameObject deployableTroops; public int troopCosts; public Sprite buttonImage; [HideInInspector] public Button button; } public class CharacterManager : MonoBehaviour { //переменные, видимые в инспекторе public int startGold; public GUIStyle rectangleStyle; public ParticleSystem newUnitEffect; public Texture2D cursorTexture; public Texture2D cursorTexture1; public bool highlightSelectedButton; public Color buttonHighlight; public Button button; public float bombLoadingSpeed; public float BombRange; public GameObject bombExplosion; [Space(10)] public List troops; // переменные не отображаются в инспекторе public static Vector3 clickedPos; public static int gold; public static GameObject target; private Vector2 mouseDownPos; private Vector2 mouseLastPos; private bool visible; private bool isDown; private GameObject[] knights; private int selectedUnit; private GameObject goldText; private GameObject goldWarning; private GameObject addedGoldText; private GameObject characterList; private GameObject characterParent; private GameObject selectButton; private GameObject bombLoadingBar; private GameObject bombButton; private float bombProgress; private bool isPlacingBomb; private GameObject bombRange; public static bool selectionMode; void Awake(){ //нахождение некоторых объектов characterParent = new GameObject("Characters"); selectButton = GameObject.Find("Character selection button"); target = GameObject.Find("target"); target.SetActive(false); } void Start(){ //установление курсора и добавление кнопки символов Cursor.SetCursor(cursorTexture, Vector2.zero, CursorMode.Auto); characterList = GameObject.Find("Character buttons"); addCharacterButtons(); //установление количество золота, чтобы начать с этим количеством золота gold = startGold; //нахождение текста, который отображает количество золота goldText = GameObject.Find("gold text"); //нахождение текста, который появляется, когда получаете дополнительное золото и установление его неактивным addedGoldText = GameObject.Find("added gold text"); addedGoldText.SetActive(false); //нахождение предупреждение, которое появляется, когда у вас недостаточно золота для развертывания войск и установите его неактивным goldWarning = GameObject.Find("gold warning"); goldWarning.SetActive(false); //функция воспроизведения добавления золото каждые пять секунд InvokeRepeating("AddGold", 1.0f, 5.0f); //нахождение бомбы игровые объекты bombLoadingBar = GameObject.Find("Loading bar"); bombButton = GameObject.Find("Bomb button"); bombRange = GameObject.Find("Bomb range"); bombRange.SetActive(false); isPlacingBomb = false; } void Update(){ if(bombProgress < 1){ //когда бомба загружается, установить красный цвет и отключить кнопку bombProgress += Time.deltaTime * bombLoadingSpeed; bombLoadingBar.GetComponent().color = Color.red; bombButton.GetComponent().enabled = false; } else{ //если бомба загружена , установите синий цвет и включите кнопку бомбы bombProgress = 1; bombLoadingBar.GetComponent().color = new Color(0, 1, 1, 1); bombButton.GetComponent().enabled = true; } //сумма к прогрессу бомбы bombLoadingBar.GetComponent().fillAmount = bombProgress; //синхроизация текста суммы с золотом goldText.GetComponent().text = "" + gold; //луч от основной камеры RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if(Physics.Raycast(ray, out hit)) //проверка, нажата ли левая кнопка мыши if (Input.GetMouseButtonDown(0)){ //если вы не нажали на выбор войск и у вас недостаточно золота, выводится предупреждение if (gold < troops[selectedUnit].troopCosts && !EventSystem.current.IsPointerOverGameObject()){ StartCoroutine(GoldWarning()); } //проверка, если вы нажмете любой коллайдер при нажатии (только для предотвращения ошибок) if (hit.collider != null){ //если вы нажмете поле боя, если клик не попал в любой пользовательский интерфейс, если пространство не вниз, и если у вас есть достаточно золота, развернуть выбранные войска и уменьшить количество золота if (hit.collider.gameObject.CompareTag("Battle ground") && !selectionMode && !isPlacingBomb && !EventSystem.current.IsPointerOverGameObject() && gold >= troops[selectedUnit].troopCosts && (!GameObject.Find("Mobile") || (GameObject.Find("Mobile") && Mobile.deployMode))){ GameObject newTroop = Instantiate(troops[selectedUnit].deployableTroops, hit.point, troops[selectedUnit].deployableTroops.transform.rotation) as GameObject; Instantiate(newUnitEffect, hit.point, Quaternion.FromToRotation(Vector3.forward, hit.normal)); newTroop.transform.parent = characterParent.transform; gold -= troops[selectedUnit].troopCosts; } //если размещена бомба и нажата кнопка... if (isPlacingBomb && !EventSystem.current.IsPointerOverGameObject()){ //instantiate explosion Instantiate(bombExplosion, hit.point, Quaternion.identity); //сбросить прогресс бомбы bombProgress = 0; isPlacingBomb = false; bombRange.SetActive(false); //нахождения врагов GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); foreach(GameObject enemy in enemies){ if(enemy != null && Vector3.Distance(enemy.transform.position, hit.point) <= BombRange/2){ //убить врага, если его в пределах бомба enemy.GetComponent().lives = 0; } } } else if(hit.collider.gameObject.CompareTag("Battle ground") && isPlacingBomb && EventSystem.current.IsPointerOverGameObject()){ //если вы разместиле бомбу и щелкннули любой элемент пользовательского интерфейса, продолжить, но не сбросить прогресс бомбы isPlacingBomb = false; bombRange.SetActive(false); } } } if(hit.collider != null && isPlacingBomb && !EventSystem.current.IsPointerOverGameObject()){ // показывать диапазон бомб в позиции мыши с помощью точечного света bombRange.transform.position = new Vector3(hit.point.x, 75, hit.point.z); // корректируем расцветку, соответствующую диапазону бомб bombRange.GetComponent().spotAngle = BombRange; bombRange.SetActive(true); } //если пространство вниз, чтобы установить положение, в котором вы впервые нажали if (Input.GetMouseButtonDown(0) && selectionMode && !isPlacingBomb && (!GameObject.Find("Mobile") || (GameObject.Find("Mobile") && !Mobile.selectionModeMove))){ mouseDownPos = Input.mousePosition; isDown = true; visible = true; } // Продолжаем отслеживать положение мыши до тех пор, пока не будет нажата кнопка мыши. if (isDown){ mouseLastPos = Input.mousePosition; // если вы отпустите кнопку мыши, удалите прямоугольник и остановите отслеживание if (Input.GetMouseButtonUp(0)){ isDown = false; visible = false; } } // находить и хранить все выбираемые объекты (объекты в сцене с меткой рыцаря) knights = GameObject.FindGameObjectsWithTag("Knight"); // если игрок нажимает d, отмените выбор всех символов if (Input.GetKey("x")){ foreach(GameObject Knight in knights){ if(Knight != null){ Knight.GetComponent().selected = false; } } } // запуск режима выбора, когда игрок нажимает пробел if (Input.GetKeyDown("space")){ selectCharacters(); } // для каждой кнопки, проверьте, есть ли у нас достаточно золота для развертывания устройства и цветной кнопки серый , если он еще не может быть развернут for (int i = 0; i < troops.Count; i++){ if(troops[i].troopCosts <= gold){ troops[i].button.gameObject.GetComponent().color = Color.white; } else{ troops[i].button.gameObject.GetComponent().color = new Color(0.7f, 0.7f, 0.7f, 1); } } } void OnGUI(){ // проверяем, должен ли быть видимым прямоугольник if (visible){ // Найдите угол окна Vector2 origin; origin.x = Mathf.Min(mouseDownPos.x, mouseLastPos.x); // Координаты графического интерфейса и мыши - наоборот. origin.y = Mathf.Max(mouseDownPos.y, mouseLastPos.y); origin.y = Screen.height - origin.y; // Вычислить размер окна Vector2 size = mouseDownPos - mouseLastPos; size.x = Mathf.Abs(size.x); size.y = Mathf.Abs(size.y); // Нарисуем окно GUI Rect rect = new Rect(origin.x, origin.y, size.x, size.y); GUI.Box(rect, "", rectangleStyle); foreach(GameObject Knight in knights){ if(Knight != null){ Vector3 pos = Camera.main.WorldToScreenPoint(Knight.transform.position); pos.y = Screen.height - pos.y; // foreach selectable character проверяет свою позицию, и если он находится внутри прямоугольника GUI, установите для него значение true if (rect.Contains(pos)){ Knight.GetComponent().selected = true; } } } } } // функция выбора другого блока public void selectUnit(int unit){ if(highlightSelectedButton){ // удаление всех контуров и отображение текущей кнопки for (int i = 0; i < troops.Count; i++){ troops[i].button.GetComponent().enabled = false; } EventSystem.current.currentSelectedGameObject.GetComponent().enabled = true; } // выбранный блок - нажатая кнопка selectedUnit = unit; } public void selectCharacters(){ // включение / выключение режима выбора selectionMode = !selectionMode; if(selectionMode){ // установить курсор и цвет кнопки, чтобы показать, что режим выбора игрока активен selectButton.GetComponent().color = Color.red; Cursor.SetCursor(cursorTexture1, Vector2.zero, CursorMode.Auto); if(GameObject.Find("Mobile")){ if(Mobile.deployMode){ GameObject.Find("Mobile").GetComponent().toggleDeployMode(); } Mobile.camEnabled = false; } } else{ // показать, что режим выбора игрока не активен selectButton.GetComponent().color = Color.white; Cursor.SetCursor(cursorTexture, Vector2.zero, CursorMode.Auto); // установить целевой объект false и отменить выбор всех единиц foreach (GameObject Knight in knights){ if(Knight != null){ Knight.GetComponent().selected = false; } } target.SetActive(false); if(GameObject.Find("Mobile")){ Mobile.camEnabled = true; } } } // предупреждение, если вам нужно больше золота IEnumerator GoldWarning(){ if(!goldWarning.activeSelf){ goldWarning.SetActive(true); //ждать 2 секунды yield return new WaitForSeconds(2); goldWarning.SetActive(false); } } void addCharacterButtons(){ //для всех войск... for(int i = 0; i < troops.Count; i++){ // добавьте кнопку в список кнопок Button newButton = Instantiate(button); RectTransform rectTransform = newButton.GetComponent(); rectTransform.SetParent(characterList.transform, false); // установка контура кнопки newButton.GetComponent().effectColor = buttonHighlight; // задаем правильную кнопку справки newButton.gameObject.GetComponent().sprite = troops[i].buttonImage; // разрешать только контур для первой кнопки if (i == 0 && highlightSelectedButton){ newButton.GetComponent().enabled = true; } else{ newButton.GetComponent().enabled = false; } // установить имя кнопки в ее позицию в списке (важно, чтобы кнопка работала позже) newButton.transform.name = "" + i; // добавим функцию onclick к кнопке с именем, чтобы выбрать соответствующий блок newButton.GetComponent().onClick.AddListener( () => { selectUnit(int.Parse(newButton.transform.name)); } ); // задаем параметры кнопки newButton.GetComponentInChildren().text = "Price: " + troops[i].troopCosts + "\n Damage: " + troops[i].deployableTroops.GetComponentInChildren().damage + "\n Lives: " + troops[i].deployableTroops.GetComponentInChildren().lives; // это новая кнопка troops[i].button = newButton; } } public void placeBomb(){ // начало размещения бомбы isPlacingBomb = true; } // функции, которая добавляет 100 к вашему количеству золота и показывает текст, чтобы сообщить игроку void AddGold(){ gold += 100; StartCoroutine(AddedGoldText()); } IEnumerator AddedGoldText(){ addedGoldText.SetActive(true); yield return new WaitForSeconds(0.7f); addedGoldText.SetActive(false); } } Код скрипта камера игрока using UnityEngine; using System.Collections; public class CamController : MonoBehaviour { public bool multiplayer; // переменные не отображаются в инспекторе public static float movespeed; public static float zoomSpeed; public static float mouseSensitivity; public static float clampAngle; float rotationY = 0; float rotationX = 0; Vector2 moveVec; Vector2 dragStartPos; void Start(){ // получить начало вращения Vector3 rot = transform.localRotation.eulerAngles; rotationY = rot.y; rotationX = rot.x; if(transform.position.x < 0 && multiplayer){ rotationY += 180; } } void Update(){ // если мобильный сборник добавлен в сцену, используйте мобильные элементы управления. Другие элементы управления ПК if (GameObject.Find("Mobile") == null && GameObject.Find("Mobile multiplayer") == null){ PcCamera(); } else if((GameObject.Find("Mobile") && Mobile.camEnabled) || (GameObject.Find("Mobile multiplayer") && MobileMultiplayer.camEnabled)){ MobileCamera(); } } void PcCamera(){ // если нажатие клавиши перемещается влево / вправо if (Input.GetKey("a")){ transform.Translate(Vector3.right * Time.deltaTime * -movespeed); } if(Input.GetKey("d")){ transform.Translate(Vector3.right * Time.deltaTime * movespeed); } // при нажатии клавиши перемещение вверх / вниз if (Input.GetKey("w")){ transform.Translate(Vector3.up * Time.deltaTime * movespeed); } if(Input.GetKey("s")){ transform.Translate(Vector3.up * Time.deltaTime * -movespeed); } // если скроллинг выключен, поверните камеру if (Input.GetMouseButton(2)){ float mouseX = Input.GetAxis("Mouse X"); float mouseY = -Input.GetAxis("Mouse Y"); rotateCamera(mouseX, mouseY); } // перемещаем камеру при прокрутке transform.Translate(new Vector3(0, 0, Input.GetAxis("Mouse ScrollWheel")) * Time.deltaTime * zoomSpeed); } void MobileCamera(){ // проверяем, не касается ли один касание экрана один палец if (Input.touchCount == 1){ // вращение камеры на основе положения касания Touch touch = Input.GetTouch(0); float mouseX = touch.deltaPosition.x; float mouseY = -touch.deltaPosition.y; rotateCamera(mouseX, mouseY); } // проверяем два касания else if (Input.touchCount == 2){ // храните два касания Touch touchZero = Input.GetTouch(0); Touch touchOne = Input.GetTouch(1); // находим позицию в предыдущем кадре каждого касания Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition; Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition; // находим величину вектора (расстояние) между касаниями в каждом кадре float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude; float touchDeltaMag = (touchZero.position - touchOne.position).magnitude; // находим разницу в расстояниях между каждым фреймом float z = (prevTouchDeltaMag - touchDeltaMag) * 0.005f * zoomSpeed; // масштабировать камеру, перемещая ее вперед / назад transform.Translate(new Vector3(0, 0, -z)); } } void rotateCamera(float mouseX, float mouseY){ // проверяем, включены ли мобильные элементы управления для настройки чувствительности if (GameObject.Find("Mobile") == null && GameObject.Find("Mobile multiplayer") == null){ rotationY += mouseX * mouseSensitivity * Time.deltaTime; rotationX += mouseY * mouseSensitivity * Time.deltaTime; } else{ rotationY += mouseX * mouseSensitivity * Time.deltaTime * 0.2f; rotationX += mouseY * mouseSensitivity * Time.deltaTime * 0.2f; } // зажмите x вращение, чтобы ограничить его rotationX = Mathf.Clamp(rotationX, -clampAngle, clampAngle); // применить поворот transform.rotation = Quaternion.Euler(rotationX, rotationY, 0.0f); } }