脚本(3D Survival Game Learning)

PlayerMovement

public class PlayerMovement : MonoBehaviour
{
    public CharacterController controller;

    public float speed = 12f;
    public float gravity = -9.81f * 2;
    public float jumpHeight = 3f;

    public Transform groundCheck;//落地检测
    public float groundDistance = 0.4f;//落地检测距离
    public LayerMask groundMask;//声明打算用于落地检测的层

    Vector3 velocity;

    bool isGrounded;

    // Update is called once per frame
    void Update()
    {
        //checking if we hit the ground to reset our falling velocity, otherwise we will fall faster the next time
        isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
        if (isGrounded && velocity.y < 0)
        {
            velocity.y = -2f;
        }

        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        //right is the red Axis, foward is the blue axis
        Vector3 move = transform.right * x + transform.forward * z;

        controller.Move(move * speed * Time.deltaTime);

        //如果在地面上,就可以跳(不许连跳)(以后多加个能力参数来解锁连跳吧)
        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            //the equation for jumping
            velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        }

        velocity.y += gravity * Time.deltaTime;

        controller.Move(velocity * Time.deltaTime);
    }
}

MouseMovement

public class MouseMovement : MonoBehaviour
{

    public float mouseSensitivity = 100f;

    float xRotation = 0f;
    float YRotation = 0f;

    void Start()
    {
        //将鼠标光标固定在屏幕中央(不可视)
        Cursor.lockState = CursorLockMode.Locked;
    }
        

    void Update()
    {
        //如果背包被打开
        if (InventorySystem.Instance.isOpen == false)
        {


            float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
            float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;

            //控制以x轴为轴心的旋转(抬头/低头)
            xRotation -= mouseY;

            //保证抬头/低头幅度不超过90°
            xRotation = Mathf.Clamp(xRotation, -90f, 90f);

            //控制以y轴为轴心的旋转(左右看)
            YRotation += mouseX;

            //applying both rotations
            transform.localRotation = Quaternion.Euler(xRotation, YRotation, 0f);
        }

    }
}

SelectionManager

public class SelectionManager : MonoBehaviour
{
    //让SelectionManager变成一个单例,为了实现这一目的还创建了awak方法,不懂
    public static SelectionManager instance { get; set; }
    public bool onTarget;

    public GameObject interaction_Info_UI;//声明一个UI交互界面,绑定之前
    Text interaction_text;//声明组件文本,用于获取UI交互界面的文本组件

    public Image centerDotImage;
    public Image handIcon;


    private void Start()
    {
        onTarget = false;
        interaction_text = interaction_Info_UI.GetComponent<Text>();//获取UI交互界面的文本组件
    }

    void Update()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//从屏幕中心向鼠标位置发射射线(之前把鼠标位置固定在屏幕的正中间)
        RaycastHit hit;//射线命中信息
        if (Physics.Raycast(ray, out hit))//如果射线击中了,射线命中信息hit存储相关信息
        {
            var selectionTransform = hit.transform;//获取被击中单位的位置

            InteractableObject ourInteractable = selectionTransform.GetComponent<InteractableObject>();

            if (ourInteractable&&ourInteractable.playerInRange)//如果被击中单位时可互动对象InteractableObject才继续 2/这里进行过修改,保证角色进入一定范围才显示
            {
                onTarget = true;
                ourInteractable.onTarget = true;
                interaction_text.text = selectionTransform.GetComponent<InteractableObject>().GetItemName();//修改UI交互界面的文本为可互动对象的名字
                interaction_Info_UI.SetActive(true);//把先前禁用的UI交互界面打开,以至于我们在屏幕上可以看到文本信息

                if(ourInteractable.CompareTag("pickable"))//如果对象为可拾取的单位
                {
                    centerDotImage.gameObject.SetActive(false);//原先准心禁用
                    handIcon.gameObject.SetActive(true);//提示用准心启用
                }
                else
                {
                    centerDotImage.gameObject.SetActive(true);
                    handIcon.gameObject.SetActive(false);
                }
            }
            else
            {
                onTarget = false;
                interaction_Info_UI.SetActive(false);//反之再次禁用UI交互界面
                centerDotImage.gameObject.SetActive(true);
                handIcon.gameObject.SetActive(false);
            }

        }
        else
        {
            onTarget = false;
            interaction_Info_UI.SetActive(false);//如果射线没有击中,也禁用UI交互界面
            centerDotImage.gameObject.SetActive(true);
            handIcon.gameObject.SetActive(false);
        }
    }

    private void Awake()
    {
        if(instance != null&& instance !=this)
        {
            Destroy(gameObject);
        }
        else
        {
            instance = this;
        }
    }
}

