存档

2016年3月 的存档

Unity3d开发(十一)编辑器DrawCall参数解析

2016年3月31日 没有评论

对于Unity运行场景中,有许多可以标记场景状况的参数。这篇文章主要探讨这些参数的意义,其中如果有错误欢迎指正。

Game Stats

点击游戏Tab下的Stats可以展开如下界面,可以方便我们查看游戏运行状态。
Unity3d开发(十一)编辑器DrawCall参数解析

FPS & Time per frame

FPS是通常说的帧率。每帧用时指的是渲染一帧需要消耗的用时,这个值只包含每帧更新游戏视图的时间,不受其他在编辑器中功能的影响,例如Profiler。FPS是这个数字的倒数。值得注意的是,这个值会比我们通常说的帧率要高,因为我们是直接计算两帧之间的时间差的。我猜想手机打包之后两个值的差会变小,但也不会绝对一样。

Unity3d开发(十一)编辑器DrawCall参数解析

Batches & DrawCall

这个是绘制图像的重要指标,可以作为衡量场景绘制效率的首要参考。

一个 Draw Call,等于呼叫一次 DrawIndexedPrimitive (DX) or glDrawElements (OGL),等于一个 Batch。

NVIDIA 在GDC上曾提出 25k batch/sec的渲染量会使1GHz的CPU达到100%的使用率,因此使用公式

25Kn(GHZ)Percentage/Framerate=Batch/Frame

可以推算出某些CPU可以抗多少Batch。例如红米手机CPU为1.5GHz,假设分出20%资源供渲染,希望游戏跑到30帧。那么能抗多少DrawCall? 25k * 1.5 * 0.2 / 30 = 250。因此从这方面也能看出,如果CPU不能分出更多的资源供渲染计算,能抗的DrawCall就会变少。

在Stats面板中看到的Batches是渲染的总Batch这个值等于同于DrawCall。但Unity中可以获取到未批次处理之前的DrawCall。因此需要注意不要混淆概念。

Saved By Batching

这个值是由于Batch减少的DrawCall,可以间接的看到场景优化的效果。这个值由两部分组成:Static BatchingDynamic Batching。这个由Unity内建自动合并虽然优点多多,但也不是没有缺陷。静态合并会引发内存和存储的额外开销,动态合并会增加CPU的负担。 这部分内容参考Unity官方文档

总体上讲所以希望批次渲染的元素要有相同的材质。通常两个材质如果只有贴图不同,可以将贴图合并到一张大图中,这就是所谓的和图。另外在使用ShadowCaster时,只要材质相同,即使贴图不同也可以合并渲染。

Dynamic Batches

动态合并在满足以下条件时是自动完成的:

  • 模型总顶点数小于900。
  • 不包含镜像transform改变。不改变Scale。
  • 如果使用动态lightmap需要指定正确。
  • 不使用多Pass的Shader.

由于需要在合并时通过CPU计算转为世界坐标,这项技术只在CPU消耗比DrawCall消耗“便宜”时才值得。这个衡量标准会根据平台产生差异,例如苹果平台上DrawCall的消耗便宜,就不应该使用这项技术。这个功能可以在Editor->Project Setting->Player中进行设置打开与关闭。

Static Batches

场景中不能移动的物件可以使用静态合并,它不受顶点数的限制,可以大幅减少DrawCall。但为了将元素合并到一个大模型中,这项技术需要额外的内存。主要的内存消耗在于共享多边形会在内存中重复创建。因此有时候需要牺牲渲染效率来避免静态合并,来保证内存够小。例如在茂密的树林中使用这项技术会导致大量的内存消耗。

Tris & Verts

摄像机下的所有三角形和顶点这个在低端硬件上也是主要瓶颈。

脚本获取值

其他数据暂时先不关注了,有必要以后可以再单写一篇。在编辑模式下这些数据是可以通过脚本获取到的,不过打出包来不太成。简单写了一下,效果如下:

Unity3d开发(十一)编辑器DrawCall参数解析

其中所有的立方体使用静态合并,面片使用动态合并。场景UI一共17个Batch。动态批次1个,静态批次2个(不知拆分规则),批次渲染一共20。每一个动态物体1个DrawCall,共20个,4个动态物体1个DrawCall,17个UI产生的DrawCall,共38个。

相关代码如下:

using UnityEngine;
using System.Collections;
using UnityEditor;

