Unity3D仿星露谷物语开发23之拿起道具的动画

1、目标

当点击库存栏上可以carry的道具时,首先arms替换为carry状态,同时手上拿着被点击的道具。当再次点击同一个道具时,ams替换为idle状态,手上放下之前的道具。

这个最主要的是要学会使用AnimatorOverrideController类。

2、概念

AnimatorOverrideController类:用于控制动画其重写控制器的接口。

动画器重写控制器的用途是重写某个控制器的动画剪辑,从而为给定化身定制动画。在运行时基于相同的AnimatorController交换Animator.runtimeAnimatorController和AnimatorOverrideController不会重置状态机的当前状态。

(1)一般使用方法

定义变量:AnimatorOverrideController animatorOverrideController;

初始化变量:animatorOverrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);

animator.runtimeAnimatorController = animatorOverrideController;

通过上面的赋值,后续对animatorOverrideController的修改将直接作用于animator.runtimeAnimatorController(运行态的动画控制器)。

运行中更新动画:animatorOverrideController["shot"] = weaponAnimationClip[weaponIndex];

(2)每帧更新多个动画剪辑

使用AnimatorOverrideController.ApplyOverrides方法,它能够将动画器剪辑绑定重新分配的数量减少到每次调用只有一个。

定义新老动画的映射:List<KeyValuePair<AnimationClip, AnimationClip>> clipOverrides;

初始化:animatorOverrideController.GetOverrides(clipOverrides);

运行中更新动画:animatorOverrideController.ApplyOverrides(clipOverrides);

其中ApplyOverrides函数:对该动画器重写控制器应用写列表

GetOverrides函数:获取该动画器重写控制器中当前定义的动画剪辑重写的列表。

3、思路

首先,创建SO_AnimationType类描述所有的动画剪辑。

其次,创建CharacterAttribute类定义想要替换的动画信息。

然后,基于AnimatorOverrideController类,根据CharacterAttribute类定义的动画信息,将Player下所有当前状态的动画替换为CharacterAttribute类定义的动画。

最后,如果是carry状态,给Player下的equippedItem对象赋以点击道具的图像。

4、创建可交互的动画的描述类

在Assets -> Scripts -> Animation下创建SO_AnimationType.cs脚本。

[CreateAssetMenu(fileName = "so_AnimationType", menuName = "Scriptable Objects/Animation/Animation Type")]
public class SO_AnimationType : ScriptableObject
{
    public AnimationClip animationClip;  // a reference to the actual animation clip  动画剪辑
    public AnimationName animationName;  // an enum of the animation name like "AnimationName.IdleUp"  动画名称
    public CharacterPartAnimator characterPart; // the gameobject name the Animator is on that controls these animation clips e.g. "arms" 动画位置对象
    public PartVariantColour partVariantColour; // to enable colour variations on a animation type to be specified e.g. "none", "bronze", "silver", "gold" 动画颜色变化
    public PartVariantType partVariantType; // the variant type to specify what variant this animation type refers to e.g. "none", "carry", "hoe", "pickaxe", "axe"..etc 动画类型
}

5、创建一个角色属性结构描述想要交换的动画

在Assets -> Scripts -> Animation 下创建CharacterAttribute.cs脚本,目标动画剪辑的属性结构。

[System.Serializable]
public struct CharacterAttribute
{
    public CharacterPartAnimator characterPart;   // 示例: CharacterPartAnimator.arms 动画位置对象
    public PartVariantColour partVariantColour;  // 示例: ParVariantColour.none 动画颜色变化
    public PartVariantType partVariantType;  // 示例: PartVariantType.carry 动画类型

    public CharacterAttribute(CharacterPartAnimator characterPart, PartVariantColour partVariantColour, PartVariantType partVariantType) 
    {
        this.characterPart = characterPart;
        this.partVariantColour = partVariantColour;
        this.partVariantType = partVariantType;
    }
}

6、创建Enum对象

在Assets -> Scripts -> Enums -> Enums.cs 中增加如下信息:

public enum AnimationName
{
    idleDown,
    idleUp,
    idleLeft,
    idleRight,
    walkUp,
    walkDown,
    walkLeft,
    walkRight,
    runUp,
    runDown,
    runLeft,
    runRight,
    useToolUp,
    useToolDown,
    useToolLeft,
    useToolRight,
    swingToolUp,
    swingToolDown,
    swingToolLeft,
    swingToolRight,
    liftToolUp,
    liftToolDown,
    liftToolLeft,
    liftToolRight,
    holdToolUp,
    holdToolDown,
    holdToolLeft,
    holdToolRight,
    pickUp,
    pickDown,
    pickLeft,
    pickRight,
    count
}


public enum CharacterPartAnimator
{
    body,
    arms,
    hair,
    tool,
    hat,
    count
}

public enum PartVariantColour
{
    none,
    count
}

