public class EndlessRotation : MonoBehaviour
{
[SerializeField] private Vector3 _rotation;
private void Update()
{
_rotation.x += 10 * Time.deltaTime;
_rotation.y += 15 * Time.deltaTime;
_rotation.z += 20 * Time.deltaTime;
for (var i = 0; i < 3; i++)
{
if (_rotation[i] >= 360f)
_rotation[i] -= 360f;
}
transform.rotation = Quaternion.Euler(_rotation);
}
}
public class Weapon : MonoBehaviour
{
public void Hide()
{
gameObject.SetActive(false);
}
}
public class PlayerInput : MonoBehaviour
{
public event Action InteractionButtonPressed = delegate { };
private void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
InteractionButtonPressed();
}
}
}
public class PickUpArea : MonoBehaviour
{
private readonly List<Weapon> _closeWeapons = new();
public bool HasItemsAround => _closeWeapons.Count > 0;
public void PickUp(PlayableCharacter character)
{
var closestWeapon = GetClosestWeapon();
_closeWeapons.Remove(closestWeapon);
character.Give(closestWeapon);
}
private Weapon GetClosestWeapon()
{
Weapon closest = null;
var minDistance = float.MaxValue;
foreach (var weapon in _closeWeapons)
{
var distance = (transform.position - weapon.transform.position).sqrMagnitude;
if (distance < minDistance)
{
closest = weapon;
minDistance = distance;
}
}
return closest;
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.TryGetComponent(out Weapon weapon))
{
_closeWeapons.Add(weapon);
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.TryGetComponent(out Weapon weapon))
{
_closeWeapons.Remove(weapon);
}
}
}
public class PlayableCharacter : MonoBehaviour
{
[SerializeField] private PlayerInput _input;
[SerializeField] private PickUpArea _pickUpArea;
[SerializeField] private List<Weapon> _weapons = new();
private void Start()
{
_input.InteractionButtonPressed += TryPickUp;
}
private void OnDestroy()
{
_input.InteractionButtonPressed -= TryPickUp;
}
public void Give(Weapon weapon)
{
weapon.Hide();
_weapons.Add(weapon);
}
private void TryPickUp()
{
if (_pickUpArea.HasItemsAround)
{
_pickUpArea.PickUp(this);
}
}
}
public GameObject[] ObjectsForDestroy;
private void CreateDirectory()
{
if (Directory.Exists(_directoryPath) == false)
{
Directory.CreateDirectory(_directoryPath);
}
}
private void CreateFile()
{
if (File.Exists(_path)) return;
var json = JsonUtility.ToJson(CompleteStatus.Locked);
using (var writer = File.CreateText(_path))
{
writer.Write(json);
writer.Close();
}
}
namespace Levels
{
public interface ILevel : IVisualization<ILevelView>
{
void Load();
void Complete(CompleteStatus status);
}
}
namespace Levels
{
public interface IVisualization<in TView>
{
void Visualize(TView view);
}
}
namespace Levels
{
public enum CompleteStatus
{
Locked,
Uncompleted,
OneStar,
TwoStars,
ThreeStars
}
public interface ILevelView
{
void DrawCompletion(CompleteStatus status);
}
}
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Levels
{
public abstract class Level : ILevel
{
private readonly string _name;
private readonly string _path;
private readonly string _directoryPath = $"{Application.persistentDataPath}/Saves";
protected Level(string name)
{
_name = name;
_path = $"{_directoryPath}/{_name}.json";
CreateDirectory();
CreateFile();
}
public void Load()
{
SceneManager.LoadScene(_name);
}
public void Complete(CompleteStatus status)
{
SaveStatus(status);
}
public void Visualize(ILevelView view)
{
view.DrawCompletion(LoadStatusFromJson());
}
private void CreateDirectory()
{
if (Directory.Exists(_directoryPath) == false)
{
Directory.CreateDirectory(_directoryPath);
}
}
private void CreateFile()
{
if (File.Exists(_path)) return;
var json = JsonUtility.ToJson(CompleteStatus.Locked);
using (var writer = File.CreateText(_path))
{
writer.Write(json);
writer.Close();
}
}
private void SaveStatus(CompleteStatus status)
{
var json = JsonUtility.ToJson(status);
File.WriteAllText(_path, json);
}
private CompleteStatus LoadStatusFromJson()
{
var file = File.ReadAllText(_path);
return JsonUtility.FromJson<CompleteStatus>(file);
}
}
}
namespace Levels
{
public class SecondLevel : Level
{
public SecondLevel() : base(nameof(SecondLevel))
{
}
}
}
using System;
using UnityEngine;
using UnityEngine.UI;
namespace Levels
{
public class LevelView : MonoBehaviour, ILevelView
{
[SerializeField] private Image _firstStar, _secondStar, _thirdStar;
[SerializeField] private Button _button;
[SerializeField] private Color _inactiveStarColor, _activeStarColor;
public void DrawCompletion(CompleteStatus status)
{
switch (status)
{
case CompleteStatus.Locked:
_firstStar.color = _inactiveStarColor;
_secondStar.color = _inactiveStarColor;
_thirdStar.color = _inactiveStarColor;
_button.interactable = false;
break;
case CompleteStatus.Uncompleted:
_button.interactable = true;
_firstStar.color = _inactiveStarColor;
_secondStar.color = _inactiveStarColor;
_thirdStar.color = _inactiveStarColor;
break;
case CompleteStatus.OneStar:
_button.interactable = true;
_firstStar.color = _activeStarColor;
_secondStar.color = _inactiveStarColor;
_thirdStar.color = _inactiveStarColor;
break;
case CompleteStatus.TwoStars:
_button.interactable = true;
_firstStar.color = _activeStarColor;
_secondStar.color = _activeStarColor;
_thirdStar.color = _inactiveStarColor;
break;
case CompleteStatus.ThreeStars:
_button.interactable = true;
_firstStar.color = _activeStarColor;
_secondStar.color = _activeStarColor;
_thirdStar.color = _activeStarColor;
break;
default: throw new ArgumentException();
}
}
}
}
using System.Collections.Generic;
using UnityEngine;
namespace Levels
{
public class LevelList : MonoBehaviour
{
[SerializeField] private LevelView _levelViewPrefab;
[SerializeField] private Transform _levelsParent;
private ILevel[] _levels;
private void Awake()
{
var first = new FirstLevel();
var second = new SecondLevel();
var third = new ThirdLevel();
var fourth = new FourthLevel();
var fifth = new FifthLevel();
_levels = new ILevel[]
{
first, second, third, fourth, fifth
};
//test
first.Complete(CompleteStatus.OneStar);
second.Complete(CompleteStatus.TwoStars);
third.Complete(CompleteStatus.ThreeStars);
fourth.Complete(CompleteStatus.Uncompleted);
fifth.Complete(CompleteStatus.Locked);
VisualizeLevels(_levels);
}
private void VisualizeLevels(IEnumerable<ILevel> levels)
{
foreach (var level in levels)
{
var view = Instantiate(_levelViewPrefab, _levelsParent);
level.Visualize(view);
}
}
}
}
public class Lever : MonoBehaviour
{
private float _rotation = 0f;
private int LapsCount => (int)(_rotation / 360);
private void Rotate(float angle)
{
_rotation += angle;
transform.rotation = Quaternion.Euler(new Vector3(0, 0, _rotation));
}
private void Update()
{
if (Input.GetKey(KeyCode.A))
{
Rotate(100f * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D))
{
Rotate(-100f * Time.deltaTime);
}
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log(LapsCount);
}
}
}
Hsys
PCont
TakeDamage
DamageCondition
namespace Coroutines
{
public interface IHealth
{
bool IsOver { get; }
void Lose(float amount);
void Restore(float amount);
}
}
using System;
using UnityEngine;
namespace Coroutines
{
public class Health : IHealth
{
private readonly float _max;
private readonly float _min;
private float _current;
public Health(float max, float min = 0)
{
_max = max;
_min = min;
_current = _max;
}
public bool IsOver => _current <= _min;
public void Lose(float amount)
{
if (amount <= 0) throw new ArgumentException();
SetCurrent(_current - amount);
}
public void Restore(float amount)
{
if (amount <= 0) throw new ArgumentException();
SetCurrent(_current + amount);
}
private void SetCurrent(float amount)
{
_current = Mathf.Clamp(amount, _min, _max);
}
}
}
namespace Coroutines
{
public interface IDamageable
{
bool IsDead { get; }
void ApplyDamage(float amount);
}
}
using UnityEngine;
namespace Coroutines
{
public class Character : MonoBehaviour, IDamageable
{
[SerializeField] private float _maxHp = 100f;
private IHealth _health;
public bool IsDead => _health.IsOver;
private void Awake()
{
_health = new Health(_maxHp);
}
public void ApplyDamage(float amount) => _health.Lose(amount);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Coroutines
{
public class Trap : MonoBehaviour
{
[SerializeField] private float _damage = 10f;
[SerializeField] private float _delay = 2f;
private readonly Dictionary<IDamageable, Coroutine> _targets = new();
private void OnTriggerEnter2D(Collider2D other)
{
if (other.TryGetComponent(out IDamageable damageable))
{
var coroutine = StartCoroutine(Damaging(damageable));
_targets.Add(damageable, coroutine);
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.TryGetComponent(out IDamageable damageable))
{
var coroutine = _targets[damageable];
StopCoroutine(coroutine);
_targets.Remove(damageable);
}
}
private IEnumerator Damaging(IDamageable target)
{
while (target.IsDead == false)
{
target.ApplyDamage(_damage);
yield return new WaitForSeconds(_delay);
}
}
}
}
using UnityEngine;
namespace Bullets
{
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public class Bullet : MonoBehaviour, IBullet
{
[SerializeField] private float _speed = 5f;
[SerializeField] private int _damage = 10;
[SerializeField] private int _maxReflections = 5;
private Rigidbody2D _rigidbody;
private Vector2 _direction = Vector2.zero;
private void Awake()
{
_rigidbody = GetComponent<Rigidbody2D>();
_rigidbody.isKinematic = true;
_rigidbody.useFullKinematicContacts = true;
}
public void Shoot(Vector2 direction)
{
_direction = direction.normalized;
}
private void FixedUpdate()
{
if (_direction == Vector2.zero) return;
var deltaMovement = _direction * _speed * Time.deltaTime;
_rigidbody.MovePosition(_rigidbody.position + deltaMovement);
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.transform.TryGetComponent(out IDamageable damageable))
{
damageable.ApplyDamage(_damage);
Destroy();
return;
}
var normal = other.GetContact(0).normal;
Reflect(normal);
}
private void Destroy()
{
Destroy(gameObject);
}
private void Reflect(Vector2 normal)
{
if (_maxReflections - 1 == 0) Destroy();
_maxReflections--;
_direction = Vector2.Reflect(_direction, normal).normalized;
}
}
}
using UnityEngine;
namespace Bullets
{
public class SomeGun : MonoBehaviour
{
[SerializeField] private Bullet _bulletPrefab;
[SerializeField] private Transform _bulletOrigin;
[SerializeField] private Camera _camera;
private void Update()
{
LookAtMouse(Input.mousePosition);
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Shoot();
}
}
private void LookAtMouse(Vector3 mousePosition)
{
var gunScreenPosition = _camera.WorldToScreenPoint(transform.position);
var direction = mousePosition - gunScreenPosition;
direction.Scale(new Vector3(1, 1, 0));
transform.up = direction;
}
private void Shoot()
{
var bullet = Instantiate(_bulletPrefab, _bulletOrigin.position, Quaternion.identity);
bullet.Shoot(transform.up);
}
}
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player"))
{
_isPlayerInside= true;
}
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.TryGetComponent(out Player player) _isPlayerInside = true;
}
private void OnTriggerEnter2D(Collider2D coll) { if (coll.TryGetComponent(out Player player)) _isPlayerInside = true; }
using UnityEngine;
namespace BehaviourTree
{
public interface IMovementInput
{
Vector3 Direction { get; }
}
}
using UnityEngine;
namespace BehaviourTree
{
[RequireComponent(typeof(Rigidbody))]
public class Bot : MonoBehaviour
{
[SerializeField] private MonoBehaviour _botInput = null;
[SerializeField] private float _speed = 10f;
private Rigidbody _rb;
private IMovementInput BotInput => (IMovementInput) _botInput;
private void OnValidate()
{
if (_botInput is IMovementInput) return;
Debug.LogError($"{nameof(_botInput)} should implement {nameof(IMovementInput)}");
_botInput = null;
}
private void Awake()
{
_rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
var velocity = new Vector3(BotInput.Direction.x, 0, BotInput.Direction.z) * _speed;
_rb.velocity = velocity;
}
public void LookAt(Vector3 direction)
{
var rotation = Quaternion.LookRotation(direction);
transform.rotation = rotation;
}
}
}
using UnityEngine;
namespace BehaviourTree
{
public class BotInput : MonoBehaviour, IMovementInput
{
public Vector3 Direction { get; set; }
}
}
using UnityEngine;
namespace BehaviourTree
{
public interface ITarget
{
Vector3 Position { get; }
}
}
using UnityEngine;
namespace BehaviourTree
{
public class SomeTarget : MonoBehaviour, ITarget
{
public Vector3 Position => transform.position;
}
}
using BehaviorDesigner.Runtime;
namespace BehaviourTree.Brain
{
public class SharedBotInput : SharedVariable<BotInput>
{
public static implicit operator SharedBotInput(BotInput input) => new SharedBotInput {Value = input};
}
}
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using UnityEngine;
namespace BehaviourTree.Brain
{
public class SetRandomDirection : Action
{
public SharedVector3 Direction;
public override TaskStatus OnUpdate()
{
Direction.Value = Vector3.Scale(Random.insideUnitSphere.normalized, new Vector3(1, 0, 1));
return TaskStatus.Success;
}
}
}
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
namespace BehaviourTree.Brain
{
public class SetInput : Action
{
public SharedBotInput SharedInput;
public SharedVector3 Input;
public override TaskStatus OnUpdate()
{
SharedInput.Value.Direction = Input.Value;
return TaskStatus.Success;
}
}
}
using BehaviorDesigner.Runtime.Tasks;
namespace BehaviourTree.Brain
{
public class LookAtDirection : Action
{
public SharedBotInput Direction;
public Bot Bot;
public override TaskStatus OnUpdate()
{
Bot.LookAt(Direction.Value.Direction);
return TaskStatus.Success;
}
}
}
using BehaviorDesigner.Runtime;
namespace BehaviourTree.Brain
{
public class SharedTarget : SharedVariable<SomeTarget>
{
public static implicit operator SharedTarget(SomeTarget target) => new SharedTarget {Value = target};
}
}
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using UnityEngine;
namespace BehaviourTree.Brain
{
public class SetDirectionToTarget : Action
{
public SomeTarget Target;
public Bot Origin;
public SharedVector3 Direction;
public override TaskStatus OnUpdate()
{
var direction = (Target.Position - Origin.transform.position).normalized;
Direction.Value = new Vector3(direction.x, 0, direction.z);
return TaskStatus.Success;
}
}
}
namespace BehaviourTree.Brain
{
public interface IEye
{
bool InSight(ITarget target);
}
}
using UnityEngine;
namespace BehaviourTree.Brain
{
public class Eye : IEye
{
private readonly float _fov;
private readonly Transform _origin;
public Eye(float fov, Transform origin)
{
_fov = fov;
_origin = origin;
}
public bool InSight(ITarget target)
{
var direction = target.Position - _origin.position;
var angle = Mathf.Acos(Vector3.Dot(_origin.forward.normalized, direction.normalized)) * Mathf.Rad2Deg;
return angle <= _fov;
}
}
}
соответственно, нет точного понимания, когда именно произойдет изменение свойства Interactable
public class SomeCanvas : MonoBehaviour
{
public event Action<bool> ActiveStateChanged;
[SerializeField] private CanvasGroup _canvasGroup;
public void Activate()
{
SetActiveState(true);
}
public void Deactivate()
{
SetActiveState(false);
}
private void SetActiveState(bool state)
{
if (_canvasGroup.interactable == state) return;
_canvasGroup.interactable = state;
ActiveStateChanged?.Invoke(state);
}
}
public class NotSomeCanvas : MonoBehaviour
{
[SerializeField] private SomeCanvas _someCanvas;
private void OnEnable()
{
_someCanvas.ActiveStateChanged += LogActiveState;
}
private void OnDisable()
{
_someCanvas.ActiveStateChanged -= LogActiveState;
}
private void LogActiveState(bool state)
{
print($"Current state - {state}");
}
}
Что я не так делаю с интерфейсами?
namespace Health
{
public interface IHealth
{
void Lose(int amount);
void Restore(int amount);
}
public interface IMutable<out T>
{
T Current { get; }
}
public interface IFinal
{
event Action Over;
}
}
using System;
using UnityEngine;
namespace Health
{
public class Health : IHealth, IFinal, IMutable<int>
{
public event Action Over;
private readonly int _max;
private const int Min = 0;
public Health(int max)
{
_max = max;
Current = _max;
}
public int Current { get; private set; }
public void Lose(int amount)
{
SetCurrent(Current - amount);
}
public void Restore(int amount)
{
SetCurrent(Current + amount);
}
private void SetCurrent(int amount)
{
Current = Mathf.Clamp(amount, Min, _max);
if (Current == Min) Over?.Invoke();
}
}
}
namespace Health
{
public interface IDamageable
{
void ApplyDamage(int amount);
}
}
using UnityEngine;
namespace Health
{
public class Knight : MonoBehaviour, IDamageable
{
[SerializeField] private int _maxHealth = 100;
private Health _health;
private void Awake()
{
_health = new Health(_maxHealth);
}
private void OnEnable()
{
_health.Over += Die;
}
private void OnDisable()
{
_health.Over -= Die;
}
public void ApplyDamage(int amount)
{
_health.Lose(amount);
Debug.Log($"Damaged, hp left - {_health.Current}");
}
private void Die()
{
Debug.Log("Died");
Destroy(gameObject);
}
}
}
using UnityEngine;
namespace Health
{
public class Enemy : MonoBehaviour
{
[SerializeField] private int _damage = 50;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.TryGetComponent(out IDamageable damageable))
{
damageable.ApplyDamage(_damage);
}
}
}
}