public class testDC : MonoBehaviour 
{
    #region Unity Messages

    void OnGUI()
    {
        GUILayout.TextField("Total DrawCall: " + UnityStats.drawCalls);
        GUILayout.TextField("Batch: " + UnityStats.batches);
        GUILayout.TextField("Static Batch DC: " + UnityStats.staticBatchedDrawCalls);
        GUILayout.TextField("Static Batch: " + UnityStats.staticBatches);
        GUILayout.TextField("DynamicBatch DC: " + UnityStats.dynamicBatchedDrawCalls);
        GUILayout.TextField("DynamicBatch: " + UnityStats.dynamicBatches);

        GUILayout.TextField("Tri: " + UnityStats.triangles);
        GUILayout.TextField("Ver: " + UnityStats.vertices);
    }

    #endregion
}

分类: 未分类 标签:

使用Multiplayer Networking做一个简单的多人游戏例子-3/3(Unity3D开发之二十七)

2016年3月29日 没有评论

使用Multiplayer Networking做一个简单的多人游戏例子-1/3
使用Multiplayer Networking做一个简单的多人游戏例子-2/3
使用Multiplayer Networking做一个简单的多人游戏例子-3/3

上一篇中血条还没有同步到所有客户端,下面添加血条同步。主要用到[SyncVar]同步变量。

13. 网络同步血条

  • 打开Health脚本
  • 添加命名空间UnityEngine.Networking
using UnityEngine.Networking;
  • 添加继承关系为NetworkBehaviour
public class Health : NetworkBehaviour
  • 为currentHealth添加[SyncVar]
[SyncVar]
public int currentHealth = maxHealth;
  • 在TakeDamage函数中增加仅在Server端执行
if (!isServer)
{
    return;
}

最终Health脚本如下:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;

public class Health : NetworkBehaviour {

    public const int maxHealth = 100;

    [SyncVar]
    public int currentHealth = maxHealth;
    public RectTransform healthBar;

    public void TakeDamage(int amount)
    {
        if (!isServer)
        {
            return;
        }

        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }

        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
}

此时运行测试,你会发现只有客户端的血条会变化,所以我们需要修改血条变化方法

  • 打开Health脚本
  • 将血条修改的代码移到新函数OnChangeHealth中
void OnChangeHealth (int currentHealth)
{
    healthBar.sizeDelta = new Vector2(health, currentHealth.sizeDelta.y);
}
  • 修改currentHealth的SyncVar属性
[SyncVar(hook = "OnChangeHealth")]

最终Health代码:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;

public class Health : NetworkBehaviour {

    public const int maxHealth = 100;

    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;

    public RectTransform healthBar;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }
    }

    void OnChangeHealth (int health)
    {
        healthBar.sizeDelta = new Vector2(health, healthBar.sizeDelta.y);
    }
}

14. 死亡和再出生

这里会用到[ClientRpc],让函数在服务端调用,执行却在客户端

  • 打开Health脚本
  • 添加出生函数Respawn
[ClientRpc]
void RpcRespawn()
{
    if (isLocalPlayer)
    {
        // move back to zero location
        transform.position = Vector3.zero;
    }
}
  • 修改TakeDamage函数中,currentHealth = maxHealth;
  • 在服务端执行RpcRespawn();
    最终Health代码如下:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;

public class Health : NetworkBehaviour {

    public const int maxHealth = 100;

    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;

    public RectTransform healthBar;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = maxHealth;

            // called on the Server, but invoked on the Clients
            RpcRespawn();
        }
    }

    void OnChangeHealth (int currentHealth )
    {
        healthBar.sizeDelta = new Vector2(currentHealth , healthBar.sizeDelta.y);
    }

    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // move back to zero location
            transform.position = Vector3.zero;
        }
    }
}
  • 保存脚本
  • Build新版本,测试

15. 增加敌人

增加敌人发射器用于创建系统玩家

  • 创建一个empty GameObject
  • 修改名称为“Enemy Spawner”
  • 保持Enemy Spawner选中
  • 添加组件Network > NetworkIdentity
  • 勾选NetworkIdentity中the Server Only选中,仅在服务端中执行
  • 给Enemy Spawner添加新脚本“EnemySpawner”
    EnemySpawner内容如下:
using UnityEngine;
using UnityEngine.Networking;

public class EnemySpawner : NetworkBehaviour {

    public GameObject enemyPrefab;
    public int numberOfEnemies;