ItemSlot

public class ItemSlot : MonoBehaviour, IDropHandler
{

    public GameObject Item
    {
        get
        {
            if (transform.childCount > 0)
            {
                return transform.GetChild(0).gameObject;
            }

            return null;
        }
    }






    public void OnDrop(PointerEventData eventData)
    {
        Debug.Log("OnDrop");

        //if there is not item already then set our item.如果选择的新父级没有子元素
        if (!Item)
        {

            DragDrop.itemBeingDragged.transform.SetParent(transform);//设置新的父级
            DragDrop.itemBeingDragged.transform.localPosition = new Vector2(0, 0);//重设相对位置,让对象图片显示在Slot的正中间

        }


    }




}

DragDrop

public class DragDrop : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{

    //[SerializeField] private Canvas canvas;//对上层对象Canvas的引用
    private RectTransform rectTransform;//矩阵位置,相对于画布的位置
    private CanvasGroup canvasGroup;//画布组,这个组件用于保证在我们点击当前游戏对象时它可以与它上层的插槽格分离

    public static GameObject itemBeingDragged;
    Vector3 startPosition;
    Transform startParent;//记录初始父级(原来所处的插槽),之后如果拖拽失败了,游戏对象还要回到初始父级



    private void Awake()
    {

        rectTransform = GetComponent<RectTransform>();
        canvasGroup = GetComponent<CanvasGroup>();//记得在挂载的对象上添加这个组件,不然会报错

    }

    //开始拖拽
    public void OnBeginDrag(PointerEventData eventData)
    {

        Debug.Log("OnBeginDrag");
        canvasGroup.alpha = 0.6f;//修改alpha值,实现当我们开始拖拽时物品图标会变得有些透明
        canvasGroup.blocksRaycasts = false; //So the ray cast will ignore the item itself.之前我们有一个射线来着,检测碰撞来获取物品信息等的,现在让射线碰撞忽视这个对象
        startPosition = transform.position;
        startParent = transform.parent;
        transform.SetParent(transform.root);//开始拖拽时,游戏对象从一开始的作为插槽Slot的子元素变成了库存屏幕InventoryScreen的子元素
        itemBeingDragged = gameObject;

    }

    //正在拖拽
    public void OnDrag(PointerEventData eventData)
    {
        //So the item will move with our mouse (at same speed)  and so it will be consistant if the canvas has a different scale (other then 1);
        rectTransform.anchoredPosition += eventData.delta;//拖动速度与鼠标速度一致  除以画布缩放倍率可以避免一些bug/canvas.scaleFactor,但这导致实例化物品图标时候会有bug,所以先注释掉

    }


    //结束拖拽
    public void OnEndDrag(PointerEventData eventData)
    {

        itemBeingDragged = null;

        //如果移动后父级还是原来那个或者超出了移动范围成为了库存屏幕InventoryScreen的子元素,那么该游戏对象的父级还是原来那个
        if (transform.parent == startParent || transform.parent == transform.root)
        {
            transform.position = startPosition;
            transform.SetParent(startParent);

        }

        Debug.Log("OnEndDrag");
        canvasGroup.alpha = 1f;
        canvasGroup.blocksRaycasts = true;
    }



}

BluePrint

public class BluePrint : MonoBehaviour
{
    public string itemName;

    public string req1 = "";
    public string req2 = "";
    public string req3 = "";
    public string req4 = "";

    public int req1Amount=0;
    public int req2Amount=0;
    public int req3Amount=0;
    public int req4Amount=0;

    public int numOfRequirements;

    public BluePrint(string name,int reqNum,string R1,int R1num,string R2,int R2num)//后面可以重载
    {
        itemName = name;

        numOfRequirements = reqNum;

        req1 = R1;
        req2 = R2;

        req1Amount = R1num;
        req2Amount = R2num;
    }

}

InteractableObject

public class InteractableObject : MonoBehaviour
{
    // Start is called before the first frame update
    public bool playerInRange;

