Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / Разные типы врагов Unity3D

Разные типы врагов Unity3D

Поделиться
bretbasПостоялецwww10 авг. 20179:13#0
Как правильно делаются разные типы врагов в Unity3D? У врагов должна быть разный массив спрайтов, разная анимация, и разное поведение.
Я сделал скрипт:
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(BoxCollider2D))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(ChangerLayer))]

public class EmenyScript : MonoBehaviour
{
  private SpriteRenderer    spriteRenderer;
  private Animator      animator;
  private BoxCollider2D    collider;
  private Rigidbody2D      rigidBody;
  private  ChangerLayer    changerLayer;
  void Awake()
  {
    spriteRenderer  = GetComponent<SpriteRenderer>();
    animator    = GetComponent<Animator>();
    collider    = GetComponent<BoxCollider2D>();
    rigidBody    = GetComponent<Rigidbody2D>();
    changerLayer  = GetComponent<ChangerLayer>();

    rigidBody.isKinematic = true;
  }
}

Этот скрипт натягиваю на каждый GameObject врага, потом ручками создаю анимацию, добавляю AnimatorController в Animator, создаю конкретный скрипт конкретного врага и натягиваю на этот GameObject. Потом создаю префаб.

Так это делается? Или есть более лучший способ создание разных врагов?

ChupakaberПостоялецwww10 авг. 201711:22#1
bretbas
> Так это делается? Или есть более лучший способ создание разных врагов?

> создаю конкретный скрипт конкретного врага и натягиваю на этот GameObject
если с наследованием от базового класса поведения, то да

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

bretbasПостоялецwww10 авг. 201714:23#2
Chupakaber, Код, который я написал выше, уместно использовать для определения общих компонентов всех врагов?
Я не сильно Вас понял. Вы написали:
или хотябы с общим интерфейсом, чтобы все враги могли впихиваться в одну коллекцию для универсального оперирования ими

Но зачем делать общий интерфейс, и где его делать, если:
1. Все враги - это GameObject, и я могу использовать их в одной коллекции соответственно
2. От GameObject наследовать нельзя, поэтому как я сделаю общий интерфейс, как вы написали?

Опять же спрашиваю, если я просто добавлю на каждого врага тот скрипт, который описал выше, и потом уже буду каждого настраивать ручками, добавлять конкретизированные скрипты для того или иного врага и тд, будет ли этот подход правильный? Да, и потом все это в префабы запихну

DimichПостоялецwww10 авг. 201714:48#3
bretbas
> 1. Все враги - это GameObject, и я могу использовать их в одной коллекции
> соответственно
> 2. От GameObject наследовать нельзя, поэтому как я сделаю общий интерфейс, как
> вы написали?
Я хз, как в юнити, но суть в чём. Есть интерфейс "враг" со всеми ресурсами, есть наследники, которые эти ресурсы устанавливают и логику описывают.
ChupakaberПостоялецwww10 авг. 201715:10#4
bretbas
> Но зачем делать общий интерфейс, и где его делать, если:

> 2. От GameObject наследовать нельзя, поэтому как я сделаю общий интерфейс, как
> вы написали?
наследоваться можно от "MonoBehaviour" , это любой компонент GameObject'а. и в нем есть ссылка на сам GameObject

> 1. Все враги - это GameObject, и я могу использовать их в одной коллекции
> соответственно
если будешь проходиться по коллекции и вызывать поведенческие методы циклические, то придется брать компонент поведения (скрипт/класс) при помощи GetComponent(), не самый эффективный способ
если есть скрипт "EmenyScript" (что бы это ни значило, полагаю буквы местами перепутаны просто), то логичнее именно эти классы в коллекции хранить, и внутри них уже ссылки на GameObject (по умолчанию есть) и графический компонент, который универсален (полагаю что так) для всех врагов

допустим есть враги - 1. лучник, 2. мечник, 3. лекарь, у них есть в поведении одинаковые детали - если нужно дойти до определенной точки маршрута - они пойдут все одинаково
и есть отличающиеся - когда атакует лучник - он начнет это делать с расстояния, и будет стараться держать дистанцию, а мечник будет бегать за врагом, лекарь же сперва проверит. ненадо ли кого полечить. а если нет друзей тогда уже будет отбиваться сам

