最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

我們來用Unity做一個局域網(wǎng)游戲(下)

2018-09-12 17:35 作者:皮皮關(guān)做游戲  | 我要投稿

作者:ProcessCA


大家好,我又來了。

廢話不多說,咱們趕緊的,接著上一篇文章把這個聯(lián)網(wǎng)項(xiàng)目搞完。


客戶端發(fā)送消息

然后在NetworkClient中提供發(fā)送消息的方法,發(fā)送消息使用消息隊(duì)列的機(jī)制(就是把給發(fā)送的消息放進(jìn)一個隊(duì)列(Queue) 通過一個協(xié)程專門向服務(wù)器發(fā)送隊(duì)列中的消息)。需要發(fā)送什么消息只需往消息隊(duì)列中添加消息即可。下面是封裝消息數(shù)據(jù)包方法:

    /// <summary>

    /// 加入消息隊(duì)列

    /// </summary>

    public static void Enqueue(MessageType type, byte[] data = null)

    {

        byte[] bytes = _Pack(type, data); // Pack方法在上文中已經(jīng)實(shí)現(xiàn)

 

        if (_curState == ClientState.Connected)

        {

            //加入隊(duì)列                                

            _messages.Enqueue();

        }

    }

 

以下是發(fā)送協(xié)程:

    private static IEnumerator _Send()

    {

        //持續(xù)發(fā)送消息

        while (_curState == ClientState.Connected)

        {

            _timer += Time.deltaTime;

            //有待發(fā)送消息

            if (_messages.Count > 0)

            {

                byte[] data = _messages.Dequeue();

                yield return _Write(data); //稍后會實(shí)現(xiàn)

            }

 

            //心跳包機(jī)制(每隔一段時間向服務(wù)器發(fā)送心跳包)

            if (_timer >= HEARTBEAT_TIME)

            {

                //如果沒有收到上一次發(fā)心跳包的回復(fù)

                if (!Received)

                {

                    _curState = ClientState.None;

                    Debug.Log("心跳包接受失敗,斷開連接");

                    yield break;

                }

                _timer = 0;

                //封裝消息

                byte[] data = _Pack(MessageType.HeartBeat);

                //發(fā)送消息

                yield return _Write(data);

 

                Debug.Log("已發(fā)送心跳包");

            }

            yield return null; //防止死循環(huán)

        }

    }

 

然后就是NetworkClient關(guān)鍵的發(fā)送信息方法Write:

    private static IEnumerator _Write(byte[] data)

    {

        //如果服務(wù)器下線, 客戶端依然會繼續(xù)發(fā)消息

        if (_curState != ClientState.Connected || _stream == null)

        {

            Debug.Log("斷開連接");

            yield break;

        }

 

        //異步發(fā)送消息

        IAsyncResult async = _stream.BeginWrite(data, 0, data.Length, null, null);

        while (!async.IsCompleted)

        {

            yield return null;

        }

        //異常處理

        try

        {

            _stream.EndWrite(async);

        }

        catch (Exception ex)

        {

            _curState = ClientState.None;

            Debug.Log("斷開連接" + ex.Message);

        }

    }

 


OK,客戶端的大坑終于快填完(其實(shí)還不到一半)。

在Network中實(shí)現(xiàn)一個發(fā)送創(chuàng)建房間請求的示例(發(fā)送一般是接受用戶操作后,所以這個方法可以綁定在UI上,由UI事件去觸發(fā)),當(dāng)然為了方便測試,放在Start方法里也沒問題:

    public void CreatRoomRequest(int roomId)

    {

        CreatRoom request = new CreatRoom();

        request.RoomId = roomId;

        byte[] data = NetworkUtils.Serialize(request);

        NetworkClient.Enqueue(MessageType.CreatRoom, data);

    }

 

現(xiàn)在,我們的客戶端只會發(fā)送消息,服務(wù)器只會接收消息,簡直就是一個聾子跟一個啞巴。


來點(diǎn)輕松點(diǎn)的,我們先把客戶端的GamePlay部分實(shí)現(xiàn)吧。


客戶端

基本游戲邏輯

棋盤

制作棋盤的時候,先把一張Sprite圖片放進(jìn)Unity場景中。為了方便我們用射線檢測我們的鼠標(biāo)在棋盤上的落點(diǎn),我們可以在棋盤的Layer中添加上ChessBoard并且在棋盤上添加BoxCollider:

