?

Log in

блог на zoxt

На блоге я уже давненько выложил цикл уроков по управлению персонажем в Юнити.

С тех пор прошло много времени. У нас на работе кардинальные изменения. Объединили с местным оператором связи. Почти половина из старого коллектива уволились. Здание в котором работали продается. Всех раскидали по разным местам. В-общем изменения большие.

Но вроде сейчас все успокаивается. Так что можно вернуться к нашим баранам.

Оригинал записи и комментарии на LiveInternet.ru

ту-ту-ту...

На блоге я уже давненько выложил цикл уроков по управлению персонажем в Юнити.

С тех пор прошло много времени. У нас на работе кардинальные изменения. Объединили с местным оператором связи. Почти половина из старого коллектива уволились. Здание в котором работали продается. Всех раскидали по разным местам. В-общем изменения большие.

Но вроде сейчас все успокаивается. Так что можно вернуться к нашим баранам.

У меня свой сайт/блог!!!

В-общем смотрите сами:
САЙТ

Я там уже кучу уроков по Юнити написал. Так что если кому интересна Unity3d - заходите, читайте...

Эта задача достаточно сложна для первого урока, поэтому будем реализовывать ее поэтапно.

На первом этапе реализуем простое перемещение персонажа и вращение камеры вокруг него. Коллизий у камеры пока не будет. Т.е. она будет проходить сквозь объекты сцены. Персонаж будет просто перемещаться, без прыжков, без воздействия гравитации. Все что недостает будет реализовано на втором этапе.

Итак. Что нам нужно.

    Организовать ввод с клавиатуры. Преобразовать введенные данные в удобные для перемещения в трехмерном пространстве.
    Организовать ввод с мышки. На основании этих данных перемещать камеру вокруг персонажа.
    На основе данных пункта 1 произвести собственно перемещение персонажа. На основании данных пункта 2 при необходимости произвести поворот персонажа.

Соответственно этим трем пунктам у нас будет три класса - CharController, CharCamera и CharMotor. Разберем первый подробнее.

Что он должен делать? Основные функции:

    Получить данные с клавиатуры.
    Построить из них вектор движения.
    Передать этот вектор классу CharMotor.

Дополнительные функции:

    Начальный поиск камеры.

Какие нам нужны данные в этом классе?

    Результат - вектор движения.
    Ссылка на компонент - CharacterController, который должен быть на персонаже, т..к. движение персонажа на этом этапе мы будем осуществлять именно с его помощью.
    Ссылка на себя - на класс CharController. Для чего это нужно? Это поле мы сделаем статическим, т..е. оно будет доступно из любого скрипта в сцене. Тем самым мы просто облегчим себе взаимодействие наших скриптов.

Какие методы будут у нас?

    Awake() - Этот метод вызывается сразу после того как наш класс появится в сцене. Здесь нужно производить первоначальные установки. У нас здесь мы запомним ссылку на себя, найдем ссылку на CharacterController и попросим у класса камеры найти камеру в сцене.
    Update() - Этот метод вызывается каждый кадр в игре. Здесь в-общем то и производится все действие. Сначала мы тут проверим существование камеры - если она вдруг исчезла прекращаем работу. Затем получаем данные с клавиатуры и вычисляем вектор движения. И напоследок говорим CharMotor что можно двигать персонаж.
    GetInput() - Чтобы не загромождать метод Update (тем более, что в будущем он будет делать гораздо больше) вынесем получение данных с клавиатуры и рассчет вектора движения в отдельный метод.

Рассмотрим подробнее третий метод - GetInput().

Как можно получить данные введенные игроком? Можно опрашивать конкретные клавиши методом Input.GetKeyUp. Однако это очень неуниверсальный метод. Есть другой способ - с использованием Input.GetAxis. Он выдает значение положения по виртуальной оси в диапазоне [-1, 1] и не зависит от устройства ввода, т.е. его можно использовать с клавиатурой, джойстиком или даже с мышкой, если понадобится. Однако при этом появляется неприятный эффект дрожания вблизи нулевых значений. Можно это предусмотреть введя некую переменную "мертвую зону" и производя любые действия, если введенные игроком изменения больше ее значения.

