unity:打飞碟小游戏

阅读: 评论:0

unity:打飞碟小游戏

unity:打飞碟小游戏

这里写目录标题

  • 游戏开发要求
  • 游戏规则
  • 工厂与对象池理解
  • UML图
  • 代码介绍
    • action
    • action不多做讲解,可以参考之前博客的牧师与魔鬼动作分离版的讲解
    • config
    • controller
    • factory
    • model
    • view
  • 创建预制&&配置ruler&&配置天空盒&&挂载脚本
  • 视频演示
  • 代码仓库

游戏开发要求

  • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
  • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
  • 必须使用对象池管理飞碟对象。
  • 建议使用 ScriptableObject 配置不同的飞碟(方便配置修改)
  • 建议使用物理引擎管理飞碟飞行路径。

使用 UML 图、伪代码辅助表达对象池的原理,对象配置方法,以及你的设计

游戏规则

  • 游戏有 n 个 round,每个 round 都包括10 次 trial。
  • 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制。
  • 每个 trial 的飞碟有随机性,总体难度随 round 上升。
  • 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
diskscore
2
4
6
绿8

工厂与对象池理解

工厂方法,即类一个方法能够得到一个对象实例,使用者不需要知道该实例如何构建、初始化等细节。

好处:

  • 游戏对象的创建与销毁高成本,必须减少销毁次数。
  • 屏蔽创建与销毁的业务逻辑,使程序易于扩展

UML图

代码介绍

代码分为下面几个模块

遵循MVC架构和cocos2d的设计思想

action

