Тема: LeoECS
Показать сообщение отдельно
Старый 25.10.2020, 16:36   #2
Nex
Гигант индустрии
 
Аватар для Nex
 
Регистрация: 13.09.2008
Сообщений: 2,893
Написано 1,185 полезных сообщений
(для 3,298 пользователей)
Ответ: LeoECS

А сейчас добавим возможность пострелять. Для этого создайте на сцене некоторое количество объектов с коллайдерами, которые будут являться препятствием для пули.
Так же нужно создать системы и добавить их в список в EcsStartup классе.
.Add(new PlayerInputShootSystem())
.
Add(new ProjectileCreateSystem())
.
Add(new ProjectileMoveSystem())
.
Add(new ProjectileDestroyEventSystem()) 
Система PlayerInputShootSystem будет создавать Entity с компонентом PlayerShootEvent и некоторым данными для создания пули.
Код очень похож на предыдущую систему где создавался компонент с событие движения игрока.
Конечно системы можно унифицировать дабы они двигали сразу все Entity допустим с компонентом Move, но в этом случае мы потеряем гибкость и при отключении этой системы перестанут двигаться и игрок и пули.
internal class PlayerInputShootSystem IEcsRunSystem {
        
// Тут для пример вместе с EcsFilter<T> есть еще и .Exclude<T> специально
        // что бы пули не могли создаваться если игрок движется или
        // вращается (если на Entity игрока есть компонент PlayerInputEvent)
        
private readonly EcsFilter<PlayerRef>.Exclude<PlayerInputEventfilter = default;
        private 
readonly EcsWorld ecsWorld = default;
        private 
readonly Config config = default;
        private 
readonly GameData gameData = default;

        public 
void Run() {
            if (
filter.IsEmpty())
                return;

            if (!
Input.GetKeyDown(KeyCode.Space))
                return;

            foreach (var 
i in filter) {
                
ref var playerRef ref filter.Get1(i);
                var 
tr playerRef.View.transform;

                
ecsWorld.NewEntity().Get<PlayerShootEvent>() = new PlayerShootEvent() {
                    
Position tr.position,
                    
Angle tr.rotation.eulerAngles.y,
                    
Speed config.ProjectileStartSpeed
                
};

                
// Как помним GameData класс был у нас для временных реалтайм-данных.
                // В данном случае при выстреле увеличиваем переменную и 
                // для примера можно где-то её отрисовать в UI или можно
                // посмотреть её значение в редакторе так как у нас
                // этот класс сериализован
                
gameData.shootCount++;
            }
        }
    }
    
    
// Данный компонент хоть и заканчивает на "Event", но добавлять
    // его в .OneFrame<T> не будем потому-что он создается вместе
    // с отдельным Entity. В этом случае лучше удалить руками
    // сам Entity, а вместе с ним удалится и этот компонент
    
internal struct PlayerShootEvent {
        public 
Vector3 Position;
        public 
float Angle;
        public 
float Speed;
    } 
В системе ProjectileCreateSystem создаем префаб и Entity пули. Так же обязательно удаляем Entity с событием PlayerShootEvent, которое сигнализирует о необходимости создания пули. Иначе пули будут создаваться в каждом цикле * на количество Entity в фильтре.
internal class ProjectileCreateSystem IEcsRunSystem {
        
// Получаем все Entity с PlayerShootEvent
        
private readonly EcsFilter<PlayerShootEventfilter = default;
        private 
readonly EcsWorld ecsWorld = default;
        private 
readonly Config config = default;

        public 
void Run() {
            foreach (var 
i in filter) {
                
ref var eventData ref filter.Get1(i);
                var 
position eventData.Position;
                
position.0.5f;
                var 
rotation Quaternion.Euler(0eventData.Angle0);

                
// Так же создаем и связываем игровой объект и Entity.
                
var entity ecsWorld.NewEntity();
                
ref var projectileRef ref entity.Get<ProjectileRef>();
                
// На Entity пули добавляем постоянный компонент,
                // который будет хранить постоянную скорость пули
                
entity.Get<ProjectileMove>().Value eventData.Speed;

                var 
view UnityEngine.Object.Instantiate(config.ProjectilePrefabpositionrotation);

                
view.Entity entity;
                
projectileRef.View view;

                
// И обязательно удаляем текущий Entity, который содержит
                // компонент-событие для создания пули
                
filter.GetEntity(i).Destroy();
            }
        }
    }

    
