Серия уроков Создание онлайн игр / Уроки по Unreal Engine 4

From ИМЛ Вики
Jump to: navigation, search

#1 - создание listen сервера игры и главного меню

Listen Server
Dedicated Server


  • Создание проекта с шаблоном от 3 лица на версии движка 4.24 по следующему тз
блок схема проекта


  • Для создания игры (хоста) в главном меню, при нажатии на кнопку "создать игру" вызывается функция "Open Level" c названием игровой карты и опцией "listen"
  • Для подключения к серверу в главном меню есть поле для ввода IP-адреса сервера и кнопка "подключиться". При нажатии на кнопку "подключиться", вызывается функция "Open Level" куда в качестве названия карты подается IP-адрес.
MainMenu


Возможные проблемы


#2 - создание выделенного (dedicated) сервера

План урока


  • Учимся работать с аргументами приложения. Пишем простейшую консольную программу на си шарпе и выводим на экран ее аргументы.
static void Main(string[] args)
{
     foreach (var item in args)
     {
          Console.WriteLine(item);
     }
     Console.ReadKey();
}
  • Модифицируем проект игры. Убираем из главного меню кнопку "создать игру". Устанавливаем в настройках Server Default Map.
  • Создаем два .bat файла для запуска игры и выделенного сервера

StartGame.bat

"C:\UE4_4.24.3_S\Engine\Binaries\Win64\UE4Editor.exe" "I:\OnlineGame3\OnlineGame.uproject" -game

StartServer.bat

"C:\UE4_4.24.3_S\Engine\Binaries\Win64\UE4Editor.exe" "I:\OnlineGame3\OnlineGame.uproject" -server -log -port=7778
Популярные аргументы


  • Собираем выделенный сервер
Собирка Выделенного Сервера


1. Меняем версию движка на движок собранный с исходников.
2. Если нужно, создаем любой с++ класс в проекте.
3. Создаем файл «[названиеПроекта]ServerTarget.cs» в папке "Source" проекта

using UnrealBuildTool;
using System.Collections.Generic;

public class [названиеПроекта]ServerTarget : TargetRules
{
	public [названиеПроекта]ServerTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Server;
		DefaultBuildSettings = BuildSettingsVersion.V2;

		ExtraModuleNames.AddRange( new string[] { "[названиеПроекта]" } );
	}
}

4. Нажимаем «Generate Visual Studio project files».
5. Собираем игру
6. Собираем сервер
7. Копируем файлы сервера в игру

projectNameServer.exe, projectNameServer.exp, projectNameServer.lib

из

projectFolder/Binaries/Win64

в

compiledGameFolder/projectName/Binaries/Win64

#3 - Пересоздание проекта на новой версии движка 4.26, с поддержкой с++ ахитектуры и упорядоченой структурой папок

  • О важности соответствия требуемой версии Visual Studio и рабочих нагрузок
  • Переработка старого или создание нового 4.26 проекта
  • Создание блупринт класса через промежуточный с++ класс
  • Переопределение родительского класса у блупринта
  • Пересоздание предыдущего проекта с с++ архитектурой и упорядоченой структурой папок
Блупринтовая и с++ архитектура


Упорядоченная структура папок


#4 - Git

#5 - Обзор плана на курс, что предстоит создать. Необходимые умения и список используемых технологий.

  • План курса, фичи которые нужно воплотить:
  1. Веб сайт с регистрацией, авторизацией и ссылкой на скачивание клиента игры
  2. Вход игру под созданной учетной записью
  3. Создание и кастомизация персонажа (раса, пол)
  4. Получение списка персонажей
  5. Невозможность создания двух персонажей с одним и тем же ником
  6. Удаление персонажа
  7. Вход персонажем в игру
  8. Невозможность войти в игру одним и тем же персонажем/аккаунтом два раза одновременно с разных окон
  9. Отображение ника персонажа (по той же системе можно передать все остальное, квесты, инвентарь, разные 3д модели для разных рас и т. д.)
  10. Сохранение координат (по той же системе сохраняется все остальное) персонажа. При повторном заходе на сервер персонаж появится там же где он был на момент выхода из игры
  11. Игровой чат для всех игроков на сервере
  • Схема входа в игру
Join server schema


Game-server methods


  • Список всех API методов логин сервера

