• 基于Unity5的TPS整理


    1、游戏管理器

    游戏管理器负责管理游戏的整体流程,还可以系统管理用于游戏的全局数据以及游戏中判断胜败的条件。
    游戏管理器并不是单一的模块,更像是能控制游戏的功能集合。
    1)怪兽出现逻辑:专门设置一些位置用于随机生成怪兽。

    public Transform[] points;
    points = GameObject.Find("MonsterPos").GetComponentsInChildren<Transform>();

    2)访问游戏管理器:声明GameManager类的变量,在Start函数中分配为局部变量。

    private GameManager gameMgr;
    gameMgr = GameObject.Find("GameManager").GetComponent<GameManager>();

    3)单例模式:该模式下只有一个对象,可以获得全局访问。

    public static GameManager instance = null;
             void Awake()
                {
                      //将GameManager带入实例
                  instance = this;
                }


    4)对象池:对于需要在游戏中反复生成的模型,可以在游戏一开始运行并加载场景时就全部生成,运行时可从中直接取出使用。

    void Start ()
        {
            //生成怪兽并保存到对象池
            for(int i = 0;i<maxMonster;i++)
            {
                GameObject monster = (GameObject)Instantiate(monsterPrefab);
                monster.name = "Monster_" + i.ToString();
                monster.SetActive(false);
                monsterList.Add(monster);
            }
        }


    5)共享函数:声音处理

    //声音共享函数
        public void PlaySfx(Vector3 pos,AudioClip sfx)
        {
            //如果静音选项为true,则立即停止声音
            if (isSfxMute) return;
            //动态生成游戏对象
            GameObject soundObj = new GameObject("Sfx");
            soundObj.transform.position = pos;
            //向生成的游戏对象添加AudioSource组件
            AudioSource audioSource = soundObj.AddComponent<AudioSource>();
            //设置AudioSource属性
            audioSource.clip = sfx;
            audioSource.minDistance = 10;
            audioSource.maxDistance = 30;
            audioSource.volume = sfxVolumn;
            audioSource.Play();
            //声音结束播放后,删除之前动态生成的游戏对象
            Destroy(soundObj, sfx.length);
        }

    2、射线投射

    射线投射不但常用于发射逻辑,还可用于游戏中的传感器以及通过鼠标移动、旋转玩家角色。

    射线无法用肉眼观察,可以使用Debug.DrawRay()函数使射线在场景视图中显示。

    void Update()
        {
            //使用以下函数在场景中显示射线
            Debug.DrawRay(FirePos.position,FirePos.forward * 10, Color.green);
        }

    使怪兽被射线击中时受伤

                RaycastHit hit;
                if (Physics.Raycast(FirePos.position, FirePos.forward, out hit, 10))
                {
                    if (hit.collider.tag == "Monster")
                    {
                        //SendMessage函数要传递的参数数组
                        object[] _params = new object[2];
                        _params[0] = hit.point;//被射线击中的位置
                        _params[1] = 20;//怪兽受到的伤害值
    
                        hit.collider.gameObject.SendMessage("OnDamage", _params, SendMessageOptions.DontRequireReceiver);
                    }
                }

     激光束

    使用Line Renderer组件,因为Line Renderer将在Player的中心位置生成,所以不需要勾选Use World Space选项。

    using UnityEngine;
    using System.Collections;
    
    public class LaserBeam : MonoBehaviour
    {
        private Transform tr;
        private LineRenderer line;
        private RaycastHit hit;
    
        void Start ()
        {
            tr = GetComponent<Transform>();
            line = GetComponent<LineRenderer>();
            line.useWorldSpace = false;
            line.enabled = false;
            //设置头部宽度和尾部宽度
            line.SetWidth(0.3f,0.01f);
        }
        
        void Update ()
        {
            Ray ray = new Ray(tr.position + (Vector3.up * 0.02f),tr.forward);
            Debug.DrawRay(ray.origin, ray.direction * 10, Color.blue);
            if (Input.GetMouseButtonDown(0))
            {
                //设置Line Renderer的初始位置
                line.SetPosition(0,tr.InverseTransformPoint(ray.origin));
                //(局部坐标基准Transform组件).InverseTransformPoint(全局坐标)
                //将物体被射线击中的位置设置为Line Renderer的终点位置
                if (Physics.Raycast(ray,out hit,100))
                {
                    line.SetPosition(1,tr.InverseTransformPoint(hit.point));
                }
                else
                    line.SetPosition(1, tr.InverseTransformPoint(ray.GetPoint(100)));
    
                StartCoroutine(ShowLaserBeam());
            }
        }
    
        IEnumerator ShowLaserBeam()
        {
            line.enabled = true;
            yield return new WaitForSeconds(Random.Range(0.01f,0.2f));
            line.enabled = false;
        }
    }

     3、制作Player

    1)玩家移动

    using UnityEngine;
    using System.Collections;
    
    [System.Serializable]
    public class Anim
    {
        public AnimationClip idle;
        public AnimationClip runForward;
        public AnimationClip runBackward;
        public AnimationClip runRight;
        public AnimationClip runLeft;
    }
    public class PlayerCtrl : MonoBehaviour
    {
        private Transform tr;
        public float moveSpeed = 5;
        public float rotSpeed = 100;
        public Anim anim;//要显示到检视视图的动画变量
        public Animation _animation;//Animation组件对象的变量
        public int hp = 100;
    
        //声明委托和事件
        public delegate void PlayerDieHandler();
        public static event PlayerDieHandler OnPlayerDie;
    
        //Player的生命初始值
        private int initHp;
        //Player的生命图像
        public Image imgHpBar;
        public Text txtHp;
    
        //private GameMgr gameMgr;
    
        void Start ()
        {
            //设置生命值
            initHp = hp;
            tr = GetComponent<Transform>();
            //查找位于自身下级的Animation组件并分配到变量
            _animation = GetComponentInChildren<Animation>();
            //保存并运行Animation组件的动画片段
            _animation.clip = anim.idle;
            _animation.Play();
    
            //gameMgr = GameObject.Find("GameManager").GetComponent<GameMgr>();
        }
        
        void Update ()
        {
            float h = Input.GetAxisRaw("Horizontal");
            float v = Input.GetAxisRaw("Vertical");
    
            Vector3 dir = h * Vector3.right + v * Vector3.forward;//前后左右移动方向向量
            tr.Translate(dir.normalized * moveSpeed * Time.deltaTime,Space.Self);//向量方向*速度*时间,以局部坐标为基准移动
            tr.Rotate(Vector3.up * rotSpeed * Time.deltaTime * Input.GetAxis("Mouse X"));
    
            //以键盘输入值为基准,执行要操作的动画
            //动画合成:让动画片段之间平滑过渡,用到CrossFade合成函数
            if (v >= 0.1f)
            {
                _animation.CrossFade(anim.runForward.name, 0.3f);
            }
            else if (v <= -0.1f)
            {
                _animation.CrossFade(anim.runBackward.name, 0.3f);
            }
            else if (h <= -0.1f)
            {
                _animation.CrossFade(anim.runLeft.name, 0.3f);
            }
            else if (h >= 0.1f)
            {
                _animation.CrossFade(anim.runRight.name, 0.3f);
            }
            else
            {
                _animation.CrossFade(anim.idle.name, 0.3f);
            }
        }
    
        void OnTriggerEnter(Collider other)
        {
            if (other.gameObject.tag == "Punch")
            {
                hp -= 10;
                //调整Image的fillAmount属性,以调整生命条长度
                float xueliang = (float)hp / (float)initHp;
                imgHpBar.fillAmount = xueliang;
                txtHp.text = "<color=#ffffff>" + (xueliang * 100).ToString() + "%</color>";
                Debug.Log("Player HP =" + hp.ToString());
    
                if (hp <= 0)
                {
                    PlayerDie();
                }
            }
        }
    
        void PlayerDie()
        {
            Debug.Log("Player Die!!");
    
            //GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster");
            //foreach (GameObject monster in monsters)
            //{
            //    monster.SendMessage("OnPlayerDie",SendMessageOptions.DontRequireReceiver);
            //}
            //或者
            //触发事件
            OnPlayerDie();
    
            //更新游戏管理器的isGameover变量以停止生成怪兽
            //gameMgr.isGameOver = true;
            GameMgr.instance.isGameOver = true;
        }
    
    
    }

    使用CharacterController组件后代码

    float h = Input.GetAxisRaw("Horizontal");
            float v = Input.GetAxisRaw("Vertical");
    
            movDir = (tr.forward * v) + (tr.right * h);//计算移动方向
            movDir.y -= 20 * Time.deltaTime;//更改y值使其受重力影响下落
            tr.Rotate(Vector3.up * rotSpeed * Time.deltaTime * Input.GetAxis("Mouse X"));
            controller.Move(movDir * moveSpeed * Time.deltaTime);//移动玩家

    2)摄像机追踪

    将该脚本添加到主摄像机下,并把Player拖拽到targetTr变量。

    using UnityEngine;
    using System.Collections;
    
    public class FollowCam : MonoBehaviour
    {
        public Transform tr;
        public Transform targetTr;//要追踪的游戏对象的Trans变量
        public float dist = 10;//与摄像机之间的距离
        public float height = 3;//设置摄像机高度
        public float dampTrace = 20;//实现平滑追踪的变量
    
        void Start ()
        {
            tr = GetComponent<Transform>();
        }
    
        //要追踪的目标游戏对象停止移动后,调用LaterUpdate函数
        void LateUpdate ()
        {
            //将摄像机放置在被追踪的目标后方的dist距离的位置
            //将摄像机向上抬离height
            tr.position = Vector3.Lerp(tr.position,                                     //起始位置
                targetTr.position - (targetTr.forward * dist) + (Vector3.up * height),  //终止位置
                Time.deltaTime * dampTrace);                                            //内插时间
            //使摄像机朝向游戏对象
            tr.LookAt(targetTr.position);
        }
    }

     4、网络管理器

    Network View组件提供与其他玩家通信的功能,必须已经在网络上生成游戏或已访问游戏,所以其前提是游戏房间已经生成或已连接其他游戏客户端。

    1)通过GUI类实现画面输入所需UI及制作网络玩家

    using UnityEngine;
    using System.Collections;
    
    public class NetworkManager : MonoBehaviour
    {
        //连接IP
        private const string ip = "127.0.0.1";
        //连接Port端口号
        private const int port = 30000;
        //是否使用NAT功能
        private bool _useNat = false;
    
        public GameObject player;
    
        void OnGUI()
        {
            //判断当前玩家是否连接网络
            if (Network.peerType == NetworkPeerType.Disconnected)
            {
                //生成启动游戏服务器按钮
                if (GUI.Button(new Rect(20,20,200,25),"Start Server"))
                {
                    //初始化游戏服务器
                    Network.InitializeServer(20,port,_useNat);//连接数,端口号,是否用NAT
                }
                //生成连接游戏的按钮
                if (GUI.Button(new Rect(20, 50, 200, 25), "Connect to Server"))
                {
                    //连接游戏服务器
                    Network.Connect(ip, port);
                }
            }
            else
            {
                //初始化服务器时输出信息
                if (Network.peerType == NetworkPeerType.Server)
                {
                    GUI.Label(new Rect(20, 20, 200, 25), "Initialization Server...");
                    //查看连接网络的用户数
                    GUI.Label(new Rect(20, 50, 200, 25), "Clint Count = " + Network.connections.Length.ToString());
                }
                //以客户端身份连接时输出信息
                if (Network.peerType == NetworkPeerType.Client)
                {
                    GUI.Label(new Rect(20, 20, 200, 25), "Connect to Server");
                }
            }
        }
    
        //游戏服务器初始化正常完成时创建player
        void OnServerInitialized()
        {
            CreatePlayer();
        }
        //有玩家连接服务器时调用
        void OnConnectedToServer()
        {
            CreatePlayer();
        }
    
        void CreatePlayer()
        {
            //随机生成玩家的初始位置
            Vector3 pos = new Vector3(Random.Range(-20,20),0,Random.Range(-20,20));
            //网络上动态生成玩家
            Network.Instantiate(player,pos,Quaternion.identity,0);
        }
    
    }

    2)摄像机追踪逻辑

    先导入SmoothFollow脚本(Import Package→Utility),并把target变量修改为public。挂在Player预设。

    using UnityEngine;
    using System.Collections;
    //为了使用SmoothFollow脚本,需要先声明命名空间
    using UnityStandardAssets.Utility;
    
    public class NetPlayerCtrl : MonoBehaviour
    {
        private Transform tr;
        private NetworkView _networkView;
    
        void Awake ()
        {
            tr = GetComponent<Transform>();
            _networkView = GetComponent<NetworkView>();
            //查看NetworkView组件是否为自己的Player组件
            if (_networkView.isMine)
            {
                //设置Main.Camera要追踪的对象
                Camera.main.GetComponent<SmoothFollow>().target = tr;
            }
        }
    }

    禁用服务器端的玩家移动脚本,使其不要根据键盘输入值执行移动逻辑。

    //如果当前玩家是远程服务器端的玩家,则禁用此脚本
            this.enabled = GetComponent<NetworkView>().isMine;

     3)平滑的同步处理

    在检视面板,把NetPlayerCtrl脚本拖拽到NetworkView组件的Observed属性。

    修改脚本,使NetworkView组件不会在每个SendRate周期都向Transform组件传送数据,而是调用NetPlayerCtrl脚本内的OnSerializeNetworkView函数。

    //声明接收传送位置信息时使用的变量并设置初始值
        private Vector3 currPos = Vector3.zero;
        private Quaternion currRot = Quaternion.identity;
    
    void Update()
        {
            if (_networkView.isMine)
            {
            }
            else//远程玩家
            {
                //将远程玩家平滑地移动旋转到NetworkView传送来的目标位置
                tr.position = Vector3.Lerp(tr.position,currPos,Time.deltaTime * 10);
                tr.rotation = Quaternion.Slerp(tr.rotation,currRot,Time.deltaTime * 10);
            }
        }
    
         //NetworkView组件调用的回调函数
       //BitStream stream:序列化数据并保存到数据流
        //NetworkMessageInfo info:保存从网络接收的信息
    void OnSerializeNetworkView(BitStream stream,NetworkMessageInfo info) { if (stream.isWriting) { Vector3 pos = tr.position; Quaternion rot = tr.rotation; //数据传送 stream.Serialize(ref pos); stream.Serialize(ref rot); } else { Vector3 revpos = Vector3.zero; Quaternion revrot = Quaternion.identity; //数据接收 stream.Serialize(ref revpos); stream.Serialize(ref revrot); currPos = revpos; currRot = revrot; } }
  • 相关阅读:
    [原][osg]Geometry详解
    [osg]节点遍历nodevisitor浅析
    [原][osg]osgconv浅析
    [原][游戏][攻略]仙之侠道玖章-- --从入门到放弃
    [转][linux]简单的linux下的tcp/udp
    [转][c++][跨平台]c++跨平台开发小结
    人机交互,来和我猜拳吧!
    从零开始学测试【1】测试方法术语总结
    从零开始学测试【2】网页登录界面
    JSP 和 ASP.NET 谁能主宰未来【转】
  • 原文地址:https://www.cnblogs.com/jiangshuai52511/p/6400090.html
Copyright © 2020-2023  润新知