在左上角,左上角,右上角都添加好錨點(diǎn)

添加完錨點(diǎn)之后,棋盤的制作就已經(jīng)完成,然后找兩個適合的棋子圖片做成預(yù)制體就OK了。

然后我們應(yīng)該想,如何去完善棋盤的數(shù)據(jù)結(jié)構(gòu),創(chuàng)造一個保存所有落點(diǎn)世界坐標(biāo)的二維數(shù)組。

一個比較好的做法是:利用這三個錨點(diǎn),求出棋盤左右的寬度與上下的高度,進(jìn)而可以求出每一個方格的寬度與高度。然后我們再根據(jù)左下角的錨點(diǎn)(原點(diǎn)),用一個雙重循環(huán)遍歷這個保存落點(diǎn)世界坐標(biāo)的二維數(shù)組并進(jìn)行賦值。以下是實(shí)現(xiàn)代碼:

using UnityEngine;

using Multiplay;  //為協(xié)議的命名空間

/// <summary>

/// 處理下棋邏輯

/// </summary>

public class NetworkGameplay : MonoBehaviour

{

    //單例

    private NetworkGameplay() { }

    public static NetworkGameplay Instance { get; private set; }

 

    [SerializeField]

    private GameObject _blackChess;                    //需要實(shí)例化的黑棋

    [SerializeField]

    private GameObject _whiteChess;                    //需要實(shí)例化的白棋

 

    //棋盤上的錨點(diǎn)

    [SerializeField]

    private GameObject _leftTop;                       //左上

    [SerializeField]

    private GameObject _leftBottom;                    //左下

    [SerializeField]

    private GameObject _rightTop;                      //右上

 

    private Vector2[,] _chessPos;                      //儲存棋子世界坐標(biāo)

    private float _gridWidth;                          //網(wǎng)格寬度

    private float _gridHeight;                         //網(wǎng)格高度

 

    private void Awake()

    {

        if (Instance == null)

            Instance = this;

 

        _chessPos = new Vector2[15, 15];

 

        Vector3 leftTop = _leftTop.transform.position;

        Vector3 leftBottom = _leftBottom.transform.position;

        Vector3 rightTop = _rightTop.transform.position;

 

        //初始化每一個格子(一共14個)的寬度與高度

        _gridWidth = (rightTop.x - leftTop.x) / 14;

        _gridHeight = (leftTop.y - leftBottom.y) / 14;

 

        //初始化每個下棋點(diǎn)的位置

        for (int i = 0; i < 15; i++)

        {

            for (int j = 0; j < 15; j++)

            {

                _chessPos[i, j] = new Vector2

                (

                    leftBottom.x + _gridWidth * i,

                    leftBottom.y + _gridHeight * j

                );

            }

        }

    }

}

 

OK,有了棋盤,接下來當(dāng)然就是在棋盤類中提供用戶輸入檢測接口與實(shí)例化棋子的接口。

在這里提供一個Vec2類型,方便表示棋子在棋盤上的下標(biāo):

public struct Vec2

{

    public int X;

    public int Y;

 

    public Vec2(int x, int y)

    {

        X = x;

        Y = y;

    }

}

 

檢測用戶輸入

   /// <summary>

    /// 下棋

    /// </summary>

    public Vec2 PlayChess()

    {

        //創(chuàng)建射線

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit hit;

        //如果用戶點(diǎn)中棋盤

        if (Physics.Raycast(ray, out hit, 100, 1 << LayerMask.NameToLayer("ChessBoard")))

        {

            //遍歷棋盤

            for (int i = 0; i < 15; i++)

            {

                for (int j = 0; j < 15; j++)

                {

                    //計算鼠標(biāo)點(diǎn)擊點(diǎn)與下棋點(diǎn)的距離(只算x,y平面距離)

                    float distance = _Distance(hit.point, _chessPos[i, j]);

                    //鼠標(biāo)點(diǎn)擊在落點(diǎn)周圍半個格子寬度就算下棋

                    if (distance < (_gridWidth / 2))

                    {

                        //返回這個落點(diǎn)在二維數(shù)組中的下標(biāo)

                        return new Vec2(i, j);

                    }

                }

            }

        }

 

        //未點(diǎn)擊到棋盤

        return new Vec2(-1, -1);

    }

 

    /// <summary>

    /// 計算兩個Vector2的距離

    /// </summary>

    private float _Distance(Vector2 a, Vector2 b)

    {

        Vector2 distance = b - a;

        return distance.magnitude;

    }

 