Client safe methods:
Log In ( Send: login, pass, GameClientVersion. Receive: Status, UserId, UserToken. )
Log Out ( Send: UserId, UserToken. Recieve: Status. )
Read Slots ( Send: UserId, UserToken. Recieve: Status, List of characters. )
Create Character ( Send: UserId, UserToken, NickName, Race, Gender. Recieve: Status. )
Delete Chatacter ( Send: UserId, UserToken, CharacterId. Recieve: Status. )
Start Game ( Send: UserId, UserToken. Recieve: Status, ServerIp. )

Server only methods:
Read Character ( Send: ServerLogin, ServerPassword, CharacterId. Recieve: Status, Character. )
GlobalServerSync ( Send: ServerLogin, ServerPassword, SyncData. Recieve: Status, SyncData. )
Log Out And Save ( Send: ServerLogin, ServerPassword, Character. Recieve: Status. )

SyncData
{
List<Characters> Characters;
List<String> MustBeLogOutedUserIds;
}

MustBeLogOutedUserIds
{
Game Server to Login Server:
Users which was no safe disconnected, without LogOut method calls.
They are currently offline, but they cannot logIn anymore, cause in database they are still isOnline = true.
Method must set isOnline = false for all this users in database.

Login Server to Game Server:
Users which try to logIn with currently online accounts.
Game server must forced kick this users from game server and put them to MustBeLogOutedUserIds queue like non safe disconnected;
}

  • Требуемые навыки:
  1. Unreal Engine - средний.
  2. C# language - средний.
  3. Mictosoft Asp.Net - базовый.
  • Список используемых технологий:
  1. Unreal engine
  2. VaRest Unreal Engine plugin
  3. Asp.Net Core mvc + web api
  4. Asp.Net Core Entity Framework
  5. Asp.Net Core Identity
  6. Microsoft SQL Server

#6 - Устанавливаем Microsoft SQL Server и утилиту SQL Server Management Studio. Создаем пустой ASP.NET Core проект.

  • Устанавливаем MSSQL Server и SQL Server Management Studio.
Скачиваем Microsoft SQL Server 2019 Express


Выбираем базовую установку


Соглашаемся


Нажимаем установить


Дожидаемся установки


Обязательно сохраняем строку подключения и нажимаем Install SSMS


SQL Server установлен, теперь нужно установить SQL Server Management Studio

В открывшимся окне скачиваем SQL Management Studio


Нажимаем установить


Ожидаем


Подтверждаем перезагрузку компьютера


Запускаем SQL Server Management Studio


Нажимаем Connect


Готово


MSSQL Server и SQL Server Management Studio успешно установлены

  • Создаем пустой ASP.NET Core проект.
Создаем новый проект в Visual Studio 2019


Выбираем из списка ASP.NET Core Web App


Указываем имя проекта и нажимаем создать


Выбираем пустой проект, он же Empty Project


Пытаемся запустить приложение


Работает!


Пустой ASP.NET Core проект создан.

#7 - Установка необходимых NuGet пакетов и требуемых client side библиотек.

  • Установка необходимых NuGet пакетов
NuGet - оффициальный менеджер пакетов .NET


Небоходимые нам NuGet пакеты:

  1. Microsoft.EntityFrameworkCore.SqlServer - Entity Framework - ORM система, для работы с базой данных MSSQL Server которую мы установили на предыдущем уроке;
  2. Microsoft.EntityFrameworkCore.Tools - Дополнение к Entity Framework для автоматического создания базы данных на основании созданных нами классов, подход Code First;
  3. Microsoft.AspNetCore.Identity.EntityFrameworkCore - Общепринятая библиотека с функционалом для регистрации, авторизации, работы с правами пользователей;
  4. Microsoft.AspNetCore.Mvc.NewtonsoftJson - Библиотека для возможности создавать сложные JSON форматы;
  5. Swashbuckle.AspNetCore - Swagger. Замечательная библиотека для наглядного тестирования и документации WEB API.
Как открыть менеджер пакетов NuGet


Как найти и установить NuGet пакет


Принять


Пакет успешно установлен


Теперь нам нужно установить все остальные требуемые NuGet пакеты

Все необходимые NuGet пакеты установлены


  • Установка необходимых client side библиотек из "cdnjs"
Cdnjs - еще один менеджер библиотек


Требуемые библиотеки:

  1. twitter-bootstrap - для базового дизайна сайта
  2. jquery - просто так
Как добавить библиотеку через cdnjs


Как найти и установить требуемую библиотеку


Bootstrap успешно добавлен


Внутри libman.json


Теперь точно так же нужно добавить вторую и последнюю библиотеку JQuery

Bootstrap и JQuery успешно установлены


libman.json:

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "library": "twitter-bootstrap@4.6.0",
      "destination": "wwwroot/lib/bootstrap/"
    },
    {
      "library": "jquery@3.6.0",
      "destination": "wwwroot/lib/jquery/"
    }
  ]
}

Готово.

#8 - Настройка файла appsettings.json, создания структуры папок и моделей сущностей

  • Настройка appsettings.json

Файл appsettings.json в ASP.NET Core это конфигурационный файл, куда можно выносить необходимые переменные вместо того что бы их хадкодить. По-умолчанию данный файл выглядит так:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Но нам нужно поместить внутри него:

  1. Строку подключения к базе данных
  2. IP-адрес игрового сервера Unreal Engine
  3. Требуемую версию клиента игры

Сейчас наш appsettings.json файл выглядит следующим образом:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=testDB;Trusted_Connection=True;"
  },
  "GameServerIPs": {
    "DefaultServer": "127.0.0.1:7777"
  },
  "GameClienVersions": {
    "Current": "1"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}
  • Создание структуры папок

Сейчас наш проект выглядит слеющим образом:

Текущая структура папок


Нам же сейчас необходимо создать следующие папки:

Новая структура папок


  • Создание моделей сущностей

Нам наобходимо создать два класа сущностей PlayerUser.cs и Character.cs
Давайте сначала создадим файл PlayerUser.cs в папке Models/Entities:

Создание класса PlayerUser.cs 1/3


Создание класса PlayerUser.cs 2/3


Создание класса PlayerUser.cs 3/3


PlayerUser.cs:

using Microsoft.AspNetCore.Identity;

namespace TutorialLoginServerV2.Models.Entities
{
    public class PlayerUser : IdentityUser
    {
        public string AuthToken { get; set; }
        public bool IsOnline { get; set; } = false;
        public bool MustBeLogOuted { get; set; } = false;
        public bool IsBanned { get; set; } = false;
    }
}

Теперь нам нужно создать второй и последний класс модели Character.cs в папке Models/Entities

Создание класса Character.cs


Character.cs:

namespace TutorialLoginServerV2.Models.Entities
{
    public class Character
    {
        public int Id { get; set; }
        public string OwnerId { get; set; }
        public string Nickname { get; set; }
        public int Race { get; set; } // 0 - Human, 1 - Elf, 2 - Orc
        public int Gender { get; set; } // 0 - Male, 1 - Female
        public int Experiance { get; set; } = 0;
        public float LocationX { get; set; } = 0.0f;
        public float LocationY { get; set; } = 0.0f;
        public float LocationZ { get; set; } = 0.0f;
        public float RotationX { get; set; } = 1.0f;
        public float RotationY { get; set; } = 1.0f;
        public float RotationZ { get; set; } = 1.0f;
    }
}

Готово.

#9 - Создание пустого класса EngineManager и класса MainDbContext

  • Создание пустого класса EngineManager

В предыдущем эпизоде мы создавали папку "Services" для модуля EngineManager.
Модуль EngineManager не является обязательным в нашем проекте, мы его создаем лишь для удобства, дабы инкапсулировать часть логики из контроллеров, что сделает код более чистым и удобочитаемым.
Модуль EngineManager будет подключен в механизм внедления зависимостей как синглтон.
Сегодня бы создадим пустую болванку класса EngineManager внутри папки "Services".

Создание пустого EngineManager 1/3


Создание пустого EngineManager 2/3


Создание пустого EngineManager 3/3


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TutorialLoginServerV2.Services
{
    public class EngineManager
    {
    }
}
  • Создание класса MainDbContext

Database Context Class (сокращенно DbContext) это часть добавленной нами в проект ранее Entity Framework Core ORM системы.
Этот класс является объектно-оринтированным представлением базы данных, как обычный c# объект.
Внутри него мы поместим классы сущностей, такие как PlayerUser.cs и Character.cs в качестве массивов. Точнее не обычных массивов, а специфических коллекций DbSet, параметризированных типами наших сущностей.
В будущем мы выполнем специфическую процедуру под названием "миграция" впоследствии чего внутри нашей базы данных Microsoft SQL Server будут автоматически созданы таблицы на основании наших сущностей.
Переменная DbSet<PlayerUser> создаст таблицу PlayerUser и переменная DbSet<Character> создаст таблицу Characters.
Когда мы закончим настройку приложения и базы, у нас появится возможность выполнять операции Создания, Чтения, Обновления и Удаления из базы с помощью обычных методов объекта Database Context. Когда-то нам бы пришлось вручную писать SQL-запросы.

Создание MainDbContext.cs 1/3


Создание MainDbContext.cs 2/3


Создание MainDbContext.cs 3/3


using TutorialLoginServerV2.Models.Entities;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace TutorialLoginServerV2.Models
{
    public class MainDbContext : IdentityDbContext
    {
        public MainDbContext(DbContextOptions<MainDbContext> options) : base(options)
        {

        }

        public DbSet<PlayerUser> PlayerUsers { get; set; }
        public DbSet<Character> Characters { get; set; }
    }
}

Обратите внимание, что наш класс MainDbContext наследуется не от обычного DbContext, а от IdentityDbContext.
Так происходит потому что мы работаем не с чистым Entity Framework, а с Entity Framework в связке с системой Identity.
Если в двух словах, то созданный нами класс "MainDbContext" наследуется от класса "IdentityDbContext", а класс "IdentityDbContext" в свою очередь наследуется от класса "DbContext"
Так же как и EngineManager, MainDbContext класс будет подключен в механизм внедрения зависимостей.

Готово.

#10 - Создание класса Startup и инициализация базы данных

  • Создание класса Startup

Класс Startup.cs является одним из важнейших классов в приложении asp.net core. Этот класс отрабатывает каждый раз при запуске приложения и настраивает его.
Внутри данного класса мы подгружаем файл настроек, добавляем сервисы, конфигурируем их, регистрируем их в механизм внедрения зависимостей, что бы их могли использовать другие классы, настраиваем шаблоны MVC и многое другое.

Сейчас ваш класс Startup.cs должен выглядить примерно следующим образом:

Startup.cs в данный момент


Но нам сегодня предстоит выполнить следующие действия в нем:

  1. Создадим конструктор в котором мы загрузим в переменную Configuration все настройки которые мы помещали в файл appsettings.json в 8 уроке. В этом нам поможет встроенная система внедрения зависимостей.
  2. Зарегистрируем созданный нами в предыдущем уроке EngineManager, в качестве сервиса, в механизме внедрения зависимостей.
  3. Зарегистрируем созданный нами в предыдущем уроке MainDbContext, в качестве сервиса, в механизме внедрения зависимостей. Собственно это является так же добавлением EntityFramework.
  4. Зарегистрируем добавленный в качестве NuGet пакета в 7 епизоде Identity, в качестве сервиса, в механизме внедрения зависимостей.
  5. Настроим систему Identity.
  6. Зарегистрируем систему Model View Controller, по факту сделаем наше приложение MVC.
  7. Зарегистрируем добавленный в 7 эпизоде, в качестве NuGet пакета Swagger.
  8. Активируем Swagger.
  9. Активируем Swagger UI.
  10. Активируем Developer Exception Page. По факту включим более подробные сообщения об ошибках, для более удобной отладки приложения.
  11. Откроем доступ для папки со статическими файлами "wwwroot".
  12. Активируем возможность аутентификации.
  13. Активируем возможность авторизации.
  14. Настроим маршрутизацию контроллеров MVC. Для примера если мы откроем сайт http://example.com/account/register тогда в нашем приложении запустится метод Register' внутри контроллера Account.
  15. Создадим специальный метод, которым мы добавим в нашу базу данных первого пользователя Админа, а так же создадим для него роль администратора.

Краткое содержание класса Startup.cs:

Startup.cs вкратце


Полный класс Startup.cs:

using TutorialLoginServerV2.Models;
using TutorialLoginServerV2.Models.Entities;
using TutorialLoginServerV2.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using System;
using System.Threading.Tasks;

namespace TutorialLoginServerV2
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<EngineManager>();
            services.AddDbContextPool<MainDbContext>(options => options.UseSqlServer(Configuration.GetSection("ConnectionStrings")["DefaultConnection"]));
            services.AddIdentity<PlayerUser, IdentityRole>().AddEntityFrameworkStores<MainDbContext>();

            services.Configure<IdentityOptions>(options =>
            {
                options.Password.RequiredLength = 6;
                options.Password.RequiredUniqueChars = 0;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireDigit = false;
                options.User.RequireUniqueEmail = true;
                options.SignIn.RequireConfirmedEmail = false;
            });

            services.AddMvc();

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "Login Server", Version = "v2" });
            });

            services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider services)
        {
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoginServer v2"));


            app.UseDeveloperExceptionPage();
            app.UseStaticFiles();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}");
            });

            //CreateDefaultAdminRoleAndAdminUser(services).Wait();
        }

        private async Task CreateDefaultAdminRoleAndAdminUser(IServiceProvider serviceProvider)
        {
            RoleManager<IdentityRole> RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            UserManager<PlayerUser> UserManager = serviceProvider.GetRequiredService<UserManager<PlayerUser>>();

            string loginForNewAdmin = "Administrator";
            string emailForNewAdmin = "admin@mail.ru";
            string passForNewAdmin = "grefdsrthdfgrbd45";

            bool roleCheck = await RoleManager.RoleExistsAsync("Admin");
            if (!roleCheck)
            {
                await RoleManager.CreateAsync(new IdentityRole("Admin"));
            }

            PlayerUser userCheck = await UserManager.FindByEmailAsync(emailForNewAdmin);
            if (userCheck == null)
            {
                PlayerUser newAdminUser = new PlayerUser { UserName = loginForNewAdmin, Email = emailForNewAdmin };
                await UserManager.CreateAsync(newAdminUser, passForNewAdmin);
                await UserManager.AddToRoleAsync(newAdminUser, "Admin");
            }
        }
    }
}

Маршрут (route): "{controller=Home}/{action=Index}"); работает следующим образом, что бы открыть страницу по-умолчанию на нашем сайте, приложение попытается найти контроллер "Home" и запустить в нем метод "Index".
У нас пока еще не создан контроллер "Home" и потому если мы сейчас попытаемся запустить наше приложение, то мы получим ошибку 404 страница не найдена. Это норма, мы создадим данные классы в следующих видео.

  • Инициализация базы данных

На данный момент мы уже создали модели сущностей, такие как PlayerUser.cs и Character.cs.
Мы создали модель контекста базы данных MainDbContext.cs внутри которой мы установили желаемые таблицы в базе данных на основании наших сущностей.
Мы поместили строку подключения к базе данных в файл appsettings.json, откуда в классе Startup.cs мы ее прочитали и записали в переменную Configuration.
Мы так же зарегистрировали наш класс контекста базы данных MainDbContext.cs внутри метода ConfigureServices внутри класса Startup.

services.AddDbContextPool<MainDbContext>(options => options.UseSqlServer(Configuration.GetSection("ConnectionStrings")["DefaultConnection"]));

Но тем не менее если мы зайдем в SQL Server Management Studio, то мы увидим, что никакой новой базы данных все еще не было создано.
И если мы попытаемся выполнить любой метод предполагающий обращение к базе данных, то мы увидим сообщение об ошибке.
Давайте разкомментируем следующую строку (что бы попытаться добавить пользователя Админа в базу данных):

//CreateDefaultAdminRoleAndAdminUser(services).Wait();

После чего запустим наше приложение..

Ошибка! Невозможно подключиться к базе данных "TutorialTestDB"!


Как вы можете помнить, в предыдущих уроках я говорил вам, что для автоматического создания базы данных на основании созданных c-sharp классов, мы должны выполнить некую магическу операцию под странным названием "миграция".
Что бы ее выпонить нужно сначала открыть "Package Manager Console":

Как открыть Package Manager Console


После чего нам нужно выполнить нашу первую "миграцию":

Как выполнить "миграцию" 1/4


Как выполнить "миграцию" 2/4


Как выполнить "миграцию" 3/4


Как выполнить "миграцию" 4/4


Теперь мы можем открыть SQL Server Management Studio и проверить была ли создана наша база данных:

Новосозданная база данных


Давайте посмотрим на таблицу PlayerUsers:

В таблице "PlayerUsers" нету пользователей


Давайте еще раз разкомментируем следующую строку:

//CreateDefaultAdminRoleAndAdminUser(services).Wait();

После чего запустим приложение еще раз:
Теперь все работает!! Да, мы все еще видим ошибку "404 Error", но так и должно быть. Больше не выскакивают исключения, а значит наше приложение заработало с базой данных!
Давайте еще раз заглянем в таблицу PlayerUsers:

Мы успешно добавили первого пользователя - Админа!


Готово.