diff --git a/common/BehaviorTreeModel.ts b/common/BehaviorTreeModel.ts
new file mode 100644
index 0000000..9ca08c0
--- /dev/null
+++ b/common/BehaviorTreeModel.ts
@@ -0,0 +1,19 @@
+export interface BehaviorNodeTypeModel {
+ name: string;
+ type?: string;
+ desc?: string;
+ args?: string[][3];
+ input?: string[];
+ output?: string;
+ doc?: string;
+}
+
+export interface BehaviorNodeModel {
+ id: number;
+ name: string;
+ desc?: string;
+}
+
+export interface BehaviorTreeModel {
+ name: string;
+}
\ No newline at end of file
diff --git a/index.html b/index.html
index cffbde8..ebb1968 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
- demo
+ 行为树编辑器
diff --git a/main-process/AppMenu.ts b/main-process/AppMenu.ts
index ae7ee79..83e78b2 100644
--- a/main-process/AppMenu.ts
+++ b/main-process/AppMenu.ts
@@ -167,16 +167,16 @@ export default class AppMenu {
}
private createNodeMenu() {
- const typeItems: MenuItemConstructorOptions[] = [];
+ const classifyItems: MenuItemConstructorOptions[] = [];
const map: { [key: string]: MenuItemConstructorOptions } = {}
- for (let t of this.settings.nodeTypes) {
+ for (let t of this.settings.nodeClassify) {
let item: MenuItemConstructorOptions = {
- id: t.type,
- label: `${t.type}(${t.desc})`,
+ id: t.classify,
+ label: `${t.classify}(${t.desc})`,
submenu: [],
};
- typeItems.push(item);
- map[t.type] = item;
+ classifyItems.push(item);
+ map[t.classify] = item;
}
const other: MenuItemConstructorOptions = {
id: 'other',
@@ -205,12 +205,12 @@ export default class AppMenu {
}
if(hasOther) {
- typeItems.push(other);
+ classifyItems.push(other);
}
return new MenuItem({
label: "新建节点",
- submenu: typeItems
+ submenu: classifyItems
});
}
}
\ No newline at end of file
diff --git a/main-process/Settings.ts b/main-process/Settings.ts
index b370f52..74edbc0 100644
--- a/main-process/Settings.ts
+++ b/main-process/Settings.ts
@@ -1,7 +1,8 @@
import * as fs from 'fs';
+import { BehaviorNodeTypeModel } from '../common/BehaviorTreeModel';
-export interface BehaviorNodeType {
- type: string;
+export interface BehaviorNodeClassify {
+ classify: string;
desc?: string;
}
@@ -9,16 +10,16 @@ export interface SettingsModel {
recentWorkspaces?: string[];
recentFiles?: string[];
nodeConfigPath?: string; // 节点配置路径
- nodeTypes?: BehaviorNodeType[];
+ nodeClassify?: BehaviorNodeClassify[];
}
const settingPath = 'settings.json';
const sampleNodeConfig = 'sample-node-config.json';
-const sampleNodeTypes: BehaviorNodeType[] = [
- { type: "Composite", desc: "复合节点" },
- { type: "Decorator", desc: "修饰节点" },
- { type: "Condition", desc: "条件节点" },
- { type: "Action", desc: "行为节点" },
+const sampleNodeClassify: BehaviorNodeClassify[] = [
+ { classify: "Composite", desc: "复合节点" },
+ { classify: "Decorator", desc: "修饰节点" },
+ { classify: "Condition", desc: "条件节点" },
+ { classify: "Action", desc: "行为节点" },
]
export default class Settings {
@@ -32,7 +33,7 @@ export default class Settings {
recentWorkspaces: [],
recentFiles: [],
nodeConfigPath: sampleNodeConfig,
- nodeTypes: sampleNodeTypes,
+ nodeClassify: sampleNodeClassify,
};
this.save();
}
@@ -49,10 +50,11 @@ export default class Settings {
}
get nodeConfig() {
const str = fs.readFileSync(this.nodeConfigPath, 'utf8');
- return JSON.parse(str);
+ const types: BehaviorNodeTypeModel[] = JSON.parse(str);
+ return types;
}
- get nodeTypes() {
- return this.settings.nodeTypes;
+ get nodeClassify() {
+ return this.settings.nodeClassify;
}
set(config: SettingsModel) {
diff --git a/main-process/main.tsx b/main-process/main.tsx
index 7c24c6d..56a285c 100644
--- a/main-process/main.tsx
+++ b/main-process/main.tsx
@@ -1,13 +1,14 @@
import { app, BrowserWindow, Menu, MenuItem, dialog, nativeTheme } from 'electron';
import AppMenu from './AppMenu'
import Settings from './Settings';
+import MainEventType from '../common/MainEventType';
export class MainProcess {
mainWindow: BrowserWindow;
appMenu: AppMenu;
settings: Settings;
constructor() {
- nativeTheme.themeSource = 'dark';
+ nativeTheme.themeSource = 'dark';
app.on('ready', () => {
this.createWindow();
})
@@ -23,7 +24,7 @@ export class MainProcess {
});
}
- createWindow (){
+ createWindow() {
this.settings = new Settings();
this.mainWindow = new BrowserWindow({
width: 1280,
@@ -41,6 +42,11 @@ export class MainProcess {
});
this.appMenu = new AppMenu(this);
this.rebuildMenu();
+
+ const lastWorkspace = this.settings.recentWorkspaces[0];
+ if(lastWorkspace) {
+ this.mainWindow.webContents.send(MainEventType.OPEN_WORKSPACE, lastWorkspace);
+ }
}
rebuildMenu() {
diff --git a/render-process/Editor/Editor.css b/render-process/Editor/Editor.css
new file mode 100644
index 0000000..523d59c
--- /dev/null
+++ b/render-process/Editor/Editor.css
@@ -0,0 +1,20 @@
+.body {
+ width : 100%;
+ height : 100%;
+ position: fixed;
+}
+
+.sider {
+ height : 100%;
+ overflow: hidden;
+}
+
+.sider:hover {
+ overflow-y : auto;
+}
+
+.content {
+ width : 100%;
+ height : 100%;
+ display: flex;
+}
\ No newline at end of file
diff --git a/render-process/Editor/Properties.tsx b/render-process/Editor/Properties.tsx
new file mode 100644
index 0000000..7460d12
--- /dev/null
+++ b/render-process/Editor/Properties.tsx
@@ -0,0 +1,44 @@
+import React, { Component } from "react";
+import { Menu, Input } from "antd";
+import * as fs from 'fs';
+import { BehaviorTreeModel } from "../../common/BehaviorTreeModel";
+
+const { Search } = Input;
+
+export interface PropertiesProps {
+ workspace: string;
+ onOpenTree: (path: string) => void;
+ trees: BehaviorTreeModel[];
+}
+
+interface PropertiesState {
+
+}
+
+export default class Properties extends Component {
+ state: PropertiesState = {
+
+ };
+
+ componentDidMount() {
+
+ }
+
+ render() {
+ return (
+
+ {
+
+ }}
+ />
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/render-process/Editor/index.tsx b/render-process/Editor/index.tsx
index 0333761..18be383 100644
--- a/render-process/Editor/index.tsx
+++ b/render-process/Editor/index.tsx
@@ -1,18 +1,26 @@
import React, { Component } from "react";
-import { DatePicker } from 'antd';
+import { Layout } from 'antd';
import { ipcRenderer } from 'electron';
+import * as path from 'path';
import * as fs from 'fs';
import MainEventType from "../../common/MainEventType";
+import Properties from "./Properties";
+import './Editor.css';
+import { BehaviorTreeModel } from "../../common/BehaviorTreeModel";
+
+const { Header, Sider, Content, Footer } = Layout;
interface EditorState {
workspace: string;
filepaths: string[];
+ trees: BehaviorTreeModel[];
}
export default class Editor extends Component {
state: EditorState = {
workspace: '',
filepaths: [],
+ trees: [],
}
componentDidMount() {
@@ -20,17 +28,51 @@ export default class Editor extends Component {
console.log("on open file", path);
const filepaths = this.state.filepaths;
filepaths.push(path);
- this.setState({filepaths});
+ this.setState({ filepaths });
});
ipcRenderer.on(MainEventType.OPEN_WORKSPACE, (event: any, workspace: any) => {
console.log("on open workspace", workspace);
- this.setState({workspace});
+ this.setState({ workspace });
+ this.loadAllTrees();
});
+
+ this.loadAllTrees();
+ }
+
+ loadAllTrees() {
+ const workspace = this.state.workspace
+ if(workspace == '' || !fs.existsSync(workspace)) {
+ console.log("workspace not exist", workspace);
+ return;
+ }
+
+ const files = fs.readdirSync(workspace);
+ const list = []
+ files.forEach((filename) => {
+ console.log("load path", filename);
+ const stat = fs.statSync(path.join(workspace, filename))
+ if (stat.isFile()) {
+ let tree = this.loadTree(filename.slice(0, -5));
+ list.push(tree);
+ console.log("loaded", filename);
+ }
+ })
+ }
+
+ loadTree(name: string) {
+ const filename = this.state.workspace + '/' + name + '.json';
+ if (!fs.existsSync(filename)) {
+ return;
+ }
+ const str = fs.readFileSync(filename, 'utf8');
+ let tree: BehaviorTreeModel = JSON.parse(str);
+ tree.name = name;
+ return tree;
}
renderJson() {
- if(this.state.filepaths.length>0) {
+ if (this.state.filepaths.length > 0) {
const path = this.state.filepaths[0];
const str = fs.readFileSync(path, 'utf8');
return ({str}
)
@@ -38,16 +80,28 @@ export default class Editor extends Component {
}
render() {
+ const { trees } = this.state;
return (
-
-
- 当前目录:{this.state.workspace}
-
-
- 当前文件:{this.state.filepaths.join("\n")}
-
- {this.renderJson()}
-
+
+
+ {
+ console.log("open tree", path);
+ }}
+ trees={trees}
+ />
+
+
+
+ 当前目录:{this.state.workspace}
+
+
+ 当前文件:{this.state.filepaths.join("\n")}
+
+ {this.renderJson()}
+
+
)
}
};
\ No newline at end of file