實(shí)例化棋子


     /// <summary>

    /// 實(shí)例化棋子

    /// </summary>

    public void InstChess(Chess chess, Vec2 pos)

    {

        //獲取該落點(diǎn)的世界坐標(biāo)

        Vector2 vec2 = _chessPos[pos.X, pos.Y];

        //棋子坐標(biāo):棋子的z坐標(biāo)不能與棋盤一致且必須更靠近攝像機(jī)近截面,

        //不然有可能會與棋盤重疊導(dǎo)致棋子不可見。

        Vector3 chessPos = new Vector3(vec2.x, vec2.y, -1);

 

        if (chess == Chess.Black)

        {

            Instantiate(_blackChess, chessPos, Quaternion.identity);

        }

        else if (chess == Chess.White)

        {

            Instantiate(_whiteChess, chessPos, Quaternion.identity);

        }

    }

 

制作好了棋盤腳本,可以先開始制作玩家類(Player)。

以下為玩家類,掛在場景中接收用戶輸入并做簡單的邏輯檢測。

using System;

using UnityEngine;

using Multiplay;   //為協(xié)議的命名空間

/// <summary>

/// 一個游戲客戶端只能存在一個網(wǎng)絡(luò)玩家

/// </summary>

public class NetworkPlayer : MonoBehaviour

{

    //單例

    private NetworkPlayer() { }

    public static NetworkPlayer Instance { get; private set; }

 

    [HideInInspector]

    public Chess Chess;                     //棋子類型

    [HideInInspector]

    public int RoomId = 0;                  //房間號碼

    [HideInInspector]

    public bool Playing = false;            //正在游戲

    [HideInInspector]

    public string Name;                     //名字

 

    private void Awake()

    {

        if (Instance == null)

            Instance = this;

    }

 

    private void Update()

    {

        if (Input.GetMouseButtonDown(0) && Playing)

        {

            //發(fā)送下棋請求 TODO

        }

    }

}

 

在這里提供個客戶端的類型,方便之后開發(fā)。

Info類型作用于UI,可以在某個Text上顯示信息:

using UnityEngine;

using UnityEngine.UI;

 

public class Info : MonoBehaviour

{

    private Info() { }

    public static Info Instance { get; private set; }

 

    private Text _text;

 

    private void Awake()

    {

        if (Instance == null)

            Instance = this;

 

        _text = GetComponent<Text>();

    }

 

    /// <summary>

    /// 打印

    /// </summary>

    public void Print(string str, bool warning = false)

    {

        if (warning)

            Debug.LogWarning(str);

        else

            Debug.Log(str);

 

        _text.text = str;

    }

}

 

至于游戲的UI部分,此處不做詳細(xì)介紹。

以下是本游戲UI提供的用戶接口,僅供參考:

  [SerializeField]

    private InputField _ipAddressIpt;     //服務(wù)器IP輸入框

    [SerializeField]

    private InputField _roomIdIpt;        //房間號碼輸入框

    [SerializeField]

    private InputField _nameIpt;          //名字輸入框

 

    [SerializeField]

    private Button _connectServerBtn;     //連接服務(wù)器按鈕

    [SerializeField]

    private Button _enrollBtn;            //注冊按鈕

    [SerializeField]

    private Button _creatRoomBtn;         //創(chuàng)建房間按鈕

    [SerializeField]

    private Button _enterRoomBtn;         //加入房間按鈕

    [SerializeField]

    private Button _exitRoomBtn;          //退出房間按鈕

    [SerializeField]

    private Button _startGameBtn;         //開始游戲按鈕

 

    [SerializeField]

    private Text _gameStateTxt;           //游戲狀態(tài)文本

    [SerializeField]

    private Text _roomIdTxt;              //房間號碼文本

    [SerializeField]

    private Text _nameTxt;                //名字文本

 

    private void Start()

    {

        //綁定按鈕事件

        _connectServerBtn.onClick.AddListener(_ConnectServerBtn);

        _enrollBtn.onClick.AddListener(_EnrollBtn);

        _creatRoomBtn.onClick.AddListener(_CreatRoomBtn);

        _enterRoomBtn.onClick.AddListener(_EnterRoomBtn);

        _exitRoomBtn.onClick.AddListener(_ExitRoomBtn);

        _startGameBtn.onClick.AddListener(_StartGameBtn);

    }

 

實(shí)現(xiàn)了基本的客戶端棋盤邏輯。接下來就是重點(diǎn)了,涉及到客戶端與服務(wù)器的網(wǎng)絡(luò)通訊部分。現(xiàn)在我們的客戶端已經(jīng)具備接受玩家輸入,并且可以把用戶鼠標(biāo)點(diǎn)擊的位置轉(zhuǎn)化為棋盤上的位置。


客戶端接受消息

有了發(fā)送消息肯定少不了接受消息,客戶端必須對服務(wù)器發(fā)來的反饋再進(jìn)行操作。

接收消息這里有個也有回調(diào)事件機(jī)制:在把數(shù)據(jù)包拆成:消息長度,消息類型之后,我們可以通過不同的消息類型去執(zhí)行不同的回調(diào)方法。

以下為NetworkClient的接收消息(Receive)方法的關(guān)鍵代碼:

         while(true)

         { 

            //解析數(shù)據(jù)包過程(服務(wù)器與客戶端需要嚴(yán)格按照一定的協(xié)議制定數(shù)據(jù)包)

            byte[] data = new byte[4]; //數(shù)據(jù)包包頭長度(2 + 2)

 

            int length;         //消息總長度

            MessageType type;   //類型

            int receive = 0;    //接收到的數(shù)據(jù)長度

 

            //異步讀取

            IAsyncResult async = _stream.BeginRead(data, 0, data.Length, null, null);

            while (!async.IsCompleted)

            {

                yield return null;

            }

            //異步讀取完畢

            receive = _stream.EndRead(async);

           

            //解析包頭

            using (MemoryStream stream = new MemoryStream(data))

            {

                BinaryReader binary = new BinaryReader(stream, Encoding.UTF8); //UTF-8格式

               

                length = binary.ReadUInt16();

                type = (MessageType)binary.ReadUInt16();

            }

 

            //如果有包體

            if (length - 4 > 0)

            {

                data = new byte[length - 4];

                //異步讀取

                async = _stream.BeginRead(data, 0, data.Length, null, null);

                while (!async.IsCompleted)

                {

                    yield return null;

                }

                //異步讀取完畢

                receive = _stream.EndRead(async);

            }

            //沒有包體

            else

            {

                data = new byte[0];

                receive = 0;

            }

           

            //反序列化回消息類型

            CreatRoom result = NetworkUtils.Deserialize<CreatRoom>(data);

         }

 

回調(diào)事件對客戶端做的具體操作這里就不放源碼了,只示范一個事件:

    private void _Heartbeat(byte[] data)

    {

        NetworkClient.Received = true;

        Debug.Log("收到心跳包回應(yīng)");

    }

 

與服務(wù)器類似,回調(diào)的核心就是把消息類型與相對應(yīng)的回調(diào)事件一起注冊一個字典(這個過程在客戶端接收服務(wù)器數(shù)據(jù)之前)。

每次接受消息后,只需要把這次的消息類型與字典中進(jìn)行匹配,進(jìn)而客戶端執(zhí)行相對應(yīng)的回調(diào)事件即可。以下為回調(diào)機(jī)制關(guān)鍵代碼:

     //注冊回調(diào)事件   

     public static void Register(MessageType type, CallBack method)

     {

        if (!_callBacks.ContainsKey(type))

            _callBacks.Add(type, method);

        else

            Debug.LogWarning("注冊了相同的回調(diào)事件");

     }

 

 

            //執(zhí)行回調(diào),這里的代碼應(yīng)該放在接收消息方法中

            if (_callBacks.ContainsKey(type))

            {

                //執(zhí)行回調(diào)事件

                CallBack method = _callBacks[type];

                method(data);

            }

 

到這里,客戶端的關(guān)鍵制作思路已經(jīng)介紹完成。



服務(wù)器發(fā)送消息

以下是服務(wù)器對客戶端的發(fā)送消息的方法,下面是代碼:

    /// <summary>

    /// 封裝并發(fā)送信息 ,寫在Server中的Player類型擴(kuò)展方法

    /// </summary>

    public static void Send(this Player player, MessageType type, byte[] data = null)

    {

        byte[] bytes = _Send(type, data); //在介紹數(shù)據(jù)包時已經(jīng)實(shí)現(xiàn)

 

        //發(fā)送消息

        player.Socket.Send(bytes);

    }

 

