Needle Engine

Changes between version 3.25.4 and 3.25.5
Files changed (6) hide show
  1. plugins/vite/config.js +11 -3
  2. plugins/vite/index.js +5 -1
  3. src/engine/engine_physics_rapier.ts +16 -12
  4. plugins/types/needleConfig.d.ts +11 -1
  5. plugins/types/userconfig.d.ts +1 -0
  6. plugins/vite/facebook-instant-games.js +100 -0
plugins/vite/config.js CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  let didLogCanNotFindConfig = false;
4
4
 
5
- /** the codegen meta file */
5
+ /** the codegen meta file
6
+ * @returns {import("../types/needleConfig").needleMeta | null}
7
+ */
6
8
  export async function loadConfig(path) {
7
9
  try {
8
10
  // First try to get the path from the config
@@ -19,7 +21,7 @@
19
21
  if (existsSync(path)) {
20
22
  const text = readFileSync(path, 'utf8');
21
23
  if (!text) return null;
22
- /**@type {import("../types/needleConfig").needleConfig} */
24
+ /**@type {import("../types/needleConfig").needleMeta} */
23
25
  const meta = JSON.parse(text);
24
26
  return meta;
25
27
  }
@@ -38,7 +40,9 @@
38
40
  }
39
41
  }
40
42
 
41
- /** get the needle.config.json */
43
+ /** get the needle.config.json
44
+ * @returns {import("../types/needleConfig").needleConfig | null}
45
+ */
42
46
  export function tryLoadProjectConfig() {
43
47
  try {
44
48
  const root = process.cwd();
@@ -64,3 +68,7 @@
64
68
  return "assets";
65
69
  }
66
70
 
71
+ export function getOutputDirectory() {
72
+ const projectConfig = tryLoadProjectConfig();
73
+ return process.cwd() + "/" + (projectConfig?.buildDirectory || "dist");
74
+ }
plugins/vite/index.js CHANGED
@@ -37,6 +37,9 @@
37
37
  import { needleDependencyWatcher } from "./dependency-watcher.js";
38
38
  export { needleDependencyWatcher } from "./dependency-watcher.js";
39
39
 
40
+ import { needleFacebookInstantGames } from "./facebook-instant-games.js";
41
+ export { needleFacebookInstantGames } from "./facebook-instant-games.js";
42
+
40
43
  import { vite_4_4_hack } from "./vite-4.4-hack.js";
41
44
 
42
45
 
@@ -68,7 +71,8 @@
68
71
  needleDrop(command, config, userSettings),
69
72
  needlePeerjs(command, config, userSettings),
70
73
  needleDependencyWatcher(command, config, userSettings),
71
- vite_4_4_hack(command, config, userSettings)
74
+ vite_4_4_hack(command, config, userSettings),
75
+ needleFacebookInstantGames(command, config, userSettings),
72
76
  ];
73
77
  array.push(await editorConnection(command, config, userSettings, array));
74
78
  return array;
src/engine/engine_physics_rapier.ts CHANGED
@@ -456,7 +456,6 @@
456
456
  public get world(): World | undefined { return this._world };
457
457
 
458
458
  private _tempPosition: Vector3 = new Vector3();
459
- private _tempPosition2: Vector3 = new Vector3();
460
459
  private _tempQuaternion: Quaternion = new Quaternion();
461
460
  private _tempScale: Vector3 = new Vector3();
462
461
  private _tempMatrix: Matrix4 = new Matrix4();
@@ -782,12 +781,10 @@
782
781
 
783
782
  if (!this.world) throw new Error("Physics world not initialized");
784
783
  let rigidBody: RigidBody | null = null;
785
- let useExplicitMassProperties = false;
786
784
 