public enum PartVariantType
{
    none,
    carry,
    hoe,
    pickaxe,
    axe,
    scythe,
    wateringCan,
    count
}

7、创建动画重写控制器AnimationOverrides

流程:

1)创建2个动画剪辑的映射,第1个是 动画剪辑 到 动画描述类的映射,第2个是 动画表示名 到 动画描述类的映射。

2)通过配置的动画描述信息,分别填充上一步的两个映射表。

3)输入characterAttributesList,需要的动作的动画描述信息,比如partVariantType为carry的动画描述。

4)根据characterPart(比如arm)扫描当前角色下已有的动画剪辑,找到characterPart这个名称的动画剪辑为currentAnimator

5)找到characterPart对应controller下所有的动画剪辑,得到animationsList列表

我们找到了arms的animatorController,

再点击进入控制器的Run分类,

点击RunRight,其对应的动画剪辑为ArmsNoneNoneRunRight = characterPart + partVariantColour + partVariantType + animationName

6)在animationsList列表中,依次遍历得到动画剪辑animationClip对应的动画描述类so_AnimationType(已有的动画)

7)然后根据characterAttribute(目标动画属性结构)中的信息+ 动画名称(比如RunRight)搜索目标动画剪辑swapSO_AnimationType

8)然后将animationClip 和 swapSO_AnimationType.animationClip的动画剪辑KeyValuePair<AnimationClip, AnimationClip>结构的animsKeyValuePairList中

9)通过aoc.ApplyOverrides让这些动画剪辑替换生效。

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnimationOverrides : MonoBehaviour
{
    [SerializeField] private GameObject character = null;
    [SerializeField] private SO_AnimationType[] soAnimationTypeArray = null;

    private Dictionary<AnimationClip, SO_AnimationType> animationTypeDictionaryByAnimation;
    private Dictionary<string, SO_AnimationType> animationTypeDictionaryByCompositeAttributeKey;

    private void Start()
    {
        // Initialise animation type dictionary keyed by animation clip
        animationTypeDictionaryByAnimation = new Dictionary<AnimationClip, SO_AnimationType>();

        foreach(SO_AnimationType item in soAnimationTypeArray)
        {
            animationTypeDictionaryByAnimation.Add(item.animationClip, item);
        }

        // Initialise animation type dictionary keyed by string
        animationTypeDictionaryByCompositeAttributeKey = new Dictionary<string, SO_AnimationType>();

        foreach (SO_AnimationType item in soAnimationTypeArray) 
        {
            string key = item.characterPart.ToString() + item.partVariantColour.ToString() + item.partVariantType.ToString() + item.animationName.ToString();
            animationTypeDictionaryByCompositeAttributeKey.Add(key, item);
        }
    }

    public void ApplyCharacterCustomisationParameters(List<CharacterAttribute> characterAttributesList)
    {

        // Loop through all character attributes and set the animation override controller for each
        foreach (CharacterAttribute characterAttribute in characterAttributesList)
        {
            Animator currentAnimator = null;
            List<KeyValuePair<AnimationClip, AnimationClip>> animsKeyValuePairList = new List<KeyValuePair<AnimationClip, AnimationClip>>();

            string animatorSOAssetName = characterAttribute.characterPart.ToString();

            // Find animators in scene that match scriptable object animator type
            Animator[] animatorArray = character.GetComponentsInChildren<Animator>();

            foreach (Animator animator in animatorArray)
            {
                if (animator.name == animatorSOAssetName)
                {
                    currentAnimator = animator;
                    break;
                }
            }

            // Get base current animations for animator
            AnimatorOverrideController aoc = new AnimatorOverrideController(currentAnimator.runtimeAnimatorController);

            List<AnimationClip> animationsList = new List<AnimationClip>(aoc.animationClips);

            foreach (AnimationClip animationClip in animationsList)
            {
                // find animation in dictionary
                SO_AnimationType so_AnimationType;
                bool foundAnimation = animationTypeDictionaryByAnimation.TryGetValue(animationClip, out so_AnimationType);

                if (foundAnimation)
                {
                    string key = characterAttribute.characterPart.ToString() + characterAttribute.partVariantColour.ToString() +
                        characterAttribute.partVariantType.ToString() + so_AnimationType.animationName.ToString();  // 目标动画string

                    SO_AnimationType swapSO_AnimationType;
                    bool foundSwapAnimation = animationTypeDictionaryByCompositeAttributeKey.TryGetValue(key, out swapSO_AnimationType);

                    if (foundSwapAnimation)
                    {
                        AnimationClip swapAnimationClip = swapSO_AnimationType.animationClip;
                        animsKeyValuePairList.Add(new KeyValuePair<AnimationClip, AnimationClip>(animationClip, swapAnimationClip));
                    }
                }
            }

            // Apply animation updates to animation override controller and then update animator with the new controller
            aoc.ApplyOverrides(animsKeyValuePairList);
            currentAnimator.runtimeAnimatorController = aoc;
        }
    }

}