action不多做讲解,可以参考之前博客的牧师与魔鬼动作分离版的讲解

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{/// <summary>/// when an action has been excuted, call the callback function/// </summary>/// <param name="source">action has been excuted</param>/// <param name="events"></param>/// <param name="intParam"></param>/// <param name="strParam"></param>/// <param name="objectParam"></param>public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competeted,int intParam = 0,string strParam = null,GameObject objectParam = null);}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SSAction : ScriptableObject
{/// <summary>/// if enable, then will call update per frame/// </summary>public bool enable = true;/// <summary>/// if destory, will be removed after a frame/// </summary>public bool destroy = false;public GameObject gameobject { get; set; }public Transform transform { get; set; }public ISSActionCallback callback { get; set; }protected SSAction() { }// Start is called before the first frame updatepublic virtual void Start(){throw new System.NotImplementedException();}// Update is called once per framepublic virtual void Update(){throw new System.NotImplementedException();}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SSActionManager : MonoBehaviour
{private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();private List<SSAction> waitingAdd = new List<SSAction> ();private List<int> waitingDelete = new List<int>();// Start is called before the first frame updateprotected void Start(){}// Update is called once per frameprotected void Update(){// add to actionsforeach(SSAction ac in waitingAdd){actions[ac.GetInstanceID()] = ac;}waitingAdd.Clear();//run actionsforeach(KeyValuePair<int, SSAction> kv in actions){SSAction ac = kv.Value;if(ac.destroy){waitingDelete.Add(ac.GetInstanceID());}else able){ac.Update();//call action update manually}}//delete actionsforeach(int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);Destroy(ac);}waitingDelete.Clear();}public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager){action.gameobject = gameobject;//add action ansform = ansform;//ser action's transformaction.callback = manager;//set action's callbackwaitingAdd.Add(action);action.Start();//not monobehavior thus call start mannually}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class NPhysicalDiskAction : SSAction
{float gravity;float time;float speed;Vector3 direction;// builder, get specific actions success SSActionpublic static NPhysicalDiskAction GetSSAction(Vector3 direction, float speed){NPhysicalDiskAction action = ScriptableObject.CreateInstance<NPhysicalDiskAction>();avity = 9.8f;action.time = 0;action.speed = speed;action.direction = direction;return action;}// Start is called before the first frame updatepublic override void Start(){gameobject.GetComponent<Rigidbody>().isKinematic = true;}// Update is called once per framepublic override void Update(){// disk move 运动合成,transform is the one of scriptable object,// which will be set equal to gameobject which excecutes this action. transform.Translate(Vector3.down * gravity * time * Time.deltaTime);transform.Translate(direction * speed * Time.deltaTime);time += Time.deltaTime;//Debug.Log("postion of the disk"+gameobject.name+" is "&#ansform.position);// 飞碟到达屏幕底部回调Camera mainCamera = Camera.main; // 获取主摄像机float cameraHeight = hographicSize * 2f; // 获取相机的视图高度// 计算相机视图的上下边界坐标float upperBoundary = ansform.position.y + cameraHeight / 2f;float lowerBoundary = ansform.position.y - cameraHeight / 2f;//Debug.Log("Upper Boundary: " + upperBoundary);//Debug.Log("Lower Boundary: " + lowerBoundary);if (ansform.position.y < lowerBoundary-100 || ansform.position.y > upperBoundary+100){// action destorythis.destroy = able = false;// action callbackthis.callback.SSActionEvent(this);//source as this}}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PhysicalDiskAction : SSAction
{private float speed;private Vector3 direction;// builderpublic static PhysicalDiskAction GetSSAction(Vector3 direction, float speed){PhysicalDiskAction ac = ScriptableObject.CreateInstance<PhysicalDiskAction>();ac.speed = speed;ac.direction = direction;return ac;}// Start is called before the first frame updatepublic override void Start(){// add clash pysical effectgameobject.GetComponent<Rigidbody>().isKinematic = false;// add init speedgameobject.GetComponent<Rigidbody>().velocity = speed * direction;}// Update is called once per framepublic override void Update(){//Debug.Log("postion of the disk"+gameobject.name+" is "&#ansform.position);// 飞碟到达屏幕底部回调Camera mainCamera = Camera.main; // 获取主摄像机float cameraHeight = hographicSize * 2f; // 获取相机的视图高度// 计算相机视图的上下边界坐标float upperBoundary = ansform.position.y + cameraHeight / 2f;float lowerBoundary = ansform.position.y - cameraHeight / 2f;//Debug.Log("Upper Boundary: " + upperBoundary);//Debug.Log("Lower Boundary: " + lowerBoundary);if (ansform.position.y < lowerBoundary - 100 || ansform.position.y > upperBoundary + 100){// action destorythis.destroy = able = false;// action callbackthis.callback.SSActionEvent(this);//source as this}}
}
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;public class DiskActionManager : SSActionManager, ISSActionCallback
{public FirstController firstController;public void ShootDisk(GameObject disk, float speed, Vector3 direction,PhysiclaType type){SSAction ac;// run actionif (type==  Physical){ac = NPhysicalDiskAction.GetSSAction(direction, speed);}else if (type == PhysiclaType.pysical){ac = PhysicalDiskAction.GetSSAction(direction, speed);}else{throw new NotImplementedException();}RunAction(disk, ac, this);}protected new void Start(){// get fisrt controllerfirstController = (FirstController)Director.GetInstance().CurrentSceneController;// set action manager as thisfirstController.actionManager = this;}// specific manager do callbackpublic void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competeted,int intParam = 0,string strParam = null,GameObject objectParam = null){//回收飞碟//source is the class success the SSAction, in this case is obj of NphysicalDiskAction ClassSingleton<RoundController>.Instance.FreeDisk(source.gameobject);}
}

config


using System;
using UnityEngine;[CreateAssetMenu(menuName ="create ruler config")]
public class Ruler : ScriptableObject
{// 对局属性public int trailIndex;public int roundIndex;public int SumRoundNum;public int SumTrailNum;public int[] diskNumPerRound;//avg disk num per roundpublic float[] shootDeltaTimePerRound;//发射间隔// 飞碟属性public DiskFeature diskFeature;[NonSerialized]public Vector3 startPos;[NonSerialized]public Vector3 startDir;}

controller

Director 导演类,管理场记

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;public class Director : System.Object
{static Director _instance;// 关联场记实例public ISceneController CurrentSceneController { get; set; }// 获取当前的场记public static Director GetInstance(){if (_instance == null){_instance = new Director();}return _instance;}public static void ReloadCurrentScene(){int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;SceneManager.LoadScene(currentSceneIndex);}
}

ISceneController场记类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public interface ISceneController
{void LoadResources();
}

IUserAction定义了用户的动作

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 抽象接口,定义用户的行为
public interface IUserAction
{void Restart();
}

FirstController 第一个场记,继承ISceneController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/* 游戏状态,0为准备进行,1为正在进行游戏,2为结束 */
public enum GameState
{Ready = 0, Playing = 1, GameOver = 2
};public class FirstController : MonoBehaviour, ISceneController
{public DiskActionManager actionManager;public RoundController roundController;public UserGUI userGUI;public GameState gameState;// ruler configpublic Ruler rulerConfig;public void LoadResources(){Director.GetInstance().CurrentSceneController = this;roundController = gameObject.AddComponent<RoundController>();Debug.Log("add roundController Component");userGUI = gameObject.AddComponent<UserGUI>();Debug.Log("add UserGUI");actionManager = gameObject.AddComponent<DiskActionManager>();Debug.Log("add DiskActionManager");gameState = (int)GameState.Ready;}public void Restart(){Director.ReloadCurrentScene();}public void JudgeResultCallBack(string result){}void Awake(){LoadResources();}void Start(){}void Update(){}public void GameOver(){gameState = GameState.GameOver;userGUI.SetGameState(GameState.GameOver);}public void SetGameMode(PhysiclaType type){roundController.phyType= type;}
}

RoundController 控制飞碟对象的运动类型为运动学运动或物理运动,并根据一定规则请求飞碟工厂生成相应的飞碟对象,控制游戏飞碟对象的发射

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;public enum PhysiclaType
{pysical,noPhysical
}public class RoundController : MonoBehaviour
{DiskActionManager actionManager;ScoreRecorder scoreRecorder;FirstController firstController;Ruler ruler;float time = 0;public PhysiclaType phyType;public void LaunchDisk(){// 使飞碟飞入位置尽可能分开,从不同位置飞入使用的数组int[] beginPosY = new int[10];float beginPosX;// shoot the disks for each trailint diskNum = ruler.undIndex] / ruler.SumTrailNum;diskNum += Random.Range(0, 5);// random num per trailfor (int i = 0; i < diskNum; ++i){// 获取随机数int randomNum = Random.Range(1, 4);// 飞碟速度随回合数增加而变快ruler.diskFeature.speed = randomNum * (undIndex + 2);// 根据随机数选择飞碟颜色randomNum = Random.Range(1, 4);if (randomNum == 1){lor = d;}else if (randomNum == 2){lor = ;}else{lor = lors.blue;}// 重新选取随机数,并根据随机数选择飞碟的大小ruler.diskFeature.size = Random.Range(1, 4);// 重新选取随机数,并根据随机数选择飞碟飞入的方向randomNum = Random.Range(0, 2);Camera mainCamera = Camera.main;float cameraW = hographicSize * 2f * Camera.main.aspect;if (randomNum == 1){ruler.startDir = new Vector3(3, 1, 0);beginPosX= ansform.position.x - cameraW / 2f;}else{ruler.startDir = new Vector3(-3, 1, 0);beginPosX = ansform.position.x + cameraW / 2f;}// 重新选取随机数,并使不同飞碟的飞入位置尽可能分开int iterNum = 0;do{// case if infinite iteriterNum++;if (iterNum>=100){int ii;for (ii = 0; ii < beginPosY.Length; ii++){beginPosY.SetValue(0, ii);}}randomNum = Random.Range(0, 10);} while (beginPosY[randomNum] != 0);beginPosY[randomNum] = 1;ruler.startPos = new Vector3(beginPosX, -0.2f * randomNum, 0);// 根据ruler从工厂中生成一个飞碟GameObject disk = Singleton<DiskFactory>.Instance.GetDisk(ruler);// 设置飞碟的飞行动作actionManager.ShootDisk(disk, ruler.diskFeature.speed, ruler.startDir,phyType);}}/// <summary>/// free the gameobject that loaded the action/// </summary>/// <param name="obj">obj is the one load the action</param>public void FreeDisk(GameObject disk){// call factory to free the diskSingleton<DiskFactory>.Instance.FreeDisk(disk);}// 用户点击碰撞public void Hit(Vector3 position){Camera camera = Camera.main;Ray ray = camera.ScreenPointToRay(position);RaycastHit[] hits;hits = Physics.RaycastAll(ray);for (int i = 0; i < hits.Length; i++){RaycastHit hit = hits[i];if (llider.gameObject != null){// 把击中的飞碟移出屏幕,触发回调释放ansform.position = new Vector3(0, -6, 0);// 记录飞碟得分scoreRecorder.llider.gameObject.GetComponent<Disk>());// 显示当前得分UserGUI userGUI = Singleton<UserGUI>.Instance;userGUI.SetScore(scoreRecorder.GetScore());}}}// Start is called before the first frame updatevoid Start(){Debug.Log("gameobject of roundcontroller is:" + this.gameObject);actionManager = gameObject.AddComponent<DiskActionManager>();scoreRecorder = gameObject.AddComponent<ScoreRecorder>();firstController = Director.GetInstance().CurrentSceneController as FirstController;ruler = firstController.rulerConfig;gameObject.AddComponent<DiskFactory>();undIndex= ailIndex= 0;Debug.Log("ruler config:");Debug.Log(JsonUtility.ToJson(ruler));phyType = Physical;Debug.Log("round controller's phytype is:"+phyType);}// Update is called once per framevoid Update(){// when playingif (firstController.gameState == GameState.Playing){//update usergui round indexUpdateRoundInfo();//Debug.Log("round cont is playing update--");//Debug.Log("roundIndex" + undIndex + "trailIndex" + ailIndex);time += Time.deltaTime;float shootDeltaTime = ruler.undIndex];// 发射一次飞碟(trial)if (time > shootDeltaTime){// reset timetime = 0;if (ailIndex<ruler.SumTrailNum && undIndex<ruler.SumRoundNum){// 发射飞碟Debug.LogFormat("call LaunchDisk at round controller--at round {0} trail {1}", undIndex, ailIndex);LaunchDisk();ailIndex++;// 回合加一,重新生成飞碟数组if (ailIndex == ruler.SumTrailNum){ailIndex = undIndex++;}}// 否则游戏结束,提示重新进行游戏else{firstController.GameOver();}}}}private void UpdateRoundInfo(){Singleton<UserGUI>.Instance.undIndex, ailIndex);Singleton<UserGUI>.Instance.SetRTNum(ruler.SumRoundNum, ruler.SumTrailNum);Singleton<UserGUI>.Instance.SetPhyMode(phyType);}}

记分类根据记分规则,对被点击中的飞碟得分进行记录

using UnityEngine;public class ScoreRecorder : MonoBehaviour
{private int score;public ScoreRecorder(){ score = 0; }public ScoreRecorder(int score){ this.score = score; }public int GetScore(){return score;}// record the scorepublic void Record(Disk disk){// 飞碟大小为1得3分,大小为2得2分,大小为3得1分int diskSize = disk.diskFeature.size;switch (diskSize){case 1:score += 3;break;case 2:score += 2;break;case 3:score += 1;break;default: break;}// 速度越快分就越高score += disk.diskFeature.speed;// 颜色为红色得1分,颜色为黄色得2分,颜色为蓝色得3分lors diskColor = lor;if (diskColor == d){score += 1;}else if (diskColor == llow){score += 2;}else if (diskColor == lors.blue){score += 3;}}/* 重置分数,设为0 */public void Reset(){score = 0;}// Use this for initializationvoid Start(){}// Update is called once per framevoid Update(){}
}

factory

Singleton单实例类,用来获取单实例的类的对象

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{protected static T instance;public static T Instance{get{ if (instance == null){instance = FindObjectOfType(typeof(T)) as T;if (instance == null){Debug.LogError("no instance of type " + typeof(T));}} return instance;}}// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){}
}

飞碟工厂类负责飞碟对象的创建和销毁,在场景中是单实例的,且使用了对象池,实现了缓存功能。

当一个飞碟对象被创建时,会首先在对象池中寻找没有被使用的空闲飞碟对象,有的话就根据规则设置飞碟对象相应属性后直接使用,没有再创建。

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using Unity.VisualScripting.Antlr3.Runtime;
using UnityEngine;
using UnityEngine.Experimental.GlobalIllumination;public class DiskFactory : MonoBehaviour
{public GameObject diskPrefab;private List<GameObject> used;private List<GameObject> free;private int diskIndex;public GameObject GetDisk(Ruler ruler){GameObject disk;// 如果free没有空闲就创建if (free.Count==0){disk = GameObject.Instantiate(diskPrefab, , Quaternion.identity);disk.name = "UFO" + diskIndex;Debug.LogFormat("factory create a disk, index is {0}",diskIndex);disk.AddComponent(typeof(Disk));diskIndex++;}else{int freeNum = free.Count;disk = free[freeNum-1];free.Remove(free[freeNum-1]);}// cacheused.Add(disk);// 初始化disk featuredisk.GetComponent<Disk>().diskFeature = ruler.diskFeature;// initial disk colorRenderer render = disk.GetComponent<Renderer>();if (lor == lors.white){lor = d;}else if (lor == lors.blue){lor = Color.blue;}else if (lor == lors.black){lor = Color.black;}else if (lor == llow){lor = llow;}else if (lor == ){lor= ;}// set disk position ansform.localScale = new Vector3(1f, (float)ruler.diskFeature.size, 1f);ansform.position = ruler.startPos;disk.SetActive(true);return disk;}public void FreeDisk(GameObject dd){foreach (GameObject d in used){if (d.GetInstanceID()==dd.GetInstanceID()){Debug.LogFormat("factory free a disk, name is {0}", d.name);d.SetActive(false);used.Remove(d);free.Add(d);break;}}}// Start is called before the first frame updatevoid Start(){diskPrefab = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/UFO"));diskPrefab.SetActive(false);Debug.Log("DiskFactory initial the diskPrefab");used = new List<GameObject>();free = new List<GameObject>();diskIndex = 0;}// Update is called once per framevoid Update(){}
}

model

飞碟的model类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Disk : MonoBehaviour
{public DiskFeature diskFeature;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){}
}

飞碟的特征类,有大小,颜色,速度等属性


public struct DiskFeature
{public enum colors{green,blue,red,purple,black,white,yellow,}public int size;public colors color;public int speed;
}

view

视图类,游戏有三种状态:就绪,游戏中,游戏结束
根据游戏状态不同显示不同的gui界面

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;struct Status
{public int score;public string tip;public int roundNum;public int trialNum;public int roundIndex;public int trialIndex;public PhysiclaType type;public GameState gameState;
}public class UserGUI : MonoBehaviour
{private Status status;private ISceneController currentSceneController;private GUIStyle playInfoStyle;// Start is called before the first frame updatevoid Start(){Init();currentSceneController = Director.GetInstance().CurrentSceneController;// set styleplayInfoStyle = new GUIStyle();Color = Color.black;playInfoStyle.fontSize= 25;}private void Init(){status.score= 0;status.tip = "";undNum = ialNum = 0;status.gameState = GameState.Ready;}// Update is called once per framevoid Update(){}private void OnGUI(){// titleGUIStyle titleStyle = new GUIStyle();Color = d;titleStyle.fontSize = 50;GUI.Label(new Rect(Screen.width / 2 - 80, 10, 60, 100), "Hit UFO", titleStyle);// show user pageShowPage();}public void SetIndex(int roundIndex, int  trailIndex){undIndex = ialIndex = trailIndex;}public void SetRTNum(int roundNum, int trailNum){undNum = ialNum= trailNum;}public void SetGameState(GameState gameState){status.gameState = gameState;}/*set property of status*/public void SetScore(int score){status.score = score;}/*base on game status to show different user view*/private void ShowPage(){switch (status.gameState){case GameState.Ready:ShowHomePage();break;case GameState.Playing:ShowPlayingPage();break;case GameState.GameOver:ShowGameoverPage();break;}}private void ShowGameoverPage(){GUI.Label(new Rect(Screen.width / 2 - 40, 60, 60, 100), "游戏结束!", playInfoStyle);if (GUI.Button(new Rect(420, 200, 100, 60), "重新开始")){FirstController f = (FirstController)currentSceneController;// set game statusf.Restart();}}private void ShowPlayingPage(){GUI.Label(new Rect(10, 10, 60, 100), "正在游戏",playInfoStyle);GUI.Label(new Rect(Screen.width  - 200, 10, 60, 100), "round:" +(undIndex+1)+"  trail:"+ (ialIndex+1), playInfoStyle);GUI.Label(new Rect(10, 40, 60, 100), "得分:"+status.score, playInfoStyle);GUI.Label(new Rect(Screen.width - 200, 35, 60, 100),"总轮数:" + undNum + "n每轮射击数:" + ialNum, playInfoStyle);GUI.Label(new Rect(10, 70, 60, 100),"当前模式:"+(pe=&#Physical?"运动学模式":"物理模式"), playInfoStyle);if (Input.GetButtonDown("Fire1")){Singleton<RoundController>.Instance.usePosition);}// chose modeif (GUI.Button(new Rect(50, 450, 80, 50), "运动学模式")){FirstController f = (FirstController)currentSceneController;f.Physical);}if (GUI.Button(new Rect(150, 450, 80, 50), "物理学模式")){FirstController f = (FirstController)currentSceneController;f.SetGameMode(PhysiclaType.pysical);}}private void ShowHomePage(){// add game modeif (GUI.Button(new Rect(Screen.width/2-50, 100, 100, 60), "开始游戏式n(默认为运动学模式)")){FirstController f = (FirstController)currentSceneController;// set game statusf.gameState = GameState.Playing;status.gameState = GameState.Playing;}}internal void SetPhyMode(PhysiclaType phyType){pe= phyType;}
}

创建预制&&配置ruler&&配置天空盒&&挂载脚本

飞碟的预制,需要提前制作好,并加上刚体组件Rigidbody,将Use Gravity项勾选上

配置游戏对局数据,这样就不用要修改都在代码里修改了

先创建一个config,之后配置我们的对局数据


再给camera配上一个天空盒

最后挂载firstcontroller和ruler,config脚本到一个空gameobject上

游戏就可以运行了

视频演示

unity 打飞碟小游戏

代码仓库

GitHub地址

本文发布于:2024-01-28 05:03:11,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17063893944981.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:小游戏   unity
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23