Введение




Прежде всего, хочу сразу отметить, что я не являюсь профессиональным разработчиком. В этой статье я постараюсь изложить свой опыт создания игры «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)


  1. Suvitruf
    10.05.2016 09:33
    +5

    Вместо обращения к файлу для чтения информации о рекорде, храните уж эти данные в PlayerPrefs.

    Ну и да, уже Unity3d 5.4 скоро, а вы всё ещё используете OnGUI. И не надо говорить, что «это только для примера».


  1. alex8turantsev
    10.05.2016 11:22

    Мой перфекционист внутри захотел отформатировать код) И конечно же с новым гуи мне кажется проще бы было.


  1. BaDP1nG
    10.05.2016 12:17
    +2

    Простите, но использовать UNITY для этого? Серьезно?! Теперь я понимаю, почему встречается даже пиксельное 2d инди, которое умудряется тормозить на i7. :-)


    1. HexGrimm
      10.05.2016 15:47

      Вот так и поддерживаются стереотипы о производительности движков. Забавно такое видеть особенно в комментариях в стиме.
      Ведь дело только лишь в том как готовить этот движок, и очевидно что криво написанных игр на юнити гораздо больше чем на многих других движках, так как налепить чего нибудь работающего легче. И UnityTechnologies винить тут совершенно не в чем (если не считать узко технических вопросов).


      1. BaDP1nG
        10.05.2016 17:31

        Товарищ, так я именно на то и намекаю. Каждому движку — свое место. Согласитесь. немного странно, когда за поделкой, визуально выглядящей на «пару мегабайт» тянется движок, утяжеляющей игру на несколько порядков с соответствующим ростом требований к железу. Утрирую… хотя, нет, не утрирую, а встречается.


        1. HexGrimm
          10.05.2016 17:50

          Движок совершенно не тянется. Во всех технологиях такого рода есть stripping, в билд попадает то что используется. И железо тоже совершенно не причем (если не задействованы новейшие технологии, от графического апи например). Если вы говорите про PC платформу, то тем более. Вы в комментарии привели логическую связь между движком UNITY и приложением, которое тормозит. Я считаю что связи такой непосредственно нет. И на UNITY и на UE есть отличные микро-проекты для самых слабых платформ.


          1. BaDP1nG
            10.05.2016 17:58

            Можно примерчики современных «отличных микро-проектов», если Вас не затруднит? Хоть запущу, да прикину, сколько бы примерно ело бы нативное приложение или на том же SpiteKit, раз Вы утверждаете, что разницы нет. ИМХО, автоматический стриппинг в Юнити не вызывает особого доверия, в UE4 пусть и ручками поработать приходится, но результат выходил получше. Кстати, а что в Юнити сейчас с IL2CPP? (ну, я так понимаю, Вы тему под iOS затронули же?)


  1. kaftanati
    10.05.2016 12:18

    Как первый опыт — замечательно (простота и законченность, наличие играбельного результата).

    Но для Google Play все же рановато — игра просто там потеряется. Не хватает ни графики, ни геймплея, ни «изюминки», в конце концов.
    Если нет идей по ее развитию — зачем сразу в маркет, если есть, то зачем терять эффект новизны и подготовленного запуска?


  1. Dimonkov
    10.05.2016 18:24

    Ни в коем случае не критикую автора, всё приятно расписано и картинок много.
    Я тоже экспертом разработки на Unity не являюсь
    (In fact являюсь 17-ти летним подростком =) ), но вот скрипт moveroad меня смутил. Update здоровый, и каждый фрейм в файл писать результат, это как-то диковато. Ну и да, выше уже про player prefs сказали.


  1. antrash
    10.05.2016 18:24
    +2

    Hе надо делать scoredata.WriteLine(score); в Update, нужно создать там event а потом в конце хранить новый рекорд. Так у вас каждый кадр будет запись в файл после первого же (score>data == true), а это крайне затратно.


  1. bazuka5801
    10.05.2016 18:25

    The 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.


  1. IlyasSalihov
    11.05.2016 11:30

    19 метров, Карл!


  1. Andrew51130
    11.05.2016 11:32
    +3

    «В момент выпуска игры на Google Play я особо не занимался ее продвижением ну и само собой закачек не набралось.»
    Вы. Это. Серьезно?


  1. inborn_killer
    11.05.2016 13:56
    -1

    Астрологи объявили месяц бесполезных туториалов по Unity на Хабре.


  1. yara_73
    11.05.2016 16:53

    Советую почитать о паттернах проектирования, для улучшения кода, разобраться с ивентами и посмотреть в сторону MVC/MVVM для упрощения взаимодействия игровой-логики и движка. ИМХО может упростить разработку и поднять скилл.