8、修改Player.cs脚本

创建相关的变量:

private AnimationOverrides animationOverrides;  // 动画重写控制器
private List<CharacterAttribute> characterAttributeCustomisationList;  // 目标动作列表

[Tooltip("Should be populated in the prefab with the equippped item sprite renderer")]
[SerializeField] private SpriteRenderer equippedItemSpriteRenderer = null; // 武器的图片

在Awake中初始化相关变量:

// Initialise swappable character attributes
armsCharacterAttribute = new CharacterAttribute(CharacterPartAnimator.arms, PartVariantColour.none, PartVariantType.none);

// Initialise character attribute list
characterAttributeCustomisationList = new List<CharacterAttribute>();

展示拿起物体/放下物体的动画:

 public void ShowCarriedItem(int itemCode)
 {
     ItemDetails itemDetails = InventoryManager.Instance.GetItemDetails(itemCode);
     if(itemDetails != null)
     {
         equippedItemSpriteRenderer.sprite = itemDetails.itemSprite;
         equippedItemSpriteRenderer.color = new Color(1f, 1f, 1f, 1f);

         // Apply 'carry' character arms customisation
         armsCharacterAttribute.partVariantType = PartVariantType.carry;
         characterAttributeCustomisationList.Clear();
         characterAttributeCustomisationList.Add(armsCharacterAttribute);
         animationOverrides.ApplyCharacterCustomisationParameters(characterAttributeCustomisationList);

         isCarrying = true;
     }
 }


 public void ClearCarriedItem()
 {
     equippedItemSpriteRenderer.sprite = null;
     equippedItemSpriteRenderer.color = new Color(1f, 1f, 1f, 0f);

     // Apply base character arms customisation
     armsCharacterAttribute.partVariantType = PartVariantType.none;
     characterAttributeCustomisationList.Clear();
     characterAttributeCustomisationList.Add(armsCharacterAttribute);
     animationOverrides.ApplyCharacterCustomisationParameters(characterAttributeCustomisationList);

     isCarrying = false;
 }

完整的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : SingletonMonobehaviour<Player>
{
    private AnimationOverrides animationOverrides;  // 动画重写控制器
    private List<CharacterAttribute> characterAttributeCustomisationList;  // 目标动作列表

    [Tooltip("Should be populated in the prefab with the equippped item sprite renderer")]
    [SerializeField] private SpriteRenderer equippedItemSpriteRenderer = null; // 武器的图片

    // Player attributes that can be swapped
    private CharacterAttribute armsCharacterAttribute;
    private CharacterAttribute toolCharacterAttribute;


    private float xInput;
    private float yInput;
    private bool isWalking;
    private bool isRunning;
    private bool isIdle;
    private bool isCarrying = false;
    private ToolEffect toolEffect = ToolEffect.none;
    private bool isUsingToolRight;
    private bool isUsingToolLeft;
    private bool isUsingToolUp;
    private bool isUsingToolDown;
    private bool isLiftingToolRight;
    private bool isLiftingToolLeft;
    private bool isLiftingToolUp;
    private bool isLiftingToolDown;
    private bool isPickingRight;
    private bool isPickingLeft;
    private bool isPickingUp;
    private bool isPickingDown;
    private bool isSwingToolRight;
    private bool isSwingToolLeft;
    private bool isSwingToolUp;
    private bool isSwingToolDown;

    private Camera mainCamera;

    private Rigidbody2D rigidbody2D;

    private Direction playerDirection;

    private float movementSpeed;

    private bool _playerInputIsDisabled = false;

    public bool PlayerInputIsDisabled { get => _playerInputIsDisabled; set => _playerInputIsDisabled = value; }

    protected override void Awake()
    {
        base.Awake();

        rigidbody2D = GetComponent<Rigidbody2D>();

        animationOverrides = GetComponentInChildren<AnimationOverrides>();

        // Initialise swappable character attributes
        armsCharacterAttribute = new CharacterAttribute(CharacterPartAnimator.arms, PartVariantColour.none, PartVariantType.none);

        // Initialise character attribute list
        characterAttributeCustomisationList = new List<CharacterAttribute>();

        mainCamera = Camera.main;
    }

    private void Update()
    {
        #region  Player Input

        if (!PlayerInputIsDisabled)
        {
            ResetAnimationTrigger();

            PlayerMovementInput();

            PlayerWalkInput();

            // Send event to any listeners for player movement input
            EventHandler.CallMovementEvent(xInput, yInput, isWalking, isRunning, isIdle, isCarrying, toolEffect,
                    isUsingToolRight, isUsingToolLeft, isUsingToolUp, isUsingToolDown,
                    isLiftingToolRight, isLiftingToolLeft, isLiftingToolUp, isLiftingToolDown,
                    isPickingRight, isPickingLeft, isPickingUp, isPickingDown,
                    isSwingToolRight, isSwingToolLeft, isSwingToolUp, isSwingToolDown,
                    false, false, false, false);
        }


        #endregion Player Input
    }

    private void FixedUpdate()
    {
        PlayerMovement();
    }

    /// <summary>
    /// 展示拿东西的动画
    /// </summary>
    /// <param name="itemCode"></param>
    public void ShowCarriedItem(int itemCode)
    {
        ItemDetails itemDetails = InventoryManager.Instance.GetItemDetails(itemCode);
        if(itemDetails != null)
        {
            equippedItemSpriteRenderer.sprite = itemDetails.itemSprite;
            equippedItemSpriteRenderer.color = new Color(1f, 1f, 1f, 1f);

            // Apply 'carry' character arms customisation
            armsCharacterAttribute.partVariantType = PartVariantType.carry;
            characterAttributeCustomisationList.Clear();
            characterAttributeCustomisationList.Add(armsCharacterAttribute);
            animationOverrides.ApplyCharacterCustomisationParameters(characterAttributeCustomisationList);

            isCarrying = true;
        }
    }


    public void ClearCarriedItem()
    {
        equippedItemSpriteRenderer.sprite = null;
        equippedItemSpriteRenderer.color = new Color(1f, 1f, 1f, 0f);

        // Apply base character arms customisation
        armsCharacterAttribute.partVariantType = PartVariantType.none;
        characterAttributeCustomisationList.Clear();
        characterAttributeCustomisationList.Add(armsCharacterAttribute);
        animationOverrides.ApplyCharacterCustomisationParameters(characterAttributeCustomisationList);

        isCarrying = false;
    }

    private void PlayerMovement()
    {
        Vector2 move = new Vector2(xInput * movementSpeed * Time.deltaTime, yInput * movementSpeed * Time.deltaTime);
        rigidbody2D.MovePosition(rigidbody2D.position + move);
    }


    private void ResetAnimationTrigger()
    {
        toolEffect = ToolEffect.none;
        isUsingToolRight = false;
        isUsingToolLeft = false;
        isUsingToolUp = false;
        isUsingToolDown = false;
        isLiftingToolRight = false;
        isLiftingToolLeft = false;
        isLiftingToolUp = false;
        isLiftingToolDown = false;
        isPickingRight = false;
        isPickingLeft = false;
        isPickingUp = false;
        isPickingDown = false;
        isSwingToolRight = false;
        isSwingToolLeft = false;
        isSwingToolUp = false;
        isSwingToolDown = false;
    }

    private void PlayerMovementInput()
    {
        xInput = Input.GetAxisRaw("Horizontal");
        yInput = Input.GetAxisRaw("Vertical");

        // 斜着移动
        if (xInput != 0 && yInput != 0) 
        {
            xInput = xInput * 0.71f;
            yInput = yInput * 0.71f;
        }

        // 在移动
        if (xInput != 0 || yInput != 0) 
        {
            isRunning = true;
            isWalking = false;
            isIdle = false;
            movementSpeed = Settings.runningSpeed;

            // Capture player direction for save game
            if (xInput < 0)
            {
                playerDirection = Direction.left;
            }
            else if (xInput > 0)
            {
                playerDirection = Direction.right;
            }
            else if (yInput < 0)
            {
                playerDirection = Direction.down;
            }
            else
            {
                playerDirection = Direction.up;
            }

        }
        else if(xInput == 0 && yInput == 0)
        {
            isRunning = false;
            isWalking = false;
            isIdle = true;
        }
    }

    // 按住Shift键移动为walk
    private void PlayerWalkInput()
    {
        if(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
        {
            isRunning = false;
            isWalking = true;
            isIdle = false;
            movementSpeed = Settings.walkingSpeed;
        }
        else
        {
            isRunning = true;
            isWalking = false;
            isIdle = false;
            movementSpeed = Settings.runningSpeed;
        }
    }

    public Vector3 GetPlayerViewportPosition()
    {
        // Vector3 viewport position for player (0,0) viewport bottom left, (1,1) viewport top right
        return mainCamera.WorldToViewportPoint(gameObject.transform.position);
    }

    public void DisablePlayerInputAndResetMovement()
    {
        DisablePlayerInpupt();
        ResetMovement();

        // Send event to any listeners for player movement input
        EventHandler.CallMovementEvent(xInput, yInput, isWalking, isRunning, isIdle, isCarrying, toolEffect,
                isUsingToolRight, isUsingToolLeft, isUsingToolUp, isUsingToolDown,
                isLiftingToolRight, isLiftingToolLeft, isLiftingToolUp, isLiftingToolDown,
                isPickingRight, isPickingLeft, isPickingUp, isPickingDown,
                isSwingToolRight, isSwingToolLeft, isSwingToolUp, isSwingToolDown,
                false, false, false, false);
    }

    private void ResetMovement()
    {
        // Reset movement
        xInput = 0f;
        yInput = 0f;
        isRunning = false;
        isWalking = false;
        isIdle = true;
    }

    public void DisablePlayerInpupt()
    {
        PlayerInputIsDisabled = true;
    }


    public void EnablePlayerInput()
    {
        PlayerInputIsDisabled = false;
    }
}

9、修改UIInventorySlot.cs脚本

在SetSelectedItem中新增如下代码:

if (itemDetails.canBeCarried == true)
{
    // Show player carrying item
    Player.Instance.ShowCarriedItem(itemDetails.itemCode);
}
else 
{
    Player.Instance.ClearCarriedItem();
}

在ClearSelectedItem中新增如下代码:

// Clear player carrying item
Player.Instance.ClearCarriedItem();

完整的代码如下:

using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;

public class UIInventorySlot : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
    private Camera mainCamera;
    private Transform parentItem; // 场景中的物体父类
    private GameObject draggedItem; // 被拖动的物体
    private Canvas parentCanvas;

    public Image inventorySlotHighlight;
    public Image inventorySlotImage;
    public TextMeshProUGUI textMeshProUGUI;

    [SerializeField] private UIInventoryBar inventoryBar = null;
    [SerializeField] private GameObject itemPrefab = null;
    [SerializeField] private int slotNumber = 0; // 插槽的序列号
    [SerializeField] private GameObject inventoryTextBoxPrefab = null;

    [HideInInspector] public ItemDetails itemDetails;
    [HideInInspector] public int itemQuantity;
    [HideInInspector] public bool isSelected = false;

    private void Awake()
    {
        parentCanvas = GetComponentInParent<Canvas>();
    }

    private void Start()
    {
        mainCamera = Camera.main;
        parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        if(itemDetails != null) 
        {
            // Disable keyboard input
            Player.Instance.DisablePlayerInputAndResetMovement();

            // Instatiate gameobject as dragged item
            draggedItem = Instantiate(inventoryBar.inventoryBarDraggedItem, inventoryBar.transform);

            // Get image for dragged item
            Image draggedItemImage = draggedItem.GetComponentInChildren<Image>();
            draggedItemImage.sprite = inventorySlotImage.sprite;

            SetSelectedItem();
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        // move game object as dragged item
        if(!draggedItem != null)
        {
            draggedItem.transform.position = Input.mousePosition;
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        // Destroy game object as dragged item
        if (draggedItem != null) 
        {
            Destroy(draggedItem);

            // if drag ends over inventory bar, get item drag is over and swap then
            if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>() != null) 
            {
                // get the slot number where the drag ended
                int toSlotNumber = eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>().slotNumber;

                // Swap inventory items in inventory list
                InventoryManager.Instance.SwapInventoryItems(InventoryLocation.player, slotNumber, toSlotNumber);


                // Destroy inventory text box
                DestroyInventoryTextBox();

                // Clear selected item
                ClearSelectedItem();
            }
            else
            {
                // else attemp to drop the item if it can be dropped
                if (itemDetails.canBeDropped)
                {
                    DropSelectedItemAtMousePosition();
                }
            }

            // Enable player input
            Player.Instance.EnablePlayerInput();
        }
    }

    /// <summary>
    /// Drops the item(if selected) at the current mouse position. called by the DropItem event
    /// </summary>
    private void DropSelectedItemAtMousePosition()
    {
        if(itemDetails != null && isSelected)
        {
            Vector3 worldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -mainCamera.transform.position.z));

            // Create item from prefab at mouse position
            GameObject itemGameObject = Instantiate(itemPrefab, worldPosition, Quaternion.identity, parentItem);
            Item item = itemGameObject.GetComponent<Item>();
            item.ItemCode = itemDetails.itemCode;

            // Remove item from player's inventory
            InventoryManager.Instance.RemoveItem(InventoryLocation.player, item.ItemCode);

            // If no more of item then clear selected
            if(InventoryManager.Instance.FindItemInInventory(InventoryLocation.player, item.ItemCode) == -1)
            {
                ClearSelectedItem();
            }

        }
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        // Populate text box with item details
        if(itemQuantity != 0)
        {
            // Instantiate inventory text box
            inventoryBar.inventoryTextBoxGameobject = Instantiate(inventoryTextBoxPrefab, transform.position, Quaternion.identity);
            inventoryBar.inventoryTextBoxGameobject.transform.SetParent(parentCanvas.transform, false);

            UIInventoryTextBox inventoryTextBox = inventoryBar.inventoryTextBoxGameobject.GetComponent<UIInventoryTextBox>();

            // Set item type description
            string itemTypeDescription = InventoryManager.Instance.GetItemTypeDescription(itemDetails.itemType);

            // Populate text box
            inventoryTextBox.SetTextboxText(itemDetails.itemDescription, itemTypeDescription, "", itemDetails.itemLongDescription, "", "");

            // Set text box position according to inventory bar position
            if (inventoryBar.IsInventoryBarPositionBottom)
            {
                inventoryBar.inventoryTextBoxGameobject.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 0f);
                inventoryBar.inventoryTextBoxGameobject.transform.position = new Vector3(transform.position.x, transform.position.y + 50f, transform.position.z);
            }
            else
            {
                inventoryBar.inventoryTextBoxGameobject.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 1f);
                inventoryBar.inventoryTextBoxGameobject.transform.position = new Vector3(transform.position.x, transform.position.y - 50f, transform.position.z);
            }
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        DestroyInventoryTextBox();
    }

    private void DestroyInventoryTextBox()
    {
        if (inventoryBar.inventoryTextBoxGameobject != null) 
        {
            Destroy(inventoryBar.inventoryTextBoxGameobject);
        }
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        // if left click
        if (eventData.button == PointerEventData.InputButton.Left)
        {
            // if inventory slot currently selected then deselect
            if (isSelected == true)
            {
                ClearSelectedItem();
            }
            else  // 未被选中且有东西则显示选中的效果
            {
                if(itemQuantity > 0)
                {
                    SetSelectedItem();
                }
            }
        }
    }

    /// <summary>
    /// Set this inventory slot item to be selected
    /// </summary>
    private void SetSelectedItem()
    {
        // Clear currently highlighted items
        inventoryBar.ClearHighlightOnInventorySlots();

        // Highlight item on inventory bar
        isSelected = true;

        // Set highlighted inventory slots
        inventoryBar.SetHighlightedInventorySlots();

        // Set item selected in inventory
        InventoryManager.Instance.SetSelectedInventoryItem(InventoryLocation.player, itemDetails.itemCode);

        if (itemDetails.canBeCarried == true)
        {
            // Show player carrying item
            Player.Instance.ShowCarriedItem(itemDetails.itemCode);
        }
        else 
        {
            Player.Instance.ClearCarriedItem();
        }
    }

    private void ClearSelectedItem()
    {
        // Clear currently highlighted item
        inventoryBar.ClearHighlightOnInventorySlots();

        isSelected = false;

        // set no item selected in inventory
        InventoryManager.Instance.ClearSelectedInventoryItem(InventoryLocation.player);

        // Clear player carrying item
        Player.Instance.ClearCarriedItem();
    }
}

