#0 (Правка: 30 янв 2022, 17:12)
14:42, 22 янв 2022
Название: Major Discoball
Жанр: 2d платформер с элементами метроидвании в "научно" фантастическом сеттинге
Движок: Unity
Платформы: PC, Android, IOS, (очень надеюсь) консоли
Языки: Русский, Английский
Сайт игры
VK
Twitter
Instagram
TikTok
Facebook
Начать тему хочу с главного нововведения в игре. Я долго откладывал это на потом, но пришло время и я наконец то заменил главного героя. Теперь это не скучная энергетическая сфера, а дроид с руками и ногами трансформирующийся в шар.
SimonSn50
Ну выглядит однозначно лучше. А он будет стрелять из центрального отверстия мега-мега-лучом?
Incvisitor
> А он будет стрелять из центрального отверстия мега-мега-лучом?
какого отверстия?
а вообще над боевкой еще думаю, пока не решил))
SimonSn50
Ну глаз это у него или что)
Incvisitor
А.... Это лицо) вообще весь шар это экран, и на нем проецируется все что относится к дроиду, хп, возможность сделать дэш.... И лицо отражающее настроение пилота, как эмодзи, когда доделаю будет все видно
>SimonSn50
Да, так совершенно точно, лучше.
Но тогда логичный вопрос, у Филеченкова, было классно реализовано типа медведь низко но сильно прыгает, сова летает но не может разбить объект. Вероятно, что то типа такого же надо будет?
FourGen
> Но тогда логичный вопрос
Шар быстрее, но имеет инерцию и отскакивает от пола как мячик. То-есть, легче управлять гуманоидом. Но шар меньше, врагам по нему попасть сложнее. Некоторые препятствия можно пройти только в виде шара. Ну а поскольку это все таки один и тот же робот, умения в разных состояниях не должны сильно отличаться. По сути я добавил гуманоидного робота просто что бы управление было удобней.
#7 (Правка: 1 фев 2022, 12:57)
23:27, 29 янв 2022
Довел до ума переход между локациями.
Правда иногда персонаж дергается назад при загрузке
Как это выглядит технически:
+ Показать
− Скрыть
Я не программист, как могу так и пишу.
Разберем пример как на гифке, ГГ идет слева на право.
Есть объект "переход" с тригером, скриптом "LevelLoader", а так же на нем висит скрипт "CheckPoint"
В инспекторе в компоненте "LevelLoader" указываем какая сцена должна будет загрузиться и направление движения ГГ что бы все правильно сработало.
"CheckPoint" определяет позицию в которой должен появиться ГГ после смерти или загрузке сцены
Когда ГГ касается тригера:
В классе "LevelLoader" в "OnTriggerEnter2D" вызывается метод "StartChangeSizeCam(false)" класса "Cam" который висит на камере. Метод "StartChangeSizeCam" в свою очередь запускает корутину "ChangeSizeCamera" которая уменьшает (приблежает) камеру, что бы ограничить поле видимости для игрока, и игрок не увидел, что впереди пустота.
+ Показать
− Скрыть
public void StartChangeSizeCam(bool reduse = false) //если reduse == true камера уменьшается
{
if(_changeSize == null) //если корутина еще не запущена
{
_changeSize = StartCoroutine(ChangeSizeCamera(reduse)); //запускаем ее
}
else //если корутина уже запущена
{
StopCoroutine(_changeSize);
_changeSize = null; //удаляем ее
_changeSize = StartCoroutine(ChangeSizeCamera(reduse)); // и запускаем заново
// просто предосторожность на случай если игрок будет бегать туда сюда.
}
}
private IEnumerator ChangeSizeCamera(bool reduse = false)
{
float startSize = _camera.orthographicSize; // узнаем какого размера камера
float targetSize = _minSize; // задаем какого размера камера должна стать,
по умолчанию должна уменьшиться
float speed = 10; // на сколько быстро уменьшится
yield return null; //ждем следующего кадра, так как при загрузке следующей сцены,
корутина запустится из старта, первый кадр будет очень длинный
и камера слишком резко увеличится
if (reduse) // уменьшаем камеру
{
for (float i = startSize; i > targetSize; i -= Time.deltaTime * speed)
{
_camera.orthographicSize = i;
yield return null;
}
}
else // увеличиваем камеру
{
targetSize = _normalSize; // задаем какого размера камера должна стать при увеличении
for (float i = startSize; i < targetSize; i += Time.deltaTime * speed)
{
_camera.orthographicSize = i;
yield return null;
}
}
_camera.orthographicSize = targetSize;
}
Когда ГГ пробегает середину "перехода" отрубаем игроку управление запускаем "LoadSceneAsync" и в "OnTriggerStay2D" класса "LevelLoader" вызываем метод движения ГГ, в этот момент игрок уже ничего не может сделать, ему только и остается, что ждать следующего уровня.
+ Показать
− Скрыть
private void OnTriggerStay2D(Collider2D collision)
{
if (!_downloading) // если загрузка не началась, начинаем загрузку
{
if (_direction.x > 0)
{
if (collision.transform.position.x > transform.position.x) // проверяем пробежал ли
//ГГ середину"перехода"
{
StartCoroutine(LoadYourAsyncScene());
}
}
else if (_direction.x < 0)
{
if (collision.transform.position.x < transform.position.x) // проверяем пробежал ли
//ГГ середину"перехода"
StartCoroutine(LoadYourAsyncScene());
}
}
}
else // если загрузка началась
{
Hero.S.MoveHorizontal(_direction.x); //двигаем ГГ вправо
}
}
IEnumerator LoadYourAsyncScene()
{
Hero.S.OnDisable(); // отрубаем управлении игроку
_downloading = true; // указываем что корутина уже запущена
_asyncLoad = SceneManager.LoadSceneAsync(_nameScene);
_asyncLoad.allowSceneActivation = false; //не даем загрузиться следующей сцене сразу же
yield return _asyncLoad;
}
Когда ГГ выходит из тригера в "OnTriggerExit2D" вызывается метод "SaveParameters()" который сохраняет следующие данные:
- на какой высоте относительно пола находится ГГ;
- на каком расстоянии от центра "перехода" находится ГГ;
- тип тела ГГ: шар или гуманоид;
- в какую сторону движется ГГ;
- имя сцены;
- булевая переменная отвечающая за автоматическую трансформацию в гуманоида;
после этого загружается следующая сцена.
+ Показать
− Скрыть
private void OnTriggerExit2D(Collider2D collision)
{
if(collision.gameObject.GetComponent<CircleCollider2D>())
{
if (_downloading)
{
SaveParameters();
_asyncLoad.allowSceneActivation = true;
}
else
{
_cam.StartChangeSizeCam();
}
}
}
private void SaveParameters()
{
Hero.S.SetHeight();
PlayerPrefs.SetFloat("StartX HeroPos", Mathf.Abs(transform.position.x - Hero.RB.position.x));
PlayerPrefs.SetInt("BodyType", (int)Body.B.Type);
PlayerPrefs.SetFloat("DirectionHero", _direction.x);
PlayerPrefs.SetString("NameScene", SceneManager.GetActiveScene().name);
if (Hero.S.Humanoid)
{
PlayerPrefs.SetInt("Humanoid", 1);
}
else
{
PlayerPrefs.SetInt("Humanoid", 0);
}
}
// этот метод находится в классе "Hero" он определяет на какой высоте относительно пола находится ГГ
public void SetHeight()
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 10, _layerPLatform);
if (hit)
{
PlayerPrefs.SetFloat("Height", hit.distance);
}
}
В следующей сцене имеется несколько объектов "переход", нам нужно найти тот который загружает предыдущую сцену. Для этого каждый "переход" сравнивает запись об имени предыдущей сцены с переменной string "_nameScene", которая у каждого "перехода" своя, и обозначает какую сцену загрузит этот "переход". Если имя предыдущей сцены равно переменной "_nameScene", этот переход помечает себя, как чекпоинт, объект возле которого ГГ должен появиться.
+ Показать
− Скрыть
public class LevelLoader : MonoBehaviour
{
private void Awake()
{
if(PlayerPrefs.GetString("NameScene", "A") == _nameScene)
{
Glob.CHECK_POINT = this.gameObject;
}
}
Потом, на основе сохраненных из предыдущей сцены данных, определяем позицию ГГ относительно "перехода". Связанные "переходы" в обеих сцены должны быть одинаковыми.
+ Показать
− Скрыть
public class Hero : MonoBehaviour
{
void Start
{
if (Glob.CHECK_POINT.GetComponent<LevelLoader>())
{
// нужно чтобы понять с какой стороны прибежал ГГ
Vector2 direction = Glob.CHECK_POINT.GetComponent<LevelLoader>().Direction;
// в переменной CHECK_POINT сейчас записан "переход"
Vector3 pos = Glob.CHECK_POINT.transform.position;
// узнаем на каком расстояние от "перехода" ГГ был в момент загрузки сцены и отправляем его туда
pos.x = Glob.CHECK_POINT.transform.position.x - (PlayerPrefs.GetFloat("StartX HeroPos", 0) * direction.x);
pos.y = GetHeight(pos); //определяем на какой высоте должен быть ГГ
transform.position = pos;
_cam.CamStartPos = pos;
}
}
public float GetHeight(Vector3 pos)
{
// стреляем лучом в землю
RaycastHit2D hit = Physics2D.Raycast(pos, Vector2.down, 10, _layerPLatform);
if (hit)
{
float floorPosY = pos.y - hit.distance; // определяем на какой высоте находится пол
floorPosY += PlayerPrefs.GetFloat("Height", 0);
// прибавляем к позиции пола сохраненную высоту от пола ГГ из предыдущей сцены
return floorPosY;
}
else
{
return 0;
}
}
А здесь просто кусочек геймплея.

#8 (Правка: 7:14)
4:02, 30 янв 2022
SimonSn50
Жестокая реализация. Сцена подгружается адитивно? Или с полным замещением?
Вывод один: у тебя не совпадают позиции при загрузке.
Или! вот в чем дело.
https://docs.unity3d.com/ru/530/Manual/ExecutionOrder.html
Исходя из порядка выполнения, вижу такое: ты сохраняешь параметры на моменте ТриггерЭксит. Однако далее ты грузишь сцену, и это сколько то занимает времени.
Игра успевает частично отрендериться, затем ты заменяешь сцену, но! герой-то уже чутка сместился.
Я предлагаю тебе ввести статичный класс (скрипт) в котором ты непосредственно! перед сменой сцены, будешь сохранять позицию игрока. Т.е. сохранил позицию, переключил сцену, установил ему корректную позицию. А так у тебя будут вечно лаги, т.е. сцена грузится определенное время.
Incvisitor
> Сцена подгружается адитивно?
Честно говоря не знаю что это значит.
> у тебя не совпадают позиции при загрузке.
> ты сохраняешь параметры на моменте ТриггерЭксит. Однако далее ты грузишь сцену
сцена начинает грузиться в LoadSceneAsync когда ГГ пересекает середину тригера. Когда ГГ выходит из тригера данные сохраняются и сразу же происходит переход между сценами, позиции четко передаются, это я проверял.
Я по кадрам пересмотрел видос, оказалось это камера не правильно позицию принимает. Камера плавно следует за ГГ, и в момент выхода из тригера слегка отстает от ГГ. А в новой сцене я придавал ей позицию ГГ. Уже исправил стало лучше.
> Жестокая реализация
кстати, что жесткого?
SimonSn50
Ну, мне показалось что жутко сложно выглядит.
Сейчас сказал название игры в слух насколько раз. Какое-то оно индийское)))
Incvisitor
> Сейчас сказал название игры в слух насколько раз. Какое-то оно индийское)))
ха)) возможно. Главное что гуглится хорошо)
nes
> Майор метройд.
В метройд ни когда не играл. Когда начал постить на англоязычных ресурсах, вдруг оказалось, что я делаю метройд(((