    public override void OnStartServer()
    {
        for (int i=0; i < numberOfEnemies; i++)
        {
            var spawnPosition = new Vector3(
                Random.Range(-8.0f, 8.0f),
                0.0f,
                Random.Range(-8.0f, 8.0f));

            var spawnRotation = Quaternion.Euler( 
                0.0f, 
                Random.Range(0,180), 
                0.0f);

            var enemy = (GameObject)Instantiate(enemyPrefab, spawnPosition, spawnRotation);
            NetworkServer.Spawn(enemy);
        }
    }
}
  • 保存脚本
  • 回到Unity

下面开始利用Player Prefab创建Enemy

  • 将Player prefab拖到场景中
  • 修改Player名称为“Enemy”
  • 将Enemy拖到Project面板中创建一个新的Enemy prefab预制体
  • 选中Enemy
  • 删除Enemy中的Gun,点击继续
  • 选中Enemy
  • 删除Bullet Spawn
  • 移除PlayerController组件
  • 修改Enemy材质为Black Material
  • 修改Enemy Visor的材质为Default-Material
  • Apply Enemy Prefab
  • 删除场景中的Enemy Prefab
  • 保存场景

将 Enemy prefab 注册到NetworkManager中

  • 选中NetworkManager
  • 展开Spawn Info foldout
  • Registered Spawnable Prefabs点击+号
  • 将Enemy prefab添加到Registered Spawnable Prefabs list
  • 选中场景中的Enemy Spawner
  • 将Enemy prefab拖到Enemy prefab输入框中
  • 设置Number Of Enemies为4
    使用Multiplayer Networking做一个简单的多人游戏例子-3/3(Unity3D开发之二十七)
  • 保存工程

build新版本,测试

16. 销毁死亡Enemy

  • 打开Health脚本
  • 添加destroyOnDeath变量
  • TakeDamage中判断销毁或者respawning
    代码如下:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;

public class Health : NetworkBehaviour {

    public const int maxHealth = 100;

    public bool destroyOnDeath;

    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;

    public RectTransform healthBar;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            if (destroyOnDeath)
            {
                Destroy(gameObject);
            } 
            else
            {
                currentHealth = maxHealth;

                // called on the Server, will be invoked on the Clients
                RpcRespawn();
            }
        }
    }

    void OnChangeHealth (int currentHealth)
    {
        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }

    [ClientRpc]
    void RpcRespawn()
    {        if (isLocalPlayer)
        {
            // Set the player’s position to origin
            transform.position = Vector3.zero;
        }
    }
}
  • 保存脚本,回到Unity
  • 选中Enemy prefab
  • 将Health中Destroy On Death 属性勾选
    使用Multiplayer Networking做一个简单的多人游戏例子-3/3(Unity3D开发之二十七)

增加出身点位置

由于Player每次出生位置一样,我们用NetworkStartPosition来增加一个出身点

  • 创建一个empty GameObject
  • 修改名称为“Spawn Position 1”
  • 选中Spawn Position 1
  • 增加组件Network > NetworkStartPosition
  • 设置Transform Position (3, 0, 0)
  • 复制一份Spawn Position 1 GameObject
  • 将复制这份修改名称为“Spawn Position 2”
  • 选中Spawn Position 2
  • 设置Transform Position (-3, 0, 0)
  • 选中Network Manager
  • 展开Spawn Info foldout
  • 修改Player Spawn Method 为 Round Robin

设置出生点位置

  • 打开Health脚本
  • 增加NetworkStartPosition变量
private NetworkStartPosition[] spawnPoints;
  • 增加Start方法
void Start ()
{
    if (isLocalPlayer)
    {
        spawnPoints = FindObjectsOfType<NetworkStartPosition>();
    }
}
  • 修改RpcRespawn中出生点坐标
    最终Health代码如下:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;

public class Health : NetworkBehaviour {

    public const int maxHealth = 100;
    public bool destroyOnDeath;

    [SyncVar(hook = "OnChangeHealth")]
    public int currentHealth = maxHealth;

    public RectTransform healthBar;

    private NetworkStartPosition[] spawnPoints;