10、创建对象/资源

(1)创建动画描述资源

在Assets -> Scriptable Object Assets下创建Animation目录。

在其下再新建Animation Types的目录,在其下再创建arms的目录,在其下再创建NoneCarry(partVariantColour=None, partVariantType=Carry)和NoneNone的目录。

新建动画描述示例:

创建的内容罗列如下:其中NoneCarryxxx的放在NoneCarry目录下,NoneNonexxx的放在NoneNone目录下。

NameAnimation ClipAnimation NameCharacter PartPart Variant ColourPart Variant Type
ArmsNoneCarryIdleDownArmsNoneCarryIdleDownIdle DownArmsNoneCarry
ArmsNoneCarryIdleLeftArmsNoneCarryIdleLeftIdle LeftArmsNoneCarry
ArmsNoneCarryIdleRightArmsNoneCarryIdleRightIdle RightArmsNoneCarry
ArmsNoneCarryIdleUpArmsNoneCarryIdleUpIdle UpArmsNoneCarry
ArmsNoneCarryRunDownArmsNoneCarryRunDownRun DownArmsNoneCarry
ArmsNoneCarryRunLeftArmsNoneCarryRunLeftRun LeftArmsNoneCarry
ArmsNoneCarryRunRightArmsNoneCarryRunRightRun RightArmsNoneCarry
ArmsNoneCarryRunUpArmsNoneCarryRunUpRun UpArmsNoneCarry
ArmsNoneCarryWalkDownArmsNoneCarryWalkDownWalk DownArmsNoneCarry
ArmsNoneCarryWalkLeftArmsNoneCarryWalkLeftWalk LeftArmsNoneCarry
ArmsNoneCarryWalkRightArmsNoneCarryWalkRightWalk RightArmsNoneCarry
ArmsNoneCarryWalkUpArmsNoneCarryWalkUpWalk UpArmsNoneCarry
ArmsNoneNoneIdleDownArmsNoneNoneIdleDownIdle DownArmsNoneNone
ArmsNoneNoneIdleLeftArmsNoneNoneIdleLeftIdle LeftArmsNoneNone
ArmsNoneNoneIdleRightArmsNoneNoneIdleRightIdle RightArmsNoneNone
ArmsNoneNoneIdleUpArmsNoneNoneIdleUpIdle UpArmsNoneNone
ArmsNoneNoneRunDownArmsNoneNoneRunDownRun DownArmsNoneNone
ArmsNoneNoneRunLeftArmsNoneNoneRunLeftRun LeftArmsNoneNone
ArmsNoneNoneRunRightArmsNoneNoneRunRightRun RightArmsNoneNone
ArmsNoneNoneRunUpArmsNoneNoneRunUpRun UpArmsNoneNone
ArmsNoneNoneWalkDownArmsNoneNoneWalkDownWalk DownArmsNoneNone
ArmsNoneNoneWalkLeftArmsNoneNoneWalkLeftWalk LeftArmsNoneNone
ArmsNoneNoneWalkRightArmsNoneNoneWalkRightWalk RightArmsNoneNone
ArmsNoneNoneWalkUpArmsNoneNoneWalkUpWalk UpArmsNoneNone