    public string itemName = "?_?";

    public bool onTarget = false;

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.E)&&playerInRange&&onTarget)
        {
            if(InventorySystem.Instance.CheckIfFull()== false)
            {
                InventorySystem.Instance.AddToInventory(itemName);
                Destroy(gameObject);

            }
            else
            {
                Debug.Log("背包满了,捡不起来");
            }
            
        }

    }


    public string GetItemName()
    {

        return itemName;
    }



    //进入检测范围,将playerInRange置为真
    private void OnTriggerEnter(Collider other)
    {
        //j仅对标签为玩家的对象做出反应
        if(other.CompareTag("Player"))
        {
            playerInRange = true;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            playerInRange = false;
        }
    }

    

}

InventorySystem

 public class InventorySystem : MonoBehaviour
{
//令InventorySystem成为一个单例子(在游戏运行中一直存在且仅存在一个),把这个脚本的所有public类型的属性和脚本公开给所有脚本使用
    public static InventorySystem Instance { get; set; }

    public GameObject inventoryScreenUI;//背包UI

    public List<GameObject> slotList = new List<GameObject>();//获取所有slot

    public List<String> itemList = new List<string>();//项目列表,获取被添加进背包的游戏对象名称

    private GameObject itemToAdd;//将要加入背包的游戏对象
    private GameObject whatSlotToEquip;//将要被游戏对象插入的slot

    public bool isOpen;//背包被打开

    public bool isFull;//背包已满

    //引用Canvas中的PickupPopUp
    public GameObject pickupAlert;
    public Text pickupName;//拾取对象的名字所要显示的文本对象
    public Image pickupImage;//拾取对象的图片所要显示的图片对象

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
    }


    void Start()
    {
        isOpen = false;
        isFull = false;
        PopulateSlotList();//可以直接打出这个方法,用alt+enter自动生成相应的方法
    }

    //初始化背包插槽的列表
    private void PopulateSlotList()
    {
        foreach(Transform child in inventoryScreenUI.transform)
        {
            if(child.CompareTag("Slot"))
            {
                slotList.Add(child.gameObject);
            }
        }
    }

    //根据名字添加游戏对象的预制体
    public void AddToInventory(string itemName)
    {
       if(isFull == false)//在背包没满的情况下添加,在没有逻辑错误情况下这个if总会通过,可以保留用以检查bug
        {
            whatSlotToEquip = FindNextEmptySlot();//找到空插槽

            //实例化想要添加到背包内的对象(根据名字实例对象,位置,转角)
            itemToAdd = Instantiate(Resources.Load<GameObject>(itemName), whatSlotToEquip.transform.position, whatSlotToEquip.transform.rotation);
            itemToAdd.transform.SetParent(whatSlotToEquip.transform);//确定游戏对象的父级

            itemList.Add(itemName);//把这个名字添加到项目列表中


            TriggerPickupPopUP(itemName, itemToAdd.GetComponent<Image>().sprite);//上面所实例化的预制件自身就有Image组件,其下有需要的sprite



            ReCalculateList();
            CraftingSystem.Instance.RefreshNeededItems();
        }
    }

    //找到下一个空的插槽,理论上调用这个方法的时候一定是有空插槽的
    private GameObject FindNextEmptySlot()
    {
        //在插槽列表中搜索一个空的插槽
        foreach(GameObject slot in slotList)
        {
            if (slot.transform.childCount == 0)//一个子元素都没就是空插槽
                return slot;
        }
        return new GameObject();//一般不会由这个返回
    }

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.I) && !isOpen)
        {
            inventoryScreenUI.SetActive(true);//唤醒库存UI
            Cursor.lockState = CursorLockMode.None;//不再锁定鼠标光标
            isOpen = true;
        }
        else if (Input.GetKeyDown(KeyCode.I) && isOpen)
        {
            inventoryScreenUI.SetActive(false);//再次点击关闭UI
            if(CraftingSystem.Instance.isOpen == false)//如果制作面版还打开着就不锁定光标
            {
                Cursor.lockState = CursorLockMode.Locked;//锁定鼠标光标
            }
            
            isOpen = false;
        }
    }

    //遍历查找背包中的空插槽
    public bool CheckIfFull()
    {
        int count = 0;
        if (isFull)
            return true;
        foreach(GameObject slot in slotList)
        {
            if(slot.transform.childCount > 0)
            {
                count++;
            }
        }

        if(count == 21)//21是目前的最大背包容量,可修改
        {
            isFull = true;
            return true;
        }
        else
        {
            return false;
        }
    }

    //根据nameToRemove与amonutToRemove从背包中移除同名的物品
    public void RemoveItem(string nameToRemove,int amonutToRemove)
    {
        Debug.Log("RemoveItem开始执行");
        int counter = amonutToRemove;
        if (counter == 0)
            return;
        for(var i = slotList.Count-1;i>=0;i--)//i = 20
        {
            if(slotList[i].transform.childCount > 0)//如果有子元素
            {
                 if(slotList[i].transform.GetChild(0).name == nameToRemove + "(Clone)"&& counter != 0)//插槽子元素的第一个元素的名字是想要移除的名字,0代表第一个元素,应当只有一个元素
                {
                    Destroy(slotList[i].transform.GetChild(0).gameObject);//摧毁游戏对象

                    counter -= 1;//计数器自减1
                }
            }
        }
        ReCalculateList();
        CraftingSystem.Instance.RefreshNeededItems();

    }

    //更新itemList并去除因为实例化预制体而出现的Clone后缀
    public void ReCalculateList()
    {
        itemList.Clear();

        foreach(GameObject slot in slotList)
        {
            if(slot.transform.childCount> 0)
            {
                string name = slot.transform.GetChild(0).name;//Stone(Clone)
                string str = "(Clone)";

                string finalName = name.Replace(str, "");//Stone


                itemList.Add(finalName);
            } 
        }
    }

    void TriggerPickupPopUP(string itemName,Sprite itemsprite)
    {
        pickupAlert.SetActive(true);

        pickupName.text = itemName;
        pickupImage.sprite = itemsprite;


    }
}

