2015-05-29 17:48:35 Stephen167 阅读数 376
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

道家有语:授人以鱼不如授人以渔。

同样,学习一样技能,不如学会如何学习。

或许这是一个值得深入思考和探讨的问题。只可惜以目前的水平可能不能给出很好的引申。所以,回到Unity的一些学习技巧吧。


1 Unity几乎所有东西都可以在Unity手册中找到。

  所以用好Unity手册。






多看一些开发相关的知识,扩展思路:

《CSDN:部分渲染相关的课程》http://edu.csdn.net/course?keywords=游戏开发&c_id=340


2018-03-18 16:04:35 MadBam_boo 阅读数 1410
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

Unity3d实战之Unity3d网络游戏实战篇(10):玩家类Player

学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍,如有侵权请联系,必删。

 玩家在登录游戏到退出游戏时,有以下流程:
这里写图片描述
 我们给Player类设计如下方法:
 Send: 提供一个Player的Send方法,该方法调用ServNet的Send方法;
 KickOff:将当前角色踢下线;
 Logout:该方法将触发HandlePlayerEvent中的OnLogout事件。

 Player类的Property:

public string id;       // user_name
public Conn conn;       // the conn connect with user[id]'s client
public PlayerData data; // player's data
public PlayerTempData tempData; // player's tempdata

 KickOff:
 当用户在两个Client上登录同一账号时,把前一个角色踢下线。

public static bool KickOff(string id, ProtocolBase protoBase)
{
    Conn[] conns = ServNet.instance.conns;      // get client list.
    for (int i = 0; i < conns.Length; i++) {
        Conn conn = conns [i];
        if (conn == null || conn.isUse == false)
            continue;
        if (conn.player == null)
            continue;

        if (conn.player.id == id) {             // find the user who named [id].
            lock (conn.player) {
                if (protoBase != null) {        // send the logout protocol to user.
                    ProtocolBytes protocol = (ProtocolBytes)protoBase;
                    protocol.AddInt32 (-1);
                    conn.player.Send ((ProtocolBase)protocol);
                }

                return conn.player.Logout ();   // execute logout function
            }
        }
    }
    return true;
}

 Logout:
 Player类的Logout方法,调用Logout方法将会触发HandlePlayerEvent中的OnLogout事件处理相关逻辑。

public bool Logout()
{       
    ServNet.instance.handlePlayerEvent.OnLogout (this);     // trigger OnLogout event

    if (!DataMgr.instance.SavePlayer (this)) {              // save player's data before logout
        return false;
    }

    conn.player = null;
    conn.Close ();
    return true;
}
2018-03-14 15:40:43 MadBam_boo 阅读数 1223
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

Unity3d实战之Unity3d网络游戏实战篇(2):代码资源分离的界面系统

  • 阅读文章前请细读以下几点:
  • 学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版。
  • 本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍 以下为个人的学习简记,与诸君共论。
  • 由于U3D的官方案例Tank Tutorial中对坦克的基本操作已经有了详尽的描述(其实是我懒..),因此本文着重于面板系统、服务端基本网络框架和客户端基本网络框架的搭建。
  • 如有疑问或者描述错误的地方,欢迎各位提出或修正,讨论交流是获取和巩固知识的重要途径之一!

