一、前言

krpano可以理解为一门小型的编程语言,和我们平时用的js相比,思想和逻辑上非常类似的,只是在语法上有所不同,只要大家能够熟悉krpano语法的套路,相信大家开发一款可定制的vr项目是不难的,下面我将以大家所熟悉的javascript语言进行对比,让大家更快的掌握和理解krpano XML语言。当然,和大家一样作为初学者,我只是把我所理解东西的分享给大家,如果有什么不足之处,请提出来,大家一起学习

准备工作:你已经看完krpano中文网初级教程,并对其有了一定的理解,此外,你已经有Sublime Text 工具。

为了方便大家的学习,我自行翻译了一下krpano官网,链接如下:

二、内容

1. 如何在全景图上写一下自定义的东西?比如导航栏

sublime如何写java sublime如何写导航栏_加载

  • 首先,大家打开生成的tour.xml,为了更好的理解,可以把tour.xml中的内容都删掉,我们自己重新写。接下来我们先让tour.xml能够运行起来且能够展示出来全景图,代码如下
<krpano version="1.20.7" title="Virtual Tour" debugmode="true">
// 大家可以看到这个action,有自己的姓名"startup",autorun是什么呢?代表自动运行,onstart代表在xml加载开始的时候运行整个action。
	<action name="startup" autorun="onstart">
// 这个条件语句只是判断有scene场景就加载,loadscene()就是加载场景的方法。
		if(
		startscene === null OR!scene[get(startscene)],
		copy(startscene,scene['scene_images'].name)
        );
//为什么要用get(startscene)呢?在上面if语句中有个copy的方法,他将scene场景中name等于"scene_images"的name属性值复制给了startscene这个变量,那么,我们用的时候就需要通过get(startscene)方法来获取出来,不能直接写startscene,这样是识别不了的.
		loadscene(get(startscene));
	</action>
//以下就是我们的第一个名为scene_images的场景
	<scene name="scene_images" title="区域沙盘" onstart="" thumburl="panos/images.tiles/thumb.jpg" lat="" lng="" heading="">
//view中的这些属性值大家可以去文档中搜索查看,简单理解就是一个视图
		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
// preview就是进入场景的时候需要先展示那个图片,preview.jpg是自动生成的
		<preview url="panos/images.tiles/preview.jpg" />
// image就是加载所有的场景图,至于里面的Cube是什么可以在文档中搜索查看
		<image>
			<Cube url="panos/images.tiles/pano_%s.jpg" />
		</image>
	</scene>
</krpano>
  • 接下来先写出来一个小按钮,代码如下
通俗一点layer可以理解为div,
<layer 
  name="sanweishapan" 
  type="text" //类型,分别有container和text两种
  css="color:#fff;" //可以写css,但是只适合type=text,其他是无效的
  bgalpha="0" // 透明度,类似于css中的opacity
  html="三维沙盘" //要展示的文本内容
  parent="" // 告诉此处的layer是属于那个父类layer的,为什么要写这个字段呢?假设你有嵌套的layer,如果要隐藏整个layer,有了parent,那么我们就可以直接给最顶层的layer设为visible="true/false",这样他的所有子layer也会统一隐藏.
  align="center" //告诉此layer的位置,如果没有指明parent,那么就以整个窗口为主,如果有parent,则相对于他的父类
  bgroundedge="50" // 类似于css中的border-radius
  bgcolor="0x000000" // 背景颜色,需要16进制,向我们平时用的css颜色是识别不了的,大家可以去网上搜一下16进制颜色对比图,比如我们要更换颜色为白色,可以这样写:
  bgcolor="0xFFFFFF"
  />

此时,我们已经会写出来一个小layer了,那么一个长的导航栏还不简单吗,思路和大家写html一样,一个大div包含多个小div就可以了,代码如下

<layer 
		name="footer" 
		keep="true" 
		style="footer_style" 
		type="container" 
		align="leftbottom" 
		bgalpha="0.5"
		x="10" y="10" 
		scalechildren="true"
		maskchildren="true"
		bgroundedge="50">
		<layer name="sanweishapan" type="text" css="color:#fff;" bgalpha="0" html="三维沙盘" parent="footer" align="center"/>
		<layer name="yuyingjiangfang" type="text" css="color:#fff;" bgalpha="0" html="语音讲房" parent="footer" align="center"/>
		<layer name="yangbanjian" type="text" css="color:#fff;" bgalpha="0" html="样板间" parent="footer" align="center"/>
		<layer name="huxingjianshang" type="text" css="color:#fff;" bgalpha="0" html="户型鉴赏" parent="contain3" align="center"/>
</layer>
大家可以看到上面的代码中,多了以下几个属性,分别是keep,style,scalechildren,maskchildren,x,y 那么他们分别是什么意思呢?
1. keep:就是当你设置为true的时候,在进行场景跳转的时候,并不会消失,比如说在我们在一个项目中进行页面跳转的时候,在上一个页面中的某些div并不会消失,而是会持续保留
2. style:大家可以看到在上面的代码中我们写了很多个类似的layer,那么这些layer中肯定有共有的一些属性,那么我们可以把它抽出来,统一放在style中进行管理,"footer_style"只是自定义的名字,<style>可以参考下面代码
	<style 
		name="footer_style"
		width="530"
		height="46"/>
3.scalechildren:如果一个由父元素分配的子图层/插件元素在当前图层/插件元素将缩放时也会缩放。
4.maskchildren:对超出父元素的子元素进行裁剪,有时你需要点击某个按钮,将导航栏收进去,在点击,缓慢弹出来,这时你就需要用到这个属性,你只需要控制父元素的宽度或者高度类似于css中的动画属性进行变化就可以了,xml中的动画方法是tween(),可在文档中详细查看
  • 接下来我们写一个简单的实例,点击一个按钮让他显示,再点击让他隐藏
我们先随便写一个layer,如下
<layer 
  name="sanweishapan" 
  group="zhengwupeitao" 
  type="text" 
  css="color:#fff;" 
  bgalpha="0" 
  html="三维沙盘" 
  align="center"
  onclick="clickHandle()"
  />
我们先想一下思路,在js中我们实现这个功能应该咋写呢?就写个事件监听点击事件,然后将div的display设为none就可以了,此处我们的思路是一样的,那就有如下几个问题需要我们解决:
第一:我们如何监听layer的事件?
第二:如何找到触发事件的layer?
第三:如何更改layer的visible属性?
第一个问题:在xml中监听事件,和js是一样的,比如点击事件可以这样写οnclick="clickHandle(name)",然后在action中写业务逻辑
<action name="clickHandle" arg="name">
第二个问题:每个layer都有自己的name,可以通过他来找出触发的元素,我们先在事件中传参,将触发的元素的name传递过来,然后通过arg来接受,注意两个名字必须是一样的,接下来我们可以处理
trace(name);//你可以先打印一下name的值,看看有没有传过来
debugvar(layer);//此方法将打印layer的详细信息,大家试一下就知道了,layer是一个数组,接下来开始循环
for(set(i,0),i LT layer.count,inc(i),
	if(
        layer[get(i)].name=== name,
		set(hotspot[get(i)].visible,true),
		set(hotspot[get(i)].visible,false);
	)
);
和
for(int i; i < layer.length;i++){
  if(layer[i].name ===name){
    layer[i].visible = true;
  }else{
    layer[i].visible = false;
  }
}
对比一下,就很好理解了,大家可以注意到layer中新出现一个group属性,它标明此处的layer是属于哪个分组下面的,如果要批量执行某个代码的时候,可以把上面代码中的name换成group,name所有属于group这个组的layer都会隐藏
</action>

注意:在action中,每行代码必须以分好";"结尾,否则,代码只能运行到当前行,后面的代码将不会执行

2.如何通过热点跳转某个场景?

想要跳转场景肯定是通过事件来触发的,xml中hotspot是热点,通常用于跳转到某个场景。还是和上面一样,先写一个简单的hotspot

