/** * Copyright 2019 The Knights Of Unity, created by Pawel Stolarczyk * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using DemoGame.Scripts.Gameplay.Cards; using DemoGame.Scripts.Gameplay.NetworkCommunication; using DemoGame.Scripts.Gameplay.NetworkCommunication.MatchStates; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace DemoGame.Scripts.Gameplay.Hands { /// /// UI element responsible for displaying card in hand. /// Can be drag around and dropped back in hand or on the battlefield to send message to host. /// public class CardGrabber : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { #region Fields /// /// Image displaying card's sprite. /// [SerializeField] private Image _cardImage = null; /// /// Textfield displaying the level of a card. /// [SerializeField] private Text _cardLevel = null; /// /// Textfield displaying the card cost. /// [SerializeField] private Text _cost = null; /// /// Card drag speed. /// Every frame this object will reduce the distance to /// by this value. /// [SerializeField] private float _dragSpeed = 0.4f; /// /// Layer used to cast ray from camera. /// This will help determine where user want to drop their card. /// [SerializeField] private LayerMask _rayPlaneMask = new LayerMask(); /// /// If true, this object is currently being dragged. /// private bool _isDragged; /// /// If true, this card grabber is currently hovered over . /// private bool _isVisualized; /// /// Object responsible for showing where dragged card will drop. /// private DropVisualizer _visualizer; /// /// If true, this object has already been played. /// Awaiting play acceptance or rejection. /// private bool _isPlayed; /// /// Set on drag begin. /// Determines how far is the mousse cursor from transform's position. /// private Vector2 _dragOffset; /// /// Position where this object is trying to move to. /// private Vector2 _targetPosition; /// /// Slot in users hand. Cards that are not currently grabbed will try /// to move to slot's position. /// private Transform _slot; /// /// Used to adjust real mouse position on a scaled canvas. /// private CanvasScaler _canvasScaler; /// /// Reference to user's hand region. Dropping card in this region /// returns the card to it's assigned hand slot. /// private RectTransform _handRegion; /// /// Reference to the region of the battlefield where this card can be played. /// private RectTransform _dropRegion; #endregion #region Properties /// /// Returns the underlying card. /// public Card Card { get; private set; } /// /// Slot in users hand. Cards that are not currently grabbed will try /// to move to slot's position. /// public Transform Slot { get; private set; } #endregion #region Events /// /// Invoked whenever user clicks on this object. /// public event Action OnDragStarted; /// /// Invoked whenever user releases this object with pointer over their hand region /// or over any region other than . /// public event Action OnCardReturned; /// /// Invoked whenever user releases this object with pointer over . /// public event Action OnCardPlayed; #endregion #region Mono /// /// Adjusts this object's position. /// private void Update() { if (_isDragged == true) { _targetPosition = (Vector2)Input.mousePosition + _dragOffset; _targetPosition.x = Mathf.Clamp(_targetPosition.x, 0, Screen.width); _targetPosition.y = Mathf.Clamp(_targetPosition.y, 0, Screen.height); } transform.position = Vector2.Lerp(transform.position, _targetPosition, _dragSpeed * Time.deltaTime); HandleVisualization(); } #endregion #region Methods /// /// Starts card drag. /// public void OnPointerDown(PointerEventData eventData) { if (_isPlayed == true) { // Card was already played return; } _isDragged = true; _dragOffset = transform.position - Input.mousePosition; OnDragStarted?.Invoke(this); } /// /// Ends card drag. /// public void OnPointerUp(PointerEventData eventData) { if (_isDragged == true) { _isDragged = false; Vector2 dropPosition = eventData.position; // Clamp the position to screen rect dropPosition.x = Mathf.Clamp(dropPosition.x, 0, Screen.width); dropPosition.y = Mathf.Clamp(dropPosition.y, 0, Screen.height); OnDropped(dropPosition); } } /// /// Creates visualizer to help showing where grabbed card will be dropped. /// private void HandleVisualization() { if (_isDragged == true && OverAssignedRegion(transform.position) == true) { if (_isVisualized == false) { _visualizer = Instantiate(Card.GetCardInfo().VisualizerPrefab); _visualizer.ShowVisualizer(this, MatchCommunicationManager.Instance.IsHost); } _isVisualized = true; _visualizer.UpdatePosition(Card.GetCardInfo().DropRegion, _rayPlaneMask); } else if (_isPlayed == false) { if (_isVisualized == true) { _visualizer.HideVisualizer(this); _visualizer = null; } _isVisualized = false; } } /// /// Returns true if this card grabber hovers over . /// public bool OverAssignedRegion(Vector2 position) { if (IsOverRegion(_handRegion, position) == true) { // Card hovered over hand; can't be played here return false; } else if (_dropRegion != null) { // Check if card is over _dropRegion if (IsOverRegion(_dropRegion, position) == true) { // Card over drop region return true; } else { // Card outside drop region return false; } } else { // _dropRegion is null, card can be played anywhere return true; } } /// /// Determines whether card was dropped in and should be played /// or returned to hand. /// private void OnDropped(Vector2 position) { bool isOverDropRegion = OverAssignedRegion(position); if (isOverDropRegion == true) { _isPlayed = true; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, _rayPlaneMask)) { Debug.Log("Point: " + hit.point); OnCardPlayed?.Invoke(this, hit.point); } else { Debug.LogError("Raycast didn't hit anything; is ground plane active?"); ReturnToHand(); OnCardReturned?.Invoke(this); } } else { ReturnToHand(); OnCardReturned?.Invoke(this); } } /// /// Returns true if is inside . /// public bool IsOverRegion(RectTransform rect, Vector2 point) { // Getting rect of drop region Rect dropRect = rect.rect; dropRect.position += (Vector2)rect.position; dropRect.size *= _canvasScaler.transform.localScale.y; if (dropRect.Contains(point) == true) { // Card over drop region return true; } else { return false; } } /// /// Cancels the play. /// Card is returned to hand. /// public void CancelPlay() { ReturnToHand(); _isPlayed = false; } /// /// Sets assigned slot as the target of this object to move to. /// public void ReturnToHand() { _targetPosition = _slot.position; } /// /// Initializes card grabber. /// Sets the UI and position. /// public void Initialize(Card card, Transform slot, RectTransform handRegion, RectTransform dropRegion, CanvasScaler canvasScaler) { this.Card = card; this.Slot = slot; this._handRegion = handRegion; this._dropRegion = dropRegion; _cardImage.sprite = card.GetCardInfo().Sprite; _cost.text = card.GetCardInfo().Cost.ToString(); _cardLevel.text = "lvl " + card.level.ToString(); _slot = slot; _canvasScaler = canvasScaler; ReturnToHand(); } /// /// Invoked when card play was allowed. /// public void Resolve(MatchMessageCardPlayed message) { if (_isVisualized == true) { _visualizer.HideVisualizer(this); _isVisualized = false; } } #endregion } }