• Unity3D&Photon制作吃鸡游戏(未完)


    Unity3D&Photon制作吃鸡游戏

    https://study.163.com/course/courseMain.htm?share=1&shareId=8348227&courseId=1004507022

    11 -- 程序书写规范

    变量名使用Camel驼峰命名法:首字母小写,其余单词首字母大写

    命名空间、类和方法名使用Pascal命名法:所有单词首字母大写

    如果使用到英文单词的缩写,则全部大写:如ID

    20 -- 使用Unity3D制作吃鸡手游的准备工作

    https://dashboard.photonengine.com/zh-cn

    为什么使用Photon服务器
      Photon服务器可以很好地实现游戏房间的匹配功能 -- 
        进入匹配房间,如果没有匹配的房间,则新建房间
        当房间人数达到一定数量,则会开始游戏

    Photon插件:AssetStore下载:全名 Photon Unity Networking Classic - Free
      Import之后,会弹出一个设置向导(向导也可通过Window->PhotonUnityNeworking->PUN Wizard打开)
      点击Cloud DashBoard Login后打开网页,登录

    创建Photon App:
      1. Photon Type = Photon PUN
      2. 点击创建

    将创建好的App的AppID复制,在Unity->Window->PhotonUnityNetworking->PUN Wizard中点击Locate PhotonServerSettings,
      Hosting选择PhotonCloud (这里Region选择Jp,延迟比较低),粘贴AppID,Protocol选择Udp
      Client Settings的Pun Logging选择Full

    通过代码与光子服务器进行连接

    https://doc.photonengine.com/en-us/pun/current/getting-started/pun-intro

    命名空间Photon.PunBehaviour,PhotonNetwork.ConnectUsingSettings(string gameVersion)
      参数gameVersion表示客户端版本号,用于分离版本

    创建脚本Network.cs,在Awake()中写上PhotonNetwork.ConnectUsingSettings("0.0.1");
      将脚本挂载空游戏物体上,运行

    与服务器连接成功

    地形系统 -- Terrain

    新建Terrain,发现没有所需的地形Texture

    从商店导入Standard Assets素材包
    人物模型https://pan.baidu.com/s/1An5Ro8pHMdjgnPAoBoRA_A,nubq

    Terrain创建 -- 略

    使用Standard Assets中的RigidBodyFPSController,用于控制人物,并导入人物模型(RifleAnimsetPro--Models) -- 略

    21 -- 实现武器基本功能并添加特效
    22 -- 完善射击功能

    将枪模型RiflePlaceholder放在人物模型手上,并通过Animator实现动作

    开枪功能 -- 在主角模型上挂脚本WeaponController.cs
      1. 当前弹夹中子弹数;2. 开枪动作播放;3. 开枪音效播放;4. Update()中检测是否按下鼠标左键
      5. 弹夹中子弹数不足则卡壳,播放卡壳音效

    换弹夹功能 --
      1. Update()中检测是否按下R键;2. 子弹总数和当前弹夹中子弹数;3. 换弹动作播放;4. 换弹音效播放

    开镜功能 -- 
      Camera的Field of View参数 即为开镜效果
      准星 -- canvas - image实现

    枪口特效 --
      RifleAnimsetPro/Particles/MuzzleFlash
      放置于枪口,并在Shoot中控制播放
      private ParticleSystem m_ShootFx;
      m_ShootFx.Play();

    射击功能的弹道 -- 
      暂时使用射线的方式实现射击功能,之后会优化改为真正的有弹道的子弹

    // 射线模拟子弹
    RaycastHit raycastHit;
        if (Physics.Raycast(m_MainCamera.transform.position, m_MainCamera.transform.forward, out raycastHit, m_FarestBulletReachableDistance)) {
        Debug.LogError(raycastHit.transform + " Shot");
    }

    22 -- 实现匹配功能

    匹配UI -- 在net scene下制作一个按钮,实现点击加入房间的功能

    加入房间相关Photon API:

    让Net.cs类继承自Photon.PunBehaviour

    加入房间: PhotonNetwork.JoinRandomRoom();

    加入房间失败时的回调方法: OnPhotonRandomJoinFailed(object[] codeAndMsg) {}

    创建房间: PhotonNetwork.CreateRoom(string roomName);

    // 创建或加入房间:  PhotonNetwork.JoinOrCreateRoom(string roomName);
      没有试过该API,无需知道该名字的房间是否已经存在,加入或创建它

    创建房间成功的回调方法: OnCreatedRoom() {}

    加入房间成功时的回调方法: OnJoinedRoom() {}

    有玩家成功连接入/加入房间的回调方法: OnPhotonPlayerConnected(PhotonPlayer newPlayer) {}

    房间内所有玩家是否同步场景的属性: PhotonNetwork.automaticallySyncScene == true;
       // defines if all clients in a room should load the same level/scene as the Master client (if that used PhotonNetwork.LoadLevel())

    判断当前客户端是否为主机(房主): PhotonNetwork.isMasterClient

    加载场景: PhotonNetwork.LoadLevel(string sceneName); // 比如 PhotonNetwork.LoadLevel("main");

    如何使用:

    1. 将之前在Start()中调用的PhotonNetwork.ConnectUsingSettings("0.0.1"); 注册到一个Connect按钮上,成功连接上服务器后才启用加入房间的按钮
    2. 一般来说,随机加入房间如果失败时,则自动创建一个房间
    3. 一个房间中玩家数足够时,则自动开始游戏
    4. 只有主机才需要加载场景,其他的客户端需要设置同步场景属性

    功能扩展: 可以再加一个DropDown来控制房间玩家数(Photon好像最大支持20人同房)

    24 -- 玩家位置同步

    一场游戏里有很多个玩家,这些玩家的模型和位置都需要同步,因此一个场景中需要多个士兵模型

    玩家脚本 -- PlayerUnit.cs

    1. 如果玩家不是客户端控制的玩家,则不需要开启模型中的摄像机
    2. 不是客户端控制的玩家,有一些组件是不需要开启的(包括1中的摄像机、角色控制脚本、武器脚本)
    3. 有关同步的API: PhotonView

    PlayerUnit脚本挂载角色身上,角色做成一个预制体,用于动态生成

    用于管理生成玩家的脚本 -- PlayerGenerator.cs

    在游戏场景main scene加载完成时,进行玩家的生成(PhotonNetwork.Instantiate())
    -- SceneManager.sceneLoaded 注册事件

    PhotonNetwork.Instantiate(string prefabName, Vector3 position, Quaternion rotation, byte group)
      -- 通过网络,实例化预制体,预制体需要位于Resources文件夹下
      -- group指的是PhotonView的组,填0即可

    在需要通过Photon进行网络同步的物体(角色)上挂载脚本

    PhotonView

    PhotonTransformView
      勾选需要同步的参数

    需要将PhotonTransformView赋值给PhotonView的OvservedComponents属性中
      表示需要监测并同步PhotonTransformView中勾选的参数

    将角色做成预制体,并在场景中删除

    25 -- 同步伤害信息

    玩家血量属性存在玩家信息类PlayerInfo中
      public int hp = 100;

    在武器脚本中存放伤害值
      public int damage = 50;

    在开火射线检测代码块判定是否打中玩家
      思路: 通过玩家tag判断
      如果是玩家,则玩家受伤,并需要将伤害同步给所有客户端

    API: PhotonView.RPC(string methodName, PhotonPlayer targetPlayer, params object[] parameters)
      Call a RPC method of this GameObject on remote clients of this room( or on all, including this client)
      methodName: 调用的同步方法名 the name of the fitting method which has the RPC attribute
      targetPlayer: 需要同步给的客户端The group of targets and the way RPC gets sent
      parameters: 传入methodName方法的参数

    if(raycastHit.collider.tag == "Player") {
        // 打到角色
        PhotonView pv = raycastHit.collider.GetComponent<PhotonView>();
        pv.RPC("GetDamage", PhotonTargets.All, m_Damage);
    }

    注意,在定义methodName方法时,需要写上 [PunRPC] -- 表示这是一个需要被实时同步的方法
      并继承自接口 IPunObserable的OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {}
      在该方法中进行血量的同步

    [PunRPC]
    public void GetDamage(int damage) {
        m_CurrentHp -= damage;
    }
    
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        // 会发送给所有客户端(包括自己)
        if (stream.isWriting) {
            // 如果是写入者
            stream.SendNext(this.m_CurrentHp);
        } else {
            // 如果是读出者
            this.m_CurrentHp = (int)stream.ReceiveNext();
        }
    }

    26 -- 血条的绘制

    在Player预制体下创建Canvas,用Slider制作简易血条(玩家自己的血条,位于屏幕下方)

    血条的屏幕分辨率自适应
      将Canvas Scaler的UI Scale Mode设为Scale With Screen Size
      将Match调至Height处,表示以Height为基础做的自适应

    在PlayerInfo中控制Slider的value值
      在同步方法中同步Slider的value值

    [PunRPC]
        m_CurrentHp -= damage;
        if(m_CurrentHp < 0) {
            m_CurrentHp = 0;
        }
        slider.value = m_CurrentHp;
    }
    
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        // 会发送给所有客户端(包括自己)
        if (stream.isWriting) {
            // 如果是写入者
            stream.SendNext(this.m_CurrentHp);
            stream.SendNext(this.slider.value);
        } else {
            // 如果是读出者
            this.m_CurrentHp = (int)stream.ReceiveNext();
            this.slider.value = (int)stream.ReceiveNext();
            // 个人理解这里不必再传slider.value的值,直接在m_CurrentHp变化的地方触发更新即可
        }
    }

    视频教程中用的方法是:每一个PlayerUnit都配备一个Slider用于显示血条,但是在PlayerUnit的ComponentsToBeHiden的列表里加上该血条
      个人做法: 只做一个血条Slider,用PhotonView.isMine判断是否为本客户端的玩家,在PlayerManager(作为总控制(所有玩家)的脚本中)控制HpBar

    27 -- IK的使用

    https://docs.unity3d.com/Manual/InverseKinematics.html ---- 看一看,介绍地很详细
    https://www.jianshu.com/p/7aec3699f29c -- 很详细

    完善玩家视角变动(抬高压低)时,枪口跟随的功能

    IK: Inverse Kinematics -- 反向动力学

    一般而言,多数动画是通过旋转骨架关节的角度来实现的,这个称为 Forward Kinematics

    而IK指的是从下至上的驱动
      是根据骨骼的终节点来推算其他父节点的位置的一种方法。比如通过手的位置推算手腕、胳膊肘的骨骼的位置。
      比如设置好手部位置后,角色起身时手部会保持不动,而小臂和大臂的古河会自动旋转到合适角度;
      或者在脚步踩入地面的行走动画(沼泽地)情况下,在运行时通过调整IK target来实现角色在不平坦的地面行走的效果
      仅支持配置正确的人形角色 (supported in Mecanim for any humanoid character with a correctly configured Avatar)

    人形角色指的是:预制体设置的Rig->AnimationType=Humanoid

    IK的使用

    1. 人物模型预制体的设置Rig->AnimationType设为Humanoid

    设置为Humanoid后,会自动在人物模型预制体下生成一个人物骨骼CharacterAvatar

    2. 在动画控制器中的Layers Pane勾选IK

    3. 在Animator组件中勾选Apply Root Motion

    To set up IK for a character, you typically have objects around the scene that a character interacts with, and then set up the IK through script, in particular, Animator functions like SetIKPositionWeight, SetIKRotationWeight, SetIKPosition, SetIKRotation, SetLookAtPositionbodyPositionbodyRotation

    4. Animator组件中的Avatar赋值为Humanoid自动生成的CharacterAvatar(这一步骤做了之后 播放动画的问题很大)

    5. 实现IK实际功能的脚本 (即https://docs.unity3d.com/Manual/InverseKinematics.html 中的案例)

    using UnityEngine;
    using System;
    using System.Collections;
    
    [RequireComponent(typeof(Animator))]
    public class IKController : MonoBehaviour {
    
        protected Animator animator;
    
        public bool ikActive = false;
        public Transform rightHandObj = null;
        public Transform lookObj = null;
    
        void Start() {
            animator = GetComponent<Animator>();
        }
    
        //a callback for calculating IK
        void OnAnimatorIK() {
            if (animator) {
    
                //if the IK is active, set the position and rotation directly to the goal. 
                if (ikActive) {
                
                // 这里只需要用到这个
                // Set the look target position, if one has been assigned
                    if (lookObj != null) {
                        // 看向某个目标点时,各个部位旋转的比重
                        animator.SetLookAtWeight(1, 1, 1, 1);
                        animator.SetLookAtPosition(lookObj.position);
                    }
    
                    // Set the right hand target position and rotation, if one has been assigned
                    if (rightHandObj != null) {
                        animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
                        animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);
                        animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandObj.position);
                        animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandObj.rotation);
                    }
                }
                //if the IK is not active, set the position and rotation of the hand and head back to the original position
                else {
                    animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0);
                    animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0);
                    animator.SetLookAtWeight(0);
    }}}}

    6. 在Player下的Camera下创建空物体(因为需要看向的位置是与摄像机有关的,所以作为Camera的子物体)
      将该空物体放置在Camera前的合适位置,作为枪口指向的点
      并将该空物体赋值给IKController.lookObj

    28 -- 使用WheelCollider控制车辆

    素材: pan.baidu.com/wap/init?surl=sA1JWo39facMXIa4M97dFQ 5n1b

    导入模型,并加上四个WheelCollider
      注:使用WheelCollider的必要条件是,父物体上需要挂有RigidBody组件

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    Swift3 重写一个带占位符的textView
    Swift3 使用系统UIAlertView方法做吐司效果
    Swift3 页面顶部实现拉伸效果代码
    Swift3 倒计时按钮扩展
    iOS 获取当前对象所在的VC
    SpringBoot在IDEA下使用JPA
    hibernate 异常a different object with the same identifier value was already associated with the session
    SpringCloud IDEA 教学 番外篇 后台运行Eureka服务注册中心
    SpringCloud IDEA 教学 (五) 断路器控制台(HystrixDashboard)
    SpringCloud IDEA 教学 (四) 断路器(Hystrix)
  • 原文地址:https://www.cnblogs.com/FudgeBear/p/10908706.html
Copyright © 2020-2023  润新知