Введение
Прежде всего, хочу сразу отметить, что я не являюсь профессиональным разработчиком. В этой статье я постараюсь изложить свой опыт создания игры «Feel Speed Racing». Данный материал, скорее всего не будет интересен тем, кто уже имеет большой опыт в разработке игр, но начинающим разработчикам, которые хоть немного работали с Unity думаю, будет интересно.
Дизайн
Концепция игры заключается в том, что автомобиль должен пройти, как можно большее расстояние при этом на дороге динамически появляются препятствия их надо объезжать мимо иначе «игра окончена» еще нужно следить за шкалой топлива и собирать на дороге топливные баки, по истечению которой игра тоже останавливается.
Разработка
Игра состоит из 2-х сцен: главное меню и сама игровая сцена:
Где «menu» это главное меню а «1» это игровая сцена.
Главное меню
Для создания столь незамысловатого меню нам потребуется элемент управления GUI, который является стандартным в Unity.
В качестве фона я использовал спрайт с именем «background» заполненный серым цветом. Вы же можете выбрать что угодно.
Далее создаем скрипт «menu.cs»(Щелкаем правой кнопкой->выбираем Create-> C# Script) и вешаем его на background.
Содержимое скрипта:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class menu : MonoBehaviour {
public GUIStyle mystyle; //объявляется для того чтобы изменять начертание GUI компонентов(шрифт, размер и.т.п.)
string score; //переменная для хранения пройденной дистанции
void Start ()
{
StreamReader scoredata = new StreamReader (Application.persistentDataPath + "/score.gd"); //создание файловой переменной
score = scoredata.ReadLine (); //чтение строки
scoredata.Close (); //закрытие файловой переменной
}
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 DISTANCE:"+score,mystyle); //создаем небольшое окошко для показа пройденного расстояния
if (GUI.Button (new Rect (Screen.width*0.15f, Screen.height*0.25f, Screen.width*0.7f, Screen.height*0.1f), "Start game",mystyle)) //создаем кнопку для запуска игровой сцены
{
Application.LoadLevel(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();//Выход из игры
}
}
}
В результате должно получиться примерно вот так:
Шрифт, цвет и размер GUI элементов вы можете изменить с помощью MyStyle.
Создание игровой сцены
Основными на этой сцене элементами является дорога, автомобиль и шкала топлива.
1. Дорога:
Ввиду того что гонка является бесконечной и останавливается только когда машина попадет в препятствие или закончится бензин, дорога является двигающейся. То есть автомобиль может перемещаться влево или вправо, а иллюзию движения по прямой создает дорога.
Кидаем спрайт с дорогой на игровую сцену и подгоняем по размерам камеры.
Затем добавляем как дочерние объекты внутрь дороги 4 блока с препятствиями, топливный бак и не забываем добавить к ним Box Collider 2D.Еще надо отметить Is Triger для пересечения с автомобилем.
Теперь создаем скрипт moveroad.cs и вешаем его на нашу дорогу.
Добавляем в него следующий код:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class moveroad : MonoBehaviour {
public GUIStyle mystyle;//создание стиля
int f,fuelst;
float score=0,speed=-0.2f,data,fuelpos;// переменные для хранения расстояния, скорости и рекорда
public GameObject block;// игровой объект для размещения блока
public GameObject block1;
public GameObject block2;
public GameObject block3;
public GameObject fuel;
bool turbotriger=false;
void Start ()
{
StreamReader scoredata = new StreamReader (Application.persistentDataPath + "/score.gd");
data = float.Parse(scoredata.ReadLine ());//чтение с файла информации о рекорде
scoredata.Close ();
}
void Update ()
{
transform.Translate (new Vector3 (0f,speed,0f));//движение дороги с заданной выше скоростью
score = score + (speed*-10);// подсчет расстояния
if (transform.position.y < -19f) // если дорога уходит за пределы камеры то она "теле портируется" вверх
{
transform.position=new Vector3(0f,33.4f,0f);//новая позиция дороги
block.transform.position=new Vector3(10.15f,block.transform.position.y,block.transform.position.z);
block1.transform.position=new Vector3(8.42f,block1.transform.position.y,block1.transform.position.z);
block2.transform.position=new Vector3(6.62f,block2.transform.position.y,block2.transform.position.z);
block3.transform.position=new Vector3(4.95f,block3.transform.position.y,block3.transform.position.z);
fuel.transform.position=new Vector3(11.86f,fuel.transform.position.y,fuel.transform.position.z);
//скрытие за пределы камеры всех препятствий(блоков)
f = Random.Range (0, 5);//случайное появление на дороге 1-го из 4-х блоков или канистры с бензином
switch (f)
{
case 0:block.transform.position=new Vector3(2.40f,block.transform.position.y,block.transform.position.z); break;
case 1:block1.transform.position=new Vector3(0.90f,block1.transform.position.y,block1.transform.position.z); break;
case 2:block2.transform.position=new Vector3(-0.80f,block2.transform.position.y,block2.transform.position.z); break;
case 3:block3.transform.position=new Vector3(-2.35f,block3.transform.position.y,block3.transform.position.z); break;
case 4:
fuelst=Random.Range(0,4);
if(fuelst==0){fuelpos=2.40f;}
if(fuelst==1){fuelpos=0.90f;}
if(fuelst==2){fuelpos=-0.80f;}
if(fuelst==3){fuelpos=-2.35f;}
fuel.transform.position=new Vector3(fuelpos,fuel.transform.position.y,fuel.transform.position.z);
break;
}
if (score>data)// если текущее пройденное расстояние превышает то что записано в файле рекорда то идет обновление данных
{
StreamWriter scoredata=new StreamWriter(Application.persistentDataPath + "/score.gd");//создаем файловую переменную для хранения пройденного расстояния
scoredata.WriteLine(score);//записываем новое значение в файл
scoredata.Close();//закрываем файловую переменную
}
}
}
void OnGUI(){
GUI.Box (new Rect (0, 0, Screen.width, Screen.height*0.05f), "Distance(m): " + score,mystyle);//создаем окно для подсчета расстояния
}
}
Должно получиться примерно вот так. Если все так оставить то после того как дорога пройдет до конца то будет оставаться пустое пространство и так будет по кругу, дорога будет исчезать.
Что бы решить эту проблему надо создать дубликат уже готовой дороги и немного изменить скрипт.
Должно получиться вот так.
2. Автомобиль:
Кидаем спрайт автомобиля на сцену и устанавливаем его в любое место на дороге. Затем создаем скрипт carcontroller.cs и вешаем его на автомобиль.
Содержимое carcontroller.cs:
using UnityEngine;
using System.Collections;
using UnityStandardAssets.CrossPlatformInput;
public class carconroller : MonoBehaviour
{
void Start ()
{
}
public void Update ()
{
if (transform.rotation.z !=0) //проверка столкновения коллайдера автомобиля и препятствия, при столкновении происходит загрузка меню
{
Application.LoadLevel (0);
}
}
}
public void OnGUI()
{
if (GUI.RepeatButton (new Rect (Screen.width*0.1f, Screen.height*0.9f, Screen.width*0.2f, Screen.height*0.08f), "L")) //создаем кнопку для движения влево
{
if (transform.position.x > -2.4f)
{
transform.Translate (new Vector3 (-0.05f, 0f, 0f));
}
}
if (GUI.RepeatButton (new Rect (Screen.width*0.7f, Screen.height*0.9f, Screen.width*0.2f, Screen.height*0.08f), "R")) //создаем кнопку для движения вправо
{
if (transform.position.x < 2.4f)
{
transform.Translate (new Vector3 (0.05f, 0f, 0f));
}
}
}
}
Теперь автомобиль может перемещаться.
3.Шкала топлива:
Для создания шкалы потребовалось 2 спрайта одинаковых размеров, но разных цветов (красный, зеленый). И сделать один из них дочерним (зеленый).
Далее создаем скрипт fuelscript.cs, вешаем его на fuel и добавляем в него код:
using UnityEngine;
using System.Collections;
public class fuelscript : MonoBehaviour {
public GameObject fuelall;
float mytimer=100f;// задание плавающего числа
// Use this for initialization
void Start ()
{
}
void Update ()
{
mytimer = 100f;
mytimer -= Time.deltaTime;//изменения числа с течением времени
if (mytimer/mytimer==1f) //проверка на период времени в 1 секунду
{
fuelall.transform.position=new Vector3(fuelall.transform.position.x-0.0011f,fuelall.transform.position.y,fuelall.transform.position.z);
fuelall.transform.localScale = new Vector3(fuelall.transform.localScale.x-0.001f, 1, 1);
//выше идет сдвижение влево и уменьшение по ширине зеленой полосы для имитации шкалы
}
if (fuelall.transform.localScale.x < 0) //если шкала исчезла то загрузка идет загрузка главного меню
{
Application.LoadLevel(0);
}
}
}
Дорога у меня это road183 и ее дубликат road183(1). В ее дочерний объект fueltrack нужно добавить скрипт для обнаружения пересечения с автомобилем и восполнения топлива.
Создаем скрипт triger.cs и вешаем его на fueltrack в обеих дорогах и отмечаем как Is Triger. Код:
using UnityEngine;
using System.Collections;
public class triger : MonoBehaviour {
public GameObject fuel;//добавляем сюда greenfuel
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.name == "playercar") //проверка пересечения автомобиля и объекта fuel
{
fuel.transform.position=new Vector3(0,fuel.transform.position.y,fuel.transform.position.z);
fuel.transform.localScale = new Vector3(1, 1, 1);
//восстановление у объекта fuel стандартных значений
}
}
}
Итог
В момент выпуска игры на Google Play я особо не занимался ее продвижением ну и само собой закачек не набралось.
В отсутствие профессионального художника, с иконкой пришлось работать самостоятельно:
Комментарии (15)
alex8turantsev
10.05.2016 11:22Мой перфекционист внутри захотел отформатировать код) И конечно же с новым гуи мне кажется проще бы было.
BaDP1nG
10.05.2016 12:17+2Простите, но использовать UNITY для этого? Серьезно?! Теперь я понимаю, почему встречается даже пиксельное 2d инди, которое умудряется тормозить на i7. :-)
HexGrimm
10.05.2016 15:47Вот так и поддерживаются стереотипы о производительности движков. Забавно такое видеть особенно в комментариях в стиме.
Ведь дело только лишь в том как готовить этот движок, и очевидно что криво написанных игр на юнити гораздо больше чем на многих других движках, так как налепить чего нибудь работающего легче. И UnityTechnologies винить тут совершенно не в чем (если не считать узко технических вопросов).BaDP1nG
10.05.2016 17:31Товарищ, так я именно на то и намекаю. Каждому движку — свое место. Согласитесь. немного странно, когда за поделкой, визуально выглядящей на «пару мегабайт» тянется движок, утяжеляющей игру на несколько порядков с соответствующим ростом требований к железу. Утрирую… хотя, нет, не утрирую, а встречается.
HexGrimm
10.05.2016 17:50Движок совершенно не тянется. Во всех технологиях такого рода есть stripping, в билд попадает то что используется. И железо тоже совершенно не причем (если не задействованы новейшие технологии, от графического апи например). Если вы говорите про PC платформу, то тем более. Вы в комментарии привели логическую связь между движком UNITY и приложением, которое тормозит. Я считаю что связи такой непосредственно нет. И на UNITY и на UE есть отличные микро-проекты для самых слабых платформ.
BaDP1nG
10.05.2016 17:58Можно примерчики современных «отличных микро-проектов», если Вас не затруднит? Хоть запущу, да прикину, сколько бы примерно ело бы нативное приложение или на том же SpiteKit, раз Вы утверждаете, что разницы нет. ИМХО, автоматический стриппинг в Юнити не вызывает особого доверия, в UE4 пусть и ручками поработать приходится, но результат выходил получше. Кстати, а что в Юнити сейчас с IL2CPP? (ну, я так понимаю, Вы тему под iOS затронули же?)
kaftanati
10.05.2016 12:18Как первый опыт — замечательно (простота и законченность, наличие играбельного результата).
Но для Google Play все же рановато — игра просто там потеряется. Не хватает ни графики, ни геймплея, ни «изюминки», в конце концов.
Если нет идей по ее развитию — зачем сразу в маркет, если есть, то зачем терять эффект новизны и подготовленного запуска?
Dimonkov
10.05.2016 18:24Ни в коем случае не критикую автора, всё приятно расписано и картинок много.
Я тоже экспертом разработки на Unity не являюсь
(In fact являюсь 17-ти летним подростком =) ), но вот скрипт moveroad меня смутил. Update здоровый, и каждый фрейм в файл писать результат, это как-то диковато. Ну и да, выше уже про player prefs сказали.
antrash
10.05.2016 18:24+2Hе надо делать scoredata.WriteLine(score); в Update, нужно создать там event а потом в конце хранить новый рекорд. Так у вас каждый кадр будет запись в файл после первого же (score>data == true), а это крайне затратно.
bazuka5801
10.05.2016 18:25The IMGUI system is not generally intended to be used for normal in-game user interfaces that players might use and interact with. For that you should use Unity’s main GameObject-based UI system, which offers a GameObject-based approach for editing and positioning UI elements, and has far better tools to work with the visual design and layout of the UI.
Andrew51130
11.05.2016 11:32+3«В момент выпуска игры на Google Play я особо не занимался ее продвижением ну и само собой закачек не набралось.»
Вы. Это. Серьезно?
yara_73
11.05.2016 16:53Советую почитать о паттернах проектирования, для улучшения кода, разобраться с ивентами и посмотреть в сторону MVC/MVVM для упрощения взаимодействия игровой-логики и движка. ИМХО может упростить разработку и поднять скилл.
Suvitruf
Вместо обращения к файлу для чтения информации о рекорде, храните уж эти данные в PlayerPrefs.
Ну и да, уже Unity3d 5.4 скоро, а вы всё ещё используете OnGUI. И не надо говорить, что «это только для примера».