CraftingSystem

public class CraftingSystem : MonoBehaviour
{
    //首先把制作系统成为一个单例
    public static CraftingSystem Instance { get; set; }

    public GameObject craftingScreenUI;//对应的UI界面
    public GameObject toolsScreenUI;//对应的UI界面

    public List<string> inventoryItemList = new List<string>();

    //Category Buttons 与选择制作类别相关的按钮
    Button toolsBTN;

    //Craft Buttons 与制作相关的按钮
    Button craftAxeBTN;

    //制作要求文本
    Text AxeReq1, AxeReq2;

    public bool isOpen;

    //All Blueprint
    public BluePrint AxeBLP;

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
    }

    //初始化开关状态,按钮,制作要求文本,蓝图 并且建立委托来添加制作的新物品到背包
    void Start()
    {
        isOpen = false;

        toolsBTN = craftingScreenUI.transform.Find("ToolsButton").GetComponent<Button>();//引用的是工具制作的按钮,名字要与Hierarchy中取的一样,不然找不到
        toolsBTN.onClick.AddListener(delegate { OpenToolsCategory(); });

        AxeReq1 = toolsScreenUI.transform.Find("Axe").transform.Find("req1").GetComponent<Text>();
        AxeReq2 = toolsScreenUI.transform.Find("Axe").transform.Find("req2").GetComponent<Text>();

        craftAxeBTN = toolsScreenUI.transform.Find("Axe").transform.Find("Button").GetComponent<Button>();
        //委托
        AxeBLP = new BluePrint("Axe", 2, "Stone", 3, "Stick", 3);
        craftAxeBTN.onClick.AddListener(delegate { CraftAnyItem(AxeBLP); });
    }

    //开启制作栏的方法
    void OpenToolsCategory()
    {
        craftingScreenUI.SetActive(false);//关闭选择页面
        toolsScreenUI.SetActive(true);//开启工具制作页面
    }

    void CraftAnyItem(BluePrint bluePrintToCraft)
    {
        Debug.Log(bluePrintToCraft.itemName);
        Debug.Log(11111111);
        //添加item到库存系统
        InventorySystem.Instance.AddToInventory(bluePrintToCraft.itemName);

        //从库存系统移除制作物品所消耗的材料
        
        InventorySystem.Instance.RemoveItem(bluePrintToCraft.req1,bluePrintToCraft.req1Amount);
        InventorySystem.Instance.RemoveItem(bluePrintToCraft.req2, bluePrintToCraft.req2Amount);

        StartCoroutine(cacilate());//这个方法解决了在一次制作后原材料没有真正减少的问题

        //RefreshNeededItems();
    }

    // Update is called once per frame
    void Update()
    {

        //RefreshNeededItems();
        //从之前库存管理脚本那里复制来的代码
        if (Input.GetKeyDown(KeyCode.C) && !isOpen)
        {
            craftingScreenUI.SetActive(true);//唤醒制作面板UI
            Cursor.lockState = CursorLockMode.None;//不再锁定鼠标光标
            isOpen = true;
        }
        else if (Input.GetKeyDown(KeyCode.C) && isOpen)
        {
            craftingScreenUI.SetActive(false);//再次点击关闭UI
            toolsScreenUI.SetActive(false);//这个也关闭
            if(InventorySystem.Instance.isOpen == false)
            { 
                Cursor.lockState = CursorLockMode.Locked;//锁定鼠标光标
            }
           
            isOpen = false;
        }
    }

    public void RefreshNeededItems()
    {

        int stone_count = 0;
        int stick_count = 0;

        inventoryItemList = InventorySystem.Instance.itemList;

        foreach(string itemName in inventoryItemList)
        {
            switch(itemName)
            {
                case "Stone":
                    stone_count += 1;
                    break;
                case "Stick":
                    stick_count += 1;
                    break;
            }
        }

        //...Axe...//

        AxeReq1.text = $"3 Stone [{stone_count}]";
        AxeReq2.text = $"3 Stick [{stick_count}]";

        if(stone_count >= 3 && stone_count >= 3)
        {
            craftAxeBTN.gameObject.SetActive(true);
        }
        else
        {
            craftAxeBTN.gameObject.SetActive(false);
        }
    }

    public IEnumerator cacilate()
    {
        //yield return new WaitForSeconds(1f);
        yield return 0;//点击制作后不会再有延迟显示材料的消耗
        InventorySystem.Instance.ReCalculateList();
        RefreshNeededItems();
    }
}