終于不是兩個殘疾人在通信了。

服務(wù)器房間系統(tǒng)

接下來還有房間類型,房間構(gòu)成了一局游戲的基礎(chǔ)。

在本游戲中,房間號碼不可重復(fù)。當(dāng)一個玩家創(chuàng)建好一個房間時,其他玩家在玩家人數(shù)未滿時加入房間,就會成為玩家。在玩家人數(shù)滿了,但是觀察者人數(shù)未滿時加入房間就是觀察者。

如果全部滿了之后就無法進(jìn)入該房間。當(dāng)一個房間的狀態(tài)進(jìn)入開始游戲狀態(tài)后,所有人都無法再進(jìn)入其中。當(dāng)一局游戲結(jié)束后,此房間會自動關(guān)閉。

然后我們在服務(wù)器的Room類中添加一個方法:

    /// <summary>

    /// 關(guān)閉房間:從房間字典中移除并且所有房間中的玩家清除

    /// </summary>

    public void Close()

    {

        //所有玩家跟觀戰(zhàn)者退出房間

        foreach (var each in Players)

        {

            each.ExitRoom();

        }

        foreach (var each in OBs)

        {

            each.ExitRoom();

        }

        Server.Rooms.Remove(RoomId);

    }

 

由于服務(wù)器回調(diào)事件較多,只展示創(chuàng)建心跳包回調(diào)事件的關(guān)鍵代碼:


    private void _HeartBeat(Player player, byte[] data)

    {

        //僅做回應(yīng)

        player.Send(MessageType.HeartBeat);

    }

 

服務(wù)器核心邏輯實(shí)現(xiàn)

服務(wù)器可以存在多個不同房間號的房間,每個房間對應(yīng)一個棋盤,每個房間上還擁有多個玩家。按照這個思路再去設(shè)計棋盤的邏輯。

以下為棋盤的關(guān)鍵代碼:

    /// <summary>

    /// 初始化棋盤

    /// </summary>

    public GamePlay()

    {

        ChessState = new Chess[15, 15];

        _totalChess = 0;

        Playing = true;

        Turn = Chess.Black;

    }

 

    public Chess[,] ChessState;                     //儲存棋子狀態(tài)

 

    private int _totalChess;                        //總棋數(shù)

 

    public bool Playing;                            //游戲進(jìn)行中

 

    public Chess Turn;                              //輪流下棋

 

游戲邏輯實(shí)現(xiàn)

服務(wù)器上的這個棋盤才是真正進(jìn)行五子棋邏輯操作的棋盤,關(guān)鍵的算法都在上面實(shí)現(xiàn)??蛻舳嗣堪l(fā)送一次下棋操作,都會在棋盤上進(jìn)行計算,結(jié)果再由服務(wù)器廣播給在這個房間中的所有人,包括玩家們與觀察者們。