    void Start ()
    {
        if (isLocalPlayer)
        {
            spawnPoints = FindObjectsOfType<NetworkStartPosition>();
        }
    }

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            if (destroyOnDeath)
            {
                Destroy(gameObject);
            } 
            else
            {
                currentHealth = maxHealth;

                // called on the Server, invoked on the Clients
                RpcRespawn();
            }
        }
    }

    void OnChangeHealth (int currentHealth )
    {
        healthBar.sizeDelta = new Vector2(currentHealth , healthBar.sizeDelta.y);
    }

    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // Set the spawn point to origin as a default value
            Vector3 spawnPoint = Vector3.zero;

            // If there is a spawn point array and the array is not empty, pick one at random
            if (spawnPoints != null && spawnPoints.Length > 0)
            {
                spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)].transform.position;
            }

            // Set the player’s position to the chosen spawn point
            transform.position = spawnPoint;
        }
    }
}

使用Multiplayer Networking做一个简单的多人游戏例子-3/3(Unity3D开发之二十七)

Download sample code on github

OK,以上就是全部内容。关于详细的使用请看 Networking Overview

分类: 未分类 标签:

使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

2016年3月29日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2Der-CSDN,谢谢!
原文地址: http://blog.csdn.net/cocos2der/article/details/51007512

使用Multiplayer Networking做一个简单的多人游戏例子-1/3
使用Multiplayer Networking做一个简单的多人游戏例子-2/3
使用Multiplayer Networking做一个简单的多人游戏例子-3/3

7. 在网络中控制Player移动

上一篇中,玩家操作移动会同时控制同屏内的所有Player,且只有自己的屏幕生效。因为咱们还没有同步Transform信息。
下面我们通过UnityEngine.Networking组件来实现玩家控制各自Player

  • 打开PlayerController脚本
  • 添加命名空间UnityEngine.Networking
using UnityEngine.Networking;
  • 修改MonoBehaviour为NetworkBehaviour
public class PlayerController : NetworkBehaviour
  • 在Update函数中添加如下方法
if (!isLocalPlayer)
{
    return;
}

最后你的PlayerController内容如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);
    }
}
  • 保存脚本
  • 回到Unity中
  • 选中Player prefab在Project面板中
  • 保持Player prefab为选中状态
  • 添加组件Network > NetworkTransform
  • 保存工程
    使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

NetworkTransform用于在网络中同步所有Client信息。加上isLocalPlayer判断,只让当前客户端操作。

8. 测试网络中多玩家移动

  • 同样Build一个Mac standalone application作为主机运行
  • 点击LAN Host作为主机开始游戏
  • 运行Unity,点击LAN Client作为另一个客户端加入游戏
  • 点击各自的WASD移动各自Player

9. 区分各自的Player

上面中两个Player外观一致,我们修改自己Player的颜色

  • 打开PlayerController脚本
  • 添加OnStartLocalPlayer方法
public override void OnStartLocalPlayer()
{
    GetComponent<MeshRenderer>().material.color = Color.blue;
}

最终PlayerController:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);
    }

    public override void OnStartLocalPlayer()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
  • Build新的Mac版本,测试效果
    使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)
  • 关闭Mac版本,停止运行Unity,回到编辑状态

10. 给Player添加射击武器

创建子弹

  • 创建一个Sphere GameObject
  • 修改名称为“Bullet”
  • 选中Bullet对象
  • 修改Transform (0.2, 0.2, 0.2)
  • 添加组件Physics > Rigidbody
  • 在Rigidbody属性中取消Use Gravity
  • 拖拽Bullet到Project面板中,制作为Prefab
  • 删除场景中Bullet
  • 保存场景

下面修改PlayerController添加发射子弹

  • 打开PlayerController脚本
  • 添加public变量bulletPrefab
public GameObject bulletPrefab;
  • 添加子弹local发射点
public Transform bulletSpawn;
  • 在Update中加入输入源
if (Input.GetKeyDown(KeyCode.Space))
{
    Fire();
}
  • 添加Fire方法
void Fire()
{
    // Create the Bullet from the Bullet Prefab
    var bullet = (GameObject)Instantiate (
        bulletPrefab,
        bulletSpawn.position,
        bulletSpawn.rotation);

    // Add velocity to the bullet
    bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

    // Destroy the bullet after 2 seconds
    Destroy(bullet, 2.0f);
}

最终PlayerController如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }
    }


    void Fire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);        
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
  • 保存脚本
  • 回到Unity