(2)创建动画重写控制器物体

在Hierarchy -> Player下创建新物体命名为CharacterCustomiser。

添加Animation Overriders组件。

(3)修改equippedItem对象

修改position y 为1.5。

(4)Player对象设置equip对象

运行程序,效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/955712.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Unity3D】远处的物体会闪烁问题(深度冲突) Reversed-Z

知识点&#xff1a;深度冲突、像素闪烁现象、Reversed-Z&#xff08;反向Z&#xff09;、浮点数精度问题 前提概要&#xff1a;深度值都是由32位浮点数存储 原因&#xff1a;深度冲突&#xff0c;多个物体之间无法正确地渲染远近关系&#xff0c;出现上一帧可能是A物体在B物体…

彻底理解JVM类加载机制

文章目录 一、类加载器和双亲委派机制1.1、类加载器1.2、双亲委派机制1.3、自定义类加载器1.4、打破双亲委派机制 二、类的加载 图片来源&#xff1a;图灵学院   由上图可知&#xff0c;创建对象&#xff0c;执行其中的方法&#xff0c;在java层面&#xff0c;最重要的有获取…

【2024年华为OD机试】 (A卷,200分)- 快递投放问题(Java JS PythonC/C++)

一、问题描述 题目解析 题目描述 有 N 个快递站点用字符串标识&#xff0c;某些站点之间有道路连接。每个站点有一些包裹要运输&#xff0c;每个站点间的包裹不重复。路上有检查站会导致部分货物无法通行&#xff0c;计算哪些货物无法正常投递。 输入描述 第一行输入 M N&…

python爬虫爬取淘宝商品比价||淘宝商品详情API接口

最近在学习北京理工大学的爬虫课程&#xff0c;其中一个实例是讲如何爬取淘宝商品信息&#xff0c;现整理如下&#xff1a; 功能描述&#xff1a;获取淘宝搜索页面的信息&#xff0c;提取其中的商品名称和价格 探讨&#xff1a;淘宝的搜索接口 翻页的处理 技术路线:requests…

【大数据】机器学习------支持向量机(SVM)

支持向量机的基本概念和数学公式&#xff1a; 1. 线性可分的支持向量机 对于线性可分的数据集 &#xff0c;其中(x_i \in R^d) 是特征向量 是类别标签&#xff0c;目标是找到一个超平面 &#xff0c;使得对于所有 的样本 &#xff0c;对于所有(y_i -1) 的样本&#xff0c;…

服务器数据恢复—EMC存储POOL中数据卷被删除的数据恢复案例

服务器数据恢复环境&故障&#xff1a; EMC Unity 400存储连接了2台硬盘柜。2台硬盘柜上一共有21块硬盘&#xff08;520字节&#xff09;。21块盘组建了2组RAID6&#xff1a;一组有11块硬盘&#xff0c;一组有10块硬盘。 在存储运行过程中&#xff0c;管理员误操作删除了 2组…

Ubuntu系统备份与还原

Ubuntu系统备份与还原 前言ClonezillaTimeshift安装图形界面使用命令行使用 前言 Linux系统备份软件有Clonezilla和TimeShift。 使用Clonezilla需要准备USB启动盘&#xff0c;而Timeshift不需要。 因此推荐使用Timeshift进行备份与还原。 Clonezilla 官网&#xff1a;https:…

CSS:语法、样式表、选择器

目录 一、语法 二、创建 外部样式表 内部样式表 内联样式 三、选择器 ID选择器 类选择器 伪类选择器 :hover a:link a:active a:visited 属性选择器 伪元素选择器 ::first-letter ::first-line ::selection ::placeholder ::before 和::after 通配选择器 标…

记录一次 centos 启动失败

文章目录 现场1分析1现场2分析2搜索实际解决过程 现场1 一次断电,导致 之前能正常启动的centos 7.7 起不来了有部分log , 关键信息如下 [1.332724] XFS(sda3): Internal error xfs ... at line xxx of fs/xfs/xfs_trans.c [1.332724] XFS(sda3): Corruption of in-memory data…