以下為五子棋玩法邏輯的算法:

    public Chess Calculate(int x, int y)

    {

        if (!Playing) return Chess.Null;

 

        //邏輯判斷

        if (x < 0 || x >= 15 || y < 0 || y >= 15 || ChessState[x, y] != Chess.None)

        {

            return Chess.Null;

        }

 

        //下棋

        _totalChess++;

        //黑棋

        if (Turn == Chess.Black)

        {

            ChessState[x, y] = Chess.Black;

        }

        //白棋

        else if (Turn == Chess.White)

        {

            ChessState[x, y] = Chess.White;

        }

 

        //計算結(jié)果

        bool? result = _CheckWinner();

        //要么平局要么勝利(任意一方勝利后不在交替下棋,游戲結(jié)束)

        if (result != false)

        {

            //游戲結(jié)束

            Playing = false;

            //勝利

            if (result == true)

            {

                return Turn;

            }

            //平局

            else

            {

                return Chess.Draw;

            }

        }

        //繼續(xù)下棋

        else

        {

            //交替下棋

            Turn = (Turn == Chess.Black ? Chess.White : Chess.Black);

            return Chess.None;

        }

    }

 

    private bool? _CheckWinner()

    {

        //遍歷棋盤

        for (int i = 0; i < 15; i++)

        {

            for (int j = 0; j < 15; j++)

            {

                //各方向連線

                int horizontal = 1, vertical = 1, rightUp = 1, rightDown = 1;

                Chess curPos = ChessState[i, j];

 

                if (curPos != Turn)

                    continue;

 

                //判斷5連

                for (int link = 1; link < 5; link++)

                {

                    //掃描橫線

                    if (i + link < 15)

                    {

                        if (curPos == ChessState[i + link, j])

                            horizontal++;

                    }

                    //掃描豎線

                    if (j + link < 15)

                    {

                        if (curPos == ChessState[i, j + link])

                            vertical++;

                    }

                    //掃描右上斜線

                    if (i + link < 15 && j + link < 15)

                    {

                        if (curPos == ChessState[i + link, j + link])

                            rightUp++;

                    }

                    //掃描右下斜線

                    if (i + link < 15 && j - link >= 0)

                    {

                        if (curPos == ChessState[i + link, j - link])

                            rightDown++;

                    }

                }

 

                //勝負(fù)判斷

                if (horizontal == 5 || vertical == 5 || rightUp == 5 || rightDown == 5)

                {

                    return true;

                }

            }

        }

 

        //棋盤下滿

        if (_totalChess == ChessState.GetLength(0) * ChessState.GetLength(1))

        {

            //平局

            return null;

        }

 

        return false;

    }

 

那么,對于服務(wù)器來說,還有一件重要的事情,如何把一個玩家的操作發(fā)送給相同房間里的所有玩家與觀察者呢?這個可以通過對房間內(nèi)保存的每一個玩家的套接字(Socket)進(jìn)行發(fā)送數(shù)據(jù)操作。

以下為廣播給所有玩家的關(guān)鍵代碼:

    //判斷結(jié)果

    Chess chess = Server.Rooms[receive.RoomId].GamePlay.Calculate(receive.X, receive.Y);

    //檢測操作:如果游戲結(jié)束

    bool over = _ChessResult(chess, result);

 

    PlayChess result = new PlayChess();

 

    result.Chess = receive.Chess;

    result.X = receive.X;

    result.Y = receive.Y;

 

    Console.WriteLine($"玩家:{player.Name}下棋成功");

 

    //向該房間中玩家與觀察者廣播結(jié)果

    data = NetworkUtils.Serialize(result);

    foreach (var each in Server.Rooms[receive.RoomId].Players)

    {

        each.Send(MessageType.PlayChess, data);

    }

    foreach (var each in Server.Rooms[receive.RoomId].OBs)

    {

        each.Send(MessageType.PlayChess, data);

    }

 

還有很多服務(wù)器對房間系統(tǒng)所做的邏輯處理都寫在服務(wù)器的回調(diào)事件中,因?yàn)榇a量較多不能一一列出。更多細(xì)節(jié)會在文章結(jié)尾放出源碼,歡迎學(xué)習(xí)或吐槽。


好了。到此為止,恭喜你已經(jīng)達(dá)成“從無到有制作局域網(wǎng)聯(lián)機(jī)游戲”這樣一個成就。過程中的代碼量和經(jīng)驗(yàn)有沒有給你一種脫胎換骨的趕腳?

完整工程如下:https://pan.baidu.com/s/19rfHgHZIe55dZ7LVIL1oUg?errno=0&errmsg=Auth%20Login%20Sucess&&bduss=&ssnerror=0&traceid=

OK,希望本文對在游戲開發(fā)的道路上的你有所啟發(fā)。


想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問 http://levelpp.com/     

游戲開發(fā)攪基QQ群:869551769   

微信公眾號:皮皮關(guān)

我們來用Unity做一個局域網(wǎng)游戲(下)的評論 (共 條)

分享到微博請遵守國家法律
松阳县| 贡山| 准格尔旗| 孝昌县| 潮安县| 黄陵县| 兴和县| 盖州市| 怀来县| 达尔| 普安县| 宜宾县| 珠海市| 柳河县| 紫阳县| 施秉县| 扎赉特旗| 白水县| 和平县| 漠河县| 临海市| 宿松县| 江山市| 南汇区| 靖宇县| 漯河市| 澎湖县| 巴青县| 永定县| 青浦区| 亳州市| 萝北县| 高尔夫| 即墨市| 长岭县| 渑池县| 安宁市| 博客| 阿城市| 苗栗市| 靖江市|