H5开发带有地图编辑器的游戏

您所在的位置:网站首页 有地图编辑器的游戏 H5开发带有地图编辑器的游戏

H5开发带有地图编辑器的游戏

2024-07-02 22:37:37| 来源: 网络整理| 查看: 265

# H5开发带有地图编辑器的游戏 首先我们需要一个编辑器相关的东西得借助什么。这块有很多种选择,比如xml,csv、json、甚至纯文本都是可以的

但是基于小巧和种种原因,我最终选择了json,并且决定地图编辑器用U3d实现,而游戏则用H5实现

首先思考何为地图编辑器,这个得从游戏开发中的脚本开始说起了,其实脚本语言和外置的json、xml高度相似,如果你读过游戏的编译原理这本书的话…

书籍地址:https://book.douban.com/subject/1927405/

1.地图编辑器到底需要什么 1.如果采取json格式如何设计数据类型

首先在游戏里,必然存在地形、障碍物、敌人、特效多种类型的元素,当然如果要设计一些比较高端的元素可能需要在json中外部引用进行扩展脚本,这里就不在这一次探讨的范围,这次我们主要还是区分地形,障碍物、敌人、特效这几种情况进行设计。

不同的敌人、障碍物、地形都得设置不同的参数,json包括的作用就很好显示出来了,也就是葫芦娃套娃,一层套一层,单位放在单位类里面,而这个单位的属性又放在单位这个类里面,单位的属性又有具体的子属性。

为了方便大家的理解,我这里直接放出一个实例给大家看看

{ "enemy": [ { "id": 207, "type": 2, "name": "瘦子", "posX": 55.0, "posY": 0.0, "C": 30.0, "Size": 0.10000000149011612, "shakeTime": 300.0, "speedipa": 300.0, "idleAni": "slim_idle", "beHitAni": "slim_die", "audioSrc": "Q", "isbeHit": true }, { "id": 901, "type": 2, "name": "女神", "posX": 80.0, "posY": 0.0, "C": 10.0, "Size": -0.15000000596046449, "shakeTime": 100.0, "speedipa": 100.0, "idleAni": "Lady_idle", "beHitAni": "Lady_die", "audioSrc": "Q", "isbeHit": false }, { "id": 207, "type": 2, "name": "瘦子2", "posX": 25.0, "posY": 0.0, "C": 30.0, "Size": 0.10000000149011612, "shakeTime": 300.0, "speedipa": 300.0, "idleAni": "slim_idle", "beHitAni": "slim_die", "audioSrc": "Q", "isbeHit": true } ], "Length": 100 }

引入眼帘就是各种参数了,这里参数并没有进行太过复杂的套娃,毕竟我目前开发这款游戏属于偏休闲横板游戏,自然也没有设计过于复杂的参数和套娃模式了。

这里还是给大家介绍下这些参数的作用。

字段名数值类型值作用idnumber207用于区分具体对象typenumber2进行功能函数调用nameString瘦子(敌人)用于给对象起名字posXnumber(float)55.0判断对象x位置posYnumber(float)0.0-为了避免参数太多,这里应该和x放在一起,string里逗号进行split分开比较好但是这里为了比较便于理解就分开了判断对象y位置Cnumber(float)30.0参数C,通过公式来计算,来为不同单位添加不同的打击感Sizenumber(float)0.10000000149011612参数SIze,通过公式来计算,来为不同单位添加不同的打击感shakeTimenumber(float)300.0单位被击杀后晃动摄像机时间idleAniStringslim_idle单位默认的动画文件beHitAnistringslim_die单位被打击事播放的动画文件audioSrcstringQ单位死亡播放的声音文件isbeHitbooltrue是否需要被击杀进行计分func1objeveryThing预留字段,保留设计func2objeveryThing(可能用作后期扩展脚本)

| 这里看上去有些参数过多的感觉,但这些参数都只属于地图编辑器内部的字段,如果每一项每一次都要使用编辑器的人来填写,那么肯定会发疯,所以我们可以设置一个保存一堆默认组件的json,使用默认组件会自动为所有其他参数赋默认值。这样使用编辑器的人就舒服了。可以直接把对象调出来