下面开始针对变化过的PlayerController修改Player Prefab

  • 将Player Prefab拖拽到场景中
  • 保持Player prefab选中
  • 创建一个Cylinder圆柱体作为其Child
  • 修改Cylinder名称为“Gun”
  • 保持Gun被选中
  • 移除Capsule Collider组件
  • 设置Transform Position (0.5, 0.0, 0.5)
  • 设置Transform Rotation (90.0, 0.0, 0.0)
  • 设置Transform Scale (0.25, 0.5, 0.25)
  • 设置Material为Black Material

最后Player效果:
使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

  • 保持Player选中状态
  • 创建一个empty GameObject作为Child
  • 修改empty GameObject名称为“Bullet Spawn”
  • 设置Bullet Spawn Position (0.5, 0.0, 1.0)

主要是将子弹发射点Bullet Spawn设置到枪口处
使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

  • 保持Player Prefab选中
  • 将Bullet prefab拖到PlayerController中的Bullet Prefab 框
  • 将Player的Child Bullet Spawn拖到PlayerController中的Bullet Spawn 框
  • 保存工程
  • Build新的Mac版本,并测试

你会发现空格键可以在各自场景中发射子弹,但是子弹没有出现在对方场景中。

  • 关闭Mac版本
  • 停止Unity,回到编辑模式

11. 增加多人射击

下面我们会将Bullet prefab注册到NetworkManager

  • 选中Bullet prefab在Project面板中
  • 保存Bullet prefab选中
  • 添加组件Network > NetworkIdentity
  • 添加组件Network > NetworkTransform
  • 设置NetworkTransform中Network Send Rate为0

子弹不会中途改变方向,所以我们不需要每帧更新位置,每个客户端自己计算Bullet坐标信息,所以将Network Send Rate设置为0,网络不需要同步坐标信息。
使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

  • 选中NetworkManager在Hierarchy面板中
  • 保持NetworkManager选中
  • 展开Spawn Info
  • 点击Registered Spawnable Prefabs右下角+
  • 将Bullet Prefab加入到Registered Spawnable Prefabs中
    使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

  • 打开PlayerController脚本

注意[Command]可以声明一个函数可以本客户端调用,但是会在服务端(主机)执行。

  • 添加[Command]给Fire函数
  • 修改Fire函数名称为“CmdFire”
[Command]
void CmdFire()
  • Update函数中修改调用为CmdFire
CmdFire();
  • 在CmdFire函数中添加NetworkServer.Spawn方法来创建bullet
NetworkServer.Spawn(bullet);

最终PlayerController如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            CmdFire();
        }
    }

    // This [Command] code is called on the Client …
    // … but it is run on the Server!
    [Command]
    void CmdFire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6;

        // Spawn the bullet on the Clients
        NetworkServer.Spawn(bullet);

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }
}
  • 保存脚本
  • 回到Unity
  • Build新版本Mac,测试

此时应该可以看到子弹同步到了每个玩家场景中

  • 关闭Mac版本
  • 停止运行Unity,回到编辑模式

添加玩家生命值

  • 给Bullet Prefab添加一个新脚本“Bullet”
  • 打开Bullet脚本
  • 添加碰撞函数
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {

    void OnCollisionEnter(Collision collision)
    {
        Destroy(gameObject);
    }
}

此时子弹碰撞到Player之后自动销毁

添加玩家生命值
– 给Player prefab添加一个新脚本“Health”
Health脚本如下:

using UnityEngine;

public class Health : MonoBehaviour 
{
    public const int maxHealth = 100;
    public int currentHealth = maxHealth;

   public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }
    }
}
  • 保存脚本

在Bullet中增加击中受伤

  • 修改Bullet中的OnCollisionEnter函数
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {

    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var health = hit.GetComponent<Health>();
        if (health  != null)
        {
            health.TakeDamage(10);
        }

        Destroy(gameObject);
    }
}

添加一个简易的玩家头顶血条

  • 在场中中创建一个UI Image
  • 修改Canvas名称为“Healthbar Canvas”
  • 修改Image名称为“Background”
  • 保存Background选中
  • 设置RectTransform Width 100
  • 设置RectTransform Height 10
  • 修改Source Image 为 built-in InputFieldBackground
  • 修改 Image Color 为红色
  • 不要修改Background的中心点和锚点
  • 复制一份Background
  • 修改复制出来的Background名称为Foreground
  • 将Foreground设置为Background的Child
  • 将Player prefab拖到场景中
  • 将Healthbar Canvas拖到Player中作为Child
    整个Player结构如下:
    使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

  • 选中Foreground

  • 设置Foreground Image为绿色
  • 将Foreground 中心点和锚点修改为Middle Left(用于血条从左到右填充)
    使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

  • 选中Healthbar Canvas

  • 点击RectTransform设置按钮(小齿轮)中的Reset
  • 设置RectTransform Scale (0.01, 0.01, 0.01)
  • 设置RectTransform Position (0.0, 1.5, 0.0)
  • 选中Player,点击apply,保存Player Prefab
  • 保存场景