AI_Movement

public class AI_Movement : MonoBehaviour
{
    //没什么意义,图一乐
    Animator animator;

    public float moveSpeed = 0.2f;

    Vector3 stopPosition;

    float walkTime;
    private float walkCounter;//原版用了public,实际在后面的start方法中还是会重置为随机,外界输入的没用
    float waitTime;
    private float waitCounter;

    int WalkDirection;

    public bool isWalking;

    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();

        //保证预制件不会同时移动/停止
        walkTime = Random.Range(3, 6);
        waitTime = Random.Range(1, 7);

        //移动/停止计时器初始化
        waitCounter = waitTime;
        walkCounter = walkTime;

        //随机选择初始移动方向
        ChooseDirection();
    }

    // Update is called once per frame
    void Update()
    {
        if (isWalking)
        {
            //向动画管理器组件传参,保证动画的工作
            animator.SetBool("isRunning", true);

            walkCounter -= Time.deltaTime;

            //根据随机选择的方向移动
            switch (WalkDirection)
            {
                case 0:
                    transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
                    transform.position += transform.forward * moveSpeed * Time.deltaTime;
                    break;
                case 1:
                    transform.localRotation = Quaternion.Euler(0f, 90, 0f);
                    transform.position += transform.forward * moveSpeed * Time.deltaTime;
                    break;
                case 2:
                    transform.localRotation = Quaternion.Euler(0f, -90, 0f);
                    transform.position += transform.forward * moveSpeed * Time.deltaTime;
                    break;
                case 3:
                    transform.localRotation = Quaternion.Euler(0f, 180, 0f);
                    transform.position += transform.forward * moveSpeed * Time.deltaTime;
                    break;
            }

            if (walkCounter <= 0)
            {
                stopPosition = new Vector3(transform.position.x, transform.position.y, transform.position.z);
                isWalking = false;
                //stop movement
                transform.position = stopPosition;
                animator.SetBool("isRunning", false);
                //reset the waitCounter
                waitCounter = waitTime;
            }


        }
        else
        {

            waitCounter -= Time.deltaTime;

            if (waitCounter <= 0)
            {
                ChooseDirection();
            }
        }
    }

    //随机选择一个方向
    public void ChooseDirection()
    {
        WalkDirection = Random.Range(0, 4);

        isWalking = true;
        walkCounter = walkTime;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值