Our company is being accelerated by FasterCapital

FasterCapital is a new type of incubator. The founders remarked that there are several weaknesses in incubators worldwide. These weaknesses are leaving an important segment of entrepreneurs with no help. It is also affecting the success rate of startups as many don’t raise enough capital at the right time which is a major cause for failure.

You can find more information about us on FasterCapital page.

Bloodstream VR

Have you been interested not only in the outer, but also in the inner world of people? «BloodstreamVR» by EJaw gives you an incredible opportunity to feel like a drop of blood. Travel along the blood vessels and discover what the inside looks like.

This application is a prototype and is available free on Google Market. Also you can download the application on our website.

Google-Play-Button-100

 

Friends from game industry, EJaw team is going to Game Connection 2015.

Let`s meet, have a talk and find out how we can be useful for each other.
Feel free to contact Kristina@ejaw.net
See you in Paris

 

Unity timers (UT)

Unity times (UT) – is an analogue of the .NET timers which works for Unity. You don`t need to create more of the same methods that return IEnumerator in your classes. UT does it all. You can create timers such as:

Endless timer – a timer that will count down the time between 0 and until you stop it;

The timer with steps and delay — the timer that will count down the time delay with a predetermined stop.

Now you can perform all the processes that require delays in time, for examples: actions of abilities, production in the building and many other things, with the help of UT.

 

Новая система GUI

Целью для написания новой структуры GUI стояла возможность отделить логику от визуального отображения, а также обезопасить себя от возможных ошибок дизайнера. Структура GUI приведена на рис.

В данной системе логика и отображение связаны двумя посредниками: синглтоном Displayer и хранилищем всех вьюверов GuiController. Задача дизайнера сводится к помещению префаба любого вьювера в дочерний объект GuiController, который при старте сцены получит все дочерние объекты, реализующие интерфейс IGuiTargetViewer. После чего подпишется на события синглтона Displayer на отображение или сокрытие окон. Также существуют вьюверы без generic компонента. Обычно они используются, если не нужно отображать конкретную модель, допустим простое окно. Рассмотрим код.

public class GuiController : MonoBehaviour
{
private readonly Dictionary<Type, IGuiTargetViewer> targetViewers = new Dictionary<Type, IGuiTargetViewer>();
private readonly Dictionary<Type, IGuiViewer> simlpeViewers = new Dictionary<Type, IGuiViewer>();
private void Awake()
{
foreach (var v in gameObject.GetGenericComponentsInChildren<IGuiTargetViewer>())
{
if (!targetViewers.ContainsKey(v.TargetType))
targetViewers.Add(v.TargetType, v);
if (v.HideOnAwake)
v.Hide();
}
foreach (var v in gameObject.GetGenericComponentsInChildren<IGuiViewer>())
{
if (!simlpeViewers.ContainsKey(v.GetType()) && !targetViewers.ContainsKey(v.GetType()))
simlpeViewers.Add(v.GetType(), v);
if (v.HideOnAwake)
v.Hide();
}
Displayer.Instance.DisplayTargetViewer += OnDisplayTargetViewer;
Displayer.Instance.Hided += OnSomethingNeedToHide;
Displayer.Instance.DisplayViewer += OnDisplaySimpleViewer;
Displayer.Instance.TargetHided += OnTargetHided;
}
private void OnDestroy()
{
Displayer.Instance.DisplayTargetViewer -= OnDisplayTargetViewer;
Displayer.Instance.Hided -= OnSomethingNeedToHide;
Displayer.Instance.DisplayViewer -= OnDisplaySimpleViewer;
Displayer.Instance.TargetHided -= OnTargetHided;
}
private void OnDisplayTargetViewer(object obj, IViewerTarget viewerTarget)
{
IGuiTargetViewer v = null;
Type type = null;
object target = obj;
if (viewerTarget != null)
{
type = viewerTarget.TargetType;
target = viewerTarget.Target;
}
else if (obj != null)
type = obj.GetType();
if (type != null && targetViewers.TryGetValue(type, out v))
{
v.Show(target);
}
}
private void OnDisplaySimpleViewer(Type type)
{
IGuiViewer v = null;
if (simlpeViewers.TryGetValue(type, out v))
v.Show();
}
private void OnSomethingNeedToHide(Type t)
{
IGuiTargetViewer v = null;
if (targetViewers.TryGetValue(t, out v))
{
v.Hide();
}
else
{
IGuiViewer v1 = null;
if (simlpeViewers.TryGetValue(t, out v1))
v1.Hide();
}
}
private void OnTargetHided(object obj)
{
IGuiTargetViewer v = null;
Type type = null;
if (obj != null)
type = obj.GetType();
if (type != null && targetViewers.TryGetValue(type, out v))
{
v.Hide();
}
}
}

GuiController имеет 4 обработчика событий, показать/спрятать вьювер с целью или тоже самое для простого вьювера. Отображение произойдет только в том случае, если в словаре найдет нужный вьювер. Таким образом мы избегаем вероятности NullReferenceException по неосторожности, ничего перетаскивать не нужно. Структура синглона Displayer представляет собой просто перенаправление событий, завуалированно от любого контроллера.

public sealed class Displayer : Singletone<Displayer>
{
public event Action<object, IViewerTarget> DisplayTargetViewer = delegate { };
public event Action<Type> DisplayViewer = delegate { };
public event Action<Type> Hided = delegate { };
public event Action<object> TargetHided = delegate { };
public void Display(object target)
{
EventSender.SendEvent(DisplayTargetViewer, target, null);
}
public void DisplayViewerWithTarget(IViewerTarget target)
{
EventSender.SendEvent(DisplayTargetViewer, null, target);
}
public void Display(Type viewerType)
{
EventSender.SendEvent(DisplayViewer, viewerType);
}
public void Hide(Type viewerType)
{
EventSender.SendEvent(Hided, viewerType);
}
public void Hide(object target)
{
EventSender.SendEvent(TargetHided, target);
}
}

Единственное, о чем следует позаботится, чтобы Awake у GuiController сработал раньше, чем у любого другого скрипта, который хочет что-либо отобразить. Иначе нужный вьювер просто не успеет попасть в словарь. Ошибки не будет, но и работать тоже не будет.

Далее предлагаю ознакомится с интерфейсом IGuiTargetViewer, базовым классом классом BaseViewer и абстрактным generic классом BaseGuiTargetViewer.

public interface IGuiTargetViewer
{
Type TargetType { get; }
bool HideOnAwake { get; }
void Show(object target);
void Hide();
}

Следует отметить возможность легко скрывать ненужные при старте сцены окна, дизайнер сам выставляет булевое поле HideOnAwake. У GuiControllerа проверяется это поле, как было показано выше.

public abstract class BaseViewer : MonoBehaviour
{
[SerializeField] protected GameObject objectToHide = null;
[SerializeField] protected bool hideOnAwake = true;
protected virtual void Awake()
{
CheckObjectToHide();
}
protected virtual void OnDestroy()
{
}
protected void CheckObjectToHide()
{
if (objectToHide == null)
objectToHide = gameObject;
}
}

Очень важно, что в структуре префаба появляется такой объект, как objectToHide, который выступает просто в виде хранилища, если вьювер нужно скрыть и скрывается именно objectToHide, чтобы к основной скрипт мог выполнять какие-либо функции, его можно было гетнуть и т.д.

public abstract class BaseGuiTargetViewer<TTarget> : BaseViewer, IGuiTargetViewer where TTarget : class
{
protected TTarget Target { get; set; }
Type IGuiTargetViewer.TargetType
{
get
{
return typeof (TTarget);
}
}
bool IGuiTargetViewer.HideOnAwake
{
get { return hideOnAwake; }
}
void IGuiTargetViewer.Show(object target)
{
Show(target as TTarget);
}
public virtual void Show(TTarget target)
{
Target = target;
CheckObjectToHide();
GameObjectHelper.SetActive(objectToHide, true);
}
public virtual void Hide()
{
Target = null;
CheckObjectToHide();
GameObjectHelper.SetActive(objectToHide, false);
}
}

Базовый класс длю любого вьювера, дает возможность переопределить методы Show и Hide. Следует отметить, что Target присваевается на вызове Show и обнуляется при вывозе Hide. Это очень важно для построения алгоритма отображения. Бывают ситуации, когда нужно начала отработать со старым Targetом, а потом переназначить его. Рассмотрим создание своего вьювера.

public sealed class CrystalsViewer : BaseGuiTargetViewer<CrystalsViewerData>
{
public override void Show(CrystalsViewerData target)
{
if (Target != null)
Target.Pouch.ResourceCountChanged -= OnResourceCountChanged;
base.Show(target);
if (Target == null) return;
Target.Pouch.ResourceCountChanged += OnResourceCountChanged;
ShowCrystals();
}
public override void Hide()
{
GameObjectHelper.SetActive(goldCrystalIcon, false);
foreach (var silverGoal in silverCrystalsIcons)
GameObjectHelper.SetActive(silverGoal, false);
if (Target == null) return;
Target.Pouch.ResourceCountChanged -= OnResourceCountChanged;
base.Hide();
}
}

Основная идея, чтобы вызвать Show только тогда, когда нужно назначить Target, обычно при первом обращении. Вот как это выглядит в сцене.

Из минусов у этой системы следует выделить не возможность использовать разные по типу Targets для одного вьювера. Сейчас ведется работа по устранению этого недочета.

Пошаговая инструкция интеграции в Kongregate

Kongregate — весьма популярный игровой портал, на котором тысячи всевозможных игр и миллионы пользователей. Публиковать игры на этом портале достаточно просто. Так почему бы не опубликовать там свою игру? Давайте подробно рассмотрим, как это можно сделать. Начнем с загрузки Kongregate API.

Загрузка API

Для того, чтобы загрузить/проинициализировать объект Kongregate API необходимо выполнить внешний вызов объекта Javascript, который будет автоматически создан для вас. Этот объект будет доступен прежде, чем запустится ваше Unity приложение, так что его можно использовать в любое время. Метод Application.ExternalEval используется для вызова объекта Javascript kongregateUnitySupport, если он существует. Когда API загрузится полностью — будет выполнен метод Unity SendMessage для вызова метода KONGREGATE_LOADER_METHOD объекта Unity с именем KONGREGATE_LOADER_GO. Также необходимо убедиться, что API не загрузится более одного раза, так как это может привести к разрыву соединения.

Ниже следует пример того, как использовать объект kongregateUnitySupport для загрузки Kongregate API и оповещения вашего приложения Unity, когда загрузка завершена.

private const string KONGREGATE_LOADER_GO = «KongregateLoader»;
private const string KONGREGATE_LOADER_METHOD = «OnKongregateAPILoaded»;
private KongregateLoader kongregateLoader = null;
public void Login(Action onComplete)
{
kongregateLoader = Object.FindObjectOfType();
if (kongregateLoader == null)
{
kongregateLoader = (new GameObject(KONGREGATE_LOADER_GO)).AddComponent<KongregateLoader>();
Object.DontDestroyOnLoad(kongregateLoader);
kongregateLoader.Logined += OnLogined;
onLoginComplete = onComplete;
Application.ExternalEval(
«if(typeof(kongregateUnitySupport) != ‘undefined’){» +
» kongregateUnitySupport.initAPI(‘» + KONGREGATE_LOADER_GO + «‘, ‘» + KONGREGATE_LOADER_METHOD +
«‘);» +
«};»
);
}
}

Метод OnKongregateAPILoaded выглядит следующим образом (JavaScript из этого метода будет использован для внутриигровых покупок):

private void OnKongregateAPILoaded(string userInfoString)
{
isKongregate = true; // We now know we’re on Kongregate

// Split the user info up into tokens

var result = userInfoString.Split(«|»[0]);
var data = new LoginData { UserId = result[0], Username = result[1], GameAccessToken = result[2] };
var handler = Logined;
if (handler != null)
handler(data);
Application.ExternalEval(@»

// Extern the JS Kongregate API

function purchaseItem(item)
{
kongregate.mtx.purchaseItems([item], onPurchaseResult);
SendUnityMessage(‘LogMessage’, ‘purchase sent….’);
}
function onPurchaseResult(result)
{
if (result.success)
{
SendUnityMessage(‘LogMessage’, ‘purchase complete…’);
SendUnityMessage(‘OnPurchaseSuccess’);
}
else
{
SendUnityMessage(‘LogMessage’, ‘purchase error…’);
SendUnityMessage(‘OnPurchaseError’);
}
}

// Utility function to send data back to Unity

function SendUnityMessage(functionName, message)
{
Log(‘Calling to: ‘ + functionName);
var unity = kongregateUnitySupport.getUnityObject();
unity.SendMessage(‘KongregateLoader’, functionName, message);
}
function Log(message)
{
var unity = kongregateUnitySupport.getUnityObject();
unity.SendMessage(‘KongregateLoader’, ‘LogMessage’, message);
}
«);
}

Для удобства поместим данные пользователя в структуру следующего вида:

public struct LoginData
{
public string UserId;
public string Username;
public string GameAccessToken;
}
После загрузки API получаем данные пользователя
private void OnLogined(KongregateLoader.LoginData data)
{
kongregateLoader.Logined -= OnLogined;
isLoggedIn = true;
userId = data.UserId;
username = data.Username;
accessToken = data.GameAccessToken;
var handler = onLoginComplete;
if (handler != null)
handler();
}

Таким образом мы выполнили загрузку API, получив при этом user id и username пользователя, а также game_auth_token.

Получение данных профиля

Важно отметить, что при отправке запросов на Kongregate во избежание ошибок лучше использовать api вместо www.

Для получения данных профиля необходимо отправить запрос следующего вида

GET url: http://api.kongregate.com/api/user_info.json

необходимые параметры:

username или user_id: username/user_id пользователя, информацию о котором вы запрашиваете

либо

usernames или user_ids: запрашивает информацию о нескольких пользователях(не более 50), их username/user_id разделяются запятой

дополнительные параметры:

page_num: страница данных запроса (по умолчанию 1)

friends: true, если вы хотите получить список друзей пользователя (для множественного запроса список друзей вернется пустым)

Результатом данного запроса является JSON строка со следующими полями:

success: true/false в зависимости от того, был ли запрос успешным

error: код ошибки типа integer в случае ее возникновения

error_description: описание кода ошибки типа string, в случае ее возникновения

В случае успеха в данном JSON объекте также получаем следующие поля:

page_num: номер текущей страницы

num_pages: общее количество страниц

user_id: Kongregate id пользователя

username: username пользователя

private: true если профиль закрыт для общего доступа

friends: массив usernames друзей пользователя (если запрашивался)

friend_ids: массив userids друзей пользователя (если запрашивался)

user_vars: JSON объект, содержащий уточняющую информацию о пользователе

level: текущий уровень пользователя (integer)

avatar_url: URL полноразмерной аватары пользователя

chat_avatar_url: URL маленькой аватары пользователя, которая используется в чате

game_title: название игры, в которую играет пользователь

game_url: URL игры, в которую играет пользователь

developer: true, если пользователь является разработчиком

moderator: true, если пользователь является модератором

admin: true, если пользователь является администратором

Для получения информации из JSON я использовал Boomlagoon JSON от Boomlagoon Ltd.

В моей игре мне нужны были только поля user_id, username, friend_ids и avatar_url.

Я получил их следующим образом:

{
var userData = JSONObject.Parse(loaderData.Text);
if (userData.GetBoolean(«success»))
var profile = new KongregateProfile(userData);
}

,где JSONObject.Parse — метод класса Boomlagoon.JSON.JSONObject, который преобразует строку в JSON объект, а loaderData.Text — строка, полученная в результате запроса http://api.kongregate.com/api/user_info.json?user_id={0}&friends=true, где {0} — Kongregate id пользователя.

Теперь рассмотрим конструктор KongregateProfile, который получает данные из JSON объекта:

public KongregateProfile(JSONObject userInfo)
{
userId = ((int) userInfo.GetNumber(«user_id»)).ToString();
username = userInfo.GetString(«username»);
if (userInfo.GetBoolean(«success»))
{
var userVars = JSONObject.Parse(userInfo.GetObject(«user_vars»).ToString());
avatarUrl = userVars.GetString(«chat_avatar_url»);
avatarUrl = avatarUrl.Replace(«16×16», «50×50»);
}
else
Debug.LogWarning(«Can not load avatar!»);
}

При использовании userVars.GetString(«avatar_url») возвращался аватар размером 1х1, поэтому пришлось использовать конструкцию

avatarUrl = userVars.GetString(«chat_avatar_url»);
avatarUrl = avatarUrl.Replace(«16×16», «50×50»);

При использовании запроса на получение информации о нескольких пользователях на выходе получаем JSON объект, состоящий из массива JSON объектов. Профили друзей я получил следующим образом:

{
var profiles = new List<IKongregateProfile>();
var playerInfo = JSONObject.Parse(loaderData.Text); // loaderData.Text — GET url: http://api.kongregate.com/api/user_info.json?user_id={0}&friends=true
var friendsString = playerInfo.GetValue(«friend_ids»).ToString();
friendsString = friendsString.Remove(0, 1); //removing «[» in the beginning
friendsString = friendsString.Remove(friendsString.Length — 1); //removing «]» in the end
var friends = JSONObject.Parse(data.Text); // data.Text — GET url: http://api.kongregate.com/api/user_info.json?user_ids= + friendsString
if (friends.GetBoolean(«success»))
{
foreach (var friend in friends.GetArray(«users»))
{
var friendData = JSONObject.Parse(friend.ToString());
var profile = new KongregateProfile(friendData);
profiles.Add(profile);
}
}
}

In-app purchases

На Kongregate внутриигровые покупки осуществляются следующим образом: пользователь совершает покупку, и предмет заносится в инвентарь на Kongregate. Для того, чтобы использовать предмет, нужно знать его идентификатор, который он получил при занесении в инвентарь. Для использования внутриигровых покупок нам снова понадобится наш объект KongregateLoader.

Для того, чтобы пользователь купил предмет, необходимо сначала создать его на Kongregate. Это делается следующим образом: переходим к настройкам игры http://www.kongregate.com/games/MyUser/GAME_NAME/items, где GAME_NAME – название вашей игры. Создаем покупку нажав на New Item и указываем все нужные данные. Идентификатор должен быть уникальным.

Часть кода из KongregateLoader, которая будет использована для внутрииигровых покупок

private Action onPurchaseSuccess = delegate { };
private Action<string> onPurchaseError = delegate { };
public void PurchaseItem(string item, Action onSuccess, Action<string> onError)
{
onPurchaseSuccess = onSuccess;
onPurchaseError = onError;
CallJSFunction(string.Format(«purchaseItem(‘{0}’)», item), null);
}
private void OnPurchaseSuccess()
{
var handler = onPurchaseSuccess;
if (handler != null)
handler();
}
private void OnPurchaseError()
{
var handler = onPurchaseError;
if (handler != null)
handler(«KongregateLoader: failed purchase»);
}
public void LogMessage(string message)
{
Debug.Log(message);
}
public void CallJSFunction(string functionCall, string callback)
{
if (isKongregate)
{
if (string.IsNullOrEmpty(callback))
{
Application.ExternalEval(functionCall);
}
else
{
Application.ExternalEval(string.Format(@»
var value = {0};
SendUnityMessage(‘{1}’, String(value));
«, functionCall, callback));
}
}
}

Также нам понадобится JavaScript из метода OnKongregateAPILoaded, код которого приведен выше в разделе «Загрузка API».

Для совершения покупки выполним следующий метод:

public void BuyItem(string itemId, Action onSuccess, Action<string> onError)
{
if (isLoggedIn) //checking if user is logged in
kongregateLoader.PurchaseItem(itemId, onSuccess, onError);
}

, где itemId — уникальный идентификатор, который вы ввели при создании нового предмета.

В случае успеха предмет появится в инвентаре. Для использования предмета нам понадобится его id. Таким образом, сначала необходимо получить содержимое инвентаря пользователя в вашей игре. Сделать это можно следующим способом:

public void GetItems(Action<Dictionary<string, string>> onGetItems)
{
if (isLoggedIn)
{
var itemsJSON = JSONObject.Parse(data.Text); //data.Text — GET url: http://api.kongregate.com/api/user_items.json?api_key={0}&user_id={1}, {0} — api key (http://www.kongregate.com/games/MyUser/GAME_NAME/api), {1} — Kongregate ID
Dictionary<string, string> items = new Dictionary<string, string>();
foreach (var item in itemsJSON.GetArray(«items»))
{
var i = JSONObject.Parse(item.ToString());
items.Add(((int)i.GetNumber(«id»)).ToString(), i.GetString(«identifier»));
}
var handler = onGetItems;
if (handler != null)
handler(items);
}
}

Теперь рассмотрим HTTP запрос, который позволяет использовать предмет с ограниченным количеством использований, принадлежащий пользователю. Чтобы его использовать, вы должны ввести id экземпляра этого предмета. Этот id мы получили из инвентаря пользователя с помощью метода GetItems. Отправка этого запроса для предмета с неограниченным количеством использований приведет к ошибке.

Для использования предмета необходимо отправить запрос: POST url: http://api.kongregate.com/api/use_item.json

необходимые параметры:

api_key: api key вашей игры (http://www.kongregate.com/games/MyUser/GAME_NAME/api)

game_auth_token: game_auth_token пользователя (был получен при логине)

user_id: Kongregate ID

id: id (из инвентаря пользователя) экземпляра предмета

результат:

success: true/false в зависимости от того, был ли запрос успешным

error: код ошибки типа integer в случае ее возникновения

error_description: описание кода ошибки типа string, в случае ее возникновения

в случае успеха:

remaining_uses: обновленное количество оставшихся использований

usage_record_id: уникальный id использования

Если remaining_uses = 0, то экземпляр предмета удаляется из инвентаря пользователя.

Вот, в принципе, все что нужно для того, чтобы опубликовать свою игру на Kongregate и настроить внутриигровые покупки.

Unity Database (UDB)

Finally on Assets Store! EJaw presents our own product.

Unity Database (UDB) — it is a flexible and convenient system for keeping and working with data such as the standard types of int, float, string, etc, as well as prefabs, GameObjects and your unique components. Data storage is based on a built-in tool serializing in Unity. Developer needs to create several classes and to fill in the fields in the editor, all the rest will be done by UDB. Exterior view is a convenient system of windows. For the core of library a universal module is written, which does not require additional settings.UDB — is the perfect solution for quick and easy work with the data.

 

 

Friends, We are happy to tell you about our just released «Trick a Slick» game on FaceBook

This game will show how much you’re cool, trick and slick 🙂

Our team will be happy for any comments and reviews from you.
Solve all this puzzles and reach the highest level. Prove that you are the smartest one in this game Смайлик «wink»

Game description:

It’s time for moneybags to share their fortune.
During the Lawless Age they’ve stolen a lot more money than they need. Now it’s time to take it back. All of it.
Are you the one to sneak into the enemy territory and get back safe with all the loot? Or is there someone better?
Use your intuition and agility on unique hand-crafted levels.
Boast your results among friends and compete for the record-breaking scores with other players for you are the trickiest and slickest thief in town.

 


Map

Map