修改Health脚本,控制血条
最终Health脚本如下:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Health : MonoBehaviour {

    public const int maxHealth = 100;
    public int currentHealth = maxHealth;
    public RectTransform healthBar;

    public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            currentHealth = 0;
            Debug.Log("Dead!");
        }

        healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
    }
}
  • 保存脚本
  • 回到Unity
  • 保持Player选中
  • 将Foreground拖到Healthbar输入框中
    使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)
  • apply Player prefab
  • 删除场景中的Player
  • 保存场景

最后,修改Healthbar永远朝向主摄像机
– 给Player prefab中的Healthbar Canvas添加新脚本“Billboard”
Billboard脚本如下:

using UnityEngine;
using System.Collections;

public class Billboard : MonoBehaviour {

    void Update () {
        transform.LookAt(Camera.main.transform);
    }
}
  • 编译新Mac版本,测试

你会发现血条只在本地变化了,没有同步到所有玩家。

分类: 未分类 标签:

使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

2016年3月29日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2Der-CSDN,谢谢!
原文地址: http://blog.csdn.net/cocos2der/article/details/51006463

本文主要讲述了如何使用Multiplayer Networking开发多人游戏,文中实例、代码来源于Unity官方教程。

原文:INTRODUCTION TO A SIMPLE MULTIPLAYER EXAMPLE
Networking Overview
The High Level API
Network System Concepts


使用Multiplayer Networking做一个简单的多人游戏例子-1/3
使用Multiplayer Networking做一个简单的多人游戏例子-2/3
使用Multiplayer Networking做一个简单的多人游戏例子-3/3

OK,现在可以开始了!

1. 开始创建工程

  • 创建一个空的3D工程
  • 保存当前场景为”Main”

2. 使用 Network Manager

主要使用NetworkManager和NetworkManagerHUD(一个简易的UI面板)

  • 创建一个empty GameObject.
  • 修改名称为”Network Manager”
  • 选中Network Manager对象体
  • 添加组件:Network > NetworkManager
  • 添加组件:Network > NetworkManagerHUD

NetworkManager组件的属性:
使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

NetworkManagerHUD组件属性:
使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

运行后,此时运行后效果(该UI就是NetworkManagerHUD):
使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

3. 设置Player prefab

本实例中玩家GameObject效果(后面会加上武器):
使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

开始创建Player GameObject:

  • 创建一个Capsule胶囊体
  • 修改名称为“Player”
  • 选中“Player”
  • 创建一个Cube作为Player的子物体
  • 修改Cube名称为“Visor”
  • 设置Visor Scale (0.95, 0.25, 0.5)
  • 设置Visor Position (0.0, 0.5, 0.24)
  • 创建一个新材质Material
  • 修改材质Material名称为“Black”
  • 选中Black Material
  • 修改其Albedo color 为黑色
  • 将Visor的Material修改为Black Material

为了给Player添加uniqueID作为网络中的唯一ID,我们需要给Player添加NetworkIdentity组件

  • 选中Player GameObject
  • 添加组件:Network > NetworkIdentity
  • 设置NetworkIdentity组件属性Local Player Authority为True,勾选上
    使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)
    将Local Player Authority勾选上,是为了后面Client能够控制Player

最后创建Player的Prefab预制体:

  • 将场景中的Player拖拽到Project面板中生成Prefab
  • 删除Scene中原来的Player
  • 保存场景

4. 注册Player prefab

  • 选中Network Manager GameObject 在 Hierarchy 面板中
  • 保存Network Manager被选中状态
  • 展开Network Manager属性面板中Spawn Info
  • 将Player prefab拖拽到Player Prefab框中
    使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

5. 创建Player 移动控制脚本

  • 新建一个C#脚本为”PlayerController”, 并将其绑定到Player prefab上

PlayerController.cs:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void Update()
    {
        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

        transform.Rotate(0, x, 0);
        transform.Translate(0, 0, z);
    }
}
  • 保存脚本
  • 保存场景