<hotspot
  name="jiaotong" 
  group="jiaotong" 
  default="true" 
  html="交通配套" 
  color="0xff2f6f"
  onclick="toScene()"
  />

然后在事件中调用加载场景的方法就可以了.

<action name="toScene">
loadscene("某个场景scene的name");
</action>

如果是小项目我们调用这个方法就可以了,就加载当前xml中的某个场景,但是如果项目比较大,我们不能把所有的代码读写在一个xml中吧,肯定是要分好几个xml的,那如果要加载别的xml中的某个场景应该怎么办?直接调用 loadpanoscene(’%VIEWER%/house.xml’,‘scene_2kt’);就可以了,可以在文档中详细查看此方法,%VIEWER%代表tour.xml所在路径

3. xml加载的生命周期

大家可以看到上面代码中有用过onstart,这个就是xml加载的生命周期,xml中具体有哪些呢?大家可以搜索文档中的事件标签,里面有详细的讲解,了解生命周期是很重要的,比如你可以在场景跳转时,在某个xml加载完之前,进行一下关闭导航栏或者是显示动画等操作

4.户型实例,自定义地图,如何实现如下这种场景?

sublime如何写java sublime如何写导航栏_xml_02

<krpano version="1.20.7" title="house" debugmode="true">
	<action name="startups" autorun="onstart">
		if(startscenes === null OR !scene[get(startscenes)], copy(startscenes,scene[0].name); );
		loadscene(get(startscenes));  
		trace("加载完成");
	</action>
	<!-- 自定义地图 -->
	<layer 
      name="mapcontainer" 
      visible="false" 
      keep="true"
      type="container" 
      bgcolor="0x000000" 
      bgalpha="0.5" 
      align="lefttop" x="0" y="0" width="264" height="264">
		<!-- handcursor当鼠标悬浮上面显示手势 -->
		<layer name="map" url="skin/b1map.png" align="top" x="4" y="4" width="prop" height="256" handcursor="false" scalechildren="true">
			<!-- 雷达遮罩部分 -->
			<!-- maskchildren:当设置为true时,父元素之外的所有子元素将被剪切/屏蔽掉。 -->
			<layer name="radarmask" type="container" align="lefttop" width="100%" height="100%" maskchildren="true">
				<!-- 雷达插件 zoder=1 在开始时为隐藏,只有激活activetespot这个action时才会显示 zorder为叠放次序 数字越大越靠前 -->
				<layer 
					name="radar" 
					visible="false"
					url="%SWFPATH%/plugins/radar.swf" //可去此文件夹查看
					alturl="%SWFPATH%/plugins/radar.js"
					align="lefttop"
					edge="center"
					zorder="1"
					scale="0.3"
			       	fillcolor="0xFF0000" fillalpha="0.8"
			       	linecolor="0xFF0000" linewidth="0.5" linealpha="0.5"
			       	headingoffset="0"
				/>
				<!-- 热点 zorder=2,用style来统一处理 所有layer都载入了一个名为spot的style,注意这里spot是由0开始,而不是由1开始的,因此在一般情况下,地图点与场景一一对应,而场景的index是从0开始的,所以我们可以利用这一点提高代码的可读性-->
				<layer name="spot0" style="spot" x="113" y="124"  />
				<layer name="spot1" style="spot" x="94" y="62"  />
				<layer name="spot2" style="spot" x="55"  y="34"   />
				<layer name="spot3" style="spot" x="13" y="58"  />
				<!-- 激活的热点 zorder=3 开始时候隐藏,在这里是一个绿色的地图点, 表示当前的场景-->
				<layer name="activespot" url="skin/vtourskin_mapspotactive.png" scale="0.5" oy="-17" align="lefttop" edge="center" zorder="3" visible="false"/>
			</layer>
		</layer>
	</layer>
