I am trying to make an eyeball in matter.js. I have a pupil and an iris that are constrained together. My problem is that I need to make both bodies ignore collision so I can place one on top of the other.
var defaultCategory = 0x0001
var iris = Bodies.circle(0, 0, 20);
var pupil = Bodies.circle(300, 200, 30)
var ground = Bodies.rectangle(400, 380, 810, 60, { isStatic: true });
var bodyA = Bodies.polygon(100, 100, 6, 20,{
collisionFilter: {
mask: defaultCategory
}
});
var bodyB = Bodies.polygon(200, 100, 10, 50,{
collisionFilter: {
category: defaultCategory
}
});
var constraint = Constraint.create({
bodyA: bodyA,
pointA: { x: -10, y: -10 },
bodyB: bodyB,
pointB: { x: -10, y: -10 },
length: 40
});
World.add(world, [pupil, iris, ground, constraint]);
Not sure if there is a better way, but instead of using categories and masks, I used groups and this allows both bodies to overlap, see below:
var Engine = Matter.Engine,
Render = Matter.Render,
Runner= Matter.Runner,
World = Matter.World,
Bodies = Matter.Bodies,
Composites = Matter.Composites,
Common = Matter.Common,
Constraint = Matter.Constraint,
Body = Matter.Body
// no longer using category
var defaultCategory = 0x0001
//using group instead
var group = Body.nextGroup(true)
var engine = Engine.create(),
world = engine.world;
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 400,
wireframes: false
}
});
var ground = Bodies.rectangle(400, 380, 810, 60, { isStatic: true });
var bodyA = Bodies.polygon(100, 100, 6, 20,{
collisionFilter: { group: group }
});
var bodyB = Bodies.polygon(200, 100, 10, 50,{
collisionFilter: { group: group }
});
var constraint = Constraint.create({
bodyA: bodyA,
pointA: { x: -10, y: -10 },
bodyB: bodyB,
pointB: { x: -10, y: -10 },
length: 40
});
World.add(world, [bodyA, bodyB, ground, constraint]);
// World.add(world, [leftEye, leftEyeBall, ground, constraint]);
Engine.run(engine);
Render.run(render);
Related
So I'm trying to create a plot with BokehJS. This plot needs 3 axes (left, right, bottom):
The plot looks like this
As you can see, the right y-axis is pretty ugly, you can't read any data from towards the bottom as the tickers are bunched up.
This is how im creating my plot and lines
function zip(arr1, arr2, floatCast){
var out = {};
if (floatCast){
arr1.map( (val,idx)=>{ out[parseFloat(val)] = arr2[idx]; } );
}
else{
arr1.map( (val,idx)=>{ out[val] = arr2[idx]; } );
}
return out;
}
function createBokehPlot(PNVibe, staticPN, transX, transY){
//empty the previous plot
$( "#graph-div" ).empty();
//Data Source for PN under vibration vs. offset frequency
const source = new Bokeh.ColumnDataSource({
data: { x: Object.keys(PNVibe), y: Object.values(PNVibe) }
});
//Data source for Static PN vs offset frequency
const source1 = new Bokeh.ColumnDataSource({
data: { x: Object.keys(staticPN), y: Object.values(staticPN) }
});
//Data source for Transmissibility line
const source2 = new Bokeh.ColumnDataSource({
data: { x: transX, y: transY}
});
//Set plot x/y ranges
var max_offset = Math.max(Object.keys(PNVibe));
const xdr = new Bokeh.Range1d({ start: 1, end: 100000 });
const ydr = new Bokeh.Range1d({ start: -180, end: -50 });
const y2_range = new Bokeh.Range1d({start: 0.001, end: 10});
// make a plot with some tools
const plot = Bokeh.Plotting.figure({
title: 'Example of random data',
tools: "pan,wheel_zoom,box_zoom,reset,save",
toolbar_location: "right",
toolbar_sticky: false,
height: 600,
width: 700,
outerWidth: 800,
legend_location: "top_left",
x_range: xdr,
y_range: ydr,
x_axis_type:"log",
x_axis_label: "Offset Frequency (Hz)",
y_axis_type: "linear",
y_axis_label: "Phase Noise (dBc/Hz)",
extra_y_ranges: {y2_range},
major_label_standoff: 1
});
//Add the second y axis on the right
const second_y_axis = new Bokeh.LogAxis({y_range_name:"y2_range", axis_label:'Vibration Profile (g^2/Hz)', x_range: xdr, bounds:[0.0001, 10]});
second_y_axis.ticker = new Bokeh.FixedTicker({ticks: [0.0001, 0.001, 0.01, 0.1, 1, 10]})
plot.add_layout(second_y_axis, "right");
// add line for vibraiton phase noise
plot.line({ field: "x" }, { field: "y" }, {
source: source,
line_width: 2,
line_color: "red",
legend_label: "Phase Noise under Vibrations"
});
//add line for static phase noise
plot.line({ field: "x" }, { field: "y" }, {
source: source1,
line_width: 2,
line_color: "blue",
legend_label: "Static Phase Noise"
});
plot.line({ field: "x" }, { field: "y" }, {
source: source2,
line_width: 2,
line_color: "green",
y_range_name:"y2_range",
legend_label: "Transmissibillity"
});
// show the plot, appending it to the end of the current section
Bokeh.Plotting.show(plot, "#graph-div");
return;
}
//Call function
var PNVibe = zip([10, 100, 1000], [-95, -100, -105], false);
var staticPN = zip([10, 100, 1000], [-90, -105, -110], false);
var transX = [10, 100, 1000];
var transY = [0.0005, 0.003, 0.05];
createBokehPlot(PNVibe, staticPN, transX, transY);
My question is, how would I be able to make it so that the right y-axis displays better? Preferably I want each tick to be the same distance from each other (ie. space between 10^0 and 10^1 is the same as space between 10^1 and 10^2)
Thanks
I also posted this on the bokeh forums:Here
Fixed my problem:
I believe I may have had some invalid javascript in my original code which is why it wasnt correctly rendering. After hours of messing around with whatever I could think of, it's fixed.
//Define range for y2
const y2_range = new Bokeh.Range1d({start: 0.0001, end: 10}); //Cannot use //array for this
// make a plot with some tools
const plot = Bokeh.Plotting.figure({
title: 'Example of random data',
tools: "pan,wheel_zoom,box_zoom,reset,save,hover",
toolbar_location: "right",
toolbar_sticky: false,
height: 600,
width: 700,
outerWidth: 800,
legend_location: "top_left",
x_range: [1, 1000000],
y_range: [-180, -70],
x_axis_type:"log",
x_axis_label: "Offset Frequency (Hz)",
y_axis_label: "Phase Noise (dBc/Hz)",
extra_y_ranges: {"y2_range": y2_range}, //This was incorrect
extra_y_scales: {"y2_range": new Bokeh.LogScale()}, //This was incorrect
major_label_standoff: 5,
});
//Add the second y axis on the right
const second_y_axis = new Bokeh.LogAxis({
y_range_name:"y2_range",
axis_label:'Vibration Profile (g^2/Hz)',
x_range: [1, 1000000],
bounds:[0.0001, 10],
});
plot.add_layout(second_y_axis, "right");
so i have a phaser group
this.cows = this.physics.add.group({
key: "cow",
repeat: 2,
setXY: { x: 160, y: 1500, stepX: 32 },
});
this.cows.children.iterate(function (child) {
child.setSize(20, 10, true);
child.setBounceY(Phaser.Math.FloatBetween(0.2, 0.4));
});
I plan to update the movement of every child in the update function, so how would i make sure every child has a different amount of movement in a different direction?
Depending on your use case you could simply use, a property destination which the child wants to reach, and when it is reached, just set a new destination.
Here a short demo showcaing this:
(it uses Vector's, not everybody likes/understtands them, but they are concise)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
physics: {
default: 'arcade',
arcade: {
debug: true
}
},
scene: {
create,
update
},
banner: false
};
function create () {
this.add.text(10,10, 'Random Movment')
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
let graphics = this.make.graphics();
graphics.fillStyle(0xffffff);
graphics.fillRect(0, 0, 10, 10);
graphics.generateTexture('cow', 10, 10);
this.cows = this.physics.add.group({
key: "cow",
repeat: 2,
setXY: { x: 50, y: 50, stepX: 32 },
});
this.cows.children.iterate(function (child) {
child.setSize(10, 10, true);
child.setBounceY(Phaser.Math.FloatBetween(0.2, 0.4));
child.speed = Phaser.Math.Between(30, 50);
child.destination = { x: child.x , y: child.y};
});
}
function update(){
this.cows.children.iterate(function (child) {
if(Phaser.Math.Distance.BetweenPoints(child.body.position, child.destination) <= 20){
//new destination
child.destination = { x:Phaser.Math.Between(50, 200), y:Phaser.Math.Between(50, 150)};
// Setup velocity
let vector = new Phaser.Math.Vector2(child.destination);
vector.subtract(child.body.position)
.setLength(child.speed);
child.setVelocity(vector.x, vector.y);
}
});
}
new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I have this code that recieves data (wind direction) from an Arduino and this script that shows a compass with the direction. The compass itself works and the data I recieve is correct. The problem is that there is a "wall" at north, so if the wind direction is NW and changes to NE, it doesnt do NW -> N -> NE, it goes like NW -> W -> SW -> S -> SE -> E -> NE
Is there a way to remove this barrier?
CODE FOR COMPASS ITSELF starts at //Kompass
Compass
var socket = io();
socket.on('data', function (data) {
console.log(data);
function safelyParseJSON(json) {
var parsed
try {
parsed = JSON.parse(json)
} catch (e) {
}
return parsed
}
obj = safelyParseJSON(data);
windSpeed = obj.windSpeed;
windDir = obj.windDir;
windFloat = obj.windFloat;
waterAmount = obj.waterAmount;
//Data i tekstform
// document.getElementById('windSpeed').innerHTML = obj.windSpeed;
document.getElementById('windDir').innerHTML = obj.windDir;
document.getElementById('waterAmount').innerHTML = obj.waterAmount;
// Vind-graf
if (chart.data.labels.length != 15) {
chart.data.labels.push(time);
chart.data.datasets.forEach((dataset) => {
dataset.data.push(windSpeed);
});
}
else {
chart.data.labels.shift();
chart.data.labels.push(time);
chart.data.datasets.forEach((dataset) => {
dataset.data.shift();
dataset.data.push(windSpeed);
});
}
chart.update();
});
//Kompas
am5.ready(function () {
var root = am5.Root.new("chartdiv");
root.setThemes([
am5themes_Animated.new(root)
]);
var compassChart = root.container.children.push(
am5radar.RadarChart.new(root, {
panX: false,
panY: false,
startAngle: -90,
endAngle: 270
})
);
var axisRenderer = am5radar.AxisRendererCircular.new(root, {
strokeOpacity: 1,
strokeWidth: 5,
minGridDistance: 10
});
var axis = compassChart.xAxes.push(
am5xy.ValueAxis.new(root, {
maxDeviation: 0,
min: 0,
max: 360,
strictMinMax: true,
renderer: axisRenderer
})
);
axisRenderer.ticks.template.setAll({
forceHidden: true
});
axisRenderer.grid.template.setAll({
forceHidden: true
});
axisRenderer.labels.template.setAll({
forceHidden: true
})
var handDataItem = axis.makeDataItem({
value: 0
});
var hand = handDataItem.set("bullet", am5xy.AxisBullet.new(root, {
sprite: am5radar.ClockHand.new(root, {
radius: am5.percent(99),
topWidth: 5,
bottomWidth: 20,
})
}));
hand.get("sprite").hand.setAll({
fill: am5.color(0xff0000),
fillOpacity: 1
});
axis.createAxisRange(handDataItem);
handDataItem.get("grid").set("visible", false);
handDataItem.get("tick").set("visible", false);
function createLabel(text, value, tickOpacity) {
var axisDataItem = axis.makeDataItem({ value: value });
axis.createAxisRange(axisDataItem);
var label = axisDataItem.get("label");
label.setAll({
text: text,
forceHidden: false,
inside: true,
radius: 20
});
var tick = axisDataItem
.get("tick")
.setAll({
forceHidden: false,
strokeOpacity: tickOpacity,
length: 12 * tickOpacity,
visible: true,
inside: true
});
}
createLabel("N", 0, 1);
createLabel("NE", 45, 1);
createLabel("E", 90, 1);
createLabel("SE", 135, 1);
createLabel("S", 180, 1);
createLabel("SW", 225, 1);
createLabel("W", 270, 1);
createLabel("NW", 315, 1);
for (var i = 0; i < 360; i = i + 5) {
createLabel("", i, 0.5);
}
setInterval(() => {
handDataItem.animate({
key: "value",
to: Math.round(windFloat),
duration: 500,
easing: am5.ease.out(am5.ease.cubic)
});
}, 500);
});
I'm creating a Minigame using Canvas HTML5 and am trying to display LEDs on top of another image, currently it works like this: Image displayed
Currently, the Image does display and change as intended however the function is not initialised straight away (i'm guessing this is due to Javascript running on one core and how the setInterval function works) the code also seems really clunky and long-winded.
Is there a better way to achieve looping of these images to form an animation?
I intend to add more animations as the minigame is 'idle' and ideally the function controlling the looping of images should be easily broken.
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
function drawSafeBuster(imageSources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get number of images
for (var src in imageSources) {
numImages++;
}
for (var src in imageSources) {
images[src] = new Image();
images[src].onload = function () {
if (++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = imageSources[src];
}
}
//Image path variables.
var imageSources = {
ledPath: './graphics/leds_safe_dial_minigame.png'
};
drawSafeBuster(imageSources, function (images) {
//Draw initial LED images.
context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100);
//LED Animation Loop
var ledRepeat = setInterval(function () {
context.fillStyle = '#999999';
var ledRepeat1 = setInterval(function () {
context.fillRect(850, 300, 120, 45);
context.fillRect(1015, 300, 120, 45);
context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100);
}, 500);
var ledRepeat2 = setInterval(function () {
context.fillRect(850, 300, 120, 45);
context.fillRect(1015, 300, 120, 45);
context.drawImage(images.ledPath, 120, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 120, 0, 115, 100, 1015, 300, 120, 100);
}, 1500);
var ledRepeat3 = setInterval(function () {
context.fillRect(850, 300, 120, 45);
context.fillRect(1015, 300, 120, 45);
context.drawImage(images.ledPath, 238, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 238, 0, 115, 100, 1015, 300, 120, 100);
}, 2500);
var clearInterval = setInterval(function () {
clearInterval(ledRepeat1);
clearInterval(ledRepeat2);
clearInterval(ledRepeat3);
}, 3500);
}, 4500);
});
}
I would suggest making each LED an object with its own state (on/off). Create a game object to track the game state and current time/tick. (Here's some light reading about game loops)
Not sure about your exact requirements, but here's an example of how I would approach something similar. In the ideal world each requestAnimationFrame() is 1/60th of a second ~ 60 frames per second... See the above link for why this may not be case and how to correct for it.
I've not used images for LEDs but this could be added to the LED object and used in the draw function.
let canvas, c, w, h,
TWOPI = Math.PI * 2;
canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
w = canvas.width = 600;
h = canvas.height = 400;
let game = {
state: "RUNNING",
tick: 0,
actors: []
};
//LED object.
let LED = function(x, y, hue, radius, on, toggleRate) {
this.position = {
x: x,
y: y
};
this.hue = hue;
this.radius = radius;
this.on = on;
this.toggleRate = toggleRate;
this.update = function(tick) {
if (tick % this.toggleRate === 0) {
this.on = !this.on;
}
};
this.draw = function(ctx) {
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, TWOPI, false);
ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 70 : 30}%)`;
ctx.fill();
ctx.beginPath();
ctx.arc(this.position.x + this.radius / 5, this.position.y - this.radius / 5, this.radius / 3, 0, TWOPI, false);
ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 90 : 50}%)`;
ctx.fill();
};
}
//create LEDs
for (let i = 0; i < 10; i++) {
game.actors.push(
new LED(
100 + i * 25,
100,
i * 360 / 10,
8,
Math.random() * 1 > 0.5 ? true : false,
Math.floor(Math.random() * 240) + 60
)
);
}
function update() {
if (game.state === "RUNNING") {
//increase game counter
game.tick++;
//update actors
for (let a = 0; a < game.actors.length; a++) {
game.actors[a].update(game.tick);
}
} else {
//noop.
}
}
function clear() {
c.clearRect(0, 0, w, h);
}
function draw() {
//draw all actors
for (let a = 0; a < game.actors.length; a++) {
game.actors[a].draw(c);
}
}
function loop() {
update();
clear();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener('click', function() {
if (game.state === "RUNNING") {
game.state = "PAUSED";
} else {
game.state = "RUNNING";
}
console.log(game.state);
});
requestAnimationFrame(loop);
body {
background: #222;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
For the best quality always render to the canvas via requestAnimationFrame's callback. Rendering using timers can result in flickering and or shearing of the animation.
setTimeout or setInterval are throttled by most browsers when the page is not in focus or visible. Nor are the callbacks always on time
If timing is important to 1/60th second use performance.now and the time argument passed to the requestAnimationFrame callback. You will never be precisely on time as the animation is only displayed every 1/60th second (16.66666...ms) so draw the next frame of the animation as soon as possible after the time required (see example)
From the information you gave in the question I could not workout what your animation looked like so I made something up as an example.
Example
Uses promise rather than callback when loading media
Image has property added that represents the position of sub images (sprites). The function drawSprite takes the imageName, spriteIdx, and locationName to draw a sub image at a location on the canvas.
The main render loop is the function renderLoop it waits until media is loaded and then uses the timing to do the animation.
The array timing contains an object for each animation event. The object has the time offset of the event, the function to be called on that event, and the arguments passed to the function.
The last timing object in this example, just resets the start time to repeat the animation.
Note that this example does not check if the browser stops the animation and will cycle through all animations to catch up.
requestAnimationFrame(renderLoop);
canvas.width = 64;
canvas.height = 16;
const ctx = canvas.getContext("2d");
var mediaLoaded = false;
const images = {};
var currentStage = 0;
var startTime; // do not assign a value to this here or the animation may not start
loadMedia({
leds: {
src: "https://i.stack.imgur.com/g4Iev.png",
sprites: [
{x: 0, y: 0, w: 16, h: 16}, // idx 0 red off
{x: 16, y: 0, w: 16, h: 16}, // idx 1 red on
{x: 0, y: 16, w: 16, h: 16}, // idx 2 orange off
{x: 16, y: 16, w: 16, h: 16}, // idx 3 orange on
{x: 0, y: 32, w: 16, h: 16}, // idx 4 green off
{x: 16, y: 32, w: 16, h: 16}, // idx 5 green on
{x: 0, y: 48, w: 16, h: 16}, // idx 6 cyan off
{x: 16, y: 48, w: 16, h: 16}, // idx 7 cyan on
]
},
}, images)
.then(() => mediaLoaded = true);
const renderLocations = {
a: {x: 0, y: 0, w: 16, h: 16},
b: {x: 16, y: 0, w: 16, h: 16},
c: {x: 32, y: 0, w: 16, h: 16},
d: {x: 48, y: 0, w: 16, h: 16},
};
function loadMedia(imageSources, images = {}) {
return new Promise(allLoaded => {
var count = 0;
for (const [name, desc] of Object.entries(imageSources)) {
const image = new Image;
image.src = desc.src;
image.addEventListener("load",() => {
images[name] = image;
if (desc.sprites) { image.sprites = desc.sprites }
count --;
if (!count) { allLoaded(images) }
});
count ++;
}
});
}
function drawSprite(imageName, spriteIdx, locName) {
const loc = renderLocations[locName];
const spr = images[imageName].sprites[spriteIdx];
ctx.drawImage(images[imageName], spr.x, spr.y, spr.w, spr.h, loc.x, loc.y, loc.w, loc.h);
}
function drawLeds(sprites) {
for(const spr of sprites) { drawSprite(...spr) }
}
function resetAnimation() {
currentStage = 0;
startTime += 4500;
}
const timing = [
{time: 0, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 4, "c"], ["leds", 6, "d"]]]},
{time: 500, func: drawLeds, args: [[["leds", 1, "a"]]]},
{time: 1500, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 3, "b"]]]},
{time: 2000, func: drawLeds, args: [[["leds", 1, "a"]]]},
{time: 3000, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 5, "c"], ["leds", 7, "d"]]]},
{time: 3250, func: drawLeds, args: [[["leds", 1, "d"]]]},
{time: 3500, func: drawLeds, args: [[["leds", 3, "d"]]]},
{time: 3750, func: drawLeds, args: [[["leds", 5, "d"]]]},
{time: 4000, func: drawLeds, args: [[["leds", 7, "d"]]]},
{time: 4250, func: drawLeds, args: [[["leds", 1, "d"]]]},
{time: 4500 - 17, func: resetAnimation, args: []},
];
function renderLoop(time) {
if (mediaLoaded) {
if (startTime === undefined) { startTime = time }
var offsetTime = time - startTime;
const stage = timing[currentStage];
if(offsetTime > stage.time) {
currentStage++;
stage.func(...stage.args);
}
}
requestAnimationFrame(renderLoop);
}
canvas {
border:1px solid black;
}
<canvas id="canvas"></canvas>
Image used in example
I want to change following code into node environment. Help me!
code is from http://mapv.baidu.com/examples/
And the content is following:
var map = new BMap.Map(slice.selector, {
enableMapClick: false
}); // 创建Map实例
map.centerAndZoom(new BMap.Point(105.403119, 38.028658), 5); // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
map.setMapStyle({
style: 'light'
});
var randomCount = 300;
var data = [];
var citys =
["北京","天津","上海","重庆","石家庄","太原","呼和浩特","哈尔滨",
"长春","沈阳","济南","南京","合肥","杭州","南昌","福州","郑州","武汉",
"长沙","广州","南宁","西安","银川","兰州","西宁","乌鲁木齐","成都",
"贵阳","昆明","拉萨","海口"];
// 构造数据
while (randomCount--) {
var cityCenter = mapv.utilCityCenter.getCenterByCityName(citys[parseInt(Math.random() * citys.length)]);
data.push({
geometry: {
type: 'Point',
coordinates: [cityCenter.lng - 2 + Math.random() * 4, cityCenter.lat - 2 + Math.random() * 4]
},
count: 30 * Math.random()
});
}
//数据集
var dataSet = new mapv.DataSet(data);
var options = {
fillStyle: 'rgba(255, 50, 50, 0.6)',
shadowColor: 'rgba(255, 50, 50, 1)',
shadowBlur: 30,
globalCompositeOperation: 'lighter',
methods: {
click: function (item) {
console.log(item);
}
},
size: 5,
draw: 'simple'
}
var mapvLayer = new mapv.baiduMapLayer(map, dataSet, options);
I use "mapv = require('mapv');" to import module mapv. But I can not get module "BMap", how can I get it?
add
in your index.html
and use var BMap = window.BMap in your js code