таким образом часть поведения - общая, другая часть - уникальная
общую часть можно наследовать от базового класса поведения (EnemyBehavior), а уникальную уже переопределять (override для virtual методов) в результирующих классов (ArcherBehavior : EnemyBehavior, SwordsmanBehavior : EnemyBehavior)

либо не наследовать, а использовать интерфейсы, т.е. не будет базового класса как такового и даже абстрактного класса не будет, будут только "правила", определяющие что у этих классов есть общее, а именно - должны быть методы: 1. движения по маршруту, 2. атаки, 3. убегания, 4... всякие реакции на события и т.д.
и коллекция будет уже не по классу а по интерфейсу, это немного не так удобно, но тоже допустимо, и в некоторых случаях более гибко

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

___

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

___

Dimich
> Я хз, как в юнити, но суть в чём. Есть интерфейс "враг" со всеми ресурсами,
> есть наследники, которые эти ресурсы устанавливают и логику описывают.
в юнити всё довольно гибко, можно сделать по человечески, как ты описываешь
главное не зацикливаться на том, что есть уже готовый функционал и не думать что нужно пользоваться только им. его конечно нужно расширять и игнорировать то, что тебе мешает сделать нормально (:

bretbasПостоялецwww10 авг. 201716:22#5
Chupakaber, Все что ты написал, я понимаю:) Мне просто не привычно наследоваться от компонент, а не от GameObject. В своем движке, когда я писал его, я сделал так, чтобы можно было наследоваться от GameObject и определить в нем заранее нужные для этого GameObject'а компоненты. Плюс я определял нужные данные в этом же наследнике, чтобы не вызывать долгие методы, аля GetComponent<> и тд. Выглядело это примерно так:
GameObject -> Vehicle -> Tank -> Tank1, Tank2, Tank3

Вообщем теперь нужно немного по другому думать. Допустим у меня есть определенное движение у каждого врага, уникальное у каждого - кто-то прыгает, кто-то ходит по зигзагу, кто-то просто идет прямо и тд. Но все они в любом случае перемещаются по одной оси.

Следовательно общий скрипт перемещения по оси будет один. Назовем его MovingEnemyScript.
Также у каждого врага должны быть по любому следующие стандартные компоненты: SpriteRenderer, Animator, BoxCollider2D, RigidBody2D ну и еще может быть какие-то.

Я должен определить базовый класс EnemyController, который будет добавлять(если нет) все компоненты, которые я описал выше: SpriteRenderer, Animator, BoxCollider2D, RigidBody2D, MovingEnemyScript.
Получаем такой код соответственно:

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(BoxCollider2D))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(ChangerLayer))]
[RequireComponent(typeof(MovingEnemyScript))]

public class EmenyScript : MonoBehaviour
{
  private   SpriteRenderer      spriteRenderer;
  private   Animator          animator;
  private   BoxCollider2D      collider;
  private   Rigidbody2D        rigidBody;
  private    ChangerLayer      changerLayer;
  private   MovingEnemyScript   moveEnemy;
  void Awake()
  {
    spriteRenderer  = GetComponent<SpriteRenderer>();
    animator    = GetComponent<Animator>();
    collider    = GetComponent<BoxCollider2D>();
    rigidBody    = GetComponent<Rigidbody2D>();
    changerLayer  = GetComponent<ChangerLayer>();
  moveEnemy  = GetComponent<MovingEnemyScript>();

    rigidBody.isKinematic = true;
  }
}
Затем наследуем от него, и делаем у каждого врага поведения движения(прыжок, ход по зигзагу и тд).

Можно и по другому сделать.
Можно выделить просто базовый класс хотьбы:

using UnityEngine;
using System.Collections;

public class EmenyMoveScript : MonoBehaviour
{

}
И от него уже наследовать движения (прыжок, ход по зигзагу и тд).
А вот этот скрипт уже отдельно на каждый GameObject врага пихать:
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(BoxCollider2D))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(ChangerLayer))]
[RequireComponent(typeof(MovingEnemyScript))]

