Вступление. (это можно пропустить)
Путь до идеи полученной игры был очень долог. Всё началось с того, что я познал JQuery. Да. Это было золотое время. С помощью тупых анимаций я делал всякую хренотень вроде движения смайлика по полю в место клика мыши. Когда этот уровень достиг совершенства, мне захотелось добавить в эту «игру» онлайновости. И тут понеслась…
Первое что мне пришло в голову, это отправка каждые N секунд запроса на сервер, не сделал ли что-то второй игрок. Всё было сделано на PHP MySQL. Данные о месте и времени клика сохранялись в базе и выдавались игрокам по запросу. Исходников чуда к сожалению не осталось, ну и ладно.
Следующим жизненно важным этапом стала производительность. Вариант с постоянными запросами к серверу естественно отпал, и была необходимость искать другие решения. Я перебирал всякие Comet-ы, флэши… но у меня это всё не получалось даже просто поставить на сервер.
Это всё было для меня как открыть Америку, я не спал ночами и писал код, делал тесты… как вдруг заметил что моя игра застряла на месте. Немного поразмыслив я стал делать своё первое творение, похожее на игру. Опять же… весть клиент был на JQuery, те же анимации, те же движения смайликов.
Всё понемногу усовершенствовалось, добавился чат, несколько видов оружия (ага, смайлики стреляли). Большая карта была обычной картинкой размером 10к х 10к пикселей. Тогда же я узнал о Chrome Dev Tools. Увидев то, как моё детище жрёт память я стал снова углубляться в недры гугла.
Узнал о том что уже существует HTML5 со своим Canvas-ом. Это стало еще одним поводом порадоваться. Опять бессонные ночи изучения API (слово ночи во множественном числе потому что JS я в общем то знал крайне поверхностно, и вот пришло время заняться и им вплотную).
Ох уж это 3D. Тупые попытки создания в браузере 3D фигур в основном ни к чему не приводили. То есть их получалось создать на странице, даже двигаться между красными шарами и серыми кракозябрами, слепленными в блендере. На удивление, при написании статьи обнаружил что на серваке эта ночь под названием «жена в ночной смене» еще жива.
Ссылка для особо любопытных. — можно даже вращать мышкой и двигаться стрелками. TreeJS показался мне очень сложным и опять гугл пришёл мне на помощь. Там я узнал о Unity3D. Вообще у меня имеется дурацкая привычка делать тупые тесты производительности.
При первом запуске юнити я был удивлён на сколько удобно сделан интерфейс, и даже не имея навыков работы с такого рода программами я сумел сделать карту с горами, тропинками и водопадами. С водопадами как то не срослось, случайно добавил их на карту несколько десятков тысяч и при рендере у меня отказала видеокарта. Ну что поделать… сделал отличную кладку кирпичей.
Как создать игровое приложение в вк
Для добавления подобного контента в социальную сеть сделайте следующее:
- Для начала откройте страницу для разработчиков. Перейти на нее можно, авторизовавшись в сети и немного пролистав свою страницу так, чтобы блок с контекстной рекламой оказался наверху, тогда как под ним можно найти кнопку «Разработчикам».
- Новое окно содержит в верхнем меню раздел «Мои Приложения», который следует открыть.
- Если человек ранее не пользовался такой возможностью, то новое окно будет пустым. Нажмите на кнопку «Создать приложение» для продолжения.
- Откроется бланк, в котором необходимо заполнить основные поля. В первую очередь – это название, после выбирается платформа. Учитывая, что необходимо добавить игру, выберите самый последний пункт «Встраиваемое приложение».
- Затем откроется еще один перечень, в котором необходимо выбрать тип («Игра»), а также определить категорию. После этого можно перейти к загрузке приложения, нажав соответствующую кнопку.
- Прежде чем приступить к загрузочному процессу может понадобиться подтвердить владение страницей путем отправки СМС-сообщения на привязанный телефон. Потребуется ввести полученный код для продолжения.
- Затем примите показанный список правил, нажав соответствующую кнопку.
- Будет открыта консоль управления новой игрой. В меню справа содержатся основные пункты для контроля и управления.
- Перейдите в раздел «Настройки». Там есть кнопка для загрузки файла в формате SWF. Естественно, перед этим необходимо создать игру самостоятельно и уже после подгружать основные файлы и обновления в дальнейшем. Создана она может быть в программе Unity или подобной.
- После загрузки можно вернуться в раздел «Информация», содержащий пункты для настройки оформления игры. Там загружаются все виды аватарок и определяется, как игровая иконка будет отображаться на iPhone, устройстве на Andriod и ПК. Указать название и описание.
На этом базовая процедура загрузки и оформления может быть окончена.
Оптимизация клиента
Итак, я всё-таки решился поплотнее почитать про синусы-косинусы и впервую очередь избавиться от отдельных расчетов для отрисовки и реальных координат. Тут рассказывать особо нечего, но код стал явно получше.
this.draw = function(t){
this.x2 = this.x;
this.y2 = this.y;
this.s2 = this.s;
this.r2 = this.r;
this.cosa2 = this.cosa;
this.a2 = this.a;
this.timeK = (t > 40) ? 1 ((t-40)/40) : 1;
this.x = this.timeK*this.koef*this.s*Math.sin((90-this.ra)*this.PI/180);
if(isNaN(this.x)) this.x = this.x2 (2*(this.timeK*this.koef*this.s*Math.sin((90-this.ra)*this.PI/180)));
this.y -= this.timeK*this.koef*this.s*Math.sin(this.ra*this.PI/180);
if(isNaN(this.y)) this.y = this.y2-(2*(this.timeK*this.koef*this.s*Math.sin(this.ra*this.PI/180)));
this.s -= this.timeK*(this.koef*Math.sin(this.ra*this.PI/180))*(this.wspeed/(this.y/this.wspeed))/4.5;
if(isNaN(this.s)) this.s = this.s2-(2*(this.timeK*(this.koef*Math.sin(this.ra*this.PI/180))*(this.wspeed/(this.y/this.wspeed))/4.5));
if(this.s <= 0) this.s = this.koef*0.0001;
this.r = this.timeK*this.s*50/this.wradius;
if(isNaN(this.r)) this.r = this.r2;
if(this.r == 0) this.r = this.koef*0.000000001;
this.cosa = this.timeK*(this.r*this.r this.r*this.r-this.s*this.s)/(2*this.r*this.r);
if(isNaN(this.cosa)) this.cosa = this.cosa2;
this.a = this.timeK*this.koef*Math.acos(this.cosa)*180/this.PI;
if(isNaN(this.a)) this.a = this.a2;
for(var i = 0; i < weapons.all.length; i ){
if(this.id && this.per[weapons.all[i]] <= this.tper[weapons.all[i]]){
this.per[weapons.all[i]] = (t > 40) ? t 40 : 40; //время выстрела (наращивается с каждым кадром)
}
}
if(this.id){
this.opacity = (this.per.M < this.tper.M) ? 0.6 : 1;
if(this.debaff.M.per != 1) this.debaff.M.per = (t > 40) ? t 40 : 40;
if(this.debaff.E.per <= this.debaff.E.tper) this.debaff.E.per = (t > 40) ? t 40 : 40;
}
if(this.x > 700 this.w) this.x = -this.w;
if(this.x < -this.w) this.x = 700 this.w;
if(this.key == 'up') this.ra = this.a
else if(this.key == 'down') this.ra -= this.a;
if(isNaN(this.x) || isNaN(this.y) || isNaN(this.r) || isNaN(this.a) || isNaN(this.ra) || isNaN(this.s) || isNaN(this.cosa) || isNaN(this.timeK)){
this.x = (par.side == 'left') ? 40 : 660;
this.y = 450;
this.s = this.wspeed;
this.r = this.s*50/this.wradius;
this.cosa = (this.r*this.r this.r*this.r-this.s*this.s)/(2*this.r*this.r);
this.a = 0;
this.ra = this.a;
}
if(this.opacity >= 0.5){
ctx.globalAlpha = this.opacity;
ctx.save();
ctx.translate(this.x, this.y);
ctx.fillStyle = '#f00';
ctx.strokeStyle = '#000';
ctx.fillRect(-34,-40,(this.life/100)*67,4);
ctx.strokeRect(-34,-40,67,4);
ctx.rotate(-this.ra*this.PI/180);
ctx.drawImage(this.image, -this.w/2, -this.h/2, this.w, this.h);
ctx.restore();
}
}
Тут вроде всё понятно кроме имён переменных, расскажу про проверки на NaN. Одним из игроков был обнаружен (и позже проверен мной) неприятный баг — самоёт резко оказывается в левом верхнем углу и всё, можно выходить из игры. Всё из-за того, что при просчете некоторые переменные с небольшой долей вероятности становились === 0, а потом на них приходилось делить, и получалась такая лажа. После вкропления в код проверки на NaN баг изчез и все довольны.
if(this.opacity >= 0.5){…} код именно отрисовки на канвасе выполняется только в случае, если самолёт не находится в режиме невидимости (да, есть и такой бонус)
Еще момент — функция принимает параметр t. Он по сути нужен для слабых компьютеров/планшетов. Тоесть в этом параметре содержится инфа о том, сколько по времени рисуется кадр, и если это больше 40мс (всё работает на скорости 25 кадров в секунду), то все передвижения самолёта необходимо пропорционально увеличить. Эти изменения сократили время отрисовки примерно на 2-3мс, что уже не плохо.
Следующим шагом стала оптимизация дыма от самолёта, т.к. больше всего памяти и процессора кушал именно он. О том как реализован дым, я писал в этой статье. Для оптимизации я сделал другую картинку — меньше в размере и которую не надо вращать (серый круглый градиент), и один раз отрисовал его на невидимом канвасе (буфер), а потом копировал с него.
Потом нашлась самая большая дырка — в отрисовке показателя перезарядки и количества для каждого оружия. В бою это выглядит так:
Стрелками показаны штуки, отрисовка которых была наиболее сложной (arc). Я решил, что т.к. большинство оружия бОльшую часть времени находится в полной перезарядке (или с отсутствием патронов (нижняя полосочка)), то можно просто нарисовать эти состояния на отдельной канве, и при необходимости просто рисовать готовое (не рисовать вообще). Самое интересное — что оптимизация этих 32 полукругов дала наибольший эффект — аш 5-6мс. И теперь миссия выполнена. При максимально возможном дыме у обоих игроков (чем меньше жизней тем больше дыма), кадр стал рисоваться 1-2мс, а без дыма вообще меньше 1мс (0.2-0.3 если брать среднее). Все цифры для Chrome 32.
Переломный момент.
Думаю многим известна история успеха игры «Гонки на клавиатурах»? (ссылку на статью не нашёл, буду рад вставить). Когда я узнал, сколько зарабатывают разработчики УСПЕШНЫХ приложений в социальных сетях, мне, наверное, как и другим захотелось создать свою игру для социальной сети.
И я начал активную работу над этим. Узнал, что еще и юнити проект можно собрать под веб… Замысел игры был относительно простой — своя линейка с чем нибудь и куртизанками. Чтобы было чем страдать в юнити, я даже уже было начал переговоры с дизайнером, который сможет отрисовать для игры мобов, карты и т.д.
Конечно, на голом энтузиазме никто работать не захотел, и я принял решение сделать что нибудь «попроще», чтобы набить карман для первоначального бюджета более серьёзного проекта. Этим «попроще» стала идея сделать онлайн гонки 2d на HTML5, только не тот онлайн, в котором нажимаешь на соперника, ждешь, и «ты выиграл!/проиграл =(», а реальный онлайн с управлением автомобиля на трассе.
Чтобы еще больше облегчить себе жизнь, гонки решил делать драговыми =) Ну а что… никаких поворотов, меньше физики… Короче говоря, спустя пол года всё закончилось тестовой версией с 20 автомобилями. Я уже выдохся искать все параметры для машин, т.к. пытался сделать реалистичную (даже 2d) физику, нужны были такие параметры, как аэродинамическое сопротивление, графики крутящего момента и прочие характеристики, которые, как оказалось, не так легко найти.
Но эти гонки были для меня чем-то обучающим. Я изучал всё новые новые технологии, придумывал велосипеды из костылей, натыкался по нескольку раз на свои грабли… И зато за эти пол года сложился некоторый идеальный для меня скелет клиент-сервера. Если говорить конкретно, то на клиента canvas, на сервере nodejs socket.io.
Банально, криво, зато работало. А если работает, значит не трогай =) В основном проблемы я испытывал из-за незнания языка, и даже самые простые ф-ии, уже встроенные в JS я велосипедил сам, не зная об их существовании. Примерно так выглядел небольшой кусок серверной части:
io = require('socket.io');
mysql = require('mysql');
players = []; //массив, содержащий игроков. Каждый игрок - объект.
races = []; //массив гонок, где каждый элемент - массив объектов 4 игроков
//тут какая-то инициализация чего-то
...
io.sockets.on('connection', function(socket){
socket.on('message', function(msg){
msg = JSON.pars(msg);
switch(msg.type){
case 1:
...
break;
...
}
}
}
(Кому уже не интересно читать а хочется посмотреть — ссылка в конце статьи)
Внутри swith был код, отправляющий определённый ответ пользователю. Кстати о том, что в case можно писать не только цифры я еще не знал, по этому приходилось держать в голове все типы сообщений, которых было ни много ни мало — около 50. Среди них и авторизация, и начало гонки, и конец гонки, и чат, и разные разные внутриигровые события.
Я знал, что для сокета вместо использования switch конструкции можно сразу писать socket.on(‘тип сообщения’…), но такой подход создаёт лишнюю порцию спагетти, и не сильно большое удобство написания кода, по этому непосредственно серверная часть конечной игры написана именно так.
То-есть скелет такой, позже то я всё таки узнал что в case можно писать что вздумается =). На клиенте конструкция один в один, только нету массива всех игроков, есть только объект самого себя (причем глобальный), и огромный набор функций (тоже глобальных), берущих на себя роль обработчиков входящих сообщений.
Постепенно я узнавал возможности JS, NodeJS и socket.io. Помню первый успешно-провалившийся тест максимального количества подключений. При 10к – 250 коннектов socket.io терял отзывчивость и не принимал новые подключения. После долгой возни помогла вот эта статья. Там всё подробно описано так что повторяться не буду.
Как я уже говорил, с гонками я бодался пол года, и на большее сил не хватило. Про идею с 3d игрой я уже совсем забыл, и от неё остался лишь ватман на комнатной двери со структурой БД. И вот, так как гонки оказались для меня слишком сложными, я решил еще больше упростить себе задачу, и сделать самолётики, летающие в воздухе и просто стреляющие друг в друга.
Простой игровой бот для вк, создаем игру для вконтакте исходники! | часть 1 »
Статьи / PHP
Данная статья, начало создания игровых механик для своего бота Вконтакте. Многие из вас завадались вопросом: А как создать игрового бота для ВК, давайте начнем создавать классного бота для совместных игр.
1. Подготовка.
Так как мы еще не определились что именно будет в нашем боте, мы будем создавать это ниже, мы подготовим наше сообщество для создания нового бота, а так же подключим сразу базу данных и сделаем регистрацию.
Создаем новое сообщество, указываем название, тематику и по желанию свой сайт (если есть).
Переходим в настройки и включаем сообщения сообщества, а так же создаем новое приветствие для новичков :), тут же в подразделе Настройки для бота не забываем включить их, и разрешить добавлять его в беседу!
Теперь переходим к настройке скрипта, назовем это основной файл отвечающий за прием событий от вк и их дальнейшей обработки
Если нет сервера, нужно приобрести, рекомендуем хостинг FirstVDS нам подойдет и обычный хостинг VDS-OVZ-Разминка, цена на время написания статьи всего 90 рублей в месяц. После заказа Вам придет на почту данные для авторизации на сервере. Использовать можете любой хостинг, на ваш вкус и цвет. Главное условие поддержка PHP 5.6 (желательно 7.0 ) и MySQL. Далее переходим на сервер, создаем любое доменное имя куда будем заливать скрипты. Мы будем использовать IP адрес. Если с с настройкой сервера не понятно, то почитайте эту статью на нашем сайте, где мы наглядно показываем как загрузить файлы на сервер.
После того как разобрались с сервером, нужно загрузить как раз необходимые библиотеки и основной файл, скачать ниже:
Для работы с PHP я использую PHPStorm, это платный продукт, но как получить его бесплатно, Вам, наверно, объяснять не нужно
Распаковываем архив на сервер и открываем наш файл bot.php и пишем код который останется неизменным и может использоваться при создании новых ботов.
<?php
require_once('simplevk-master/autoload.php'); // БЛИБЛИОТЕКИ
require './vendor/autoload.php';// БЛИБЛИОТЕКИ
use KrugozorDatabaseMysqlMysql as Mysql; // КЛАССЫ ДЛЯ РАБОТЫ С БД
use DigitalStarvk_apivk_api; // Основной класс
use DigitalStarvk_apiMessage; // Конструктор сообщений
use DigitalStarvk_apiVkApiException; // Обработка ошибок
$host = 'localhost'; // По умолчанию localhost или ваш IP адрес сервера
$name = ''; // логин для авторизации к БД
$pass = ''; // Пароль для авторизации к БД
$bdname = ''; // ИМЯ базы данных
$vk_key = ''; // Длинный ключ сообщества, который мы получим чуть позже
$confirm = ''; // СТРОКА которую должен вернуть сервер
$v = '5.103'; // Версия API, последняя на сегодняшнее число, оставлять таким если на новых работать в будущем не будет
$db = Mysql::create($host, $name, $pass)->setDatabaseName($bdname)->setCharset('utf8mb4');
$vk = vk_api::create($vk_key, $v)->setConfirm($confirm);
$my_msg = new Message($vk);
$data = json_decode(file_get_contents('php://input')); //Получает и декодирует JSON пришедший из ВК
$vk->sendOK();
// ТУТ УЖЕ БУДЕМ ПИСАТЬ КОД //
Здесь нам нужно заполнить следующие поля:
$host = ‘localhost’; // По умолчанию localhost или ваш IP адрес сервера
$name = ”; // логин для авторизации к БД
$pass = ”; // Пароль для авторизации к БД
$bdname = ”; // ИМЯ базы данных
$vk_key = ”; // Длинный ключ сообщества, который мы получим чуть позже
$confirm = ”; // СТРОКА которую должен вернуть сервер
$v = ‘5.103’; // Версия API, последняя на сегодняшнее число, оставлять таким если на новых работать в будущем не будет
Получение ключа сообщества:
А так же в разделе Тип событий обязательно включите Входящие сообщения
Так как данные я буду вносить своего сервера, соответственно показывать их я не буду. После настройки возвращаемся снова в сообщество.
Надеюсь с этим все ясно, почти в каждой статье на нашем сайте мы каждый раз объясняем как настроить бот, почитайте их, если здесь не ясно что делать. После успешного подключения переходим уже ко второй части статьи, где будем делать регистрацию пользователей которые первый раз напишут боту.
2. Регистрация новых пользователей в боте.
Для создания игрового бота понадобится регистрация используя базу данных. Мы будем Использовать для этого обертку на родным MySQLi. Для начала создаем через PhpMyAdmin новую таблицу users с полями:
Или используем запрос ниже:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = " 00:00";
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`vk_id` int(11) NOT NULL,
`nick` varchar(20) NOT NULL,
`status` int(11) NOT NULL,
`time` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
Переходим к самому интересному, написанию кода.
Для начала как всегда, создание переменных которые чаще всего будут использоваться в боте:
// ТУТ УЖЕ БУДЕМ ПИСАТЬ КОД //
// Переменные для удобной работы в будущем
$id = $data->object->message->from_id; // ИД того кто написал
$peer_id = $data->object->message->peer_id; // Только для бесед (ид беседы)
$time = time();
$cmd = explode(" ", mb_strtolower($data->object->message->text)); // Команды
$message = $data->object->message->text; // Сообщение полученное ботом
$new_ids = current($data->object->message->fwd_messages)->from_id ?? $data->object->message->reply_message->from_id; // ИД того чье сообщение переслали
$userinfo = $vk->userInfo($id);
// Закончили с переменными
Пишем первую проверку, на входящее сообщение, именно туда мы будем вносить весь код
if ($data->type == 'message_new') {
// ТУТ все что будем делать дальше
}
Это нужно что бы исключить все другие события, ведь у нас игровой бот который использует только текст.
Обращаемся к базе данных, что бы проверить есть ли такой пользователь в ней:
if ($id < 0){exit();} // ПРОВЕРЯЕМ что сообщение прислал юзер а не сообщество
if ($data->type == 'message_new') {
$id_reg_check = $db->query('SELECT vk_id FROM users WHERE ids = ?i', $id)->fetch_assoc()['vk_id']; // Пытаемся получить пользователя который написал сообщение боту
if (!$id_reg_check and $id > 0) { // Если вдруг запрос вернул NULL (0) это FALSE, то используя знак ! перед переменной, все начинаем работать наоборот, FALSE становится TRUE
// Так же мы проверяем что $id больше нуля, что бы не отвечать другим ботам, но лучше в самом верху добавить такую проверку что бы не делать лишних обращений к БД!
$db->query("INSERT INTO users (vk_id, nick, status, time) VALUES (?i, '?s', ?i, ?i)", $id, "$userinfo[first_name] $userinfo[last_name]", 0, $time);
$vk->sendMessage ($peer_id, "Приветствую тебя, @id$id ($userinfo[first_name] $userinfo[last_name]), ты теперь один из нас, вступай в ряды мощных панамеровцев!");
}
}
Давайте сразу добавим кнопки, ведь это удобно, так как у нас бот называется PANAMERA, давайте сделаем какую-нибудь автомобильную игру, где ваша задача получить Porshe PANAMERA, но не будем ограничиваться этим, а начнем развивать механику в разные направления.
После регистрации у вас ничего нет, нужны деньги, что бы их получить, можно использовать кнопку бонус, давайте ее и добавим в бот.
Пишем следующий код:
К переменным
$bonus = $vk->buttonText('⏰ Бонус!', 'green', ['command' => 'bonus']);
Далее:
if ($data->type == 'message_new') {
if (isset($data->object->message->payload)) { //получаем payload
$payload = json_decode($data->object->message->payload, True); // Декодируем кнопки в массив
} else {
$payload = null; // Если пришел пустой массив кнопок, то присваиваем кнопке NULL
}
$payload = $payload['command'];
$id_reg_check = $db->query('SELECT vk_id FROM users WHERE vk_id = ?i', $id)->fetch_assoc()['vk_id']; // Пытаемся получить пользователя который написал сообщение боту
if (!$id_reg_check and $id > 0) { // Если вдруг запрос вернул NULL (0) это FALSE, то используя знак ! перед переменной, все начинаем работать наоборот, FALSE становится TRUE
// Так же мы проверяем что $id больше нуля, что бы не отвечать другим ботам, но лучше в самом верху добавить такую проверку что бы не делать лашних обращений к БД!
$db->query("INSERT INTO users (vk_id, nick, status, time) VALUES (?i, '?s', ?i, ?i)", $id, "$userinfo[first_name] $userinfo[last_name]", 0, $time);
$vk->sendButton($peer_id, "Приветствую тебя, @id$id ($userinfo[first_name] $userinfo[last_name]), ты теперь один из нас, вступай в ряды мощных панамеровцев!", [[$bonus]]);
}
// Давайте для обработки кнопки воспльзуемся SWITCH - CASE
switch ($payload) // Проще говоря мы загрузили кнопки кнопки в свич, теперь проверяем что за кнопка была нажата и обрабатываем ее
{
case 'bonus';
$vk->sendMessage($peer_id, "Вы взяли бонус, Вам выпало N монет");
break;
}
}
Давайте протестируем регистрацию и ответ на нажатие кнопки:
Ответил не сразу, в коде были допущены следующие ошибки:
ids заменить на vk_id и добавить переменную $userinfo = $vk->userInfo ($id);
Так как статья пишется параллельно созданию бота, могут быть допущены ошибки, выше в исходных файлах они будут исправлены.
Давайте сделаем что бы бонус начислялся на баланс. Добавляем 2 поля к таблице USERS:
ALTER TABLE `users` ADD `balance` INT NOT NULL AFTER `time`, ADD `time_bonus` INT NOT NULL AFTER `balance`;
переходим к коду и пишем следующую проверку и рандом:
case 'bonus';
$time_bonus = $id_reg_check = $db->query('SELECT time_bonus FROM users WHERE vk_id = ?i', $id)->fetch_assoc()['time_bonus'];
if ($time_bonus < $time){
// 21600 минут = 6 часов
$next_bonus = $time 21600; // Прибавляем 6 часов для следующего бонуса!
$rand_money = mt_rand(100, 5000); // Рандомно выбираем число от 100 до 5000, используя встроенную функцию PHP mt_rand
$db->query('UPDATE users SET time_bonus = ?i, balance = balance ?i WHERE vk_id = ?i',$next_bonus, $rand_money, $id); // Обновляем данные
$vk->sendMessage($peer_id, "Вы взяли бонус, Вам выпало $rand_money монет");
} else { // Иначе сообщим о том что бонус уже взят!
$next_bonus = date("d.m в H:i:s",$time_bonus);
$vk->sendMessage($peer_id,"Вы уже брали бонус ранее, следующий будет доступен "$next_bonus"");
}
break;
Проверяем:
Так это выглядит в базе данных:
Отлично у нас есть деньги, пора бы начать их и тратить самое распространенное в играх конечно же казино, давайте сделаем что-то подобное, самое простейшее казино. Пока мы еще не придумали игровые механики для нашего игрового бота, сделаем фарм денег.
Как будет работать наше казино:
Казино все, казино 100, казино 1кк и прочие, означающие ставки. Погнали делать команду:
if ($cmd[0] == 'казино'){ // Первая команда
if (!$cmd[1]){ // если вторая команда пустая она вернет FALSE
$vk->sendMessage($peer_id, 'Вы не указали ставку!');
}elseif ($cmd[1] == 'все' or $cmd[1] == 'всё'){ // Если указано все
$balance = $db->query('SELECT balance FROM users WHERE vk_id = ?i', $id)->fetch_assoc()['balance']; // вытягиваем весь баланс
if($balance == 0) {
$vk->sendMessage($peer_id, 'У Вас нет денег :(');
} else {
$result = mt_rand(1, 4); // 1 - проиграл половину, 2 - победа x1.5, 3 - победа x2, 4 - проиграл все
$win_money = ($result == 1 ? $balance / 2 : ($result == 2 ? $balance * 1.5 : ($result == 3 ? $balance * 2 : 0)));
$win_nowin = ($result == 1 ? 'проиграли половину' : ($result == 2 ? 'выиграли x1.5' : ($result == 3 ? 'выиграли x2' : 'проиграли все')));
$vk->sendMessage($peer_id, "Вы $win_nowin, ваш баланс теперь составляет $win_money монет.");
$db->query('UPDATE users SET balance = ?i WHERE vk_id = ?i', $win_money, $id); // Обновляем данные
}
} else {
$sum = str_replace(['к','k'], '000', $cmd[1]); // наши Кk превращаем в человеческий вид, заменяя их на нули :)
$sum = ltrim(mb_eregi_replace('[^0-9]', '', $sum),'0'); // удаляем лишние символы, лишние нули спереди и все что может поломать систему :), подробнее о функциях можно почитать в интернете
$balance = $db->query('SELECT balance FROM users WHERE vk_id = ?i', $id)->fetch_assoc()['balance']; // вытягиваем весь баланс
if($balance < $sum) {
$vk->sendMessage($peer_id, 'У вас не достаточно денег');
} else {
$result = mt_rand(1, 4); // 1 - проиграл половину, 2 - победа x1.5, 3 - победа x2, 4 - проиграл все
$win_money = ($result == 1 ? $balance - ($sum / 2) : ($result == 2 ? $balance ($sum * 1.5) : ($result == 3 ? $balance ($sum * 2) : $balance - $sum)));
$win_nowin = ($result == 1 ? 'проиграли половину' : ($result == 2 ? 'выиграли x1.5' : ($result == 3 ? 'выиграли x2' : 'проиграли все')));
$vk->sendMessage($peer_id, "Вы $win_nowin, ваш баланс теперь составляет $win_money монет.");
$db->query('UPDATE users SET balance = ?i WHERE vk_id = ?i', $win_money, $id); // Обновляем данные
}
}
}
Получилось довольно много кода, в будущем возможно мы еще вернемся для доработок, все зависит только от Вас, давайте попробуем это в действии:
Получилось не плохо, теперь в нашем игровом боте есть казино!
На этом наша первая часть по созданию игрового бота вконтакте подходит к концу, теперь ваши идеи и предложения будут появляться в новой статье, во второй части мы уже начнем реализацию ваших идей и предложений!
Если статья понравилась, оцените ее лайком.
Исходники готового проекта:
Ссылка на группу для теста
Работа над ошибками
Пораскинув мозгами о том, что
кладут в шампунь для волос
не так, решил что дело в свистелках, перделках и отсутствии социального спама на стенах (законного разумеется, от имени приложения). И как вы думаете что я сделал? Не угадали. Я снова стал переписывать игру. Начал снова с дизайна.
Ошибка 1. Я думал что из меня выйдет неплохой дизайнер.Решил упростить ещё сильнее, сделал огромные кнопки с некрасивыми надписями — это чудо я тоже не хочу показывать из религиозных соображений.
Ошибка 2. Мало свистелок (оружия, обвеса, как хотите)Тут была проделана огромная работа (над ошибками, конечно). Кроме того, что я добавил 12 новых видов оружия/бонусов, я сделал для каждого из них «спецэффект» при попадании — кстати смотрелось ничего.
На этом этапе переписывание себя оправдало. Появилась некая модульность, благодаря которой я сейчас могу добавлять хоть по новому оружию в минуту, были бы идеи. Тут же была сделана суперзащита от кулхацкинга.Если раньше было так: клиент стреляет, клиент проверяет есть ли вообще оружие такое, всё он же (клиент) проверяет — не попала ли ракета в соперника, и если попала, то только тогда отправляем сообщение на сервер и он передает сообщение оппоненту о том что в него попали.
То стало вот так: клиент нажимает клавишу, на сервер передаётся код клавиши, сервер смотрит — на какое оружие назначена у игрока эта клавиша, проверяет перезаряжено ли оружие, есть ли оно вообще, можно ли им пользоваться на этом самолёте (самолётов тоже добавил, у каждого свои преимущества, в том числе какое оружие можно использовать), и ещё куча мелких условий.
В случае, если не было ни одного false, данные о выстреле отправлялись обоим игрокам. Если false все таки нашелся, то у стрелявшего вежливо (красными большими буквами) показывалось сообщение вроде «ПЕРЕЗАРЯДКА!….». Собственно, если интересно то вот так:
case 'shot':
if(Date.now() < players[socket.id].per[msg.typeW]){
console.log('перезарядка');
}
else if(players[socket.id].weapons[msg.typeW] <= 0){
players[socket.id].weapons[msg.typeW] = 0;
console.log('закончились боеприпасы');
}
else if(weapons[players[socket.id].plane].weapons.indexOf(msg.typeW) < 0 && weapons.weapons.indexOf(msg.typeW) >= 0){
console.log('оружие недоступно');
}
else if(weapons[players[socket.id].plane].bonuses.indexOf(msg.typeW) < 0 && weapons.bonuses.indexOf(msg.typeW) >= 0){
console.log('бонус недоступен');
}
else{
if(players[socket.id].wrate != weapons[players[socket.id].plane].rate players[socket.id].rate && Date.now() >= players[socket.id].per.P){
players[socket.id].wrate = weapons[players[socket.id].plane].rate players[socket.id].rate;
}
if(weapons.weapons.indexOf(msg.typeW) >= 0){
msg.per = weapons[msg.typeW].rate*1000/players[socket.id].wrate;
players[socket.id].per[msg.typeW] = Date.now() msg.per;
players[socket.id].weapons[msg.typeW] -= 1;
players[socket.id].pot[msg.typeW] = 1;
msg.hit = weapons[msg.typeW].radius;
msg.speed = weapons[msg.typeW].speed;
msg.val = players[socket.id].weapons[msg.typeW];
msg.side = players[socket.id].side;
}
else if(weapons.bonuses.indexOf(msg.typeW) >= 0){
msg.per = weapons[msg.typeW].rate*1000*players[socket.id].wdamage;
players[socket.id].per[msg.typeW] = Date.now() msg.per;
players[socket.id].weapons[msg.typeW] -= 1;
players[socket.id].pot[msg.typeW] = 1;
msg.val = players[socket.id].weapons[msg.typeW];
msg.side = players[socket.id].side;
switch(msg.typeW){
case 'I':
players[socket.id].life = 10;
msg.life = Math.ceil(players[socket.id].life);
if(players[socket.id].life > 100){
players[socket.id].life = 100;
msg.life = 100;
}
break;
case 'J':
players[socket.id].wsuspension = 2;
break;
case 'L':
players[socket.id].kamikadze = true;
break;
case 'P':
players[socket.id].wrate = 100000;
break;
};
}
socket.json.send(msg);
io.sockets.socket(players[socket.id].oppSocket).json.send(msg);
}
break;
Немного пояснения к коду. players — массив всех игроков и с идентификацией по socket.id. Каждый элемент массива — это объект игрока, который создается сразу при входе в игру, методом присваивания того, что вернула MySQL. На самом деле, между каждым else if стояла отправка ошибки юзеру, но я этот код не нашёл. weapons — это массив параметров оружий и бонусов.
Просчет попадания я сделал по хитрому: просчитывает оппонент. Как бы это описать дальше… после успешного выстрела 1 игрока, 2 игрок отрисовывает у себя ракету и смотрит, не попала ли она в него (в себя). Если попала, то отправляет мэссадж на сервер с содержанием «что попало и в каком месте на экране».
Сервер высчитывает урон, и отправляет обоим игрокам. При этом рисуется сказочный спецэффект и ракета исчезает. Заодно на сервере проверяется сколько там чего осталось, жизней и т.д. — и если жизней вдруг у игрока 2 стало меньше 0, то записываем всё в БД, отправляем игрокам инфу о том кто выиграл а кто проиграл и все счастливы.Если интересно, то вот клиентский код рассчета попадания:
for(var i = 0; i < weapons_1.length; i ){
if(weapons_1[i].draw({t:avatars[0].time2, x:avatars[0].x, y:avatars[0].y, ra:avatars[0].ra}) == 'coordLimit'){
weapons_1.splice(i,1);
}
else if(weapons_1[i].s && Math.abs(avatars[0].x-weapons_1[i].x) < 80 && Math.abs(avatars[0].y-weapons_1[i].y) < 80){
var dl = Math.sqrt(((avatars[0].x-weapons_1[i].x)*(avatars[0].x-weapons_1[i].x)) ((avatars[0].y-weapons_1[i].y)*(avatars[0].y-weapons_1[i].y)));
if(dl < (avatars[0].w/4 weapons_1[i].w/4) weapons_1[i].hit){
sock.send($.toJSON({type: 'hit', typeW: weapons_1[i].typeW, i: i}));
weapons_1.splice(i,1);
};
}
}
weapons_1 — ничто иное как массив всех выпущенных оппонентом ракет. Как видно, чтобы не грузить память, когда ракета покидает пределы канваса, она удаляется из массива.
Второй кусочек — небольшая оптимизация. Я не знаю, на сколько это полезно, но вот чтобы не считать каждый кадр гипотенузу (в треугольнике ракета — самолёт), сначала идет небольшой расчет абсолютного положения на экране. И если ракета по приблизительным меркам слишком далеко, то и гипотенузу считать незачем.
avatars[0] и avatars[1] — это объекты игроков. После прочтения этой статьи, почему-то решил назвать это аватарами, так и повелось.
«Событие» попадания на клиенте выгладит так:
case 'hit':
avatars[0].life = msg.life;
if(avatars[0].life <= 0) avatars[0].life = 0;
if(avatars[0].life > 100) avatars[0].life = 100;
$('#life span, #life2 span').html(msg.life);
if(msg.damage <= 0){
particles.push(new Effect({type: 'message', text: ('ПРОМАХ!'), x: avatars[0].x, y: avatars[0].y, t: 1000}));
}
else{
particles.push(new Effect({type: 'message', text: ('-' (Math.ceil(msg.damage))), x: avatars[0].x, y: avatars[0].y, t: 1000}));
}
switch(msg.typeW){
case 'C':
avatars[0].ra -= msg.damage*msg.damage*avatars[1].koef;
break;
case 'E':
avatars[0].debaff.E.per = Date.now();
avatars[0].debaff.E.tper = Date.now() (msg.damage*500);
particles.push(new Effect({type: 'message', text: ('Управление заблокировано!'), x: 350, y: 350, t: 2000}));
break;
}
particles.push(new Effect({type: msg.typeW, x: avatars[0].x, y: avatars[0].y, side: 0, damage: msg.damage}));
break;
case 'pop':
avatars[1].life = msg.life;
if(avatars[1].life <= 0) avatars[1].life = 0;
if(avatars[1].life > 100) avatars[1].life = 100;
if(weapons_0.length > msg.i){
weapons_0.splice(msg.i,1);
}
if(msg.damage <= 0){
particles.push(new Effect({type: 'message', text: ('ПРОМАХ!'), x: avatars[1].x, y: avatars[1].y, t: 1000}));
}
else{
particles.push(new Effect({type: 'message', text: ('-' (Math.ceil(msg.damage))), x: avatars[1].x, y: avatars[1].y, t: 1000}));
}
switch(msg.typeW){
case 'C':
avatars[1].ra = msg.damage*msg.damage*avatars[1].koef;
break;
case 'E':
particles.push(new Effect({type: 'message', text: ('Прямое попадание! Управление соперника заблокировано!'), x: 300, y: 220, t: 2000}));
break;
}
if(msg.typeW == 'F') msg.damage = 1;
particles.push(new Effect({type: msg.typeW, x: avatars[1].x, y: avatars[1].y, side: 1, damage: msg.damage}));
break;
Тут case ‘pop’ — ничто иное как перевод слова «попал», так что те кто не может похвастаться знанием языка может меня поймут. particles — массив частиц (спецэффектов). Тут и прокомментировать нечего. Событие выстрела происходит по аналогии, только вместо частиц используются массивы weapons_0 (мои ракеты) и weapons_1 (его ракеты), с напихиванием в них новеньких объектов.
Ошибка 3. Множество багов.Если не считать мелких падений сервера из за резкого отсутствия каких-либо переменных, то основной проблемой для меня стал пинг. Так случилось, что на работе с патч-корда слетел коннектор, а в запасе как то не оказалось, и пришлось сидеть по wifi.
На работе он гниленький, так что задержки были обеспечены качественные (150-200мс вместо 18-20 привычных). Тестируя очередное оружие, заметил что через некоторое время в двух окнах браузера картина расположения самолётов совсем не одинаковая. А как оказалось, виной тому wifi, а точнее пинг а то и вовсе потери пакетов. Как решил — читаем дальше если не устали.