6. 测试Player的在线移动

  • 运行Unity进入Play模式
  • 运行模式下,NetworkManagerHUD将会显示默认的UI
    使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

  • 点击LAN Host(H),将本机作为主机Host开始游戏

此时NetworkManager将会创建一个Player在场景中,NetworkManagerHUD将会显示为游戏中UI
使用Multiplayer Networking做一个简单的多人游戏例子-1/3(Unity3D开发之二十五)

  • 键盘WASD控制玩家Player移动方向
  • 点击UI中的Stop(X) 回到离线模式
  • 点击停止运行Unity,退出运行模式

开始测试在客户端中控制玩家Player

为了测试多玩家,所以我们需要两个客户端。在这里我们可以Build一个Mac版本(或者Windows版本)作为一个玩家,然后Unity自己运行作为一个玩家。OK,下面开始Build一个Mac版本.

  • 打开Build Settings面板,并添加当前场景。
  • 保存工程
  • Build一个Mac standalone application
  • 完成之后,点击运行刚才Mac版本,并选择Windows窗口模式运行,不要全屏运行。
  • 点击LAN Host(H),作为主机玩家
  • 点击WASD按键,移动一下Mac版本中的Player(不然另一个玩家加入的时候,位置在同一个位置,你有可能认为只有一个玩家)

现在回到Unity,将Unity作为另一个玩家加入游戏。

  • 运行Unity,进入Play模式
  • 点击LAN Client(C)加入游戏。

此时你应该看到了两个玩家在游戏中。

  • 关闭Mac客户端
  • 停止运行Unity
  • 退出Play模式

分类: 未分类 标签:

Unity3d 开发(十)使用uTomate自动化打包流程

2016年3月22日 没有评论

在开发过程中,打包是个比较烦的事情。当不能全自动进行时,这种厌恶会达到极致。uTomate作为一种流程化的解决方案,能比较友好的解决打包问题,这篇文章主要探讨,通过这个插件完成基础的打包流程。更多关于这个插件的使用和API信息可以去它的官网查看。

基本原理

在uTomate中有两个层级的概念,即动作和计划。动作是一个原子操作,比如复制文件夹,烘焙场景等。计划是一个流程,它会按照一定规则执行动作。

uTomate中预制了许多动作,可以通过在Project面板中右键->Create->uTomate然后选择对应的动作。有了这些,就可以流程化繁琐的工作了。

打包示例

基于这个插件我编写了Android打包流程,可以一键出包。定制的情况是将AssetBundle中的资源暂时放置到包里,未来应该会编写将AssetBundle上传到服务器上的工作流。我的文件结构如下:

Unity3d 开发(十)使用uTomate自动化打包流程
其中Action目录放置的是基础动作,这些动作根据不同的目的放置到对应的目录中。下面有两个计划分别是打Android包和打IOS包。

制作流程计划首先理清思路创建一系列动作,我的打包流程大体上是这样的:

  1. 打包AssetBundle
  2. 复制内容到StreamingAssets目录
  3. 删除AssetBundle的生成目录
  4. 更新项目的AssetDataBase
  5. 导出Andorid包

创建好对应的动作后,在Project面板中右键->Create->uTomate->AutomationPlan可以创建一个Plain。然后点击Window->uTomate->Edit Automation plan...,选择对应的Plan,会打开Plan Editor的窗口。将刚才创建的动作拖入到窗口中,并连好线,如下图所示:

Unity3d 开发(十)使用uTomate自动化打包流程

运行Plan可以点击Plan Editor界面右下角的按钮,或打开Window->uTomate->MainWindow,然后选择对应计划点击右下角的Run Plan

目录迁移

由于插件中脚本较多,我将Chili4UuTomate两个目录移动到了Plugins目录下。代码要对应修改如下:

  • CUEditorResourcesLocator.cs
 public const string ResourcePath = "Plugins/Chili4U/Editor/EditorResources";
  • UTEditorResourcesLocator.cs
 public const string ResourcePath = "Plugins/uTomate/Editor/EditorResources";

这样可以保证资源正常加载。

分类: 未分类 标签:

Unity3d开发(九) 动画模型资源导入预处理

2016年3月14日 没有评论