{ "enemy": [ { "id": 901, "type": 2, "name": "女神", "posX": 5.0, "posY": 0.0, "C": 10.0, "Size": -0.15, "shakeTime": 100.0, "speedipa": 100.0, "idleAni": "Lady_idle", "beHitAni": "Lady_die", "audioSrc": "Q", "isbeHit": false }, { "id": 101, "type": 2, "name": "草坪", "posX": 5.0, "posY": 0.0, "C": 10.0, "Size": 0.02, "shakeTime": 100.0, "speedipa": 100.0, "idleAni": "grass_idle.png", "beHitAni": "null", "audioSrc": "Q", "isbeHit": false }, { "id": 102, "type": 1, "name": "酒瓶", "posX": 10.0, "posY": 0.0, "C": 20.0, "Size": 0.05, "shakeTime": 200.0, "speedipa": 200.0, "idleAni": "bottle_idle.png", "beHitAni": "null", "audioSrc": "duang", "isbeHit": false }, { "id": 103, "type": 3, "name": "地刺", "posX": 15.0, "posY": 0.0, "C": 20.0, "Size": -0.25, "shakeTime": 200.0, "speedipa": 300.0, "idleAni": "spikes_idle.png", "beHitAni": "null", "audioSrc": "Q", "isbeHit": false }, { "id": 201, "type": 2, "name": "碎石", "posX": 20.0, "posY": 0.0, "C": 10.0, "Size": 0.05, "shakeTime": 200.0, "speedipa": 200.0, "idleAni": "debris.png", "beHitAni": "null", "audioSrc": "pu", "isbeHit": false }, { "id": 202, "type": 2, "name": "水坑", "posX": 25.0, "posY": 0.0, "C": 20.0, "Size": 0.01, "shakeTime": 200.0, "speedipa": 200.0, "idleAni": "pool_idle.png", "beHitAni": "null", "audioSrc": "pu", "isbeHit": false }, { "id": 203, "type": 2, "name": "广告牌", "posX": 30.0, "posY": 0.0, "C": 20.0, "Size": 0.01, "shakeTime": 200.0, "speedipa": 200.0, "idleAni": "NoPeeSign_idle", "beHitAni": "NoPeeSign_die", "audioSrc": "Q", "isbeHit": false }, { "id": 205, "type": 2, "name": "汽车", "posX": 41.0, "posY": 0.0, "C": 50.0, "Size": 0.1, "shakeTime": 400.0, "speedipa": 400.0, "idleAni": "car_idle", "beHitAni": "car_die", "audioSrc": "Q", "isbeHit": false }, { "id": 206, "type": 2, "name": "油桶", "posX": 46.0, "posY": 0.0, "C": 40.0, "Size": 0.01, "shakeTime": 400.0, "speedipa": 400.0, "idleAni": "oildrum_idle", "beHitAni": "oildrum_die", "audioSrc": "oil", "isbeHit": false }, { "id": 207, "type": 2, "name": "瘦子", "posX": 51.0, "posY": 0.0, "C": 30.0, "Size": 0.1, "shakeTime": 300.0, "speedipa": 300.0, "idleAni": "slim_idle", "beHitAni": "slim_die", "audioSrc": "Q", "isbeHit": true }, { "id": 208, "type": 2, "name": "胖子", "posX": 56.0, "posY": 0.0, "C": 50.0, "Size": 0.1, "shakeTime": 400.0, "speedipa": 400.0, "idleAni": "fat_idle", "beHitAni": "fat_die", "audioSrc": "Q", "isbeHit": true }, { "id": 105, "type": 1, "name": "易拉罐", "posX": 66.0, "posY": 0.0, "C": 10.0, "Size": 0.05, "shakeTime": 100.0, "speedipa": 100.0, "idleAni": "can_idle.png", "beHitAni": "null", "audioSrc": "duang", "isbeHit": false }, { "id": 106, "type": 1, "name": "篮球", "posX": 71.0, "posY": 0.0, "C": 30.0, "Size": 1.0, "shakeTime": 100.0, "speedipa": 200.0, "idleAni": "ball_idle.png", "beHitAni": "null", "audioSrc": "box", "isbeHit": false } ], "Length": 100 } 2.编辑器的代码实现

下面这个章节,着重描述如何设计出地图编辑器的核心功能