代码资源分离的界面系统
 刚刚入门U3D的时候,如果要写一个界面系统肯定会瞎写一通(抱歉我菜,基础功不扎实..),比如说,当需要在其他面板的逻辑中关闭另一个面板时,通常都是xxx.enable = false就ok了,或者在每一个面板的逻辑中都写了关闭面板的public方法供其他调用,当做的游戏渐渐大了起来之后发现有些地方很不好管理,而且代码也显得凌乱,每次写新面板的时候都要重新造轮子,看着心烦。这时就要找回基础(没错,基础功不扎实说的就是我…),复习一下类的继承、虚函数、泛型编程等。要记住,我们是面向对象…编程的!
 要构建一个通用的界面系统,首先要了解到代码与资源分离是游戏程序设计的核心思想之一,因为游戏公司里,美工和程序是两个人,如果代码和资源是黏在一起的话会有很多问题产生,而且不利于代码的重复使用。
 然后构思面板的生命周期(即面板对象被创建出来后到销毁的整个过程),书中对面板周期这样定义:
 Init -> OnShowing -> OnShowed -> Update -> OnClosing -> OnClosed
 
 Init:面板初始化阶段,用于处理一些参数;
 OnShowing:面板显示前调用OnShowing,可用于处理面板中的监听事件;
 OnShowed:面板显示后调用OnShowed,可在OnShowed前插播一个面板飞入动画;
 Update:每帧更新一些东东
 OnClosing:面板关闭前调用OnClosing;
 OnClosed:面板关闭后调用OnClosed,可在OnClosed前插播一个面板飞出动画
 
 插入一段面板基类的定义:
 

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

public class PanelBase : MonoBehaviour {

    #region Properties
    public string skinPath;             // skin's actual path = 'Assets/Resources/' + skinPath
    public GameObject skin;             // an instance based on skinPath.
    public PanelMgr.PanelLayer layer;   // current panel's layer(equal to which group the panel belong to.)
    public object[] args;               // panel's arguments.
    #endregion

    #region Operation Cycle
    /*
     *  Panel's basic life-time:
     *      Init -> OnShowing -> OnShowed -> Update -> OnClosing -> OnClosed
     */
    /// <summary>
    /// Init the panel with args.
    /// </summary>
    /// <param name="args">panel initia arguments.</param>
    public virtual void Init(params object[] args)
    {
        this.args = args;
    }
    /// <summary>
    /// Do something before show the panel.
    /// </summary>
    public virtual void OnShowing() { }

    /// <summary>
    /// Do something after show the panel.
    /// </summary>
    public virtual void OnShowed() { }

    /// <summary>
    /// Update info each frame during the panel is opening.
    /// </summary>
    public virtual void Update() { }

    /// <summary>
    /// Do something before close the panel.
    /// </summary>
    public virtual void OnClosing() { }

    /// <summary>
    /// Do something after close the panel.
    /// </summary>
    public virtual void OnClosed() { }
    #endregion

    #region Operation
    /// <summary>
    /// Close the panel by calling PanelMgr.ClosePanel function.
    /// </summary>
    protected virtual void Close()
    {
        string name = this.GetType ().ToString ();
        PanelMgr.instance.ClosePanel (name);
    }
    #endregion
}

 往后创建的任何面板都继承自PanelBase并根据需要对生命周期内的方法进行重载。
 例如:
 

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

public class LoginPanel : PanelBase {

    private InputField idInput;
    private InputField pwInput;
    private Button loginBtn;
    private Button regBtn;
    private Button exitBtn;

    #region Operation-Cycle
    public override void Init (params object[] args)
    {
        base.Init (args);
        skinPath = "LoginPanel";
        layer = PanelMgr.PanelLayer.Panel;
    }

    public override void OnShowing ()
    {
        base.OnShowing ();
        Transform skinTrans = skin.transform;
        idInput = skinTrans.Find ("idInput").GetComponent<InputField> ();
        pwInput = skinTrans.Find ("pwInput").GetComponent<InputField> ();
        loginBtn = skinTrans.Find ("loginBtn").GetComponent<Button> ();
        regBtn = skinTrans.Find ("regBtn").GetComponent<Button> ();
        exitBtn = skinTrans.Find ("exitBtn").GetComponent<Button> ();

        loginBtn.onClick.AddListener (OnLoginButtonClick);
        regBtn.onClick.AddListener (OnRegisterButtonClick);
        exitBtn.onClick.AddListener (OnExitButtonClick);
    }
    #endregion

    #region OnButtonClickEvent
    void OnLoginButtonClick()
    {
        // write you logic
    }
    void OnRegisterButtonClick()
    {
        PanelMgr.instance.OpenPanel<RegisterPanel> ("");
        Close ();
    }