YOLOv5训练长方形图像详解

文章目录 YOLOv5训练长方形图像详解一、引言二、数据集准备1、创建文件夹结构2、标注图像3、生成标注文件 三、配置文件1、创建数据集配置文件2、选择模型配置文件 四、训练模型1、修改训练参数2、开始训练 五、使用示例1、测试模型2、评估模型 六、总结 YOLOv5训练长方形图像详…

“AI智能防控识别系统:守护安全的“智慧卫士”

在如今这个科技飞速发展的时代&#xff0c;安全问题始终是大家关注的焦点。无论是企业园区、学校校园&#xff0c;还是居民社区&#xff0c;都希望能有一双“慧眼”时刻守护着&#xff0c;及时发现并防范各种安全隐患。而AI智能防控识别系统&#xff0c;就像一位不知疲倦、精准…

使用FFmpeg和Python将短视频转换为GIF的使用指南

使用FFmpeg和Python将短视频转换为GIF的使用指南 在数字时代&#xff0c;GIF动图已成为表达情感和分享幽默的重要媒介。无论是社交媒体上的搞笑片段还是创意项目中的视觉效果&#xff0c;GIF都能迅速抓住观众的注意力。然而&#xff0c;很多人不知道如何将短视频转换为GIF。本…

黑马Java面试教程_P1_导学与准备篇

系列博客目录 文章目录 系列博客目录导学Why?举例 准备篇企业是如何筛选简历的(筛选简历的规则)HR如何筛选简历部门负责人筛选简历 简历注意事项简历整体结构个人技能该如何描述项目该如何描述 应届生该如何找到合适的练手项目项目来源找到项目后&#xff0c;如何深入学习项目…

【前端动效】HTML + CSS 实现打字机效果

目录 1. 效果展示 2. 思路分析 2.1 难点 2.2 实现思路 3. 代码实现 3.1 html部分 3.2 css部分 3.3 完整代码 4. 总结 1. 效果展示 如图所示&#xff0c;这次带来的是一个有趣的“擦除”效果&#xff0c;也可以叫做打字机效果&#xff0c;其中一段文本从左到右逐渐从…

C#学习笔记 --- 基础补充

1.operator 运算符重载&#xff1a;使自定义类可以当做操作数一样进行使用。规则自己定。 2.partial 分部类&#xff1a; 同名方法写在不同位置&#xff0c;可以当成一个类使用。 3.索引器&#xff1a;使自定义类可以像数组一样通过索引值 访问到对应的数据。 4.params 数…

「刘一哥GIS」系列专栏《GRASS GIS零基础入门实验教程(配套案例数据)》专栏上线了

「刘一哥GIS」系列专栏《GRASS GIS零基础入门实验教程》全新上线了&#xff0c;欢迎广大GISer朋友关注&#xff0c;一起探索GIS奥秘&#xff0c;分享GIS价值&#xff01; 本专栏以实战案例的形式&#xff0c;深入浅出地介绍了GRASS GIS的基本使用方法&#xff0c;用一个个实例讲…

当当网书籍信息爬虫

1.基本理论 1.1概念体系 网络爬虫又称网络蜘蛛、网络蚂蚁、网络机器人等&#xff0c;可以按照我们设置的规则自动化爬取网络上的信息&#xff0c;这些规则被称为爬虫算法。是一种自动化程序&#xff0c;用于从互联网上抓取数据。爬虫通过模拟浏览器的行为&#xff0c;访问网页…

【12】Word:张老师学术论文❗

目录 题目 ​NO2 NO3 NO4 NO5 NO6 NO7.8 题目 NO2 布局→页面设置→纸张&#xff1a;A4→页边距&#xff1a;上下左右边距→文档网格&#xff1a;只指定行网格→版式&#xff1a;页眉和页脚&#xff1a;页脚距边界&#xff1a;1.4cm居中设置论文页码&#xff1a;插入…

RabbitMQ实现延迟消息发送——实战篇

在项目中&#xff0c;我们经常需要使用消息队列来实现延迟任务&#xff0c;本篇文章就向各位介绍使用RabbitMQ如何实现延迟消息发送&#xff0c;由于是实战篇&#xff0c;所以不会讲太多理论的知识&#xff0c;还不太理解的可以先看看MQ的延迟消息的一个实现原理再来看这篇文章…

【PCL】Segmentation 模块—— 欧几里得聚类提取(Euclidean Cluster Extraction)

1、简介 PCL 的 Euclidean Cluster Extraction&#xff08;欧几里得聚类提取&#xff09; 是一种基于欧几里得距离的点云聚类算法。它的目标是将点云数据分割成多个独立的簇&#xff08;clusters&#xff09;&#xff0c;每个簇代表一个独立的物体或结构。该算法通过计算点与点…