瀏覽代碼

added virtualJoystick customizations (#8337)

* added virtualJoystick customizations

* enabled public properties for _alwaysVisible / _puckSize / _containerSize, created an interface for customizations && misc tweaks

* making whats new modification in right file

* making whats new modification in right file

* tiny tweaks
Rockwell15 5 年之前
父節點
當前提交
4d56baf635
共有 2 個文件被更改,包括 330 次插入32 次删除
  1. 1 0
      dist/preview release/what's new.md
  2. 329 32
      src/Misc/virtualJoystick.ts

+ 1 - 0
dist/preview release/what's new.md

@@ -38,6 +38,7 @@
 - Optimized frozen instances ([Deltakosh](https://github.com/deltakosh))
 - Add support for detail maps in both the standard and PBR materials ([Popov72](https://github.com/Popov72))
 - Added abstractMesh method to get all particle systems that use the mesh as an emitter ([PirateJC](https://github.com/PirateJC))
+- Added customization options to VirtualJoystick ([#7398](https://github.com/BabylonJS/Babylon.js/issues/7398)) ([Rockwell15](https://github.com/Rockwell15))
 
 ### NME
 

+ 329 - 32
src/Misc/virtualJoystick.ts

@@ -19,6 +19,45 @@ export enum JoystickAxis {
 }
 
 /**
+ * Represents the different customization options available
+ * for VirtualJoystick
+ */
+interface VirtualJoystickCustomizations {
+    /**
+     * Size of the joystick's puck
+     */
+    puckSize: number;
+    /**
+     * Size of the joystick's container
+     */
+    containerSize: number;
+    /**
+     * Color of the joystick && puck
+     */
+    color: string;
+    /**
+     * Image URL for the joystick's puck
+     */
+    puckImage?: string;
+    /**
+     * Image URL for the joystick's container
+     */
+    containerImage?: string;
+    /**
+     * Defines the unmoving position of the joystick container
+     */
+    position?: { x: number, y: number };
+    /**
+     * Defines whether or not the joystick container is always visible
+     */
+    alwaysVisible: boolean;
+    /**
+     * Defines whether or not to limit the movement of the puck to the joystick's container
+     */
+    limitToContainer: boolean;
+}
+
+/**
  * Class used to define virtual joystick (used in touch mode)
  */
 export class VirtualJoystick {
@@ -43,12 +82,30 @@ export class VirtualJoystick {
      */
     public static Canvas: Nullable<HTMLCanvasElement>;
 
+    /**
+     * boolean indicating whether or not the joystick's puck's movement should be limited to the joystick's container area
+     */
+    public limitToContainer: boolean;
+
     // Used to draw the virtual joystick inside a 2D canvas on top of the WebGL rendering canvas
     private static _globalJoystickIndex: number = 0;
+    private static _alwaysVisibleSticks: number = 0;
     private static vjCanvasContext: CanvasRenderingContext2D;
     private static vjCanvasWidth: number;
     private static vjCanvasHeight: number;
     private static halfWidth: number;
+    private static _GetDefaultOptions(): VirtualJoystickCustomizations {
+        return {
+            puckSize: 40,
+            containerSize: 60,
+            color: "cyan",
+            puckImage: undefined,
+            containerImage: undefined,
+            position: undefined,
+            alwaysVisible: false,
+            limitToContainer: false,
+        };
+    }
 
     private _action: () => any;
     private _axisTargetedByLeftAndRight: JoystickAxis;
@@ -63,6 +120,18 @@ export class VirtualJoystick {
     private _deltaJoystickVector: Vector2;
     private _leftJoystick: boolean;
     private _touches: StringDictionary<{ x: number, y: number, prevX: number, prevY: number } | PointerEvent>;
+    private _joystickPosition: Vector2;
+    private _alwaysVisible: boolean;
+    private _puckImage: HTMLImageElement;
+    private _containerImage: HTMLImageElement;
+
+    // size properties
+    private _joystickPuckSize: number;
+    private _joystickContainerSize: number;
+    private _clearPuckSize: number;
+    private _clearContainerSize: number;
+    private _clearPuckSizeOffset: number;
+    private _clearContainerSizeOffset: number;
 
     private _onPointerDownHandlerRef: (e: PointerEvent) => any;
     private _onPointerMoveHandlerRef: (e: PointerEvent) => any;
@@ -72,8 +141,14 @@ export class VirtualJoystick {
     /**
      * Creates a new virtual joystick
      * @param leftJoystick defines that the joystick is for left hand (false by default)
+     * @param customizations Defines the options we want to customize the VirtualJoystick
      */
-    constructor(leftJoystick?: boolean) {
+    constructor(leftJoystick?: boolean, customizations?: Partial<VirtualJoystickCustomizations>) {
+        const options = {
+            ...VirtualJoystick._GetDefaultOptions(),
+            ...customizations
+        };
+
         if (leftJoystick) {
             this._leftJoystick = true;
         }
@@ -140,8 +215,30 @@ export class VirtualJoystick {
         }
         VirtualJoystick.halfWidth = VirtualJoystick.Canvas.width / 2;
         this.pressed = false;
+        this.limitToContainer = options.limitToContainer;
+
         // default joystick color
-        this._joystickColor = "cyan";
+        this._joystickColor = options.color;
+
+        // default joystick size
+        this.containerSize = options.containerSize;
+        this.puckSize = options.puckSize;
+
+        if (options.position) {
+            this.setPosition(options.position.x, options.position.y);
+        }
+        if (options.puckImage) {
+            this.setPuckImage(options.puckImage);
+        }
+        if (options.containerImage) {
+            this.setContainerImage(options.containerImage);
+        }
+        if (options.alwaysVisible) {
+            VirtualJoystick._alwaysVisibleSticks++;
+        }
+
+        // must come after position potentially set
+        this.alwaysVisible = options.alwaysVisible;
 
         this._joystickPointerID = -1;
         // current joystick position
@@ -195,10 +292,22 @@ export class VirtualJoystick {
         if (positionOnScreenCondition && this._joystickPointerID < 0) {
             // First contact will be dedicated to the virtual joystick
             this._joystickPointerID = e.pointerId;
-            this._joystickPointerStartPos.x = e.clientX;
-            this._joystickPointerStartPos.y = e.clientY;
-            this._joystickPointerPos = this._joystickPointerStartPos.clone();
-            this._joystickPreviousPointerPos = this._joystickPointerStartPos.clone();
+
+            if (this._joystickPosition) {
+                this._joystickPointerStartPos = this._joystickPosition.clone();
+                this._joystickPointerPos = this._joystickPosition.clone();
+                this._joystickPreviousPointerPos = this._joystickPosition.clone();
+
+                // in case the user only clicks down && doesn't move:
+                // this ensures the delta is properly set
+                this._onPointerMove(e);
+            } else {
+                this._joystickPointerStartPos.x = e.clientX;
+                this._joystickPointerStartPos.y = e.clientY;
+                this._joystickPointerPos = this._joystickPointerStartPos.clone();
+                this._joystickPreviousPointerPos = this._joystickPointerStartPos.clone();
+            }
+
             this._deltaJoystickVector.x = 0;
             this._deltaJoystickVector.y = 0;
             this.pressed = true;
@@ -216,11 +325,36 @@ export class VirtualJoystick {
     private _onPointerMove(e: PointerEvent) {
         // If the current pointer is the one associated to the joystick (first touch contact)
         if (this._joystickPointerID == e.pointerId) {
-            this._joystickPointerPos.x = e.clientX;
-            this._joystickPointerPos.y = e.clientY;
+            // limit to container if need be
+            if (this.limitToContainer) {
+                let vector = new Vector2(e.clientX - this._joystickPointerStartPos.x, e.clientY - this._joystickPointerStartPos.y);
+                let distance = vector.length();
+
+                if (distance > this.containerSize) {
+                    vector.scaleInPlace(this.containerSize / distance);
+                }
+
+                this._joystickPointerPos.x = this._joystickPointerStartPos.x + vector.x;
+                this._joystickPointerPos.y = this._joystickPointerStartPos.y + vector.y;
+            } else {
+                this._joystickPointerPos.x = e.clientX;
+                this._joystickPointerPos.y = e.clientY;
+            }
+
+            // create delta vector
             this._deltaJoystickVector = this._joystickPointerPos.clone();
             this._deltaJoystickVector = this._deltaJoystickVector.subtract(this._joystickPointerStartPos);
 
+            // if a joystick is always visible, there will be clipping issues if
+            // you drag the puck from one over the container of the other
+            if (0 < VirtualJoystick._alwaysVisibleSticks) {
+                if (this._leftJoystick) {
+                    this._joystickPointerPos.x = Math.min(VirtualJoystick.halfWidth, this._joystickPointerPos.x);
+                } else {
+                    this._joystickPointerPos.x = Math.max(VirtualJoystick.halfWidth, this._joystickPointerPos.x);
+                }
+            }
+
             var directionLeftRight = this.reverseLeftRight ? -1 : 1;
             var deltaJoystickX = directionLeftRight * this._deltaJoystickVector.x / this._inversedSensibility;
             switch (this._axisTargetedByLeftAndRight) {
@@ -259,8 +393,8 @@ export class VirtualJoystick {
 
     private _onPointerUp(e: PointerEvent) {
         if (this._joystickPointerID == e.pointerId) {
-            VirtualJoystick.vjCanvasContext.clearRect(this._joystickPointerStartPos.x - 64, this._joystickPointerStartPos.y - 64, 128, 128);
-            VirtualJoystick.vjCanvasContext.clearRect(this._joystickPreviousPointerPos.x - 42, this._joystickPreviousPointerPos.y - 42, 84, 84);
+            this._clearPreviousDraw();
+
             this._joystickPointerID = -1;
             this.pressed = false;
         }
@@ -277,14 +411,83 @@ export class VirtualJoystick {
     }
 
     /**
-    * Change the color of the virtual joystick
-    * @param newColor a string that must be a CSS color value (like "red") or the hexa value (like "#FF0000")
-    */
+     * Change the color of the virtual joystick
+     * @param newColor a string that must be a CSS color value (like "red") or the hexa value (like "#FF0000")
+     */
     public setJoystickColor(newColor: string) {
         this._joystickColor = newColor;
     }
 
     /**
+     * Size of the joystick's container
+     */
+    public set containerSize(newSize: number) {
+        this._joystickContainerSize = newSize;
+        this._clearContainerSize = ~~(this._joystickContainerSize * 2.1);
+        this._clearContainerSizeOffset = ~~(this._clearContainerSize / 2);
+    }
+    public get containerSize() {
+        return this._joystickContainerSize;
+    }
+
+    /**
+     * Size of the joystick's puck
+     */
+    public set puckSize(newSize: number) {
+        this._joystickPuckSize = newSize;
+        this._clearPuckSize = ~~(this._joystickPuckSize * 2.1);
+        this._clearPuckSizeOffset = ~~(this._clearPuckSize / 2);
+    }
+    public get puckSize() {
+        return this._joystickPuckSize;
+    }
+
+    /**
+     * Clears the set position of the joystick
+     */
+    public clearPosition() {
+        this.alwaysVisible = false;
+
+        delete this._joystickPosition;
+    }
+
+    /**
+     * Defines whether or not the joystick container is always visible
+     */
+    public set alwaysVisible(value: boolean) {
+        if (this._alwaysVisible === value) {
+            return;
+        }
+
+        if (value && this._joystickPosition) {
+            VirtualJoystick._alwaysVisibleSticks++;
+
+            this._alwaysVisible = true;
+        } else {
+            VirtualJoystick._alwaysVisibleSticks--;
+
+            this._alwaysVisible = false;
+        }
+    }
+    public get alwaysVisible() {
+        return this._alwaysVisible;
+    }
+
+    /**
+    * Sets the constant position of the Joystick container
+    * @param x X axis coordinate
+    * @param y Y axis coordinate
+    */
+    public setPosition(x: number, y: number) {
+        // just in case position is moved while the container is visible
+        if (this._joystickPointerStartPos) {
+            this._clearPreviousDraw();
+        }
+
+        this._joystickPosition = new Vector2(x, y);
+    }
+
+    /**
      * Defines a callback to call when the joystick is touched
      * @param action defines the callback
      */
@@ -326,29 +529,123 @@ export class VirtualJoystick {
         }
     }
 
+    /**
+     * Clears the canvas from the previous puck / container draw
+     */
+    private _clearPreviousDraw() {
+        var jp = this._joystickPosition || this._joystickPointerStartPos;
+
+        // clear container pixels
+        VirtualJoystick.vjCanvasContext.clearRect(
+            jp.x - this._clearContainerSizeOffset,
+            jp.y - this._clearContainerSizeOffset,
+            this._clearContainerSize,
+            this._clearContainerSize
+        );
+
+        // clear puck pixels
+        VirtualJoystick.vjCanvasContext.clearRect(
+            this._joystickPreviousPointerPos.x - this._clearPuckSizeOffset,
+            this._joystickPreviousPointerPos.y - this._clearPuckSizeOffset,
+            this._clearPuckSize,
+            this._clearPuckSize
+        );
+    }
+
+    /**
+     * Loads `urlPath` to be used for the container's image
+     * @param urlPath defines the urlPath of an image to use
+     */
+    public setContainerImage(urlPath: string) {
+        var image = new Image();
+        image.src = urlPath;
+
+        image.onload = () => this._containerImage = image;
+    }
+
+    /**
+     * Loads `urlPath` to be used for the puck's image
+     * @param urlPath defines the urlPath of an image to use
+     */
+    public setPuckImage(urlPath: string) {
+        var image = new Image();
+        image.src = urlPath;
+
+        image.onload = () => this._puckImage = image;
+    }
+
+    /**
+     * Draws the Virtual Joystick's container
+     */
+    private _drawContainer() {
+        var jp = this._joystickPosition || this._joystickPointerStartPos;
+
+        this._clearPreviousDraw();
+
+        if (this._containerImage) {
+            VirtualJoystick.vjCanvasContext.drawImage(
+                this._containerImage,
+                jp.x - this.containerSize,
+                jp.y - this.containerSize,
+                this.containerSize * 2,
+                this.containerSize * 2
+            );
+        } else {
+            // outer container
+            VirtualJoystick.vjCanvasContext.beginPath();
+            VirtualJoystick.vjCanvasContext.strokeStyle = this._joystickColor;
+            VirtualJoystick.vjCanvasContext.lineWidth = 2;
+            VirtualJoystick.vjCanvasContext.arc(jp.x, jp.y, this.containerSize, 0, Math.PI * 2, true);
+            VirtualJoystick.vjCanvasContext.stroke();
+            VirtualJoystick.vjCanvasContext.closePath();
+
+            // inner container
+            VirtualJoystick.vjCanvasContext.beginPath();
+            VirtualJoystick.vjCanvasContext.lineWidth = 6;
+            VirtualJoystick.vjCanvasContext.strokeStyle = this._joystickColor;
+            VirtualJoystick.vjCanvasContext.arc(jp.x, jp.y, this.puckSize, 0, Math.PI * 2, true);
+            VirtualJoystick.vjCanvasContext.stroke();
+            VirtualJoystick.vjCanvasContext.closePath();
+        }
+    }
+
+    /**
+     * Draws the Virtual Joystick's puck
+     */
+    private _drawPuck() {
+        if (this._puckImage) {
+            VirtualJoystick.vjCanvasContext.drawImage(
+                this._puckImage,
+                this._joystickPointerPos.x - this.puckSize,
+                this._joystickPointerPos.y - this.puckSize,
+                this.puckSize * 2,
+                this.puckSize * 2
+            );
+        } else {
+            VirtualJoystick.vjCanvasContext.beginPath();
+            VirtualJoystick.vjCanvasContext.strokeStyle = this._joystickColor;
+            VirtualJoystick.vjCanvasContext.lineWidth = 2;
+            VirtualJoystick.vjCanvasContext.arc(this._joystickPointerPos.x, this._joystickPointerPos.y, this.puckSize, 0, Math.PI * 2, true);
+            VirtualJoystick.vjCanvasContext.stroke();
+            VirtualJoystick.vjCanvasContext.closePath();
+        }
+    }
+
     private _drawVirtualJoystick() {
+        if (this.alwaysVisible) {
+            this._drawContainer();
+        }
+
         if (this.pressed) {
             this._touches.forEach((key, touch) => {
                 if ((<PointerEvent>touch).pointerId === this._joystickPointerID) {
-                    VirtualJoystick.vjCanvasContext.clearRect(this._joystickPointerStartPos.x - 64, this._joystickPointerStartPos.y - 64, 128, 128);
-                    VirtualJoystick.vjCanvasContext.clearRect(this._joystickPreviousPointerPos.x - 42, this._joystickPreviousPointerPos.y - 42, 84, 84);
-                    VirtualJoystick.vjCanvasContext.beginPath();
-                    VirtualJoystick.vjCanvasContext.lineWidth = 6;
-                    VirtualJoystick.vjCanvasContext.strokeStyle = this._joystickColor;
-                    VirtualJoystick.vjCanvasContext.arc(this._joystickPointerStartPos.x, this._joystickPointerStartPos.y, 40, 0, Math.PI * 2, true);
-                    VirtualJoystick.vjCanvasContext.stroke();
-                    VirtualJoystick.vjCanvasContext.closePath();
-                    VirtualJoystick.vjCanvasContext.beginPath();
-                    VirtualJoystick.vjCanvasContext.strokeStyle = this._joystickColor;
-                    VirtualJoystick.vjCanvasContext.lineWidth = 2;
-                    VirtualJoystick.vjCanvasContext.arc(this._joystickPointerStartPos.x, this._joystickPointerStartPos.y, 60, 0, Math.PI * 2, true);
-                    VirtualJoystick.vjCanvasContext.stroke();
-                    VirtualJoystick.vjCanvasContext.closePath();
-                    VirtualJoystick.vjCanvasContext.beginPath();
-                    VirtualJoystick.vjCanvasContext.strokeStyle = this._joystickColor;
-                    VirtualJoystick.vjCanvasContext.arc(this._joystickPointerPos.x, this._joystickPointerPos.y, 40, 0, Math.PI * 2, true);
-                    VirtualJoystick.vjCanvasContext.stroke();
-                    VirtualJoystick.vjCanvasContext.closePath();
+                    if (! this.alwaysVisible) {
+                        this._drawContainer();
+                    }
+
+                    this._drawPuck();
+
+                    // store current pointer for next clear
                     this._joystickPreviousPointerPos = this._joystickPointerPos.clone();
                 }
                 else {