787
785
  if (collider.attachedRigidbody) {
788
786
  const rb = collider.attachedRigidbody;
789
787
  rigidBody = rb[$bodyKey];
790
- useExplicitMassProperties = rb.autoMass === false;
791
788
  if (!rigidBody) {
792
789
  const kinematic = rb.isKinematic && !debugColliderPlacement;
793
790
  if (debugPhysics)
@@ -1090,18 +1087,25 @@
1090
1087
  // body.setBodyType(bodyType);
1091
1088
  }
1092
1089
 
1090
+ private readonly _tempCenterPos: Vector3 = new Vector3();
1091
+ private readonly _tempCenterVec:Vector3 = new Vector3();
1092
+ private readonly _tempCenterQuaternion:Quaternion = new Quaternion();
1093
1093
  private tryApplyCenter(collider: ICollider, targetVector: Vector3) {
1094
1094
  const center = collider.center;
1095
1095
  if (center && collider.gameObject) {
1096
- getWorldScale(collider.gameObject, this._tempScale);
1097
- this._tempPosition2.x = center.x;
1098
- this._tempPosition2.y = center.y;
1099
- this._tempPosition2.z = center.z;
1100
- this._tempPosition2.multiply(this._tempScale);
1101
- // TODO: fix export of center in editor integrations so we dont have to flip here
1102
- targetVector.x -= this._tempPosition2.x;
1103
- targetVector.y += this._tempPosition2.y;
1104
- targetVector.z += this._tempPosition2.z;
1096
+ if (center.x !== 0 || center.y !== 0 || center.z !== 0) {
1097
+ // TODO: fix export of center in editor integrations so we dont have to flip here
1098
+ this._tempCenterPos.x = -center.x;
1099
+ this._tempCenterPos.y = center.y;
1100
+ this._tempCenterPos.z = center.z;
1101
+ getWorldScale(collider.gameObject, this._tempCenterVec);
1102
+ this._tempCenterPos.multiply(this._tempCenterVec);
1103
+ const rot = getWorldQuaternion(collider.gameObject, this._tempCenterQuaternion);
1104
+ this._tempCenterPos.applyQuaternion(rot);
1105
+ targetVector.x += this._tempCenterPos.x;
1106
+ targetVector.y += this._tempCenterPos.y;
1107
+ targetVector.z += this._tempCenterPos.z;
1108
+ }
1105
1109
  }
1106
1110
  }
1107
1111
 
plugins/types/needleConfig.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type needleConfig = {
1
+ export type needleMeta = {
2
2
  needleEditor: string | null,
3
3
  meta: string | null;
4
4
  absolutePath: string | null,
@@ -9,4 +9,14 @@
9
9
  allowHotReload: boolean,
10
10
  license: string | null,
11
11
  useRapier: boolean,
12
+ }
13
+
14
+
15
+ /** The content of the needle.config.json in the project directory */
16
+ export type needleConfig = {
17
+ baseUrL?: string;
18
+ buildDirectory?: string;
19
+ assetsDirectory?: string;
20
+ scriptsDirectory?: string;
21
+ codegenDirectory?: string;
12
22
  }
plugins/types/userconfig.d.ts CHANGED
@@ -21,6 +21,7 @@
21
21
  /** set to true to disable reload plugin */
22
22
  noReload: boolean;
23
23
  noCodegenTransform: boolean;
24
+ noFacebookInstantGames: boolean;
24
25
 
25
26
  /** required for @serializable https://github.com/vitejs/vite/issues/13736 */
26
27
  vite44Hack: boolean;
plugins/vite/facebook-instant-games.js ADDED
@@ -0,0 +1,100 @@
1
+ import { copyFile, copyFileSync, existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { getOutputDirectory } from './config.js';
3
+
4
+ const loadInstantGamesStr = `/* needle: injected to initialize facebook instant games */
5
+ let __progress = 0;
6
+ function onNeedleEngineInstantGamesLoadingProgress(_ctx, evt) {
7
+ FBInstant.setLoadingProgress(evt.detail.totalProgress01 * 100);
8
+ }
9
+ document.addEventListener("DOMContentLoaded", function () {
10
+ FBInstant.initializeAsync().then(function () { FBInstant.startGameAsync() });
11
+ });
12
+ `
13
+
14
+ // https://regex101.com/r/4ApFD9/1
15
+ function insertCallToOnProgress(html) {
16
+ const match = /(?<component><needle-engine.*?.*?>)/g.exec(html);
17
+ if (!match) {
18
+ log("!!! Could not find needle-engine component in index.html");
19
+ return html;
20
+ }
21
+ const progressEvtSignature = "progress=\"onNeedleEngineInstantGamesLoadingProgress\"";
22
+ const component = match.groups.component;
23
+ if (component.includes("progress")) return html;
24
+ log("Inserting progress callback event into needle-engine component")
25
+ const newComponent = component.replace(">", ` ${progressEvtSignature}>`);
26
+ return html.replace(component, newComponent);
27
+ }
28
+
29
+ function log(...any) {
30
+ console.log("[needle facebook instant games] ", ...any);
31
+ }
32
+
33
+ /**
34
+ * @param {{facebookInstantGames?:object}} config
35
+ * @param {import('../types').userSettings} userSettings
36
+ */
37
+ export const needleFacebookInstantGames = (command, config, userSettings) => {
38
+
39
+ if (userSettings.noFacebookInstantGames === true) return;
40
+
41
+ // If the config is not present it means that we don't want to use fb instant games
42
+ if (!config.facebookInstantGames) return;
43
+
44
+ log("Setup Facebook Instant Games", config.facebookInstantGames);
45
+
46
+
47
+ // https://developers.facebook.com/docs/games/build/instant-games/reference/bundle-config
48
+ let bundleConfig = {}
49
+ const bundleConfigPath = process.cwd() + "/fbapp-config.json";
50
+ if (existsSync(bundleConfigPath)) {
51
+ log("Found facebook bundle config exists at " + bundleConfigPath + " ...");
52
+ bundleConfig = JSON.parse(readFileSync(bundleConfigPath, 'utf8'));
53
+ }
54
+ else {
55
+ log("No facebook bundle config exists at " + bundleConfigPath + " - will generate one now");
56
+ }
57
+
58
+ // Make sure the bundle has the required arguments:
59
+ if (!bundleConfig.instant_games) bundleConfig.instant_games = {}
60
+ const gamesConfig = bundleConfig.instant_games;
61
+ if (!gamesConfig.orientation) gamesConfig.orientation = "PORTRAIT";
62
+ if (!gamesConfig.override_web_orientation) gamesConfig.override_web_orientation = "LANDSCAPE";
63
+ if (!gamesConfig.navigation_menu_version) gamesConfig.navigation_menu_version = "NAV_BAR";
64
+
65
+ const outputDir = getOutputDirectory();
66
+
67
+ return {
68
+ name: 'needle-facebook-instant-games',
69
+ transformIndexHtml: {
70
+ enforce: 'post',
71
+ transform(html, _ctx) {
72
+ // post transform so we want to linebreak after the vite logs
73
+ console.log("\n");
74
+
75
+ writeFileSync(outputDir + "/fbapp-config.json", JSON.stringify(bundleConfig), 'utf8');
76
+
77
+ html = insertCallToOnProgress(html);
78
+
79
+ return {
80
+ html,
81
+ tags: [
82
+ {
83
+ tag: 'script',
84
+ attrs: {
85
+ src: 'https://connect.facebook.net/en_US/fbinstant.6.3.js',
86
+ async: true,
87
+ },
88
+ injectTo: 'head',
89
+ },
90
+ {
91
+ tag: 'script',
92
+ children: loadInstantGamesStr,
93
+ injectTo: 'body',
94
+ },
95
+ ]
96
+ }
97
+ }
98
+ },
99
+ }
100
+ }