    void OnExitButtonClick()
    {
        Application.Quit ();
    }
    #endregion


}

想必细心的同学都发现了PanelMgr这一个类,PanelMgr使用了单例模式(即整个Scene中只能有一个PanelMgr实例),用于管理当前场景中的各个面板,其中使用泛型编程实现了对面板的打开方法以及关闭方法,并在打开面板方法中调用了面板的Init->OnShowed周期。
具体代码如下:

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

public class PanelMgr : MonoBehaviour {

    #region Properties
    public enum PanelLayer                                  // panel layer list.
    {
        Panel,
        Tips,
    }

    public static PanelMgr instance;                        // PanelMgr's single instance.

    public Dictionary<string, PanelBase> dict;              // key:paneltype value:panel instance.

    private GameObject canvas;                              // current panel's canvas.
    private Dictionary<PanelLayer, Transform> layerDict;    // each layer's actual transfrom in scenes.
    #endregion

    #region Operation Cycle
    void Awake()
    {
        instance = this;
        InitLayer ();
        dict = new Dictionary<string, PanelBase> ();
    }
    #endregion

    #region Help Function
    /// <summary>
    /// Initialize layerDict.
    /// </summary>
    void InitLayer()
    {
        canvas = GameObject.Find ("Canvas");
        if (canvas == null) {
            Debug.LogError ("[PanelMgr.InitLayer] GameObject canvas is not exist.");
        }

        layerDict = new Dictionary<PanelLayer, Transform> ();
        foreach (PanelLayer pl in Enum.GetValues(typeof(PanelLayer))) {
            string name = pl.ToString ();
            Transform transform = canvas.transform.Find (name);
            layerDict.Add (pl, transform);
        }
    }
    #endregion

    #region Operation Function
    /// <summary>
    /// Open a panel which type is skinPath. At the same time, current scene's canvas will add a same type script.
    /// </summary>
    /// <param name="skinPath">Skin path.</param>
    /// <param name="args">Arguments.</param>
    /// <typeparam name="T">Type T must inherit PanelBase</typeparam>
    public void OpenPanel<T> (string skinPath, params object[] args)
        where T : PanelBase
    {
        string name = typeof(T).ToString ();
        if (dict.ContainsKey (name))
            return;

        PanelBase panel = canvas.AddComponent<T> ();
        panel.Init (args);
        dict.Add (name, panel);

        skinPath = (skinPath != "" ? skinPath : panel.skinPath);
        GameObject skin = Resources.Load<GameObject> (skinPath);
        if (skin == null) {
            Debug.LogError ("[PanelMgr.OpenPanel] Can't find " + skinPath + " under Assets/Resources/");
        }
        PanelLayer layer = panel.layer;
        Transform parent = layerDict [layer];
        panel.skin = (GameObject)Instantiate (skin, parent);

        panel.OnShowing ();

        panel.OnShowed ();
    }

    /// <summary>
    /// Close a panel which type is name.
    /// </summary>
    /// <param name="name">Panel Instance's name</param>
    public void ClosePanel(string name)
    {
        PanelBase panel = (PanelBase)dict [name];
        if (panel == null)
            return;

        panel.OnClosing ();
        dict.Remove (name);
        panel.OnClosed ();
        GameObject.Destroy (panel.skin);
        Component.Destroy (panel);
    }
    #endregion
}

 Scene中的Canvas下有两个空物体分别为Panel和Tips,两者的作用仅在于分组。
 PanelLayer的意义在于对Scene中的所有面板进行一个分组,面板为Panel组,提示为Tips组。
 美工通过把制作好的面板拖到Asset/Resources中,程序便可以通过代码实例化对应的面板,以此实现了代码与资源的分离。(你说不清楚Resources的作用?传送门了解一下)

2018-03-16 15:16:05 MadBam_boo 阅读数 1063
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

Unity3d实战之Unity3d网络游戏实战篇(6):服务端框架的搭建

