- PVSM.RU - https://www.pvsm.ru -
Доброго времени суток! В этой статье я хочу поделиться опытом разработки своей игры с использованием игрового движка Unity.
Концепция игры заключается в том что вам нужно взять на себя управление звездолетом и уничтожить как можно большее количество метеоритов. На вашем пути будут появляться вражеские звездолеты, которые будут мешать вам и после их уничтожения будут появляться «капсулы» после подбора которых будет доступен новый тип оружия. Игра будет называться Galaxy Desteroid.
Графика игры состоит из следующих текстур:
На основе этих текстур были созданы следующие префабы
где:
asteroidrotate — метеорит который нужно уничтожать
enemy — вражеский звездолет
explosionasteroid, explosionenemy, explosionplayer — это анимации взрыва созданные с использованием particle system
gunactivator(s) — это капсулы которые будут активировать разный тип оружия в игре
Все остальное типа laser и т.п. это и есть оружие.
Игра будет включать в себя 2 сцены: главное меню и игровая сцена.
Где «menu» это главное меню а «1» это игровая сцена.
В качестве фона я использовал спрайт с именем «space», на нем нарисован космос.
Далее создаем скрипт «menu.cs» (Щелкаем правой кнопкой → выбираем Create → C# Script) и «вешаем» его на background. background это спрайт со 100% прозрачностью на котором создаются основные элементы управления (START/EXIT) а space служит просто для декорации.
Содержимое скрипта:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using UnityEngine.SceneManagement;
public class menu : MonoBehaviour {
public GUIStyle mystyle;//объявляется для того чтобы изменять начертание GUI компонентов(шрифт, размер и.т.п.)
string score;
void Start ()
{
StreamReader scoredata = new StreamReader (Application.persistentDataPath + "/score1.gd");//создание файловой переменной
score = scoredata.ReadLine ();//чтение строки
scoredata.Close ();//закрытие файловой переменной
}
// Update is called once per frame
void Update () {
}
void OnGUI(){
GUI.Box (new Rect (Screen.width*0.15f, Screen.height*0.8f, Screen.width*0.7f, Screen.height*0.1f), "MAX DESTROYED:"+score,mystyle);
if (GUI.Button (new Rect (Screen.width*0.15f, Screen.height*0.25f, Screen.width*0.7f, Screen.height*0.1f), "START",mystyle))
{
SceneManager.LoadScene (1);//Загрузка игровой сцены
}
if (GUI.Button (new Rect (Screen.width*0.15f, Screen.height*0.4f, Screen.width*0.7f, Screen.height*0.1f), "EXIT",mystyle))
{
Application.Quit();//Выход из игры
}
}
}
Еще не забываем повесить на «space» скрипт activemenu. Он служит для того чтобы создать анимацию движения фона меню. Потом создаем копию «space» и ставим ее чуть выше.
Содержимое скрипта activemenu:
using UnityEngine;
using System.Collections;
public class activemenu : MonoBehaviour {
float speed=-0.1f;
void Start () {
}
// Update is called once per frame
void Update () {
transform.Translate (new Vector3 (0f,speed,0f));
if (transform.position.y < -12f)
{
transform.position=new Vector3(0f,13f,0f);
}
}
}
Должно получиться примерно вот так:
Игровая сцена состоит из следующих ключевых объектов:
космос (спрайт «space»)
игрок
метеориты
вражеские корабли
прочее(лазеры и взрывы).
Космос организован также как в главном меню. Здесь можно ничего не трогать.
Далее нужно обратить внимание на то что в игре будут постоянно генерироваться из префабов, такие объекты как метеориты, выстрелы и враги. И те объекты которые упустил игрок нужно удалять, чтобы в лишний раз не нагружать память.
Это можно сделать следующим образом. Создаем новый игровой объект на сцене(у меня это «controlcountobjects»), добавляем к нему компонент boxcollider и растягиваем его вокруг игровой зоны.
Далее добавляем на него скрипт со следующим содержимым:
using UnityEngine;
using System.Collections;
public class systemcontrolobjects : MonoBehaviour {
void Start ()
{
}
void Update () {
}
void OnTriggerExit2D(Collider2D col)
{
Destroy(col.gameObject);
}
}
В этом скрипте происходит удаление элементов при выходе из box collider объекта controlcountobject. Таким образом получается некая зона при выходе из которой происходит удаление объектов.
Добавляем данный скрипт на камеру:
using UnityEngine;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class blockgenerator : MonoBehaviour {
public GameObject asteroid;//Добавляем сюда метеорит из префабов
float x,y,timer;
float timerespawn=0.25f;//Период возрождения метеоритов. С помощью данной переменной можно контролировать плотность трафика метеоритов.
bool trigtime=false;//Переключатель для отсчета времени. Если его значение true то тогда отсчитывается время для генерации следующего метеорита.
public int score;//Подсчет общего числа подбитых метеоритов за текущую игру и если данное число превысит рекорд то тогда оно будет записано в файл рекорда. С этой переменной будут взаимодействовать другие скрипты. Каждый созданный метеорит будет передавать команду, которая будет увеличивать данную переменную на единицу.
public float data;
void Start ()
{
score = 0;//
timer = timerespawn;
StreamReader scoredata = new StreamReader (Application.persistentDataPath + "/score1.gd");
data = float.Parse(scoredata.ReadLine ());
scoredata.Close ();
}
void Update ()
{
if (timer==timerespawn)//Если установлено время для отсчета генерации метеорита то:
{
x = Random.Range (-2.5f, 2.5f);//1)Выбираем рандомную координату в которой появится метеорит из динамического диапазона.
Instantiate(asteroid, new Vector3(x,5.5f,-2.17f),transform.rotation);//2)Генерация метеорита из префаба. x выбирается рандомно из указанного выше диапазона.
trigtime = true;//3)Активируем переключатель для отсчета времени. После его активации в течение времени указанного в timerrespawn метеориты не будут появляться.
}
if (trigtime==true)//Проверка переключателя
{
timer = timer-Time.deltaTime;//Отсчет времени для генерации слудующего метеорита.
}
if (timer < 0)//Если время периода истекает то тогда:
{
timer = timerespawn;//1)Сброс времени на число записанное в timerespawn
trigtime = false;//2)Блокирование отсчета времени
//Здесь происходит возврат значений на "по умолчанию". При их сбросе снова будет происходить генерация метеоритов и отсчет времени. И так по кругу
}
}
}
Выше можно заметить, то что метеориты которые появляются на сцене разного размера и еще они вращаются. Это все потому что метеорит реализован с помощью двух GameObject, где один находится внутри другого (матрешка).
Внешний объект «asteroidrotate» описывает движение метеорита, содержит circle collider и запускает эффект взрыва, в случае столкновения, а «aster» рандомно при своем создании задает скорость направления вращения и размер.
Скрипт для asteroidrotate:
using UnityEngine;
using System.Collections;
public class asteroidlogic : MonoBehaviour {
public GameObject explosion;//Здесь нужно добавить префаб взрыва
float speedasteroid=-0.1f;//Скорость движения метеорита
void Start ()
{
}
void Update ()
{
transform.Translate (new Vector3 (0, speedasteroid, 0f));//Движение метеорита вниз по экрану с заранее указанной скоростью
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.tag == "systemcontrol")
{
return;//Возврат необходим чтобы избежать конфликта с объектом controlcountobjects
}
if (col.tag == "laser") //Проверка на попадание выстрелов игрока в метеорит
{
Destroy (col.gameObject);//Удаляем сам выстрел
GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().score++;//Добавляем единицу к общему числу подбитых метеоритов
Instantiate(explosion, transform.position,transform.rotation);//Генерируем взрыв из префабов
Destroy (this.gameObject);//Удаление метеорита
}
}
}
Скрипт для aster:
using UnityEngine;
using System.Collections;
public class rotator : MonoBehaviour {
int f;
float sc;
void Start ()
{
f = Random.Range (-5, 5);
sc = Random.Range (0.1f,0.2f);
transform.localScale = new Vector3 (sc,sc,sc);//размер
}
void Update ()
{
transform.rotation *= Quaternion.AngleAxis (f,new Vector3(0,0,1));//вращение
}
}
Кидаем спрайт с звездолетом на игровую сцену, добавляем на него box collider(не забываем отмечать «Is Triger»), добавляем rigibody(нужно заморозить Y координату). Все это необходимо чтобы игрок мог взаимодействовать с другими игровыми объектами.
Можно еще добавить компонент particle system, который будет изображать струю из двигателя.
В скрипте для игрока происходит установление «связи» с такими префабами как explosionplayer, laser, laser3x, laser3xhor и т.п. а также организация управления и подбор «капсул» для активации других видов оружия. Управление устроено так что игровой объект просто движется за пальцем пользователя и попутно ведет огонь.
Содержимое скрипта:
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class carconroller : MonoBehaviour
{
public GameObject laser;//Эта переменная будет связана с префабом laser
public GameObject laser3x;//Эта переменная будет связана с префабом laser3x
public GameObject laserhor;//Эта переменная будет связана с префабом laserhor
public GameObject laser3xhor;//Эта переменная будет связана с префабом laser3xhor
public GameObject sphere;//Эта переменная будет связана с префабом sphere
public GameObject sphere3x;//Эта переменная будет связана с префабом sphere3x
public GameObject explosionplayer;//Эта переменная будет связана с префабом explosionplayer
float x,y,z,x1,y1;
bool trigtime=false;
public float speedreset=0.25f;//период перезарядки орудия в миллисекундах
float timer;
Vector2 startpos;
Vector2 startcar;
//объекты laser...sphere3x являются оружием а ниже булевы переменные их соответствующие активаторы
public bool gun1 = true;//По умолчанию активировано 1 оружие(laser), оно является базовым и имеет неограниченный боезапас, в отличие от других видов(laser3x...)
public bool gun2 = false;
public bool gun3 = false;
public bool gun4 = false;
public bool gun5 = false;
public bool gun6 = false;
//При активации оружия отличного от базового боезапас будет заканчиваться. Когда боезапас закончится будет автоматически активировано базовое оружие.
int guncount=0;//Количество боезапаса для оружия, которое отличается от базового. Для каждого из дополнительного вида оружия устанавливается свое число боезапаса.
void Start ()
{
timer = speedreset;
y = laser.transform.position.y;
z = laser.transform.position.z;
}
public void Update ()
{
if (timer < 0)
{
timer = speedreset;
trigtime = false;
}
if (Input.GetMouseButton(0))//Отслеживание нажатия на экран
{
Vector2 pos = Camera.main.ScreenToWorldPoint (Input.mousePosition);//Запись в переменную pos координат места, где произошло касание экрана.
transform.position = pos;//присвоение позиции игровому объекту координат из переменной pos
transform.position = new Vector2 (transform.position.x,transform.position.y+1f);//корректировка координат игрока(необязательно)
if (timer==speedreset)//проверка истечения времени(время перезарядки)
{
if (gun1 == true)//проверка базового орудия
{
Instantiate (laser, new Vector2(transform.position.x,transform.position.y+1.1f), transform.rotation);//Генерация выстрелов из префаба laser в месте текущей позиции игрока(с некоторыми поправками)
trigtime = true;
}
if (gun2 == true && guncount > 0)//В случае активации 2-го орудия выстрелы будут генерироваться по данному коду
{
guncount--;//Снижение числа боеприпасов
if (guncount == 0) //В случае если боеприпасы закончатся то активируем 1 орудие а остальные блокируем
{
gun1 = true;
gun2 = false;
gun3 = false;
gun4 = false;
gun5 = false;
gun6 = false;
}
Instantiate (laser3x, new Vector2(transform.position.x,transform.position.y+1.1f), transform.rotation);//Генерация выстрелов из префаба laser3x
trigtime = true;
}
//Ниже для остальных видов "вооружения" все происходит аналогично
if (gun3 == true && guncount > 0)
{
guncount--;
if (guncount == 0)
{
gun1 = true;
gun2 = false;
gun3 = false;
gun4 = false;
gun5 = false;
gun6 = false;
}
Instantiate (laserhor, new Vector2(transform.position.x,transform.position.y+1.1f), transform.rotation);
trigtime = true;
}
if (gun4 == true && guncount > 0)
{
guncount--;
if (guncount == 0)
{
gun1 = true;
gun2 = false;
gun3 = false;
gun4 = false;
gun5 = false;
gun6 = false;
}
Instantiate (laser3xhor, new Vector2(transform.position.x,transform.position.y+2f), transform.rotation);
trigtime = true;
}
if (gun5 == true && guncount > 0)
{
guncount--;
if (guncount == 0)
{
gun1 = true;
gun2 = false;
gun3 = false;
gun4 = false;
gun5 = false;
gun6 = false;
}
Instantiate (sphere, new Vector2(transform.position.x,transform.position.y+1.1f), transform.rotation);
trigtime = true;
}
if (gun6 == true && guncount > 0)
{
guncount--;
if (guncount == 0)
{
gun1 = true;
gun2 = false;
gun3 = false;
gun4 = false;
gun5 = false;
gun6 = false;
}
Instantiate (sphere3x, new Vector2(transform.position.x,transform.position.y+2f), transform.rotation);
trigtime = true;
}
}
if (trigtime == true)
{
timer = timer - Time.deltaTime;//Отсчет времени для перезарядки
}
}
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.tag == "systemcontrol")
{
return;//Возврат необходим чтобы избежать конфликта с объектом controlcountobject
}
if (col.tag == "gunactivator2")//проверка на пересечение с "капсулой" для gunactivator2
{
gun2 = true;//активация второго орудия
guncount = 85;//установка количества боеприпасов
gun1 = false;//отключение остальных "орудий"
gun3 = false;
gun4 = false;
gun5 = false;
gun6 = false;
Destroy (col.gameObject);//уничтожение "капсулы" с которой произошло пересечение
return;
}
//Ниже все аналогично
if (col.tag == "gunactivator3")
{
gun3 = true; guncount = 50;
gun1 = false;
gun2 = false;
gun4 = false;
gun5 = false;
gun6 = false;
Destroy (col.gameObject);
return;
}
if (col.tag == "gunactivator4")
{
gun4 = true; guncount = 15;
gun1 = false;
gun3 = false;
gun2 = false;
gun5 = false;
gun6 = false;
Destroy (col.gameObject);
return;
}
if (col.tag == "gunactivator5")
{
gun5 = true; guncount = 100;
gun1 = false;
gun3 = false;
gun4 = false;
gun2 = false;
gun6 = false;
Destroy (col.gameObject);
return;
}
if (col.tag == "gunactivator6")
{
gun6 = true; guncount = 50;
gun1 = false;
gun3 = false;
gun4 = false;
gun5 = false;
gun2 = false;
Destroy (col.gameObject);
return;
}
//Если столкновение с "капсулами" не произошло то это значит что игрок столкнулся с лазером противника, метеоритом или с вражеским звездолетом. Значит нужно удалить игрока со сцены
Handheld.Vibrate ();//активация вибрации(для смартфонов)
if (GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().score>GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().data)//Если число сбитых метеоритов в текущей игровой сессии выше чем в файле с игровым рекордом, то делаем запись нового рекорда в файл
{
StreamWriter scoredata=new StreamWriter(Application.persistentDataPath + "/score1.gd");
scoredata.WriteLine(GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().score);
scoredata.Close();
}
Instantiate (explosionplayer, transform.position, transform.rotation);//Генерация взрыва звездолета игрока
Destroy (col.gameObject);//Удаление объекта с которым произошло пересечение
Destroy(this.gameObject);//Удаление игрока с игровой сцены
}
}
Активаторы оружия.
Результат работы скрипта.
Когда игрок будет удален а в файл будет записан новый рекорд уничтоженных метеоритов, то тогда нужно переходить на главное меню.
Создаем скрипт со следующим содержимым и подключаем его на камеру:
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class Exit : MonoBehaviour {
public GameObject target;//Добавляем сюда звездолет игрока
float timer=3f;
void Start ()
{
}
void Update ()
{
if (Input.GetKey (KeyCode.Escape))//Если будет нажата кнопка назад во время игры, то:
{
if (GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().score>GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().data)
{
StreamWriter scoredata=new StreamWriter(Application.persistentDataPath + "/score1.gd");
scoredata.WriteLine(GameObject.Find ("Main Camera").GetComponent<blockgenerator> ().score);
//Запись в файл рекорда переменную score из "blockgenerator", если она больше нее
scoredata.Close();
}
SceneManager.LoadScene (0);//Загрузка главного меню
}
if (!GameObject.Find ("playercar"))//Если игрок был удален то по истечению времени(в секундах) указанного в timer будет открыто главное меню
{
timer = timer - Time.deltaTime;
if (timer < 0)
{
SceneManager.LoadScene (0);
}
}
}
}
Данный скрипт наблюдает за состоянием игрока.
Эффект взрыва можно создать с помощью Particle System. Но тогда он будет зацикленным и вечно повторяться. Чтобы этого не было, на префаб взрыва нужно добавить следующий скрипт.
using UnityEngine;
using System.Collections;
public class DestroyAsteroid : MonoBehaviour {
void Start ()
{
}
void Update ()
{
Destroy (this.gameObject,0.4f);//Удаление игрового объекта после его создания. Время жизни 400 миллисекунд. Значение можно изменить.
}
}
Выстрелы генерируются из места нахождения игрока во время его движения.
В скрипте для выстрела нужно задать только скорость и направление.
using UnityEngine;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class laser : MonoBehaviour {
float speedlaser=0.5f;
void Start ()
{
}
void Update ()
{
transform.Translate (new Vector3 (0, speedlaser, 0f));
}
}
Генерация противников устроена аналогично генерации метеоритов.
Содержимое скрипта Enemygenerator:
using UnityEngine;
using System.Collections;
public class enemygenerator : MonoBehaviour {
public GameObject enemy;
bool trigtime=false;
float speedreset=2f;
float timer,x;
void Start ()
{
timer = speedreset;
}
void Update ()
{
if (timer < 0)
{
timer = speedreset;
trigtime = false;
}
if (timer == speedreset)
{
x = Random.Range (-2.5f, 2.5f);//Задаем местоположение
Instantiate(enemy, new Vector2(x,5.5f),transform.rotation);//Генерация противника из префаба
trigtime = true;
}
if (trigtime == true)
{
timer = timer - Time.deltaTime;
}
}
}
Поведение противника организовано таким образом, что он просто летит по прямой, ведет огонь и в случае своего поражения может оставить капсулу для активации игроком нового оружия
Выглядит это вот так:
using UnityEngine;
using System.Collections;
public class enemylogic : MonoBehaviour {
public GameObject explosionenemy;//Префаб вызрыва
public GameObject laserenemy;//Префаб выстрела
//Префабы капсул для активации других видов оружия
public GameObject gunactivator2;
public GameObject gunactivator3;
public GameObject gunactivator4;
public GameObject gunactivator5;
public GameObject gunactivator6;
//
bool trigtime=false;
float speedreset=1.5f;//Время перезарядки
float timer;
float speedenemy = -0.02f;//Скорость и направление
float x;
void Start ()
{
timer = speedreset;
}
void Update ()
{
if (timer < 0)
{
timer = speedreset;
trigtime = false;
}
if (timer == speedreset)
{
Instantiate (laserenemy, new Vector2(transform.position.x,transform.position.y-0.4f), transform.rotation);
trigtime = true;
}
if (trigtime == true)
{
timer = timer - Time.deltaTime;
}
transform.Translate (new Vector3 (0, speedenemy, 0f));
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.tag == "systemcontrol")
{
return;
}
if (col.tag == "Player")
{
Instantiate (explosionenemy, transform.position, transform.rotation);
Destroy(this.gameObject);
}
if (col.tag == "laser")
{
x = Random.Range (0f, 100f);//Генерируется любое число от 0 до 100
if (x > 1f && x < 5f) //если число в диапазоне от 1 до 5 то создается капсула gunactivator2
{
Instantiate (gunactivator2, transform.position, transform.rotation);
}
//Внизу все аналогично
if (x > 20f && x < 25f)
{
Instantiate (gunactivator3, transform.position, transform.rotation);
}
if (x > 40f && x < 45f)
{
Instantiate (gunactivator4, transform.position, transform.rotation);
}
if (x > 60f && x < 65f)
{
Instantiate (gunactivator5, transform.position, transform.rotation);
}
if (x > 80f && x < 85f)
{
Instantiate (gunactivator6, transform.position, transform.rotation);
}
Instantiate (explosionenemy, transform.position, transform.rotation);
Destroy(this.gameObject);
}
}
}
Каждый префаб «капсулы» с оружием просто движется по направлению к игроку и в случае пересечения уничтожается.
using UnityEngine;
using System.Collections;
public class gunactivatorlogic : MonoBehaviour {
float speed = -0.025f;
void Start ()
{
}
void Update ()
{
transform.Translate (new Vector3 (0, speed, 0f));
}
}
Выход игры [1] на google play не увенчался особым успехом. За все время пребывания в маркете набралось чуть больше 500 загрузок.
Автор: WRXM4STER
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/250011
Ссылки в тексте:
[1] игры: https://play.google.com/store/apps/details?id=com.WRXM4STER.SpaceStriker
[2] Источник: https://habrahabr.ru/post/324288/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.