public class EmenyScript : MonoBehaviour
{
  private   SpriteRenderer      spriteRenderer;
  private   Animator          animator;
  private   BoxCollider2D      collider;
  private   Rigidbody2D        rigidBody;
  private    ChangerLayer      changerLayer;
  private   MovingEnemyScript   moveEnemy;
  void Awake()
  {
    spriteRenderer  = GetComponent<SpriteRenderer>();
    animator    = GetComponent<Animator>();
    collider    = GetComponent<BoxCollider2D>();
    rigidBody    = GetComponent<Rigidbody2D>();
    changerLayer  = GetComponent<ChangerLayer>();
  moveEnemy  = GetComponent<MovingEnemyScript>();

    rigidBody.isKinematic = true;
  }
}

ChupakaberПостоялецwww10 авг. 201717:09#6
moveEnemy  = GetComponent<MovingEnemyScript>();
не нужно

если ты префаб собираешь всё равно - сделай свойство публичным и ручками накидай

тогда ты сможешь от MovingEnemyScript отнаследоваться в EmenyMoveScript, Emeny2MoveScript и т.д. и этот компонент ручками перетянутый в свойство  moveEnemy  подцепится по базовому классу MovingEnemyScript

в юнити управление кодом не всегда лучше "визуального программирования" (: хоть там и паблик свойства будут лишние, но тебе я думаю от них хуже не будет, тем более гибче сможешь в ним обращаться из соседних компонентов через основной, если обратные ссылки проставишь (а они думаю понадобятся если не усеешь всё событийно-ориентированным подходом)

UPD: короче, возми просто и отбрось названия классов юнитевские, и воспринимай MonoBehaviour класс как GameObject , а GameObject просто как посредник, просто компонент основного твоего класса "врага"
т.е. представь зависимости GameObject и класса отнаследованного от MonoBehaviour в зеркальном виде, и всё встанет на свои места

Правка: 10 авг. 2017 17:12

bretbasПостоялецwww10 авг. 201717:40#7
Chupakaber,
в юнити управление кодом не всегда лучше "визуального программирования"

Вот, в точку! Я из-за этого и путаюсь. На самом деле, я все это дело вижу так:

1. Делаем скрипт, который добавляет автоматически нужные компоненты для врага( чтобы я ручками каждый раз не собирал его )

тогда ты сможешь от MovingEnemyScript отнаследоваться в EmenyMoveScript, Emeny2MoveScript и т.д. и этот компонент ручками перетянутый в свойство  moveEnemy  подцепится по базовому классу MovingEnemyScript

2. Зачем тогда вообще наследование?:) Просто делаем разные скрипты для разных поведений врагов. Создаем разных врагов из пункта 1, и перетягиваем на каждый конкретный ему скрипт. Все. Зачем наследование и тд и тп?

3. С графикой аналогично. Так как мы добавили в пункте 1 компоненты SpriteRenderer и Animator, то просто опять для каждого врага настраиваем их определенным образом.

4. Запихиваем все это в префабы.

5. Пишем генератор для создания случайно выбранных врагов из определенной точки.

6. Запускаем и радуемся

ChupakaberПостоялецwww10 авг. 201719:35#8
bretbas
> 1. Делаем скрипт, который добавляет автоматически нужные компоненты для врага(
> чтобы я ручками каждый раз не собирал его )
ручками компоненты собираются один раз для префаба, а дальше можешь его клонировать и вносить тока мелкие изменения по параметрам например

bretbas
> 2. Зачем тогда вообще наследование?:) Просто делаем разные скрипты для разных
> поведений врагов. Создаем разных врагов из пункта 1, и перетягиваем на каждый
> конкретный ему скрипт. Все. Зачем наследование и тд и тп?
чтобы в том скрипте, в котором у тебя будет коллекция всех игровых объектов, ты не делал для каждого GetComponent<..> да ещё как-то по тегу или названию выбирая какой класс тебе нужен
наследуешься от базового, в котором есть полный набор виртуальных методов, и обращаешься к каждому объекту в коллекции как к базовому классу
а поведение будет уже у них разное, как в переопределенных наследных классах

bretbas
> 3. С графикой аналогично. Так как мы добавили в пункте 1 компоненты
> SpriteRenderer и Animator, то просто опять для каждого врага настраиваем их
> определенным образом.
графику конечно придется визуально мышкой настраивать, кодом коротко и удобно не будет, кодом только для особых нестандартных случаев стоит графикой морочиться

и дальше да, генератор по хорошему должен быть из одной строчки:

public LinkedList<EnemyScript> objects = new LinkedList<EnemyScript>();

public CreateEnemy(string prefabName, Vector3 position, Quaternion rotation)
{
    objects.AddLast(Instantiate(Resources.Load("Prefabs/Enemies/" + prefabName), position, rotation).GetComponent<EnemyScript>());
}

префабы должны при этом лежать в папке Assets/Resources/Prefabs/Enemies/
prefabName - название префаба
position - точка размещения нового врага в мире
rotation - понятно думаю, его вращение, куда он смотрит
objects - коллекция всех врагов с компонентом в наличии - базовым классом EnemyScript, или любым другим от него отнаследованным
из коллекции ты будешь сразу получать класс EnemyScript, а не GameObject, с которым лишние GetComponent нужны

всю настройку самих себя эти твои враги должны сделать сами в своем методе Start()

Правка: 10 авг. 2017 19:37

bretbasПостоялецwww10 авг. 201719:53#9
Chupakaber,
чтобы в том скрипте, в котором у тебя будет коллекция всех игровых объектов, ты не делал для каждого GetComponent<..> да ещё как-то по тегу или названию выбирая какой класс тебе нужен

В каком скрипте у меня должна быть коллекция игровых объектов? Я этого не говорил. Я говорил, что просто хочу реализовать разное поведение у врагов и все. Но чтобы они полиморфно где-то лежали...я этого не сказал.
У каждого уникального скрипта поведения врага, есть Update(), так пусть он и выполняется для этого врага. Но в кучу собирать всех врагов, пока что мне не нужно:)

и дальше да, генератор по хорошему должен быть из одной строчки

Я вот такой написал:
void Update ()
{
  accumulator += Time.deltaTime;

  if( accumulator >= interval )
  {
    BoxCollider2D componentCollider = areaEnemyAppear.GetComponent<BoxCollider2D>();
    float halfHeightArea = componentCollider.size.y / 2;
    float halfWidthArea = componentCollider.size.x / 2;
    Vector3 positionArea = areaEnemyAppear.transform.position;
    foreach( var enemyObject in enemyObjects )
    {
        Vector3 position = 
            new Vector3( 
              Random.Range( positionArea.x - halfWidthArea, positionArea.x + halfWidthArea ),
              Random.Range( positionArea.y - halfHeightArea, positionArea.y + halfHeightArea )
                  );

      Instantiate( enemyObject, position, Quaternion.identity );
    }
    accumulator = 0;
  }
}

Где enemyObjects - массив врагов, которые передаются через инспектор. Я думаю так удобно, так как я могу быстренько убрать для генерации того или иного врага.
areaEnemyAppear - GameObject, у которого есть BoxCollider2D. Он служит площадкой, внутри которой будут генерироваться враги.
interval - генератор служит как бы таймером для генерации, поэтому это время, когда таймер должен сработать. Задается также через инспектор. Можно в принципе разделить таймер и генератор на два скрипта.

ChupakaberПостоялецwww10 авг. 201720:46#10
bretbas
> Но чтобы они полиморфно где-то лежали...
ну пусть они полиморфно нигде не лежат
но полиморфно взаимодействовать они же будут? например какие-то базовые параметры у них общие, элементы поведения, рассчет атаки/ущерба там
это же нужно? или будет отдельный компонент без наследования эти общие для всех штуки содержащий?
bretbasПостоялецwww10 авг. 201721:13#11
Chupakaber,
ну пусть они полиморфно нигде не лежат
но полиморфно взаимодействовать они же будут? например какие-то базовые параметры у них общие, элементы поведения, рассчет атаки/ущерба там
это же нужно? или будет отдельный компонент без наследования эти общие для всех штуки содержащий?

Ладно, это уже пошло угадывание:) Враги умирают с одного выстрела, сами они атаки не имеют. Вообщем, если разобраться, а я уже в принципе разобрался...у них общее только набор компонентов, больше ничего в них общего то и нет...

/ Форум / Программирование игр / 2D графика и изометрия

2001—2017 © GameDev.ru — Разработка игр