Добрый день. Сегодня я хочу рассказать о разработке игры с использованием языка программирования Kotlin. Также приведу небольшой пример работы с RxJava в конце статьи.
Первый commit для этой игры случился 4 июня сего года, то есть до открытой беты я дошёл примерно за 3 с половиной 4 недели. Нельзя сказать что это первая игра или программа под Android которую я разрабатываю, но и опытным разработчикам под Андроид я также не являюсь. Мой предыдущий опыт в основном составляла Enterprise разработка.
Я хочу обозначить несколько тем в этой статье и пробежаться по ним коротенечко. Тему Kotlin’a постараюсь раскрыть подробно, по остальным возможны дополнительные статьи если будет такой запрос от вас (ну и плюс уточняющие вопросы в комментариях помогут улучшить подачу материала). Итак к списку: это сравнение стоимости разработка на Kotlin vs Java, где брать графику для вашей игры. Немного про деньги (пока про затраты, т.к. статистики по доходам пока нет). Также я считаю очень важно коснуться мотивировочной части. Начнем пожалуй с конца.
Если вы изначально рассматриваете монетизацию как один из своих интересов, еще до самого старта работ вы должны понимать на чём вы будете зарабатывать деньги. Самое простое и самое неэффективное это рекламные баннеры. Чуть сложнее или примерно так же просто — показ рекламы на весь экран. Далее идет магазин. Но для того чтобы магазин заработал, вам необходимо зацепить пользователя чем-то. Чем раньше вы получите просмотр рекламы, тем больше это будет подпитывать вашу мотивацию продолжать работать дальше.
Существует значительное количество стоков ресурсов где графика продается, существуют ресурсы с бесплатными картинками. И в принципе да, действительно, так оно и есть. Есть только одна проблема — эти ресурсы выполнены в разных художественном стиле и комбинация из них даёт крайне странные результаты. Поэтому если вы разрабатываете свою первую игру, мой вам настоятельный совет — не ищите графику под свои задумки. Ищите готовые наборы графики под игру, и исходя из графики решайте что вы можете реализовать. Такие наборы есть, их не так чтобы много, но по крайней мере для первого второго раза вам хватит.
В моем же случае получилось что я потратил 2 недели на разработку, купил какие-то ресурсы в маркетах, но естественно их не хватило для того чтобы реализовать мою задумку. Скажу честно — купленная графика составляла менее чем 30% от необходимого. Но ведь нет нерешаемых вопросов, можно найти исполнителей которые нарисуют то, что вам нужно. И здесь мы с удивлением узнаём, что это не так просто. Особенно если нас интересует качественный результат. Что делал я — я просматривал стоки, находил интересные в плане графики экземпляры, переходил на описание автора и пробовал связаться.
Уже на этом этапе у вас должен быть перечень элементов которые вы хотите заказать и уже на этом этапе скорее всего ваши розовые слоники приопустят хоботы. Но пока это только предчувствие. Вы отправляете письмо с вопросом “можно ли обратиться для заказа графики” и в зависимости от профессионализма исполнителя (а те люди с которыми я обращался были профессионалами за исключением одного) вы получите сразу ответ что “Да можно, часовая ставка 25$/час”, ваш запрос будет выполнен за 30-40 часов не включая доработки по замечаниям (свободное время для заказа будет через полтора месяца). Или “Могу сделать за месяц-полтора, будет стоить 50 тысяч рублей”. Либо “Нет, у меня я слишком много работы и я дополнительные заказ не беру”. Это всё профессиональный варианты ответа. А самое неудачное что со мной случилось это следующее:
— А сколько вы готовы заплатить? (То есть человек не оценивает ресурсоемкость или сложность поставленной задачи, а пытается адаптироваться под платежеспособность заказчика)
— от 10000 до 20000 руб (Это был первый художник с которым я связался и в тот момент я считал что данная сумма Вполне себе подходит для небольшой, как я считал, работы)
Человек большого интереса не проявил, на вроде как согласился. Сам назначил срок когда даст описание что он за эту сумму может мне предоставить… и пропал. Это даже не звоночек это колокол что дальнейшая работа с подобным человеком для вас закончится неудачей. Тем не менее через 3 дня после озвученного им же срока, он связался со мной и сказал что сделает всё что мне надо за 15000 рублей. Я очень настороженно отношусь к обобщениям, поэтому попросил перечислить что же будет входить в это “всё” по пунктам. После чего человек пропадает с концами и я считаю что это счастливый конец истории.
Kotlin
Разработка личного проекта существенно отличается от рабочего. Различий несколько, но я остановлюсь на самом (для меня) существенном. Это отношение к стоимости разработки. В подавляющем большинстве случаев работодатель определяет требования, часто инструмент. Он же несет финансовую ответственность. Есть тестирование или нет, какие библиотеки и какой стек технологий. В личном проекте все права и все обязанности — на вас лично. Хотите использовать новый фреймворк — ради бога. Но время которое вы потратите на его освоение вы будете “оплачивать” из своего кармана. И да, не стоит думать что вы работаете бесплатно. Выкиньте из головы эту мысль, напишите на стикере вашу ставку в час и крайне желательно чтобы она соответствовала хотя бы средней стоимости специалиста вашей квалификации по городу. Не надо занижать себе цену.
Таким образом вопрос эффективности вашей работы встает на первый план. А сделал ли я что-то полезное за эти 8 часов, за что в ином случае я получил бы (к примеру) 4000 рублей? То есть вопрос Kotlin vs Java я предлагаю решать исключительно с финансовой точки зрения. Оба языка тьюринг-полные и значит любую программу написанную на Kotlin можно реализовать на Java и наоборот. За счет чего же мы можем получить разницу в стоимости разработки/стоимости владения продуктом?
Стоимость разработки — это количество денег для реализации функциональности. Стоимость владения = стоимость разработки + стоимость поддержки. В некоторых случая стоимость поддержки несоизмеримо выше стоимости разработки. Это особенно характерно для write-only языков (пример RegExp. Гораздо проще написать новое выражение чем понять где ошибка в существующем).
В идеале язык должен быть дешевым и в разработке, и в поддержке. Сокращение boilerplate code однозначно удешевляет разработку. Синтаксический сахар удешевляет разработку, но может (подчеркну может, но не обязан) приводить к увеличению стоимости владения. Синтаксическая соль удорожает разработку но удешевляет стоимость владения. Ниже я приведу примеры кода на Kotlin и Java и опишу какой вариант на мой взгляд дешевле и почему. Часть примеров будет из моего проекта, часть нет.
class Car(val id: String) {
var speed: Double = 0.0
}
public class Car() {
public final String id;
public Double speed;
public Car(String id) {
this.id = id;
this.speed = 0.0;
}
}
* Для DTO классов нет необходимости в геттерах/сеттерах
** Геттеры/сеттеры имеют смысл только и только в том случае, если они меняют поведение при работе с полями
В данном сравнении мы видим что код на Kotlin более читаемый и самое главное он защищен от “ошибки на миллиард” — NPE. И id, и speed в Kotlin не могут быть null.
var car: Car = Car(null) // compile error
car.speed = null // compile error
Второй, не менее важный момент в приведенном выше примере — это мутабельность.
fun toRoman(value: Int): String {
val singles = arrayOf("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX")
val tens = arrayOf("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC")
val hundreds = arrayOf("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM")
val thousands = arrayOf("", "M", "MM", "MMM")
val roman = thousands[value / 1000] + hundreds[value % 1000 / 100] + tens[value % 100 / 10] + singles[value % 10]
return roman
}
public String toRoman(int value) {
final String[] singles = new String[] { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
final String[] tens = new String[] { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
final String[] hundreds = new String[] { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
final String[] thousands = new String[] { "", "M", "MM", "MMM" };
final String roman = thousands[value / 1000] + hundreds[value % 1000 / 100] + tens[value % 100 / 10] + singles[value % 10];
return roman;
}
Не так много разницы, не правда ли? Тем не менее модификаторы final смотрятся крайне неуместно и визуально засоряют программу. Это приводит к тому, что final используется в Java гораздо реже чем следовало бы. А ведь иммьютбл гораздо дешевле и в разработке и во владении.
А вот пример трансформации данных:
val data:List<Int> = ArrayList()
val sum = data.filter { it > 0 }.sum()
List<Integer> data = new ArrayList<>();
final Integer[] sum = { 0 }; //Variable used in lambda expression should be final or effectively final
data.stream().filter(value -> value > 0).forEach(value -> sum[0] += value);
Тут два момента. Kotlin выполняется на jvm 6, Java требует jvm 8 для стримов. А для андроида, это на минуточку 24 API. На 5 июня это всего 9,5% устройств. И второй момент — final or effectively final переменные в лямбдах Java.
Инициализация объекта
private val buttonGroup = ButtonGroup<Button>().apply {
setMinCheckCount(0)
}
private ButtonGroup<Button> buttonGroup = new ButtonGroup<>();
…
public constructor(...) {
…
buttonGroup.setMinCheckCount(0);
…
}
Инициализация объекта в Kotlin’e возможна в одном месте что сокращает контекст для программиста и снижает стоимость вложения
В принципе, из того что я попробовал в Kotlin это самые серьезные вещи, влияющие на стоимость владения продуктом. Я сейчас не хочу говорить про вкусовщину типа data classes, перегрузку операторов, string template, lazy properties и т.п. Все это вещи интересные но они могут как сокращать, так и увеличивать стоимость владения.
В заключение небольшой пример Kotlin + RxJava + Jackson. Я хочу иметь dto класс, который позволит не просто хранить данные, но и уведомлять об их изменениях. Пример упрощенный для более наглядной демонстрации.
interface Property<T> {
var value: T
val rx: Observable<T>
}
open class BaseProperty<T>(defaultValue: T) : Property<T> {
override var value: T = defaultValue
set(value) {
field = value
_rx.onNext(field)
}
private val _rx = PublishSubject.create<T>()
override val rx: Observable<T> = _rx
get() {
return field.startWith(value)
}
}
Здесь хочу обратить внимание на перегрузку val rx. При подписке на Observable сразу же приходит текущее значение. Это важно т.к. десериализация из json'a случается раньше чем верстка экрана и подвязывание графических элементов к свойству. А при startWith мы сразу инициализируем графический элемент текущим значением и меняем по ходу пьесы.
class Car {
private val _speed = BaseProperty(0.0)
var speed: Double
get() = _speed.value
set(value) {
_speed.value = value
}
@JsonIgnore
val rxSpeed: Observable<Double> = _speed.rx
}
class Police {
val cars: List<Car> = listOf(Car("1st"), Car("2nd"), Car("3rd"))
init {
cars.forEach {
car -> car.rxSpeed
.map { speed -> speed > 60 } // преобразует double скорость в boolean скоростьПревышена
.distinctUntilChanged()
.filter { aboveLimit -> aboveLimit == true }
.subscribe { writeTicket(car) }
}
}
private fun writeTicket(car: Car) {
// do some stuff
}
}
Класс Car прекрасно сериализуется/десериализуется Jackson'ом, может быть использован как классический dto класс, но в то же время позволяет обрабатывать изменения свойств в реактив стиле.
Ниже пример подвязки Label к свойству объекта:
Label("", assets.skin, "progress-bar-time-indicator").apply {
setAlignment(Align.center)
craft.rx(DURATION).subscribe {
setText(TimeFormat.format(it))
}
})
Заключение:
К сожалению я не могу представить объективных цифр насколько стоимость владения продуктом на Kotlin'e дешевле Java. Да и само это утверждения я уверен будет не раз оспорено в комментариях. Могу сказать только по своему субъективному суждению, цифра в 1.5 — 2 раза для меня реальна. Причем сокращение стоимости владения в полтора раза характерно для начала перехода с Java на Kotlin, примерно через неделю-две я думаю на двойную эффективность вышел. В основном за счет NPE-proof, immutable, lambda & функции высшего порядка.
Автор: TerraV