<!-- 地图点中相同的代码,都写在了style里面,修改的时候只需要修改style里面的代码,提高了效率。在onclick里,先是用subtxt得出spot后面的数字,也就是index,然后检查是否点击的热点就是当前场景,因为没有必要点击当前场景的热点又载入一遍,如果是点击其他的热点,则载入其他场景,载入场景的loadscene中利用了之前得到的spotid,这样就不用每次都写场景的名字了。-->
	<style 
		name="spot"
		url="skin/vtourskin_mapspot.png"
		scale="0.5"
		oy="-17"
		align="lefttop"
		edge="center"
		zorder="2"
		onclick="activeScene()"
		/>
	<action name="activeScene">
		<!-- 将当前点击的layer的名字截取一下,得到后面的数字,spot1 => 1,然后将该数字和场景的下标进行对比,如果是点击的是当前已展示的scene则不加载,如果是其他的场景,则重新通过loadscene重新加载 -->
		subtxt(spotid,get(name),4,2);
		if(spotid != scene[get(xml.scene)].index,
			loadscene(get(scene[get(spotid)].name),null,MERGE,BLEND(1,easeinoutback));
		);
	</action>
	<action name="activatespot">
		trace("hahahha");
	   <!-- 因此绿色地图点会在每次激活时替换蓝色普通地图点,因此每次激活之前,首先保证所有蓝色地图点是可见的,如果没有下面这个代码,则会使得上一个场景的蓝色地图点消失,这里用的是一个循环语句-->  
	    for(set(i,0),i LT scene.count,inc(i),  
	    txtadd(spotname,'spot',get(i));  
	    set(layer[get(spotname)].visible, true);  
	    );  
	    <!--这样就使得这个activetespot的参数只要一个就可以了-->  
	    trace(get(xml.scene));
	    <!-- 将当前激活的场景下标与spot和并,得到spot1,在通过合成后的名字得到layer与此名称相同的标记的坐标,然后将此坐标赋给雷达插件 -->
	    txtadd(spotidnow,'spot',get(scene[get(xml.scene)].index));
	    trace(get(spotidnow));
	    copy(layer[radar].x, layer[get(spotidnow)].x);  
	    copy(layer[radar].y, layer[get(spotidnow)].y);  
	    copy(layer[activespot].x, layer[get(spotidnow)].x);  
	    copy(layer[activespot].y, layer[get(spotidnow)].y);  
	  
	    <!-- 将第二个参数赋值到雷达的heading -->  
	    set(layer[radar].heading, %1);  
	  
	        <!-- 显示雷达和绿色激活热点,以及隐藏当前场景的地图点 -->  
	    set(layer[radar].visible, true);  
	    set(layer[activespot].visible, true);  
	    set(layer[get(spotidnow)].visible, false);
	</action>
	<scene name="scene_2kt" title="2kt" onstart="activatespot(90)" thumburl="panos/2kt.tiles/thumb.jpg" lat="" lng="" heading="">
		<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
		<preview url="panos/2kt.tiles/preview.jpg" />
		<image>
			<cube url="panos/2kt.tiles/pano_%s.jpg" />
			<mobile>
				<cube url="panos/2kt.tiles/mobile_%s.jpg" />
			</mobile>
		</image>

	</scene>
	<scene name="scene_b1cf" title="b1cf" onstart="activatespot(90)" thumburl="panos/b1cf.tiles/thumb.jpg" lat="" lng="" heading="">
		<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
		<preview url="panos/b1cf.tiles/preview.jpg" />
		<image>
			<cube url="panos/b1cf.tiles/pano_%s.jpg" />
			<mobile>
				<cube url="panos/b1cf.tiles/mobile_%s.jpg" />
			</mobile>
		</image>
	</scene>
	<scene name="scene_b1ct" title="b1ct" onstart="activatespot(90)" thumburl="panos/b1ct.tiles/thumb.jpg" lat="" lng="" heading="">
		<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
		<preview url="panos/b1ct.tiles/preview.jpg" />
		<image>
			<cube url="panos/b1ct.tiles/pano_%s.jpg" />
			<mobile>
				<cube url="panos/b1ct.tiles/mobile_%s.jpg" />
			</mobile>
		</image>
	</scene>
	<scene name="scene_b1etf" title="b1etf" onstart="activatespot(90)" thumburl="panos/b1etf.tiles/thumb.jpg" lat="" lng="" heading="">
		<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
		<preview url="panos/b1etf.tiles/preview.jpg" />
		<image>
			<cube url="panos/b1etf.tiles/pano_%s.jpg" />
			<mobile>
				<cube url="panos/b1etf.tiles/mobile_%s.jpg" />
			</mobile>
		</image>
	</scene>