学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍
以下为个人的学习简记,与诸君共论 由于U3D的官方案例Tank Tutorial中对坦克的基本操作已经有了详尽的描述,因此本文着重于面板系统、服务端基本网络框架和客户端基本网络框架的搭建
如有疑问或者描述错误的地方,欢迎各位提出或修正,讨论交流是获取和巩固知识的重要途径之一!

 整个服务端框架分为3层:
这里写图片描述
 底层:负责Client的监听、数据处理、协议构建、数据库操作等。
 中间层:对客观世界事物的抽象,实现类对象的各种方法;
 逻辑层:实现游戏逻辑,根据接收到的不同协议实现不同的逻辑。

 服务端框架的目录结构如下:
 这里写图片描述
 底层:
  ServNet:处理Server与各Client的连接以及数据接收;
  DataMgr:根据需求实现对数据库的各种操作;
  Protocol:编写协议实现Server与Client之间的有效通信;
 中间层:
  Player:对玩家的抽象,拥有玩家id、角色、下线等方法的实现;
  Conn:实现Server与Client之间的连接的管理,其作用与第四节描述的一致;
 逻辑层:
  HandleConnMsg:处理连接相关的事务,如心跳机制、用户登陆登出等;
  HandlePlayerMsg:处理玩家相关的事务,如位置同步、得分获取等;
  HandlePlayerEvent:处理玩家事件,某个时间发生时要处理的事情,如玩家登陆登出等;
  PlayerData:指定需要保存到数据库的角色属性,如金币、等级、胜负数等;
  PlayerTempData:指定玩家角色的临时属性,如是否在房间内、是否在战斗中、是否已准备等;

2018-03-21 22:51:26 MadBam_boo 阅读数 1926
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

Unity3d实战之Unity3d网络游戏实战篇(12):客户端网络模块

学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍,如有侵权请联系,必删。

 客户端的网络部分需要处理的数据并没有服务端那么多,要做的事情还是有相似之处的。流程:
 异步Socket接收消息 -> 消息按顺序存入消息列表 -> 依次处理先接收到的消息 -> 根据消息类型执行不同的逻辑。
 注意,客户端给服务端发送数据后,服务端会返回相应的处理结果,我们通过建立监听表来实现对返回消息的监听,当客户端发送数据后,注册相应的监听事件,当接收到对应的返回消息时,调用相应的处理方法。监听表分两类,一种是永久监听,一种是一次性监听。但是,有时候我们接收到返回消息时需要调用多个处理方法,我们可以通过使用委托来实现。
这里写图片描述
这里写图片描述
 1、建立MsgDistribution类管理消息列表和监听表。
 用到的变量有:

public delegate void MsgDelegate(ProtocolBase protoBase);

public List<ProtocolBase> msgList = new List<ProtocolBase>();
public int maxMsgCount = 15;

private Dictionary<string, MsgDelegate> eventDict = new Dictionary<string, MsgDelegate> ();
private Dictionary<string, MsgDelegate> onceDict = new Dictionary<string, MsgDelegate> ();

 其中:
  MsgDelegate:委托类型,它能够引用其他的方法,跟C/C++中的函数指针类似,可以避免使用大量的if-else等语句的问题。但注意,引用的方法必须和MsgDelegate具有相同的参数和返回值类型;
  msgList:消息列表,保存从服务端接收到的消息,先入先出原则;
  maxMsgCount:每帧处理的最大消息数;
  eventDict:永久性监听表,注册之后一直保持监听特定消息,key为监听的协议名,value为回调方法;
  onceDict:一次性监听表,注册之后只监听一次,key为监听的协议名,value为回调方法;。

定义用于添加事件和删除事件的方法:

#region Extra Operation
/// <summary>
/// listen a new item permanent unless it was deleted.
/// </summary>
/// <param name="protoName">type string protoName</param>
/// <param name="callback">type MsgDelegate callback</param>
public void AddListener(string protoName, MsgDelegate callback)
{
    if (eventDict.ContainsKey (protoName)) {
        eventDict [protoName] += callback;
    } else {
        eventDict [protoName] = callback;
    }
}

/// <summary>
/// listen a new item disposable or unless it was deleted.
/// </summary>
/// <param name="protoName">type string protoName</param>
/// <param name="callback">type MsgDelegate callback</param>
public void AddOnceListener(string protoName, MsgDelegate callback)
{
    if (onceDict.ContainsKey (protoName)) {
        onceDict [protoName] += callback;
    } else {
        onceDict [protoName] = callback;
    }
}

/// <summary>
/// delete an item permanent from permanent listener list.
/// </summary>
/// <param name="protoName">type string protoName</param>
/// <param name="callback">type MsgDelegate callback</param>
public void DelListener(string protoName, MsgDelegate callback)
{
    if (eventDict.ContainsKey (protoName)) {
        eventDict [protoName] -= callback;
        if (eventDict [protoName] == null)
            eventDict.Remove (protoName);
    }
}

/// <summary>
/// delete an item permanent from disposable listener list.
/// </summary>
/// <param name="protoName">type string protoName</param>
/// <param name="callback">type MsgDelegate callback</param>
public void DelOnceListener(string protoName, MsgDelegate callback)
{
    if (onceDict.ContainsKey (protoName)) {
        onceDict [protoName] -= callback;
        if (onceDict [protoName] == null)
            onceDict.Remove (protoName);
    }
}
#endregion

 处理消息分发时候,有可能会出现线程竞争的情况,因此,在对msdList进行操作的时候要注意使用lock,例如在分发掉一条消息后需要把该消息从消息列表移除时:

lock (msgList) {
    msgList.RemoveAt (0);
}

 2、建立Connection类实现消息收发
 客户端的Connection和服务端的ServNetManager相似,需要实现对消息的监听和处理数据粘包分包,不一样的在于客户端的Connection主需要保持ReceiveCallback以及在接收到消息后,把消息插入消息列表即可。值得一提的是本书对Send方法进行了多次重载:
 

/// <summary>
/// send a protocol to server.
/// </summary>
/// <param name="protoBase">type ProtocolBase</param>
public bool Send(ProtocolBase protoBase)
{
    byte[] bytes = protoBase.Encode ();
    byte[] lenBytes = BitConverter.GetBytes (bytes.Length);
    byte[] sendBytes = lenBytes.Concat (bytes).ToArray ();

    try {
        clientSk.Send(sendBytes);
        Debug.Log("[Connection.Send] Send message: " + protoBase.GetName());
        return true;
    } catch (Exception ex) {
        Debug.LogError ("[Connection.Send] cannot send message. " + ex.Message);
        return false;
    }
}   

/// <summary>
/// send a protocol to server and listen the server's return message with a specified cbName, at the same time, assign a callback function.
/// </summary>
/// <param name="protoBase">type ProtocolBase protoBase</param>
/// <param name="cbName">type string cbName</param>
/// <param name="callback">type MsgDistribution.MsgDelegate callback</param>
public bool Send(ProtocolBase protoBase, string cbName, MsgDistribution.MsgDelegate callback)
{
    if (status != Status.Connected) {
        Debug.LogError ("[Connection.Send] Connection has not established.");
        return false;
    }

    msgDistribution.AddOnceListener (cbName, callback);
    return Send (protoBase);
}

/// <summary>
/// send a protocol to server and listen the server's return message. the specified cbName equal to protoName.
/// </summary>
/// <param name="protoBase">Proto base.</param>
/// <param name="callback">Callback.</param>
public bool Send(ProtocolBase protoBase, MsgDistribution.MsgDelegate callback)
{
    string protoName = protoBase.GetName ();
    return Send (protoBase, protoName, callback);
}

 第一个Send方法就是原始的给信息添加长度头部然后打包发送给服务端;
 第二个Send方法在打包消息前注册一次性监听事件,传入的参数可以决定注册监听的协议名,当需要监听的消息协议名跟发送的消息协议名不同时,可以使用这个Send方法;
 第三个Send方法在打包消息前注册一次监听事件,默认监听的消息协议名和要发送的消息协议名相同。

以上。

没有更多推荐了,返回首页