// ref - компонент для Entity пуль
    
internal struct ProjectileRef {
        public 
ProjectileView View;
    }
    
    
// View - компонент для пули. Так же тут показан пример как из MonoBehaviour
    // взаимодействовать с Entity игрового объекта
    
public class ProjectileView MonoBehaviour {
        public 
EcsEntity Entity;

        
// Если пуля столкнулась с чем-то кроме игрока (PlayerView),
        // то добавляем на Entity пули компонент-событие ProjectileDestroyEvent
        // благодаря которому эта пуля будет удалена в последующей системе
        
private void OnTriggerEnter(Collider other) {
            if (
other.GetComponent<PlayerView>())
                return;

            
// Просто пример как использовать это.
            // В данном случае мы получаем ссылку на реалтайм-данные и увеличиваем
            // значение переменной, которая будет показывать количество
            // столкновений пуль за игру
            // Можно так же получать ссылку на мир EcsWorld и создавать Entity
            
Service<GameData>.Get().collisionsCount++;
            
            
// Для примера добавлен параметр в который записывается
            // игровой объект с которым произошло столкновение
            
Entity.Get<ProjectileDestroyEvent>() = new ProjectileDestroyEvent() {
                
Value other.gameObject
            
};
        }
    } 
В данной небольшой системе мы просто двигаем все пули с постоянной скоростью, которая сохранена в компоненте ProjectileMove. Опять же это сохранение скорости пули только как пример. Данные скорости пули можно так же брать из Config, как и в системе с движением игрока.
internal class ProjectileMoveSystem IEcsRunSystem {
        private 
readonly EcsFilter<ProjectileRefProjectileMovefilter = default;

        public 
void Run() {
            foreach (var 
i in filter) {
                
// Помним что "ref" нужен обязательно при получении компонентов,
                // которые являются "struct"
                
ref var projectileRef ref filter.Get1(i);
                
ref var projectileMove ref filter.Get2(i);
                
                
// Непосредственно двигаем игровой объект на сцене
                
var step projectileMove.Value Time.deltaTime;
                var 
transform projectileRef.View.transform;
                
transform.position += transform.forward step;
            }
        }
    }

    
internal struct ProjectileMove {
        public 
float Value;
    } 
А в этой последней системе ProjectileDestroyEventSystem просто удаляем и игровой объект и Entity. Сам Entity не удалится с "мира", а только пометится как удаленный и в любой момент будет использован заново.
internal class ProjectileDestroyEventSystem IEcsRunSystem 
        private 
readonly EcsFilter<ProjectileRefProjectileDestroyEventfilter = default;

        public 
void Run() {
            foreach (var 
i in filter) {
                
ref var projectileRef ref filter.Get1(i);
                
ref var destroyData ref filter.Get2(i);                
                
// Тут просто для примера выводим к консоль название
                // игрового объекта с которым столкнулась пуля
                
Debug.Log(destroyData.Value);
                
// Удаление gameObject пули на сцене и Entity из "мира"
                
Object.Destroy(projectileRef.View.gameObject);
                
filter.GetEntity(i).Destroy();
            }
        }
    }
    
    
internal struct ProjectileDestroyEvent {
        public 
GameObject Value;
    } 

Всё. Выглядит это конечно более сложнее, чем классический код и синхронизация с игровыми объектами на сцене тоже такое себе. Но за то мы получаем максимально разграниченный функционал, который не зависит друг от друга. Так же это всё легко тестится и расширяется хоть до уровня ММО-игры.
Изображения
Тип файла: png Пуля.PNG (108.8 Кб, 603 просмотров)
(Offline)
 
Ответить с цитированием
Эти 3 пользователя(ей) сказали Спасибо Nex за это полезное сообщение:
Evgen (26.10.2020), pax (26.10.2020), Randomize (25.10.2020)