在Unity导入资源后,我们通常需要做许多设置,这些操作通常很繁琐,并且容易出错,最好的办法是使用自动的pipline处理,因为不是项目中的每一个人都十分清楚资源的结构设计。这篇文章主要探讨如何通过自动的方式实现动画模型资源的定制处理。

AssetPostprocessor

在Unity中,AssetPostprocessor类能够在导入资源时,或导入资源后捕获到相应的信息。

模型(Model)导入的过程中,钩子函数的调用顺序如下:

  • OnPreporcessModel 在刚进入导入阶段前,会调用这个函数。我们能够重写ModelImporter的设置来更改导入的模型的通用信息。
  • OnAssignMaterialModel 一旦网格和材质导入,这些信息就会关联到模型上,在关联之前,会调用这个函数。因此改变模型材质通常要更改这个函数。
  • OnPostprocessGameObjectWithUserProperties关联好模型的渲染信息后,会调用这个函数来处理用户信息。
  • OnPostprocessModel 这是最后一个函数,当函数运行完,则对象创建完成。这个函数有一个参数,它是这个实例对象自己。由于GameObject是引用对象,因此改变属性的操作,例如:增加脚本,更改位置,是可以带出函数作用域,保存到最终预设中的。更多情况下,我们是要更改它的ModelImporter属性。
ModelImporter model = (ModelImporter) assetImporter;

另一方面,我们可以获取gameObject.name,并依赖某些命名规则,以对导入的资源进行分类。

实际案例

由于使用了状态机控制角色动作,有些导入的FBX动画需要更改为Loop。经统计名称中包含waitwalkrunairdizziness的动画需要设置为循环,依照上面的原理,实现
代码如下:

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

public class Importer : AssetPostprocessor 
{

    #region Override Methods
    void OnPostprocessModel(GameObject g)
    {
        ModelImporter model = (ModelImporter)assetImporter;
        if (model != null)
        {
            if (isLoopAnimation(g.name))
            {
                //由于我们采用动画分离的导出策略,每个fbx只有一个动画
                if (model.defaultClipAnimations.Length > 0) 
                {
                    List<ModelImporterClipAnimation> actions = new List<ModelImporterClipAnimation>();
                    ModelImporterClipAnimation anim = model.defaultClipAnimations[0];
                    anim.loopTime = true;
                    actions.Add(anim);
                    model.clipAnimations = actions.ToArray();
                }
            }
        }
    }
    #endregion

    #region Inner
    bool isLoopAnimation(string objectName)
    {
        bool res = false;
        if (objectName.Contains("wait"))
        {
            res = true;
        }
        else if (objectName.Contains("walk"))
        {
            res = true;
        }
        else if (objectName.Contains("run"))
        {
            res = true;
        }
        else if (objectName.Contains("air"))
        {
            res = true;
        }
        else if (objectName.Contains("dizziness"))
        {
            res = true;
        }
        return res;
    }
    #endregion
}

将这个文件放置在工程中,可以在导入对应资源时自动对其循环属性进行转换。

其他信息

另外我还发现一个小Bug,如果在未导入资源的情况下,拖动资源经过Scene界面,Unity会报错:

NullReferenceException: Object reference not set to an instance of an object
UnityEditor.AssetDatabase.Contains (UnityEngine.Object obj) (at C:/buildslave/unity/build/artifacts/generated/common/editor/AssetDatabaseBindings.gen.cs:39)
UnityEditor.SpriteUtility.GetSpriteFromDraggedPathsOrObjects () (at C:/buildslave/unity/build/Editor/Mono/Sprites/SpriteUtility.cs:201)
UnityEditor.SpriteUtility.OnSceneDrag (UnityEditor.SceneView sceneView) (at C:/buildslave/unity/build/Editor/Mono/Sprites/SpriteUtility.cs:46)
UnityEditor.SceneView.CallEditorDragFunctions () (at C:/buildslave/unity/build/Editor/Mono/SceneView/SceneView.cs:1773)
UnityEditor.SceneView.HandleDragging () (at C:/buildslave/unity/build/Editor/Mono/SceneView/SceneView.cs:1804)
UnityEditor.SceneView.OnGUI () (at C:/buildslave/unity/build/Editor/Mono/SceneView/SceneView.cs:1284)
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)

这是由于资源还未导入到项目中,所以在AssetBase中找不到对应资源UUID,出现这个错误的版本为5.3.0。

另外在OnPostprocessModel函数中,直接更改它的name,似乎不可以改变导入资源的名字。

分类: 未分类 标签: