laya商业级3d游戏开发
本节目标:无缝随机创建地图
编辑器开发核心思想(unity对象序列化,反序列化)
新建scripts\Game\BuildSpawn.ts
模型层设计
//物体属性模型
export class SpwanItemData {
public constructor() { }
public goName = ‘’;
public length = 0;
}
//生产对象模型
export class SpawnItem {
public spwanItemData: SpwanItemData;
public gob: Laya.Sprite3D;
}
//创建器配置模型
export class SpwanConfigObj {
public constructor() {
}
//资源查找的路径
public findRoot = '';
public spwanItemDatas: SpwanItemData[];
//起始创建值
public startCreateZ = 0;
//创建的长度
public CreateLength = 0;
//回收偏移值,考虑到相机的位置,避免物体在镜头内被回收
public recoverOffset = 0;
}
//建筑物创建组件
export default class BuildSpawn extends Laya.Script {
scene: Laya.Scene;
spwanConfigObj: SpwanConfigObj;
}
Main.ts
DoExample_RndBuildLoop() {
let node = new Laya.Node();
Laya.stage.addChild(node);
node.addComponent(UnityEnagine)
SceneManager.LoadSceneByName('Example_RandomBuild', this, (p_Scene3D) => {
Laya.stage.addChild(p_Scene3D);
let buildSpawn = new BuildSpawn()
buildSpawn.scene = p_Scene3D;
//构造模型实体,参数配置
buildSpawn.spwanConfigObj = new SpwanConfigObj();
buildSpawn.spwanConfigObj.CreateLength = 150;
buildSpawn.spwanConfigObj.findRoot = 'Resources/BuildItem';
buildSpawn.spwanConfigObj.recoverOffset = -5;
buildSpawn.spwanConfigObj.startCreateZ = 0;
buildSpawn.spwanConfigObj.spwanItemDatas = [];
buildSpawn.spwanConfigObj.spwanItemDatas[0] = new SpwanItemData();
buildSpawn.spwanConfigObj.spwanItemDatas[0].goName = 'IndustrialWarehouse01'
buildSpawn.spwanConfigObj.spwanItemDatas[0].length = 18;
//假如物体的增加减少或者属性发生变化,都要进行代码修改
//物体数量很多或者业务更为复杂时,编码较为繁琐
//而且策划也无法进行配置
//把模块封装为数据驱动模式以解决上述的问题
//可利用u3d可视化编辑优势,进行序列化和反序列化
node.addComponentIntance(buildSpawn);
});
}
Unity3d序列化模型
在u3d环境中操作
新建脚本BuildSpawn.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
using Newtonsoft.Json;
[System.Serializable]
public class SpwanItemData
{
public string goName ;
public int length ;
}
[System.Serializable]
public class SpwanConfigObj
{
public string findRoot = "";
public SpwanItemData[] spwanItemDatas;
//起始创建值
public float startCreateZ = 0;
//创建的长度
[SerializeField]
public float CreateLength;
//回收偏移值,考虑到相机的位置,避免物体在镜头内被回收
public float recoverOffset = -5;
}
public class SpawnItem
{
public SpwanItemData spwanItemData;
public GameObject gob;
}
public class BuildSpawn : MonoBehaviour
{
[SerializeField]
protected SpwanConfigObj cfg = new SpwanConfigObj();
[ContextMenu("LogAndCopyJson")]
void LogAndCopyJson()
{
var jsondata = Newtonsoft.Json.JsonConvert.SerializeObject(this.cfg);
UnityEngine.GUIUtility.systemCopyBuffer = jsondata;
Debug.Log("已经复制到粘帖板");
Debug.Log(jsondata);
}
}
经过一番调整,最终得到以下数据
Resources/BuildItem
通过json序列化之后,得到一串字符串
把字符串转换成实体
对象反序列化
Mian.ts
新建方法
example_DeserializeObject() {
let buildSpawn = new BuildSpawn()
let bulidjsonStr = ‘{“findRoot”:“Resources/BuildItem”,“spwanItemDatas”:[{“goName”:“IndustrialWarehouse01”,“length”:18.0},{“goName”:“IndustrialWarehouse03”,“length”:27.0}],“startCreateZ”:0.0,“CreateLength”:150.0,“recoverOffset”:-10.0}’;
buildSpawn.spwanConfigObj = JSON.parse(bulidjsonStr);
console.log(buildSpawn);
}
onConfigLoaded(): void {
//this.example_spwan()
this.example_DeserializeObject()
}
F8 f5
成员成功反序列化后拥有了定义好的属性
这个时候,策划对地图有更多的需求和想法,把编辑器交给策划,前端只需要场景和导出数据即可
建筑物的具体实现
由于用到的了伪随机算法和类似C#字典数据结构等,导入类库
LayaIde\framework\JFrameWork到src\scripts
GameSample.ts到src\scripts
export default class BuildSpawn extends Laya.Script {
spwanConfigObj: SpwanConfigObj;
runtimeItems: SpawnItem[] = [];
//返回首页时清理对象池用到
poolsignMap: Dictionary = new Dictionary();
currentZ = 0;
startCreateZ = 0;
scene: Laya.Scene3D;
seed: SeedRnd = new SeedRnd(0);
onStart() {
this.startCreateZ = this.spwanConfigObj.startCreateZ;
this.Create2End();
}
protected endZ(): number {
return this.currentZ + this.spwanConfigObj.CreateLength;
}
Create2End() {
var p_endZ = this.endZ();
while (this.startCreateZ < p_endZ) {
let spawnItem = this.SpawnItem(this.startCreateZ);
this.startCreateZ += spawnItem.spwanItemData.length;
}
}
protected SpawnItem(z: number): SpawnItem {
let rndIdx = this.seed.getRandomInt_NotIncludeMax(0, this.spwanConfigObj.spwanItemDatas.length);
//随机数据
var spwanItemData = this.spwanConfigObj.spwanItemDatas[rndIdx];
//对象池创建
var item = Laya.Pool.getItemByCreateFun(spwanItemData.goName, () => { return this.CreateSpwanItem(spwanItemData); }, this) as SpawnItem;
this.scene.addChild(item.gob);
this.onSpawn(item.gob, spwanItemData, z);
return item;
}
//对应上一章的CreateItem,返回固定长度改为返回SpwanItemData,该数据模型就包含了物体的长度信息
protected CreateSpwanItem(spwanItemData: SpwanItemData): SpawnItem {
var gob = GameObject.Find
var newGo = Laya.Sprite3D.instantiate(gob);
this.scene.addChildren(newGo);
newGo.active = true;
let spawnItem = new SpawnItem();
spawnItem.gob = newGo;
spawnItem.spwanItemData = spwanItemData;
//给回收用的
this.runtimeItems.push(spawnItem);
//返回首页时清理对象池用到
this.poolsignMap.add(spwanItemData.goName, 'poolsign');
return spawnItem;
}
//置创建物体创建的属性,
protected onSpawn(newGo: Laya.Sprite3D, spwanItemData: SpwanItemData, z) {
newGo.transform.position = new Laya.Vector3(0, 0, z);
var scale = newGo.transform.localScale;
var arryNum = [-1, 1];
this.seed.getRandomIntArry(arryNum);
//物体镜像 x=1 x=-1,构成建筑物朝向,文章中见图1
newGo.transform.localScale = scale;
}
onUpdate() {
this.Create2End();
this.recoverLessZ();
}
//回收超出镜头的物体
recoverLessZ() {
for (const spwanitem of this.runtimeItems) {
if (spwanitem.gob.displayedInStage) {
let length = spwanitem.spwanItemData.length;
if (spwanitem.gob.transform.position.z + length * 0.5 < this.currentZ + this.spwanConfigObj.recoverOffset) {
Laya.Pool.recover(spwanitem.spwanItemData.goName, spwanitem);
spwanitem.gob.removeSelf();
}
}
}
}
//返回首页会删除对象池实例化的资源,所以要清空对象池,避免游戏开始时取到空对象
onDestroy() {
for (const iterator of this.poolsignMap.keys) {
Laya.Pool.clearBySign(iterator)
}
}
}
主干部分
this.Create2End();
this.recoverLessZ();
//从DoSpawnItem开始 演示流程
回到
Main.ts
新建测试方法
example_BuildSpawn() {
let node = new Laya.Node();
Laya.stage.addChild(node);
node.addComponent(UnityEnagine)
SceneManager.LoadSceneByName('Example-buildloop', this, (p_Scene3D) => {
Laya.stage.addChild(p_Scene3D);
let buildSpawn = new BuildSpawn()
let bulidjsonStr = '{"findRoot":"Resources/BuildItem","spwanItemDatas":[{"goName":"IndustrialWarehouse01","length":18.0},{"goName":"IndustrialWarehouse03","length":27.0}],"startCreateZ":0.0,"CreateLength":150.0,"recoverOffset":-10.0}';
buildSpawn.spwanConfigObj = JSON.parse(bulidjsonStr);
buildSpawn.scene = p_Scene3D;
//硬编码
//buildSpawn.spwanConfigObj = new SpwanConfigObj();
//buildSpawn.spwanConfigObj.CreateLength = 150;
//buildSpawn.spwanConfigObj.findRoot = 'Resources/BuildItem';
//buildSpawn.spwanConfigObj.recoverOffset = -5;
//buildSpawn.spwanConfigObj.startCreateZ = 0;
//buildSpawn.spwanConfigObj.spwanItemDatas = [];
//buildSpawn.spwanConfigObj.spwanItemDatas[0] = new SpwanItemData();
//buildSpawn.spwanConfigObj.spwanItemDatas[0].goName = 'IndustrialWarehouse01'
//buildSpawn.spwanConfigObj.spwanItemDatas[0].length = 18;
//buildSpawn.spwanConfigObj.spwanItemDatas[1] = new SpwanItemData();
//buildSpawn.spwanConfigObj.spwanItemDatas[1].goName = 'IndustrialWarehouse03'
//buildSpawn.spwanConfigObj.spwanItemDatas[1].length = 27;
node.addComponentIntance(buildSpawn);
});
}
对比硬编码和数据序列化方式
美术或者地图数据一但更新,硬编码方式就要重新写一遍,且地图编辑策划也无法同步进行
采用数据反序列化方式,只需重新导入3d资源,更新bulidjsonStr 的字符串即可
什么时候采用哪种实现方式,视具体情况而定
变化频繁的模块就用数据封装变化
需求明确可以直接写死