</krpano>

5.如何进行自动浏览?

<layer 
  name="control_auto" 
  css="color:#fff;" 
  type="text" 
  html="浏览" 
  bgalpha="0" 
  align="top" 
  onclick="autuRateHandle"/>
  <action name="autuRateHandle">
    if(
      autorotate.enabled,
      autorotate.stop(),
      autorotate.start()
      );
  </action>
  在xml中随便哪个地方加入一下代码,不要加在<scene>中
  <autorotate enabled="false" waittime="1" speed="10.0"/>

6.如何与js进行交互?

如果你看到这儿,并且对前面所讲的这些都了解了,那么你一定会发现.这个xml语言好繁琐啊,一点儿都没有js用起来爽,是的,我也是这么想的.

krpano也设定了与js的打交道的api,大家可以想一想,两者之间如果要打交道,如何才能做到呢?肯定是事件.

假设我能够在js中直接响应xml中的action事件,那么我是不是可以直接把前面所说的导航栏写在html里?

这样事件在xml中执行,模板在html中,对于你这种前端大佬来说是不是就很简单了?具体如下:

  • 打开我们的首页tour.html.大家可以先看看里面的内容,其实没多少东西,看下面代码
<!DOCTYPE html>
<html>
<head>
    <title>krpano - images</title>
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover"/>
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    <meta name="mobile-web-app-capable" content="yes"/>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <meta http-equiv="x-ua-compatible" content="IE=edge"/>
    <style>
        html {
            height: 100%;
        }
        body {
            height: 100%;
            overflow: hidden;
            margin: 0;
            padding: 0;
            font-family: Arial, Helvetica, sans-serif;
            font-size: 16px;
            color: #000000;
            background-color: #000000;
        }
    </style>
</head>
<body>
<script src="tour.js"></script>
<div id="pano" style="width:100%;height:100%;text-align: center;position: relative">
    <noscript>
        <table style="width:100%;height:100%;">
            <tr style="vertical-align:middle;">
                <td>
                    <div style="text-align:center;">ERROR:<br/><br/>Javascript not activated<br/><br/></div>
                </td>
            </tr>
        </table>
    </noscript>
    <button onclick="fun1()">点击</button>-->
    <script>
        var krpano = null;
        embedpano({
            swf: "tour.swf",
            consolelog: "true",
            xml: "tour.xml",
            target: "pano",
            html5: "auto",
            mobilescale: 1.0,
            passQueryParameters: true,
            onready : callback //打开这个回调函数即可,名称自定义
        });
        function callback(krpano_interface){
            krpano = krpano_interface;
        }
        function fun1(){
            console.log(krpano)
            if(krpano){
                krpano.call("frameHandle")
                //触发xml中action名称为frameHandle的事件
            }
        }
  
    </script>
</div>
</body>
</html>

以上只是一个简单的例子,复杂一点的交互,大家可以查看官网examples中的例子右键查看源码即可,地址如下:
https://krpano.com/releases/1.20.9/viewer/examples/javascript-interface/js-api-examples/index.html

  • 我们也可以在xml中写js代码,如下
<action name="..." type="Javascript"><![CDATA[
    ...
    Javascript code
    ...
]]></action>

三、 总结

在前面的代码学习中,大家也可以感受到xml语言并没有很难,如果遇到问题,可以参照你平时使用的代码思想来想问题,想想用原来的语言是怎么完成某个功能的,然后只需要在文档中查找自己所需要的的方法即可。我给大家列举的一些知识,仅仅只是xml语言所需要的一些基础知识,如果要我列举出官网中所有的例子,显然是不可能的,我只是带大家入个门,后面的学习还需要靠大家自己.加油!