如何读写文件(读取/载入写好的界面)设计地图编辑器的ui(懒狗就用OnGui,效率低,大家可以换成Ugui)设计交互逻辑,增加限定条件 如何读写文件(读取/载入写好的界面) private void OnSave() { OpenFileName openFileName = new OpenFileName(); openFileName.structSize = Marshal.SizeOf(openFileName); openFileName.filter = "json文件(*.json)\0*.json"; openFileName.file = new string(new char[256]); openFileName.maxFile = openFileName.file.Length; openFileName.fileTitle = new string(new char[64]); openFileName.maxFileTitle = openFileName.fileTitle.Length; openFileName.initialDir = Application.streamingAssetsPath.Replace('/', '\\');//默认路径 openFileName.title = "选择地图生成的路径"; openFileName.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008; if (LocalDialog.GetSaveFileName(openFileName)) { Debug.Log(openFileName.file); Debug.Log(openFileName.fileTitle); EnemyData inputDate = new EnemyData { enemy = new Enemy[enemyList.Count] }; for (int i=0;i 0) { isOpenWindow = true; titleContents = "当前场景有还在编辑的对象,是否放弃编辑,打开场景"; return; } OnOpen(); } private void OnOpen() { OpenFileName openFileName = new OpenFileName(); openFileName.structSize = Marshal.SizeOf(openFileName); openFileName.filter = "json文件(*.json)\0*.json"; openFileName.file = new string(new char[256]); openFileName.maxFile = openFileName.file.Length; openFileName.fileTitle = new string(new char[64]); openFileName.maxFileTitle = openFileName.fileTitle.Length; openFileName.initialDir = Application.streamingAssetsPath.Replace('/', '\\');//默认路径 openFileName.title = "选择地图的路径"; openFileName.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008; EnemyData enemyDate=null; if (LocalDialog.GetOpenFileName(openFileName)) { Debug.Log(openFileName.file); Debug.Log(openFileName.fileTitle); if (!File.Exists(openFileName.file)) return ; StreamReader sr = new StreamReader(openFileName.file); if (sr == null) return ; string json = sr.ReadToEnd(); if (json.Length > 0) enemyDate = JsonUtility.FromJson(json); if (enemyDate != null) { //地图长度生成 groud.SetActive(true); boardLenth = enemyDate.Length; Text groundLength = groud.GetComponentInChildren(); groundLength.text = "地图长度" + boardLenth + "米"; foreach (Enemy t in enemyDate.enemy) { //530是终点 float len = (t.posX / boardLenth) * 530; GameObject game = GameObject.Instantiate(beInstance, canvas.gameObject.transform); game.name = t.name; game.GetComponent().anchoredPosition = new Vector2(-393 + len, -25 + t.posY); game.GetComponentInChildren().text = t.name; game.GetComponent().e = t; Button btn = game.GetComponent(); enemyList.Add(game); btn.onClick.AddListener(() => { if (btn != null) { deleteGameObj = game; // enemyList.Remove(game); Debug.Log(deleteGameObj.name); titleContents = "选是删除对象,选否取消操作"; isDeleteWindow = true; } }); Debug.Log(t.posX + "y:" + t.posY); } } } }

前半部分是读取的代码,读取到json值赋予给地图编辑器的各个编辑窗口

最后再来描述一种我之前讲的内容,预物体,大概的思路也是读取json,通过一张完整的json表(具体可以看第一节),然后通过id获取json表的值对象

public void Find() { if (id.text.Length == 0) { titleContents = "查找id必须Id有值"; isShowWindow = true; return; } String openFileName = Application.streamingAssetsPath + "/jsonData.json"; int idValue = int.Parse(id.text); EnemyData enemyData = new EnemyData(); if (!File.Exists(openFileName)) return; StreamReader sr = new StreamReader(openFileName); if (sr == null) return; string json = sr.ReadToEnd(); if (json.Length > 0) { enemyData = JsonUtility.FromJson(json); foreach (Enemy t in enemyData.enemy) { if (idValue == t.id) { id.text = t.id+""; nameInput.text=t.name; type.text=t.type+""; posX.text=t.posX+""; posY.text=t.posY+""; C.text=t.C+""; Size.text=t.Size+""; shakeTime.text=t.shakeTime+""; speedipa.text=t.speedipa+""; beHitAni.text=t.beHitAni+""; audioSrc.text=t.audioSrc+""; toggle.isOn=t.isbeHit; idleAni.text = t.idleAni; return; } } } 设计地图编辑器的ui(懒狗就用OnGui,效率低,大家可以换成Ugui) void OnGUI(){ Rect windowRect; Rect deleteRect; Rect openRect; Rect listWindowRect; bool isShowWindow1 = isShowWindow; if (isShowWindow1 == true) windowRect = GUI.Window(0,new Rect(150,100, 320, 250), DoMyWindow, "提示"); if (isDeleteWindow == true) deleteRect = GUI.Window(1, new Rect(150, 100, 320, 250), DeleteWindow, "是否删除这个对象"); if(isOpenWindow==true) openRect = GUI.Window(2, new Rect(150, 100, 320, 250), DisCardWindow, "是否抛弃当前场景编辑的对象"); if (isListWindows == true) { listWindowRect = GUI.Window(3, new Rect(150, 0, 400, 600), ListWindow, "当前所有可获取的对象"); } } private void ListWindow(int id) { GUIStyle mystyle = new GUIStyle(); mystyle.fontSize = 25; mystyle.fontStyle = FontStyle.Bold; mystyle.normal.textColor = new Color(163f / 256f, 163f / 256f, 163f / 256f, 256f / 256f); GUI.Label(new Rect(15, 50, 320, 250), showContent, mystyle); if (GUI.Button(new Rect(10, 500, 100, 50), "了解")) { isListWindows = false; } } private void DoMyWindow(int id) { GUI.Label(new Rect(10, 50, 320, 250), titleContents); if (GUI.Button(new Rect(10, 200, 100, 50), "了解")) { isShowWindow = false; } } ...

这里没什么好说的,一个ugui的简单实现,其实是懒得画ui。写了跟多无意义的代码,酌情使用哈

设计交互逻辑,增加限定条件 public void GenerateGroud() { if (length.text.Length > 0) { boardLenth = int.Parse(length.text); // length.contentType = if (boardLenth > 0) { groud.SetActive(true); //groundLength.gameObject.SetActive(true); Text groundLength = groud.GetComponentInChildren(); groundLength.text = "地图长度" + boardLenth + "米"; } else { titleContents = "地形长度必须是一个合法的值"; isShowWindow = true; return; } } } public void AddObj() { if (boardLenth ==0) { titleContents = "必须先创建一个地形"; isShowWindow = true; return; } if (posX.text.Length > 0) { float posx = float.Parse(posX.text); if (posx < 0 || posx > boardLenth) { titleContents = "PosX不符合规范(大于0且小于地形的长度)"; isShowWindow = true; return; } foreach (GameObject t in enemyList) { EnemyObj enemy = t.GetComponent(); if (Math.Abs(posx - enemy.e.posX) < boardLenth / 30.0f) { titleContents = "每个对象至少相隔地形长度的1/30"; isShowWindow = true; return; } Debug.Log("实际相隔长度" + (posx - enemy.e.posX)); Debug.Log("至少相隔长度"+(boardLenth / 30.0)); } } if (nameInput.text.Length>0) { foreach (GameObject t in enemyList) { EnemyObj enemy = t.GetComponent(); if (nameInput.text.Equals(enemy.name)){ nameInput.text = nameInput.text + "1"; titleContents = "对象名字不能相同"; isShowWindow = true; return; } } } if (posY.text.Length > 0) { float posy = float.Parse(posY.text); if (posy > 17 || posy < -31) { titleContents = "PosY不符合规范(大于-31且小于17)"; isShowWindow = true; return; } }

这里大概就是创建地形时,以及使用组件时,为游戏里可能报错的条件加入一个限制,限制地图编辑器的无脑使用,也为后期进行修改和程序提供了便利

游戏该怎么做

这个,这篇文章,主要还是讲地图编辑器相关的,但我会放出游戏地址和地图编辑器Github的相关代码。

游戏地址:能玩的

Github:能康的

虽然我觉得大家看看源码就应该能看懂这个项目了,不过我还是想看看大家的反馈,对游戏制作相关方面有没有兴趣,如果反馈的好,就会有下集哦



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