Demo: Unity + XLua + TypescriptToLua + SolidJS
Unity 竟然能使用最新潮的 Web 前端技术来开发游戏?太疯狂了!
本项目是一个 Unity + XLua + TypescriptToLua + SolidJS 的 Demo,用于演示如何在 Unity 中移植 SolidJS 中的 Signal 系统,来管理 uGUI 或者其他什么GameObject 的逻辑与生命周期。
Unity :这是个懂游戏的人都知道的游戏引擎,不用介绍了。
XLua :能让 Unity 支持 Lua 脚本的东西。手游业内人士都知道。为了能随意更新 patch 而不用重新发包,很多手游都会用到。
TypescriptToLua :这是个能把 TypeScript 编译为 Lua 的东西。很小众。它能大幅提升 Lua 代码的可维护性。在这个 Demo 中,更是对移植 SolidJS 帮助巨大。
SolidJS :这是个Web 前端框架。API 看起来像是 React,更新机制却更类似于 Vue。性能极高。估计只有部分 Web 前端开发者知道。它没有 VDOM,JSX 编译器是选配,核心代码很少,所以很方便移植。
main_test.ts
import { Accessor , createSignal } from "solid/signal" ;
import { renderOn , For , LoadPrefab , Bind , H } from "solid/unity" ;
// 一个自定义组件,参数是位置
const testSphere = ( pos : Accessor < CS . UnityEngine . Vector3 > ) => {
// 支持普通的 GameObject,不一定非要是 uGUI 节点。
// 返回的元素只是为了让外层组件获取。并不意味着一定会被 parent 到外层组件的 GameObject上。
return LoadPrefab ( 'sphere' , {
transform : [
// 通过绑定,属性能根据数据源变化自动更新,也会自动清理监听。
Bind . prop ( 'position' , pos ) ,
] ,
} )
}
const testUiItem = ( value : Accessor < number > , posY : Accessor < number > ) => {
// 通过自定义组件 testSphere 创建元素
// 如果当前组件被删除时,这个闭包内部创建的元素会被自动删除。即,自动生命周期管理。
// 由于没有进行 insert 操作,它会直接放在场景根节点。
// 生命周期管理,不要求必须 parent 到某个节点。
H (
testSphere ,
( ) => new CS . UnityEngine . Vector3 ( value ( ) , posY ( ) + 1 , 0 )
) ;
// 返回一个 uGUI 节点
return LoadPrefab ( 'test_ui_item' , {
text : [
Bind . prop ( 'text' , ( ) => `value: ${ value ( ) } ` ) ,
] ,
} )
}
const testUiPanel = ( ) => {
const [ count , setCount ] = createSignal ( 0 ) ;
const [ list , setList ] = createSignal ( new Array < number > ( ) ) ;
const [ posY , setPosY ] = createSignal ( 1 ) ;
function onClickAdd ( ) {
const c = setCount ( c => c + 1 ) ;
setList ( l => {
if ( c < 4 ) {
return [ ...l , c ] ;
} else {
return l . slice ( 0 , - 1 ) ;
}
} ) ;
}
function onClickPos ( ) {
setPosY ( y => ( y + 1 ) % 4 ) ;
}
// 根据 list 数据,利用自定义组件 testUiItem 生成 children 元素
// For 会自动管理 children 的生命周期,当 list 变化时,会自动更新 children。
// 并且会尽量复用已有的元素,减少创建和销毁。
const children = For ( list , ( item ) => H (
testUiItem ,
( ) => item , // value
posY , // posY
) ) ;
return LoadPrefab ( 'test_ui_panel' , {
text_count : [
Bind . prop ( 'text' , count ) ,
] ,
button_add : [
Bind . unityEvent ( 'onClick' , onClickAdd ) ,
] ,
button_pos : [
Bind . unityEvent ( 'onClick' , onClickPos ) ,
] ,
even : [
Bind . invoke ( 'SetActive' , ( ) => count ( ) % 2 == 0 ) ,
] ,
list : [
// 把 children insert 到 list 节点下
Bind . insert ( ( ) => children ) ,
] ,
} )
}
function runTestUi ( ) {
const canvas = CS . UnityEngine . GameObject . Find ( 'Canvas' ) ;
renderOn ( canvas , ( ) => H ( testUiPanel ) ) ;
}
runTestUi ( ) ;
数据驱动 :数据变化时,自动更新 GameObject 的属性。
生命周期管理 :自动管理 GameObject 的生命周期。能非常方便的处理异步加载资源。不用手动管理 GameObject 和监听的清理。
和 “面向对象” 说再见。
Unity + SolidJS 产生的一些神奇的特性
组件闭包中可以创建不挂接到任何父节点的 Element,在组件被销毁时,会自动销毁。
在 Web 开发中,没有父节点的节点没有任何用处,于是这个特性看出不来有什么用。
但是游戏开发中很有用,大量 GameObject 是不挂接到任何父节点的。
复杂结构的 UI 可以同时异步加载每个层级的节点。而不用等待父节点加载完成再开始加载子节点。
在 Web 开发中,所有节点都是同步创建的,于是这个特性看出不来有什么用。
但是游戏开发中很有用。可以把 UI 的 prefab 拆得很碎,也不用担心加载慢的问题。
SignalJS 特性
Web 前端开发体验:一边运行一边修改代码
自动生成 TypeScript 类型定义
资源加载卸载支持对象池