Названия виртуальных осей метода Input.GetAxis - Vertical и Horizontal. Обычно они соответствуют движениям персонажа вперед/назад и влево/вправо соответственно. Поэтому значение по оси Vertical преобразуем в движение по оси Z, а Horizontal - X.

    Итак сначала мы обнуляем вектор движения.
    Затем проверяем вышли ли введеные значения из мертвой зоны, и если вышли прибавляем введенные значения к вектору движения.

Теперь у нас все готово, чтобы написать наш класс CharController:

  1. using UnityEngine;  
  2.   
  3. class CharController : MonoBehaviour  
  4. {  
  5.     public static CharacterController unityController;  
  6.     public static CharController instance;  
  7.     public Vector3 move;  
  8.   
  9.     private const float _DEAD_ZONE = 0.1f;  
  10.   
  11.     void Awake()  
  12.     {  
  13.         instance = this;  
  14.         unityController = GetComponent("CharacterController"as CharacterController;  
  15.         CharTPSCamera.GetCamera();  
  16.     }  
  17.   
  18.     void Update()  
  19.     {  
  20.         if(Camera.mainCamera == nullreturn;  
  21.         GetInput();  
  22.         CharMotor.instance.UpdateMotor();  
  23.     }  
  24.   
  25.     private void GetInput()  
  26.     {
  27.         float vert = Input.GetAxis("Vertical");  
  28.         float horiz = Input.GetAxis("Horizontal");  
  29.   
  30.         move = Vector3.zero;  
  31.         if (vert > _DEAD_ZONE || vert < -_DEAD_ZONE)  
  32.             move += new Vector3(vert, 0, 0);  
  33.         if (horiz > _DEAD_ZONE || horiz < -_DEAD_ZONE)  
  34.             move += new Vector3(0, 0, horiz);  
  35.         
  36.     }  
  37. }  

UDK vs Unity3d

Полугодичный перерыв!
Я весь в этом. Загорюсь, вспыхну - потухну...

Ну - это лирика. А дело в том, что US меня достал окончательно. В результате я перешел в Unity3D.
Он, конечно, дает менее красивую картинку. Однако - огромный плюс - там стандартный C#! Программировать в нем - одно удовольствие. Конечно нужно изучить API, но в остальном в нем лучше.

PS: попробую все же тут пописывать, не забрасывать так надолго...

Оригинал записи и комментарии на LiveInternet.ru

Итератор

Сколько ж ограничений в UnrealScript!

Вот, оказывается  свой итератор нельзя объявить.  Функция-итератор должна быть нативной. Нативную функцию можно объявить только в нативном классе. А нативный класс просто нельзя объявлять...

Но я это обошел.

Создаем функцию, подобную итератору, например:

function RPGInvActor(class<Inventory> BaseClass, out Inventory Inv)

Она подобна итератору:

native final iterator function InventoryActors( class<Inventory> BaseClass, out Inventory Inv );

из класса  InventoryManager. Единственное условие - в Inv должна вернуть None, если не нашла.

Теперь вместо foreach используем:

do
{
  ...
} until( Inv != None);

 

Естестсвенно RPGInvActor должна при каждом вызове возвращать следующее значение. Этого можно достичь введя переменную в классе, которая сохраняет предыдущее найденное значение:

var Inventory        InvCash;            //Для итератора

И использовать ее для нахождения следующего айтема в списке для поиска...

Для чего мне все это понадобилось? Для своей реализации инвентаря.

Инвентарь у Эпиков представляет собой обычный односвязный список. Естественно поиск в нем не быстр, естественно Инвентаря в нем много хранить нельзя (Эпики об этом прямо пишут). Более того, хоть это и возможно принципиально, но у Эпиков в инвентарь нельзя добавлять одинаковые предметы.

Ну а в ролевых играх в рюкзаке у героя очень много предметов, очень много одинаковых...

Как это реализовать? - сделать свою структуру для инвентаря. Я сделал комбинированную.

Есть динамический массив, элементы которого - первые элементы в связанных списках. Тип элемента в массиве повторяться не может. А вот в списке, на который указывает элемент массива как раз наоборот - все элементы одного типа.

 

Оригинал записи и комментарии на LiveInternet.ru

Тултипсы

Сделал тултипсы к объектам. Появляются, когда ГГ смотрит на объект. Работают на всех типах объектов.

Работы еще много - главное на данный момент вывести его не где попало а чуть выше объекта. К сожалению разные типы имеют разные переменные высоты. К тому же у скелеталмешей почему-то боундинг в одном измерении значительно больше самого предмета... Вроде я алгорите придумал не зависящий от всего этого, 

но нужно проверять еще...

good (393x505, 28 Kb)

Оригинал записи и комментарии на LiveInternet.ru

Tags:

Камеры в УДК

В УДК есть две основные ветки классов камер:

Camera -> GamePlayerCamera -> ВашаКамера

GameCameraBase ->  GameThirdPersonCamera/GameFixedCamera -> Ваши реализации камер от третьего лица и фиксированной.

и одна дополнительная:

 GameThirdPersonCameraMode -> GameThirdPersonCameraMode_Default -> Ваша камера. В них просто уточняются характеристики камеры.

Есть еще класс CameraActor, представляющий физическое воплощение камеры в мире.

Остальные классы связанные с камерами - служебные, представляют анимацию камеры и т.п.

Где происходит перемещение камеры, настройка фокуса и т.п.

В GamePlayerCamera есть функция UpdateViewTarget. Там кроме прочего вызывается CurrentCamera.UpdateCamera. CurrentCamera изначально GameThirdPersonCamera (или Fixed).

Функция UpdateCamera в нем кроме прочего вызывает PlayerUpdateCamera. В GameThirdPersonCamera PlayerUpdateCamera пустая.Также UpdateCamera вызывает Pawn.CalcCamera. При использовании UTPawn все рассчеты производятся именно там. Что не очень удобно.

Какой нормальный процесс. Создаем класс производный от GameThirdPersonCamera (RPGTPS). В нем определяем UpdateCamera. Там просто переписываем все из GameThirdPersonCamera для вызова нашей PlayerUpdateCamera, которую тоже определяем в RPGTPS. Так приходится делать, т.к. UpdateCamera не виртуальная (можно попробовать переписать GameThirdPersonCamera - сделать UpdateCamera виртуальной, тогда ее не нужно будет снова писать  в нашем классе. Тогда придется и в GameCameraBase). В PlayerUpdateCamera производим все нужные нам рассчеты.

Однако все рассчеты удобнее проводить не в RPGTPS, а в классе производном от GamePlayerCamera. Почему? Потому что там есть многие нужные для рассчета переменные, которые, конечно можно достать и в RPGTPS, но это куча лишних вызовов... Поэтому в RPGTPS делаем простую "затычку", типа:
protected function PlayerUpdateCamera(Pawn P, GamePlayerCamera CameraAct, float DeltaTime, out TViewTarget OutVT)
{
    RPGPlayerCamera(PlayerCamera).UpdateThirdPCamera(P, CameraAct, DeltaTime, OutVT);
}

PlayerCamera - типа GamePlayerCamera, поэтому приходится преобразовывать к нашему типу. UpdateThirdPCamera - функция в RPGPlayerCamera, в которой и проводим все расчеты...

Внимание!

В  GamePlayerCamera есть ошибка. В:

protected function GameCameraBase FindBestCameraType(Actor CameraTarget)

нужно вместо:

    if (CameraStyle == 'default')

Сделать

    if (CameraStyle != 'default')
Иначе она будет возвращать None...


 

Оригинал записи и комментарии на LiveInternet.ru

Tags: