Работаю с Unity3D уже 5 лет.
Занимаюсь игровой логикой: Геймплей, механика, ИИ, физика, UI, UX и т.п. (не работаю с networking)
Пишу с комментариями, визуализирую код в инспекторе, название переменных/классов/методов соответствуют логике;
Логические отступы, регионы и т.п. имеются.
Цена варьируется в зависимости от сложности задачи.
Пишу в соответствии с вашими условиями/предпочтениями - от них цена не зависит, но от них может зависеть сложность.
Писать на cortexmethod@yandex.ru.
Пример ИИ скрипта и его короткая демонстрация:
Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class fightAI : MonoBehaviour {
public bool seenPlayer;
public GameObject player;
public TrailRenderer[] allTrail;
Animator anim;
[Header("Типы атак")]
public List<AttackType> attackType;
[Header("Коллайдер оружия")]
public Collider weapon;
public Collider specialWeapon;
[Header("Части тела")]
public Collider[] myGibs;
[Header("ХП'бар")]
public Image[] HPimg;
public GameObject pickHP;
[Header("Контроллеры ходьбы")]
public float runX;
public float runY;
[Header("Шанс агрессии")]
[Range(1,100)]
public float aggressive;
[Header("Запас здоровья")]
public int maxHP;
public int HP;
int hpBack;
[Header("Скорость поворота")]
public float rotateSpeed;
[Header("Множитель урона")]
public int damageMultiple;
[Header("Скорость принятия решений")]
public float reactSpeed;
[Header("Границы дистанций")]
public float[] distance;
[Header("Действия")]
public List<WhatIDoDoing> Doing;
#region AIhelp
int attackNow;
int animNow;
bool checkAN { get { return attackType[animNow].AnimationAttack.Length > (attackNow + 1); } }// проверяет переполнение AnimationAttack
int getDamage { get { return attackType[animNow].damageForAttack[attackNow]; } } // получает значение урона
int getStrenght { get { return attackType[animNow].strenghtBeat[attackNow]; } } // получает значение силы
float getStunTime { get { return attackType[animNow].stunTime[attackNow]; } } // получает значение времени стана
int getAttackClip { get { return attackType[animNow].AnimationAttack[attackNow]; } } // получает следующее значение анимации
bool SwitchTrail { set { allTrail[attackType[animNow].trailNum].enabled = value; } } // переключает trail
bool getLowerDistance { get { return Vector3.Distance(gameObject.transform.position, player.transform.position) < distance[0]; } } // сравнивает минимальную дистанцию с дистанцией игрока
bool getHighestDistance { get { return Vector3.Distance(gameObject.transform.position, player.transform.position) > distance[1]; } } // сравнивает максимальную дистанцию с дистанцией игрока
bool IsAttack // Если я атакую
{
get
{
bool t = false;
if (anim.GetCurrentAnimatorStateInfo(0).IsTag("fight"))
t = true;
return t;
}
}
//переменные сета
int damageNow;
float stunNow;
int strNow;
//умер ли "я"
bool dieN;
#endregion
/* Класс, который собирает все или некоторые анимации атак в сет, с дополнительными
* параметрами:
урон на каждую анимацию сета
сила на каждую анимацию сета
время стана на каждую анимацию сета
защита во время сета
какой трейл от меча поставить*/
[System.Serializable]
public class AttackType : System.Object
{
[Header("Номера параметра attack")]
public int[] AnimationAttack;
[Header("Урон во время анимаций")]
public int[] damageForAttack;
[Header("Сила удара")]
public int[] strenghtBeat;
[Header("Время стана")]
public float[] stunTime;
[Header("Степень защиты во время атаки")]
public float resistance;
[Header("Какой трейл включать")]
public int trailNum;
}
/* Класс, представляющий собой действия, которые носитель будет выполнять, конечно,
* в определенных в скрипте ситуациях
Выбор атаки и шанс на нее
Куда идти
Идти, прыгать, стоять
Доп. команды */
[System.Serializable]
public class WhatIDoDoing : System.Object
{
[Header("Действия в AttackType (если нет, то -1) и шанс")]
public int AttackType;
[Range(1,100)]
public int chanceToAttack;
[Header("Настройки runX и runY")]
[Range(-1.00f, 1.00f)]
public float runXnow;
[Range(-1.00f, 1.00f)]
public float runYnow;
[Header("Идти(0) или прыгать(1), или не делать ничего(-1)")]
[Range(-1,1)]
public int Do;
[Header("Дополнительные действия (ключевые слова)")]
public string[] otherAction;
public string[] param;
}
// Use this for initialization
void Start () {
HP = maxHP;
player = GameObject.FindWithTag("Player");
StartCoroutine("move");
anim = gameObject.GetComponent<Animator>();
//Выключаю коллайдеры частей тела
foreach (var a in myGibs)
a.enabled = false;
//...и включаю им кинематику
foreach (var a in myGibs)
a.GetComponent<Rigidbody>().isKinematic = true;
hpBack = HP;
}
// Update is called once per frame
void Update () {
#region HUD
var rt = HPimg[0].transform as RectTransform;
rt.sizeDelta = new Vector2((HP * 872) / maxHP, rt.sizeDelta.y);
var rt2 = HPimg[1].transform as RectTransform;
rt2.sizeDelta = Vector2.Lerp(rt2.sizeDelta, new Vector2((hpBack * 872) / maxHP, rt.sizeDelta.y), 12 * Time.deltaTime);
pickHP.transform.LookAt(new Vector3(
player.GetComponent<Control>().Cam.transform.position.x,
player.GetComponent<Control>().Cam.transform.position.y,
player.GetComponent<Control>().Cam.transform.position.z));
#endregion
#region WeaponSetting
weapon.GetComponent<weaponAI>().damage = damageNow;
weapon.GetComponent<weaponAI>().strenght = strNow;
weapon.GetComponent<weaponAI>().timeStun = stunNow;
weapon.GetComponent<weaponAI>().mult = damageMultiple;
#endregion
#region Other
gameObject.transform.eulerAngles = new Vector3(0, gameObject.transform.eulerAngles.y, 0);
if (dieN)
return;
if (HP <= 0)
die();
//Параметры ходьбы в аниматоре
anim.SetFloat("runX",
Mathf.Lerp(anim.GetFloat("runX"), runX, 10 * Time.deltaTime)
);
anim.SetFloat("runY",
Mathf.Lerp(anim.GetFloat("runY"), runY, 10 * Time.deltaTime)
);
#endregion
//Если "я" вижу игрока
if (seenPlayer)
{
//Переключить в аниматоре
anim.SetBool("aim", true);
//Если "я" атакую, то я не поворачиваюсь за игроком
if (!IsAttack)
gameObject.transform.rotation = Quaternion.Lerp(gameObject.transform.rotation, Quaternion.LookRotation(player.transform.position - gameObject.transform.position), rotateSpeed * Time.deltaTime);
//Не соблюдены дистанции
#region BadDistance
//Игрок слишком близко
if (getLowerDistance)
{
//Doing[0] - Если игрок слишком близко
//Идти в соответствии с переменной в этом классе
runX = Doing[0].runXnow;
//Если есть какой-то параметр
if (Doing[0].otherAction.Length >= 1)
//... и он записан в коде (снизу)
if (Doing[0].otherAction[0] == "randomY")
{
//... исполнять его
runY = Doing[0].runYnow + Random.Range(
-(float)System.Convert.ToInt32(Doing[0].param[0]),
(float)System.Convert.ToInt32(Doing[0].param[0])
);
}
else { }
else //... или нет
runY = Doing[0].runYnow;
}
//Игрок слишком далеко
if (getHighestDistance)
{
//Doing[1] - Если игрок слишком далеко
int w = Random.Range(0, 100);
//Если есть атака - атаковать
if (Doing[1].AttackType != -1)
if (Doing[1].chanceToAttack >= w)
SetAttackType(-1, Doing[1].AttackType);
//Идти в соответствии с переменными в этом классе
runX = Doing[1].runXnow;
runY = Doing[1].runYnow;
}
#endregion
}
else
{
//Если "я" его не вижу, то ничего и не делаю
anim.SetBool("aim", false);
}
/*
attack [1...3] - это удара
attack [100, 200] - спец удары и перекаты
*/
//Если я атакую и перекатываюсь, то я только атакую
if (IsAttack && anim.GetInteger("attack") >= 100)
{
anim.SetInteger("attack", 100);
}
}
#region WEAPONset
//Активировать оружие
public void weaponOn()
{
weapon.enabled = true;
SwitchTrail = true;
}
//Деактивировать оружие
public void weaponOff()
{
weapon.enabled = false;
SwitchTrail = false;
}
//Переключение на следующую стадию анимации
public void NextAn()
{
//Закончился ли сет анимаций
if (checkAN)
attackNow++;
anim.SetInteger("attack", getAttackClip);
damageNow = getDamage;
strNow = getStrenght;
stunNow = getStunTime;
}
//снятие режима атаки
public void attackOff(int num)
{
if (num == anim.GetInteger("attack"))
anim.SetInteger("attack", 0);
}
#endregion
#region AI
//Атака
public void SetAttackType(int tp, int howBeat)
{
//Какую анимацию из AttackType "я" выбираю
animNow = howBeat;
//Устанавливаю изначальный кадр
attackNow = 0;
//Получаю урон из соотв. анимации
damageNow = getDamage;
//Получаю силу из соотв. анимации
strNow = getStrenght;
//Получаю время стана из соотв. анимации
stunNow = getStunTime;
//Запускаю
anim.SetInteger("attack", getAttackClip);
}
//Ходьба
public IEnumerator move()
{
while (true)
{
yield return new WaitForSeconds(reactSpeed);
//Если заметил игрока
if (seenPlayer)
{
//Если соблюдены дистанции между игроком и "мной"
if (!getLowerDistance && !getHighestDistance)
{
//шанс на агрессию
int fightt = Random.Range(0, 100);
//Если не повезло - выбор куда идти (вправо или влево)
if (fightt > aggressive)
{
int moveTo = Random.Range(2, 4);
runX = Doing[moveTo].runXnow;
runY = Doing[moveTo].runYnow;
}
//Если повезло
else
{
//Выбрать случайный тип атаки
SetAttackType(-1, Random.Range(0, attackType.ToArray().Length));
}
}
//Если игрок подошел слишком близко
if (getLowerDistance)
{
//Шанс на агрессию в Действии[0] (параметр ChanceToAttack)
int w = Random.Range(0, 100);
//Если ли вообще "удары" в таком случае (когда игрок подошел)
if (Doing[0].AttackType != -1)
//Если есть - проверяет, повезло ли
if (Doing[0].chanceToAttack >= w)
//Устанавливает атаку на номер ее анимации в AttackType
SetAttackType(-1, Doing[0].AttackType);
}
}
}
}
#endregion
//Специальная атака
public void specAttack(int swc)
{
//переключатель коллайдера спец. атаки
switch (swc)
{
case 1:
specialWeapon.enabled = true;
anim.SetInteger("attack", 0);
break;
case 2:
specialWeapon.enabled = false;
break;
}
}
void HPBackUpd()
{
hpBack = HP;
}
//Смерть
void die()
{
//Отключаю спец. атаку
specialWeapon.enabled = false;
dieN = true;
//Выключаю контроллер
gameObject.GetComponent<CharacterController>().enabled = false;
//Делаю его оружие "самостоятельным"
weapon.transform.parent = null;
weapon.isTrigger = false;
weapon.enabled = true;
weapon.gameObject.AddComponent<Rigidbody>();
weapon.GetComponent<weaponAI>().enabled = false;
//Останавливаю все кроутины
StopAllCoroutines();
//Выключаю аниматор
anim.enabled = false;
//Включаю коллайдеры частей тела
foreach (var a in myGibs)
a.enabled = true;
//Выключаю им кинематику
foreach (var a in myGibs)
a.GetComponent<Rigidbody>().isKinematic = false;
//Делаю "удар" по случайной части тела
myGibs[Random.Range(0, myGibs.Length)].GetComponent<Rigidbody>().AddForceAtPosition(-gameObject.transform.forward * 600, player.transform.position, ForceMode.Impulse);
}
//Метод для аниматора
void CB() { }
}
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class fightAI : MonoBehaviour {
public bool seenPlayer;
public GameObject player;
public TrailRenderer[] allTrail;
Animator anim;
[Header("Типы атак")]
public List<AttackType> attackType;
[Header("Коллайдер оружия")]
public Collider weapon;
public Collider specialWeapon;
[Header("Части тела")]
public Collider[] myGibs;
[Header("ХП'бар")]
public Image[] HPimg;
public GameObject pickHP;
[Header("Контроллеры ходьбы")]
public float runX;
public float runY;
[Header("Шанс агрессии")]
[Range(1,100)]
public float aggressive;
[Header("Запас здоровья")]
public int maxHP;
public int HP;
int hpBack;
[Header("Скорость поворота")]
public float rotateSpeed;
[Header("Множитель урона")]
public int damageMultiple;
[Header("Скорость принятия решений")]
public float reactSpeed;
[Header("Границы дистанций")]
public float[] distance;
[Header("Действия")]
public List<WhatIDoDoing> Doing;
#region AIhelp
int attackNow;
int animNow;
bool checkAN { get { return attackType[animNow].AnimationAttack.Length > (attackNow + 1); } }// проверяет переполнение AnimationAttack
int getDamage { get { return attackType[animNow].damageForAttack[attackNow]; } } // получает значение урона
int getStrenght { get { return attackType[animNow].strenghtBeat[attackNow]; } } // получает значение силы
float getStunTime { get { return attackType[animNow].stunTime[attackNow]; } } // получает значение времени стана
int getAttackClip { get { return attackType[animNow].AnimationAttack[attackNow]; } } // получает следующее значение анимации
bool SwitchTrail { set { allTrail[attackType[animNow].trailNum].enabled = value; } } // переключает trail
bool getLowerDistance { get { return Vector3.Distance(gameObject.transform.position, player.transform.position) < distance[0]; } } // сравнивает минимальную дистанцию с дистанцией игрока
bool getHighestDistance { get { return Vector3.Distance(gameObject.transform.position, player.transform.position) > distance[1]; } } // сравнивает максимальную дистанцию с дистанцией игрока
bool IsAttack // Если я атакую
{
get
{
bool t = false;
if (anim.GetCurrentAnimatorStateInfo(0).IsTag("fight"))
t = true;
return t;
}
}
//переменные сета
int damageNow;
float stunNow;
int strNow;
//умер ли "я"
bool dieN;
#endregion
/* Класс, который собирает все или некоторые анимации атак в сет, с дополнительными
* параметрами:
урон на каждую анимацию сета
сила на каждую анимацию сета
время стана на каждую анимацию сета
защита во время сета
какой трейл от меча поставить*/
[System.Serializable]
public class AttackType : System.Object
{
[Header("Номера параметра attack")]
public int[] AnimationAttack;
[Header("Урон во время анимаций")]
public int[] damageForAttack;
[Header("Сила удара")]
public int[] strenghtBeat;
[Header("Время стана")]
public float[] stunTime;
[Header("Степень защиты во время атаки")]
public float resistance;
[Header("Какой трейл включать")]
public int trailNum;
}
/* Класс, представляющий собой действия, которые носитель будет выполнять, конечно,
* в определенных в скрипте ситуациях
Выбор атаки и шанс на нее
Куда идти
Идти, прыгать, стоять
Доп. команды */
[System.Serializable]
public class WhatIDoDoing : System.Object
{
[Header("Действия в AttackType (если нет, то -1) и шанс")]
public int AttackType;
[Range(1,100)]
public int chanceToAttack;
[Header("Настройки runX и runY")]
[Range(-1.00f, 1.00f)]
public float runXnow;
[Range(-1.00f, 1.00f)]
public float runYnow;
[Header("Идти(0) или прыгать(1), или не делать ничего(-1)")]
[Range(-1,1)]
public int Do;
[Header("Дополнительные действия (ключевые слова)")]
public string[] otherAction;
public string[] param;
}
// Use this for initialization
void Start () {
HP = maxHP;
player = GameObject.FindWithTag("Player");
StartCoroutine("move");
anim = gameObject.GetComponent<Animator>();
//Выключаю коллайдеры частей тела
foreach (var a in myGibs)
a.enabled = false;
//...и включаю им кинематику
foreach (var a in myGibs)
a.GetComponent<Rigidbody>().isKinematic = true;
hpBack = HP;
}
// Update is called once per frame
void Update () {
#region HUD
var rt = HPimg[0].transform as RectTransform;
rt.sizeDelta = new Vector2((HP * 872) / maxHP, rt.sizeDelta.y);
var rt2 = HPimg[1].transform as RectTransform;
rt2.sizeDelta = Vector2.Lerp(rt2.sizeDelta, new Vector2((hpBack * 872) / maxHP, rt.sizeDelta.y), 12 * Time.deltaTime);
pickHP.transform.LookAt(new Vector3(
player.GetComponent<Control>().Cam.transform.position.x,
player.GetComponent<Control>().Cam.transform.position.y,
player.GetComponent<Control>().Cam.transform.position.z));
#endregion
#region WeaponSetting
weapon.GetComponent<weaponAI>().damage = damageNow;
weapon.GetComponent<weaponAI>().strenght = strNow;
weapon.GetComponent<weaponAI>().timeStun = stunNow;
weapon.GetComponent<weaponAI>().mult = damageMultiple;
#endregion
#region Other
gameObject.transform.eulerAngles = new Vector3(0, gameObject.transform.eulerAngles.y, 0);
if (dieN)
return;
if (HP <= 0)
die();
//Параметры ходьбы в аниматоре
anim.SetFloat("runX",
Mathf.Lerp(anim.GetFloat("runX"), runX, 10 * Time.deltaTime)
);
anim.SetFloat("runY",
Mathf.Lerp(anim.GetFloat("runY"), runY, 10 * Time.deltaTime)
);
#endregion
//Если "я" вижу игрока
if (seenPlayer)
{
//Переключить в аниматоре
anim.SetBool("aim", true);
//Если "я" атакую, то я не поворачиваюсь за игроком
if (!IsAttack)
gameObject.transform.rotation = Quaternion.Lerp(gameObject.transform.rotation, Quaternion.LookRotation(player.transform.position - gameObject.transform.position), rotateSpeed * Time.deltaTime);
//Не соблюдены дистанции
#region BadDistance
//Игрок слишком близко
if (getLowerDistance)
{
//Doing[0] - Если игрок слишком близко
//Идти в соответствии с переменной в этом классе
runX = Doing[0].runXnow;
//Если есть какой-то параметр
if (Doing[0].otherAction.Length >= 1)
//... и он записан в коде (снизу)
if (Doing[0].otherAction[0] == "randomY")
{
//... исполнять его
runY = Doing[0].runYnow + Random.Range(
-(float)System.Convert.ToInt32(Doing[0].param[0]),
(float)System.Convert.ToInt32(Doing[0].param[0])
);
}
else { }
else //... или нет
runY = Doing[0].runYnow;
}
//Игрок слишком далеко
if (getHighestDistance)
{
//Doing[1] - Если игрок слишком далеко
int w = Random.Range(0, 100);
//Если есть атака - атаковать
if (Doing[1].AttackType != -1)
if (Doing[1].chanceToAttack >= w)
SetAttackType(-1, Doing[1].AttackType);
//Идти в соответствии с переменными в этом классе
runX = Doing[1].runXnow;
runY = Doing[1].runYnow;
}
#endregion
}
else
{
//Если "я" его не вижу, то ничего и не делаю
anim.SetBool("aim", false);
}
/*
attack [1...3] - это удара
attack [100, 200] - спец удары и перекаты
*/
//Если я атакую и перекатываюсь, то я только атакую
if (IsAttack && anim.GetInteger("attack") >= 100)
{
anim.SetInteger("attack", 100);
}
}
#region WEAPONset
//Активировать оружие
public void weaponOn()
{
weapon.enabled = true;
SwitchTrail = true;
}
//Деактивировать оружие
public void weaponOff()
{
weapon.enabled = false;
SwitchTrail = false;
}
//Переключение на следующую стадию анимации
public void NextAn()
{
//Закончился ли сет анимаций
if (checkAN)
attackNow++;
anim.SetInteger("attack", getAttackClip);
damageNow = getDamage;
strNow = getStrenght;
stunNow = getStunTime;
}
//снятие режима атаки
public void attackOff(int num)
{
if (num == anim.GetInteger("attack"))
anim.SetInteger("attack", 0);
}
#endregion
#region AI
//Атака
public void SetAttackType(int tp, int howBeat)
{
//Какую анимацию из AttackType "я" выбираю
animNow = howBeat;
//Устанавливаю изначальный кадр
attackNow = 0;
//Получаю урон из соотв. анимации
damageNow = getDamage;
//Получаю силу из соотв. анимации
strNow = getStrenght;
//Получаю время стана из соотв. анимации
stunNow = getStunTime;
//Запускаю
anim.SetInteger("attack", getAttackClip);
}
//Ходьба
public IEnumerator move()
{
while (true)
{
yield return new WaitForSeconds(reactSpeed);
//Если заметил игрока
if (seenPlayer)
{
//Если соблюдены дистанции между игроком и "мной"
if (!getLowerDistance && !getHighestDistance)
{
//шанс на агрессию
int fightt = Random.Range(0, 100);
//Если не повезло - выбор куда идти (вправо или влево)
if (fightt > aggressive)
{
int moveTo = Random.Range(2, 4);
runX = Doing[moveTo].runXnow;
runY = Doing[moveTo].runYnow;
}
//Если повезло
else
{
//Выбрать случайный тип атаки
SetAttackType(-1, Random.Range(0, attackType.ToArray().Length));
}
}
//Если игрок подошел слишком близко
if (getLowerDistance)
{
//Шанс на агрессию в Действии[0] (параметр ChanceToAttack)
int w = Random.Range(0, 100);
//Если ли вообще "удары" в таком случае (когда игрок подошел)
if (Doing[0].AttackType != -1)
//Если есть - проверяет, повезло ли
if (Doing[0].chanceToAttack >= w)
//Устанавливает атаку на номер ее анимации в AttackType
SetAttackType(-1, Doing[0].AttackType);
}
}
}
}
#endregion
//Специальная атака
public void specAttack(int swc)
{
//переключатель коллайдера спец. атаки
switch (swc)
{
case 1:
specialWeapon.enabled = true;
anim.SetInteger("attack", 0);
break;
case 2:
specialWeapon.enabled = false;
break;
}
}
void HPBackUpd()
{
hpBack = HP;
}
//Смерть
void die()
{
//Отключаю спец. атаку
specialWeapon.enabled = false;
dieN = true;
//Выключаю контроллер
gameObject.GetComponent<CharacterController>().enabled = false;
//Делаю его оружие "самостоятельным"
weapon.transform.parent = null;
weapon.isTrigger = false;
weapon.enabled = true;
weapon.gameObject.AddComponent<Rigidbody>();
weapon.GetComponent<weaponAI>().enabled = false;
//Останавливаю все кроутины
StopAllCoroutines();
//Выключаю аниматор
anim.enabled = false;
//Включаю коллайдеры частей тела
foreach (var a in myGibs)
a.enabled = true;
//Выключаю им кинематику
foreach (var a in myGibs)
a.GetComponent<Rigidbody>().isKinematic = false;
//Делаю "удар" по случайной части тела
myGibs[Random.Range(0, myGibs.Length)].GetComponent<Rigidbody>().AddForceAtPosition(-gameObject.transform.forward * 600, player.transform.position, ForceMode.Impulse);
}
//Метод для аниматора
void CB() { }
}
Скрытый текст:
Для лучшего результата стоит конкретно описать задачу, которую вы мне ставите.
(я также могу чего-нибудь спросить, если понадобиться)
Основам программирования не учу.